def getTitleImage(request, response): if len(request.bits) < 3: return Server.Response404(request, response) id = request.bits[2] try: width = int(request.bits[3]) except: return Server.Response404(request, response) if width < 32 or width > 1024: return Server.Response404(request, response) if not Titles.contains(id): return Server.Response404(request, response) path = Titles.get(id).iconFile(width) or Titles.get(id).frontBoxArtFile( width) if not path: return Server.Response404(request, response) response.setMime(path) response.headers['Cache-Control'] = 'max-age=31536000' if os.path.isfile(path): with open(path, 'rb') as f: response.write(f.read()) return Server.Response500(request, response)
def isUpdateAvailable(self): title = self.title() if self.titleId and title.version != None and self.version < title.version and str( title.version) != '0': return { 'id': title.id, 'baseId': title.baseId, 'currentVersion': self.version, 'newVersion': title.version } if not title.isUpdate and not title.isDLC and Titles.contains( title.updateId): updateFile = self.getUpdateFile() if updateFile: return updateFile.isUpdateAvailable() updateTitle = Titles.get(title.updateId) if updateTitle.version and str(updateTitle.version) != '0': return { 'id': updateTitle.id, 'baseId': title.baseId, 'currentVersion': None, 'newVersion': updateTitle.version } return None
def getScreenshotImage(request, response): if len(request.bits) < 3: return Server.Response404(request, response) id = request.bits[2] try: i = int(request.bits[3]) except: return Server.Response404(request, response) if not Titles.contains(id): return Server.Response404(request, response) path = Titles.get(id).screenshotFile(i) if not path: return Server.Response404(request, response) response.setMime(path) response.headers['Cache-Control'] = 'max-age=31536000' if os.path.isfile(path): with open(path, 'rb') as f: response.write(f.read()) return Server.Response500(request, response)
def scanLatestTitleUpdates(): initTitles() initFiles() for k, i in CDNSP.get_versionUpdates().items(): id = str(k).upper() version = str(i) if not Titles.contains(id): if len(id) != 16: Print.info('invalid title id: ' + id) continue continue t = Title() t.setId(id) Titles.set(id, t) Print.info('Found new title id: ' + str(id)) t = Titles.get(id) if str(t.version) != str(version): Print.info('new version detected for %s[%s] v%s' % (t.name or '', t.id or ('0' * 16), str(version))) t.setVersion(version, True) Titles.save()
def downloadThread(i): Print.info('starting thread ' + str(i)) global status while Config.isRunning: try: id = Titles.queue.shift() if id and Titles.contains(id): activeDownloads[i] = 1 t = Titles.get(id) path = CDNSP.download_game(t.id.lower(), t.lastestVersion(), t.key, True, '', True) if os.path.isfile(path): nsp = Fs.Nsp(path, None) nsp.move() Nsps.files[nsp.path] = nsp Nsps.save() status.add() activeDownloads[i] = 0 else: time.sleep(1) except KeyboardInterrupt: pass except BaseException as e: Print.error(str(e)) activeDownloads[i] = 0 Print.info('ending thread ' + str(i))
def updateVersions(force = True): initTitles() initFiles() i = 0 for k,t in Titles.items(): if force or t.version == None: if (t.isDLC or t.isUpdate or Config.download.base) and (not t.isDLC or Config.download.DLC) and (not t.isDemo or Config.download.demo) and (not t.isUpdate or Config.download.update) and (t.key or Config.download.sansTitleKey) and (len(titleWhitelist) == 0 or t.id in titleWhitelist) and t.id not in titleBlacklist: v = t.lastestVersion(True) Print.info("%s[%s] v = %s" % (str(t.name), str(t.id), str(v)) ) i = i + 1 if i % 20 == 0: Titles.save() for t in list(Titles.data().values()): if not t.isUpdate and not t.isDLC and t.updateId and t.updateId and not Titles.contains(t.updateId): u = Title() u.setId(t.updateId) if u.lastestVersion(): Titles.set(t.updateId, u) Print.info("%s[%s] FOUND" % (str(t.name), str(u.id)) ) i = i + 1 if i % 20 == 0: Titles.save() Titles.save()
def download(id): bits = id.split(',') version = None key = None if len(bits) == 1: id = bits[0].upper() elif len(bits) == 2: id = bits[0].upper() key = bits[1].strip() elif len(bits) == 3: id = bits[0].upper() key = bits[1].strip() version = bits[2].strip() else: Print.info('invalid args: ' + download) return False if key == '': key = None if version == '': version = None if len(id) != 16: raise IOError('Invalid title id format') if Titles.contains(id): title = Titles.get(id) CDNSP.download_game(title.id.lower(), version or title.lastestVersion(), key or title.key, True, '', True) else: CDNSP.download_game(id.lower(), version or Title.getCdnVersion(id.lower()), key, True, '', True) return True
def get_name(titleId): titleId = titleId.upper() if Titles.contains(titleId): try: t = Titles.get(titleId) return (re.sub(r'[/\\:*?!"|???]+', "", unidecode.unidecode(t.name.strip())))[:70] except: pass return 'Unknown Title'
def get_name(titleId): titleId = titleId.upper() lines = titlekey_list if Titles.contains(titleId): try: t = Titles.get(titleId) return re.sub(r'[/\\:*?!"|™©®]+', "", unidecode.unidecode(t.name.strip())) except: pass return 'Unknown Title'
def unlock(self): #if not self.isOpen(): # self.open('r+b') if not Titles.contains(self.titleId): raise IOError('No title key found in database!') self.ticket().setTitleKeyBlock(int(Titles.get(self.titleId).key, 16)) Print.info('setting title key to ' + Titles.get(self.titleId).key) self.ticket().flush() self.close() self.hasValidTicket = True self.move()
def getFiles(request, response): r = {} for path, nsp in Nsps.files.items(): if Titles.contains(nsp.titleId): title = Titles.get(nsp.titleId) if not title.baseId in r: r[title.baseId] = {'base': [], 'dlc': [], 'update': []} if title.isDLC: r[title.baseId]['dlc'].append(nsp.dict()) elif title.isUpdate: r[title.baseId]['update'].append(nsp.dict()) else: r[title.baseId]['base'].append(nsp.dict()) response.write(json.dumps(r))
def scanDLC(id, showErr=True, dlcStatus=None): id = id.upper() title = Titles.get(id) baseDlc = Title.baseDlcId(id) for i in range(0x1FF): scanId = format(baseDlc + i, 'X').zfill(16) if Titles.contains(scanId): continue ver = CDNSP.get_version(scanId.lower()) if ver != None: t = Title() t.setId(scanId) Titles.set(scanId, t) Titles.save() Print.info('Found new DLC ' + str(title.name) + ' : ' + scanId) elif showErr: Print.info('nothing found at ' + scanId + ', ' + str(ver)) if dlcStatus: dlcStatus.add()
def scanBaseThread(baseStatus): while Config.isRunning: try: id = getRandomTitleId() if Titles.contains(id): continue ver = CDNSP.get_version(id.lower()) if ver != None: Print.info('Found new base ' + id) t = Title() t.setId(id) Titles.set(id, t) Titles.save() baseStatus.add() except BaseException as e: print('exception: ' + str(e))
def getNsuIds(titleIds, type='title', region='US', shop_id=4): j = ids(titleIds, type, region, shop_id) lst = {} try: for i in j['id_pairs']: titleId = i['title_id'].upper() nsuId = int(['id']) lst[titleId] = nsuId if Titles.contains(titleId): Titles.get(titleId).nsuId = nsuId else: title = Title.Title() title.setId(titleId) title.nsuId = nsuId Titles.set(titleId, title) except BaseException as e: pass return lst
def getAddOns(titleId, shop_id=3): url = 'https://superfly.hac.%s.d4c.nintendo.net/v1/a/%s/dv' % ( Config.cdn.environment, titleId) j = makeJsonRequest('GET', url, {}, '%d/a/%s/dv.json' % (shop_id, titleId)) lst = [] if not j: return lst for i in j: id = i['title_id'].upper() if not Titles.contains(id): Print.info('New DLC found: ' + id) title = Titles.get(id, None, None) title.setVersion(int(i['version'])) lst.append(id) return lst
def getBannerImage(request, response): if len(request.bits) < 3: return Server.Response404(request, response) id = request.bits[2] if not Titles.contains(id): return Server.Response404(request, response) path = Titles.get(id).bannerFile() if not path: return Server.Response404(request, response) response.setMime(path) response.headers['Cache-Control'] = 'max-age=31536000' if os.path.isfile(path): with open(path, 'rb') as f: response.write(f.read()) return Server.Response500(request, response)
def removeTitleRights(self): if not Titles.contains(self.titleId): raise IOError('No title key found in database! ' + self.titleId) ticket = self.ticket() masterKeyRev = ticket.getMasterKeyRevision() titleKeyDec = Keys.decryptTitleKey( ticket.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) rightsId = ticket.getRightsId() Print.info('rightsId =\t' + hex(rightsId)) Print.info('titleKeyDec =\t' + str(hx(titleKeyDec))) Print.info('masterKeyRev =\t' + hex(masterKeyRev)) for nca in self: if type(nca) == Nca: if nca.header.getCryptoType2() != masterKeyRev: pass raise IOError('Mismatched masterKeyRevs!') ticket.setRightsId(0) for nca in self: if type(nca) == Nca: if nca.header.getRightsId() == 0: continue Print.info('writing masterKeyRev for %s, %d' % (str(nca._path), masterKeyRev)) crypto = aes128.AESECB( Keys.keyAreaKey(Keys.getMasterKeyIndex(masterKeyRev), nca.header.keyIndex)) encKeyBlock = crypto.encrypt(titleKeyDec * 4) nca.header.setRightsId(0) nca.header.setKeyBlock(encKeyBlock) Hex.dump(encKeyBlock)
def getAddOns(titleId): url = 'https://superfly.hac.%s.d4c.nintendo.net/v1/a/%s/dv' % ( Config.cdn.environment, titleId) j = makeJsonRequest('GET', url) lst = [] if not j: return lst for i in j: id = i['title_id'].upper() if Titles.contains(id): Titles.get(id).setVersion(int(i['version'])) else: Print.info('New DLC found: ' + id) title = Title.Title() title.setId(id) title.setVersion(int(i['version'])) Titles.set(id, title) lst.append(id) return lst
key = bits[1].strip() version = bits[2].strip() else: print('invalid args: ' + download) continue if key == '': key = None if version == '': version = None if len(id) != 16: raise IOError('Invalid title id format') if Titles.contains(id): title = Titles.get(id) CDNSP.download_game(title.id.lower(), version or title.lastestVersion(), key or title.key, True, '', True) else: CDNSP.download_game(id.lower(), version or Title.getCdnVersion(id.lower()), key, True, '', True) if args.scan: initTitles() initFiles() scan() if args.refresh: refresh() if args.organize:
def getName(self): baseId = getBaseId(self.id) if self.isUpdate and Titles.contains(baseId): return Titles.get(baseId).name return self.name or ''
def scrapeTitles(region='US', shop_id=4): Print.info('Scraping %s' % region) pageSize = 50 offset = 0 total = 1 c = 0 while offset < total: url = 'https://bugyo.hac.%s.eshop.nintendo.net/shogun/v1/titles?shop_id=%d&lang=%s&country=%s&sort=new&limit=%d&offset=%d' % ( Config.cdn.environment, shop_id, countryLanguage(region), region, pageSize, offset) #print(url) j = makeJsonRequest( 'GET', url, {}, '%d/%s/%s/titles/index/%d-%d.json' % (shop_id, countryLanguage(region), region, pageSize, offset)) if not j: break total = int(j['total']) try: for i in j['contents']: title = Titles.getNsuid(i['id']) n = getTitleByNsuid(i['id'], region) if title: for x in cdn.Superfly.getAddOns(title.id): getNsuIds(x, 'aoc', region) title.parseShogunJson(n) rt = Title.Title() rt.setId(title.id) rt.setRegion(region) rt.parseShogunJson(n) Titles.set(title.id, rt, region) scrapeDlc(i['id'], region) else: try: if n and len(n["applications"]) > 0: titleId = n["applications"][0]["id"].upper() for x in cdn.Superfly.getAddOns(titleId): getNsuIds(x, 'aoc', region) if titleId: if Titles.contains(titleId): title = Titles.get(titleId) title.setId(titleId) title.parseShogunJson(n) #print('existing title found!') else: title = Title.Title() title.setId(titleId) title.parseShogunJson(n) Titles.set(titleId, title) print('added new title %s %s' % (title.id, title.name)) rt = Title.Title() rt.setId(titleId) rt.setRegion(region) rt.parseShogunJson(n) Titles.set(titleId, rt, region) scrapeDlc(i['id'], region) else: print('Could not get title json!') else: #print('no title id found in json!') pass except Exception as e: #print(str(e)) pass except Exception as e: print(str(e)) raise break offset = offset + len(j['contents']) #c = c + 1 #if c % 100 == 0: # Print.info('.') # Titles.save() Titles.save()
def setMasterKeyRev(self, newMasterKeyRev): if not Titles.contains(self.titleId): raise IOError('No title key found in database! ' + self.titleId) ticket = self.ticket() masterKeyRev = ticket.getMasterKeyRevision() titleKey = ticket.getTitleKeyBlock() newTitleKey = Keys.changeTitleKeyMasterKey( titleKey.to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev), Keys.getMasterKeyIndex(newMasterKeyRev)) rightsId = ticket.getRightsId() if rightsId != 0: raise IOError('please remove titlerights first') if (newMasterKeyRev == None and rightsId == 0) or masterKeyRev == newMasterKeyRev: Print.info('Nothing to do') return Print.info('rightsId =\t' + hex(rightsId)) Print.info('titleKey =\t' + str(hx(titleKey.to_bytes(16, byteorder='big')))) Print.info('newTitleKey =\t' + str(hx(newTitleKey))) Print.info('masterKeyRev =\t' + hex(masterKeyRev)) for nca in self: if type(nca) == Nca: if nca.header.getCryptoType2() != masterKeyRev: pass raise IOError('Mismatched masterKeyRevs!') ticket.setMasterKeyRevision(newMasterKeyRev) ticket.setRightsId((ticket.getRightsId() & 0xFFFFFFFFFFFFFFFF0000000000000000) + newMasterKeyRev) ticket.setTitleKeyBlock(int.from_bytes(newTitleKey, 'big')) for nca in self: if type(nca) == Nca: if nca.header.getCryptoType2() != newMasterKeyRev: Print.info('writing masterKeyRev for %s, %d -> %s' % (str(nca._path), nca.header.getCryptoType2(), str(newMasterKeyRev))) encKeyBlock = nca.header.getKeyBlock() if sum(encKeyBlock) != 0: key = Keys.keyAreaKey( Keys.getMasterKeyIndex(masterKeyRev), nca.header.keyIndex) Print.info('decrypting with %s (%d, %d)' % (str(hx(key)), Keys.getMasterKeyIndex(masterKeyRev), nca.header.keyIndex)) crypto = aes128.AESECB(key) decKeyBlock = crypto.decrypt(encKeyBlock) key = Keys.keyAreaKey( Keys.getMasterKeyIndex(newMasterKeyRev), nca.header.keyIndex) Print.info('encrypting with %s (%d, %d)' % (str(hx(key)), Keys.getMasterKeyIndex(newMasterKeyRev), nca.header.keyIndex)) crypto = aes128.AESECB(key) reEncKeyBlock = crypto.encrypt(decKeyBlock) nca.header.setKeyBlock(reEncKeyBlock) if newMasterKeyRev >= 3: nca.header.setCryptoType(2) nca.header.setCryptoType2(newMasterKeyRev) else: nca.header.setCryptoType(newMasterKeyRev) nca.header.setCryptoType2(0)
def isUnlockable(self): return (not self.hasValidTicket) and self.titleId and Titles.contains( self.titleId) and Titles.get(self.titleId).key
def scrapeDlc(baseNsuid, region='US', shop_id=3): pageSize = 50 offset = 0 total = 1 while offset < total: url = 'https://bugyo.hac.%s.eshop.nintendo.net/shogun/v1/titles/%d/aocs?shop_id=%d&lang=%s&country=%s' % ( Config.cdn.environment, baseNsuid, shop_id, countryLanguage(region), region) #print(url) #exit(0) j = makeJsonRequest( 'GET', url, {}, '%d/%s/%s/titles/aocs/%d.json' % (shop_id, countryLanguage(region), region, baseNsuid)) if not j: break total = int(j['total']) if total == 0: break try: for i in j['contents']: title = Titles.getNsuid(i['id']) n = getDlcByNsuid(i['id']) if title: title.parseShogunJson(n, region) rt = Title.Title() rt.setId(title.id) rt.setRegion(region) rt.parseShogunJson(n) Titles.set(title.id, rt, region) else: try: if n and len(n["applications"]) > 0: titleId = n["applications"][0]["id"].upper() if titleId: if Titles.contains(titleId): title = Titles.get(titleId) title.setId(titleId) title.parseShogunJson(n, region) #print('existing title found!') else: title = Title.Title() title.setId(titleId) title.parseShogunJson(n, region) Titles.set(titleId, title) print('added new DLC %s %s' % (title.id, title.name)) rt = Title.Title() rt.setId(titleId) rt.setRegion(region) rt.parseShogunJson(n, region) Titles.set(titleId, rt, region) else: print('Could not get title json!') else: #print('no title id found in json!') pass except Exception as e: #print(str(e)) pass except Exception as e: print(str(e)) raise break offset = offset + len(j['contents'])
def getName(self): baseId = getBaseId(self.id) if hasattr(self, 'isUpdate') and self.isUpdate and Titles.contains(baseId): return (Titles.get(baseId).name or '').replace('\n', ' ') return (self.name or '').replace('\n', ' ')
def getBaseName(self): baseId = getBaseId(self.id) if Titles.contains(baseId): return (Titles.get(baseId).name or '').replace('\n', ' ') return ''
Hex.dump(buf) j.seek(0x28) #j.writeInt64(0) Print.info('min: ' + str(j.readInt64())) #f.flush() #f.close() ''' if args.scrape_shogun: scrapeShogun() if args.scrape_title: initTitles() initFiles() if not Titles.contains(args.scrape_title): Print.error('Could not find title ' + args.scrape_title) else: Titles.get(args.scrape_title).scrape(False) Titles.save() #Print.info(repr(Titles.get(args.scrape_title).__dict__)) pprint.pprint(Titles.get(args.scrape_title).__dict__) if args.scrape or args.scrape_delta: initTitles() initFiles() threads = [] for i in range(scrapeThreads): t = threading.Thread(target=scrapeThread, args=[i, args.scrape_delta])