Full Code of Bob123a1/CDNSP-GUI for AI

master 30479f773d0e cached
59 files
967.0 KB
379.5k tokens
129 symbols
1 requests
Download .txt
Showing preview only (1,033K chars total). Download the full file or copy to clipboard to get everything.
Repository: Bob123a1/CDNSP-GUI
Branch: master
Commit: 30479f773d0e
Files: 59
Total size: 967.0 KB

Directory structure:
gitextract_45569mqu/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── CDNSP-GUI-Bob.py
├── LICENSE
├── README.md
└── locales/
    ├── af/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── ar/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── de/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── el/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── en/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── es/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── fa/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── fr/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── he/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── hu/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── id/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       ├── language.po
    │       └── language_old.po
    ├── it/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       ├── language.po
    │       └── language_old.po
    ├── ja/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       ├── language.po
    │       └── language_.po
    ├── ko/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── ms/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── nl/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── pl/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── pt/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       ├── language.po
    │       ├── language_old.mo
    │       └── language_old.po
    ├── ru/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── th/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── tr/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── vi/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── zh-cn/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    └── zh-tw/
        └── LC_MESSAGES/
            ├── language.mo
            └── language.po

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .gitignore
================================================
# IDE
/.idea/
/*.iml

# language.mo files
#/locales/*/LC_MESSAGES/language.mo


================================================
FILE: CDNSP-GUI-Bob.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Credit:
# Thanks to Zotan (DB and script), Big Poppa Panda, AnalogMan, F!rsT-S0uL
# Design inspiration: Lucas Rey's GUI (https://darkumbra.net/forums/topic/174470-app-cdnsp-gui-v105-download-nsp-gamez-using-a-gui/)
# Thanks to the developer(s) that worked on CDNSP_Next for the cert fix!
# Thanks to the help of devloper NighTime, kvn1351, gizmomelb, theLorknessMonster, vertigo
# CDNSP - GUI - Bob - v6.0.1
import sys
import time
import random
import gettext
import platform
import locale
import json
import os

__gui_version__ = "6.0.2"
__lang_version__ = "1.0.0"

global sys_locale

if platform.system() != 'Darwin':
    sys_locale = locale.getdefaultlocale() # Detect system locale to fix Window size on Chinese Windows Computer
    sys_locale = sys_locale[0]
else:
    sys_locale = "Mac"

if sys_locale != "zh_CN":
    main_win = "1076x684+100+100"
    queue_win = "620x300+1177+100"
    scan_win = "415x100+100+170"
    base64_win = "497x100+95+176"
else:
    main_win = "1250x684+100+100"
    queue_win = "720x300+770+100"
    scan_win = "420x85+100+100"
    base64_win = "500x105+100+100"
        
config = {"Options": {
            "Download_location":  "",
            "NSP_location": "",
            "Game_location": "",
            "NSP_repack":         "True",
            "Mute":               "False",
            "Titlekey_check":     "True",
            "noaria":             "True",
            "Disable_game_image": "False",
            "Shorten": "False",
            "Tinfoil": "False",
            "SysVerZero": "False",
            "Main_win": main_win,
            "Queue_win": queue_win,
            "Update_win": "600x400+120+200",
            "Scan_win": scan_win,
            "Base64_win": base64_win,
            "Language": "en",
            "Mode": "CDNSP",
            "No_demo": "False",
            "No_japanese_games": "False",
            "Disable_description": "False"}
          }

# Check if the GUI config JSON file has the needed keys
f = open("CDNSP-GUI-config.json", 'r')
f_load = json.load(f)
f.close()

if os.path.isfile("CDNSP-GUI-config.json"):
    for json_key in config["Options"]:
        if json_key not in f_load["Options"]:
            print("Missing json key -",json_key,", it has been added in for you")
            f_load["Options"][json_key] = config["Options"][json_key]

    f = open("CDNSP-GUI-config.json", 'w')
    json.dump(f_load, f, indent=4)
    f.close()
else:
    f = open("CDNSP-GUI-config.json", 'w')
    json.dump(config, f, indent=4)
    f.close()    

f = open("CDNSP-GUI-config.json", 'r')

j = json.load(f)

try:
    chosen_lang = j["Options"]["Language"]
except:
    f.close()
    j["Options"]["Language"] = "en" # Default to English language
    with open("CDNSP-GUI-config.json", 'w') as f:
        json.dump(j, f, indent=4)
    f.close()
    chosen_lang = "en"

def set_lang(default_lang = "en"):
    try:
        lang = gettext.translation('language', localedir='locales', languages=[default_lang])
        lang.install()
        print("Current language: {}".format(default_lang))
    except:
        lang = gettext.translation('language', localedir='locales', languages=["en"])
        lang.install()
        print("Language files not available yet!")
set_lang(chosen_lang)

if not os.path.isdir("Config"):
    os.mkdir("Config")

if not os.path.isdir("Images"):
    os.mkdir("Images")

build_text = _("\nBuilding the current state list... Please wait, this may take some time \
depending on how many games you have.")

# Check that user is using Python 3
if (sys.version_info > (3, 0)):
    # Python 3 code in this block
    pass
else:
    # Python 2 code in this block
    print(_("\n\nError - Application launched with Python 2, please install Python 3 and delete Python 2\n"))
    time.sleep(1000)
    sys.exit()

from tkinter import *
import os
from tkinter import messagebox
import tkinter.ttk as ttk
from importlib import util
import subprocess
import urllib.request
import pip
from pathlib import Path

def check_req_file(file):
    if not os.path.exists(file):
        url = 'https://raw.githubusercontent.com/Bob123a1/CDNSP-GUI-Files/master/{}'.format(file)  
        urllib.request.urlretrieve(url, file)

def install_module(module):
    try:
        subprocess.check_output("pip3 install {}".format(module), shell=True)
    except:
        print(_("Error installing {0}, close the application and you can install the module manually by typing in CMD: pip3 install {0}").format(module))

def add_to_installed(tid, ver):
    print(tid, ver)
    installed_tid = []
    installed_ver = []
    if os.path.isfile("Config/installed.txt"):
        file = open("Config/installed.txt", "r", encoding="utf8")
        for game in file.readlines():
            installed_tid.append(game.split(",")[0].strip())
            installed_ver.append(game.split(",")[1].strip())
        file.close()
        if tid in installed_tid:
            if int(ver) > int(installed_ver[installed_tid.index(tid)]):
                installed_ver[installed_tid.index(tid)] = ver
                
        else:
            installed_tid.append(tid)
            installed_ver.append(ver)
        file = open("Config/installed.txt", "w", encoding="utf8")
        for i in range(len(installed_tid)):
            file.write("{}, {}\n".format(installed_tid[i], installed_ver[i]))
        file.close()
    
    
print(_("\nChecking if all required modules are installed!\n\n"))
try:
    import requests
except ImportError:
    install_module("requests")
    import requests       

try:
    from tqdm import tqdm
except ImportError:
    install_module("tqdm")
    from tqdm import tqdm  

try:
    import unidecode   
except ImportError:
    install_module("unidecode")
    import unidecode    

try:
    from PIL import Image, ImageTk   
except ImportError:
    install_module("Pillow")
    from PIL import Image, ImageTk

try:
    from bs4 import BeautifulSoup
except ImportError:
    install_module("beautifulsoup4")
    from bs4 import BeautifulSoup

try:
    import ssl
except:
    install_module("pyopenssl")
    import ssl

ssl._create_default_https_context = ssl._create_unverified_context # Thanks to user rdmrocha on Github

req_file = ["CDNSPconfig.json", "keys.txt", "nx_tls_client_cert.pem", "titlekeys.txt",\
            "titlekeys_overwrite.txt", "Nut_titlekeys.txt", "cert_dead.jpg"]
try:
    for file in req_file:
        check_req_file(file)
    print(_("Everything looks good!"))
except Exception as e:
    print(_("Unable to get required files! Check your internet connection: [{}]").format(str(e)))

# CDNSP script

import argparse
import base64
import platform
import re
import shlex
import xml.dom.minidom as minidom
import xml.etree.ElementTree as ET
from binascii import hexlify as hx, unhexlify as uhx
from io import TextIOWrapper
import os, sys
import subprocess
import urllib3
import json
import shutil
import argparse
import configparser
from hashlib import sha256
from struct import pack as pk, unpack as upk
from binascii import hexlify as hx, unhexlify as uhx
import xml.etree.ElementTree as ET, xml.dom.minidom as minidom    
import re
import datetime
import calendar
import operator
import base64
import shlex
from distutils.version import StrictVersion as StrV
import re
import shutil
import requests
import xml.etree.ElementTree as ET, xml.dom.minidom as minidom
import unicodedata as ud

from tkinter import filedialog
import threading
sys_name = "Win"
global noaria
noaria = True
import webbrowser
titlekey_list = []

global tqdmProgBar
tqdmProgBar = True

sysver0 = False

#Global Vars
truncateName = False
tinfoil = False
enxhop = False
current_mode = ""
nsp_location = ""
pause_download = False
downloading = False # Thanks to rockbass2560 on Github
installed_global = {}

def read_at(f, off, len):
    f.seek(off)
    return f.read(len)

def read_u8(f, off):
    return upk('<B', read_at(f, off, 1))[0]

def read_u16(f, off):
    return upk('<H', read_at(f, off, 2))[0]

def read_u32(f, off):
    return upk('<I', read_at(f, off, 4))[0]
    
def read_u48(f, off):
    s = upk('<HI', read_at(f, off, 6))
    return s[1] << 16 | s[0]

def read_u64(f, off):
    return upk('<Q', read_at(f, off, 8))[0]
    
def sha256_file(fPath):
    f = open(fPath, 'rb')
    fSize = os.path.getsize(fPath)
    hash = sha256()
    
    if fSize >= 10000:
        t = tqdm(total=fSize, unit='B', unit_scale=True, desc=os.path.basename(fPath), leave=False)
        while True:
            buf = f.read(4096)
            if not buf:
                break
            hash.update(buf)
            t.update(len(buf))
        t.close()
    else:
        hash.update(f.read())
    f.close()
    return hash.hexdigest()
    
def bytes2human(n, f='%(value).3f %(symbol)s'):
    n = int(n)
    if n < 0:
        raise ValueError('n < 0')
    symbols = ('B', 'KB', 'MB', 'GB', 'TB')
    prefix = {}
    for i, s in enumerate(symbols[1:]):
        prefix[s] = 1 << (i + 1) * 10
    for symbol in reversed(symbols[1:]):
        if n >= prefix[symbol]:
            value = float(n) / prefix[symbol]
            return f % locals()
    return f % dict(symbol=symbols[0], value=n)

def get_name(tid):
    if current_mode_global == "Nut":
        try:
            if tid.endswith('800'):
                tid = '%s000' % tid[:-3]
            name = title_list[titleID_list.index(tid.lower())]
            name = re.sub(r'[/\\:*?!"|™©®()]+', "", unidecode.unidecode(name))
            print(name)
            return name
        except:
            return "UNKNOWN TITLE"
    elif current_mode_global == "CDNSP":
        try:
            with open('titlekeys.txt',encoding="utf8") as f:
                lines = f.readlines()
        except Exception as e:
            print("Error:", e)
            exit()
        for line in lines:
            if line.strip() == '':
                return
            temp = line.split("|")
            if tid.endswith('800'):
                tid = '%s000' % tid[:-3]
            if tid.strip() == temp[0].strip()[:16]:
                return re.sub(r'[/\\:*?!"|™©®()]+', "", unidecode.unidecode(temp[2].strip()))
    return "UNKNOWN TITLE"
    
def safe_name(name):
    return re.sub('[^\x00-\x7f]', '', ud.normalize('NFD', name))
    
def safe_filename(safe_name):
    return re.sub('[<>.:"/\\|?*]+', '', safe_name)
    
def check_tid(tid):
    return re.match('0100[0-9a-fA-F]{12}', tid)
    
def check_tkey(tkey):
    return re.match('[0-9a-fA-F]{32}', tkey)
    
def load_config(fPath):
    dir = os.path.dirname(__file__)

    config = {'Paths': {
        'hactoolPath': 'hactool',
        'keysPath': 'keys.txt',
        'NXclientPath': 'nx_tls_client_cert.pem',
        'ShopNPath': 'ShopN.pem'},
        'Values': {
            'Region': 'US',
            'Firmware': '5.1.0-3',
            'DeviceID': '6265A5E4140FF804',
            'Environment': 'lp1',
            'TitleKeysURL': '{}'.format(base64.b64decode("aHR0cDovL3NuaXAubGkvbmV3a2V5ZGI=").decode("ascii")),
            'NspOut': '_NSPOUT',
            'AutoUpdatedb': 'False'}}
    try:
        f = open(fPath, 'r')
    except FileNotFoundError:
        f = open(fPath, 'w')
        json.dump(config, f)
        f.close()
        f = open(fPath, 'r')

    j = json.load(f)

    hactoolPath = j['Paths']['hactoolPath']
    keysPath = j['Paths']['keysPath']
    NXclientPath = j['Paths']['NXclientPath']
    ShopNPath = j['Paths']['ShopNPath']

    reg = j['Values']['Region']
    fw = j['Values']['Firmware']
    did = j['Values']['DeviceID']
    env = j['Values']['Environment']
    dbURL = j['Values']['TitleKeysURL']
    nspout = j['Values']['NspOut']

    if platform.system() == 'Linux':
        hactoolPath = './' + hactoolPath + '_linux'

    if platform.system() == 'Darwin':
        hactoolPath = './' + hactoolPath + '_mac'

    return hactoolPath, keysPath, NXclientPath, ShopNPath, reg, fw, did, env, dbURL, nspout
    
def gen_tik(fPath, rightsID, tkey, mkeyrev):
    f = open(fPath, 'wb')

    f.write(b'\x04\x00\x01\x00')
    f.write(0x100 * b'\xFF')
    f.write(0x3C  * b'\x00')
    f.write(b'Root-CA00000003-XS00000020')
    f.write(0x6   * b'\x00')
    f.write(0x20  * b'\x00')
    if tkey:
        f.write(uhx(tkey))
    else:
        f.write(0x10 * b'\x00')
    f.write(0xF0  * b'\x00')
    f.write(b'\x02\x00\x00\x00\x00')
    f.write(pk('<B', int(mkeyrev)))
    f.write(0xA   * b'\x00')
    f.write(0x10  * b'\x00')
    f.write(uhx(rightsID))
    f.write(0x8   * b'\x00')
    f.write(b'\xC0\x02\x00\x00\x00\x00\x00\x00')

    f.close()
    return fPath
    
def gen_cert(fPath):
    f = open(fPath, 'wb')
    f.write(uhx(b'''\
00010003704138efbbbda16a987dd901
326d1c9459484c88a2861b91a312587a
e70ef6237ec50e1032dc39dde89a96a8
e859d76a98a6e7e36a0cfe352ca89305
8234ff833fcb3b03811e9f0dc0d9a52f
8045b4b2f9411b67a51c44b5ef8ce77b
d6d56ba75734a1856de6d4bed6d3a242
c7c8791b3422375e5c779abf072f7695
efa0f75bcb83789fc30e3fe4cc839220
7840638949c7f688565f649b74d63d8d
58ffadda571e9554426b1318fc468983
d4c8a5628b06b6fc5d507c13e7a18ac1
511eb6d62ea5448f83501447a9afb3ec
c2903c9dd52f922ac9acdbef58c60218
48d96e208732d3d1d9d9ea440d91621c
7a99db8843c59c1f2e2c7d9b577d512c
166d6f7e1aad4a774a37447e78fe2021
e14a95d112a068ada019f463c7a55685
aabb6888b9246483d18b9c806f474918
331782344a4b8531334b26303263d9d2
eb4f4bb99602b352f6ae4046c69a5e7e
8e4a18ef9bc0a2ded61310417012fd82
4cc116cfb7c4c1f7ec7177a17446cbde
96f3edd88fcd052f0b888a45fdaf2b63
1354f40d16e5fa9c2c4eda98e798d15e
6046dc5363f3096b2c607a9d8dd55b15
02a6ac7d3cc8d8c575998e7d796910c8
04c495235057e91ecd2637c9c1845151
ac6b9a0490ae3ec6f47740a0db0ba36d
075956cee7354ea3e9a4f2720b26550c
7d394324bc0cb7e9317d8a8661f42191
ff10b08256ce3fd25b745e5194906b4d
61cb4c2e000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
526f6f74000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000001434130303030303030330000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
000000007be8ef6cb279c9e2eee121c6
eaf44ff639f88f078b4b77ed9f9560b0
358281b50e55ab721115a177703c7a30
fe3ae9ef1c60bc1d974676b23a68cc04
b198525bc968f11de2db50e4d9e7f071
e562dae2092233e9d363f61dd7c19ff3
a4a91e8f6553d471dd7b84b9f1b8ce73
35f0f5540563a1eab83963e09be90101
1f99546361287020e9cc0dab487f140d
6626a1836d27111f2068de4772149151
cf69c61ba60ef9d949a0f71f5499f2d3
9ad28c7005348293c431ffbd33f6bca6
0dc7195ea2bcc56d200baf6d06d09c41
db8de9c720154ca4832b69c08c69cd3b
073a0063602f462d338061a5ea6c915c
d5623579c3eb64ce44ef586d14baaa88
34019b3eebeed3790001000100000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00010004969fe8288da6b9dd52c7bd63
642a4a9ae5f053eccb93613fda379920
87bd9199da5e6797618d77098133fd5b
05cd8288139e2e975cd2608003878cda
f020f51a0e5b7692780845561b31c618
08e8a47c3462224d94f736e9a14e56ac
bf71b7f11bbdee38ddb846d6bd8f0ab4
e4948c5434eaf9bf26529b7eb83671d3
ce60a6d7a850dbe6801ec52a7b7a3e5a
27bc675ba3c53377cfc372ebce02062f
59f37003aa23ae35d4880e0e4b69f982
fb1bac806c2f75ba29587f2815fd7783
998c354d52b19e3fad9fbef444c48579
288db0978116afc82ce54dacb9ed7e1b
fd50938f22f85eecf3a4f426ae5feb15
b72f022fb36ecce9314dad131429bfc9
675f58ee000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
526f6f742d4341303030303030303300
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000001585330303030303032300000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
0000000000000000d21d3ce67c1069da
049d5e5310e76b907e18eec80b337c47
23e339573f4c664907db2f0832d03df5
ea5f160a4af24100d71afac2e3ae75af
a1228012a9a21616597df71eafcb6594
1470d1b40f5ef83a597e179fcb5b57c2
ee17da3bc3769864cb47856767229d67
328141fc9ab1df149e0c5c15aeb80bc5
8fc71be18966642d68308b506934b8ef
779f78e4ddf30a0dcf93fcafbfa131a8
839fd641949f47ee25ceecf814d55b0b
e6e5677c1effec6f29871ef29aa3ed91
97b0d83852e050908031ef1abbb5afc8
b3dd937a076ff6761ab362405c3f7d86
a3b17a6170a659c16008950f7f5e06a5
de3e5998895efa7deea060be9575668f
78ab1907b3ba1b7d0001000100000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000'''.replace(b'\n', b'')))
    f.close()
    return fPath
    
def decrypt_NCA(fPath, outDir=''):
    if not outDir:
        outDir = os.path.splitext(fPath)[0]
    os.makedirs(outDir, exist_ok=True)
    
    commandLine = hactoolPath + ' "' + fPath + '"' + keysArg\
                  + ' --exefsdir="'    + outDir + '/exefs"'\
                  + ' --romfsdir="'    + outDir + '/romfs"'\
                  + ' --section0dir="' + outDir + '/section0"'\
                  + ' --section1dir="' + outDir + '/section1"'\
                  + ' --section2dir="' + outDir + '/section2"'\
                  + ' --section3dir="' + outDir + '/section3"'\
                  + ' --header="'      + outDir + '/Header.bin"'
                  
    pipes = subprocess.Popen(commandLine, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    _, std_err = pipes.communicate()

    if pipes.returncode != 0:
        err_msg = '%s. Code: %s' % (std_err.strip(), pipes.returncode)
        raise Exception(err_msg)
    elif len(std_err):
        raise Exception(std_err)
        
    return outDir
    
def get_name_from_nacp(fPath):
    dir = decrypt_NCA(fPath)
    nacpPath = os.path.join(dir, 'romfs', 'control.nacp')
    try: 
        f = open(nacpPath, 'rb')
        name = f.read(0x200).strip(b'\x00').decode()
        f.close()
        return safe_name(name)
    except FileNotFoundError:
        return ''

def cert_dead():
    print("\n\n" + _('Request rejected by server! You may need a new cert.'))

    messagebox.showinfo("",  _('Request rejected by server! You may need a new cert.'))

    if os.path.isfile('cert_dead.jpg'):
        img2 = ImageTk.PhotoImage(Image.open('cert_dead.jpg'))
        imageLabel_widget.configure(image=img2, text="")
        imageLabel_widget.image = img2

    sys.exit()

def make_request(method, url, certificate='', hdArgs={}):
    if edgeToken == None:
        print("\n"+_("Error, Unable to make request without the token"))
        return None

    try:
        expiry_time = int(edgeToken[edgeToken.index("=")+1:edgeToken.index("~")])
    except ValueError:
        print("\n"+_("Your token file is not formatted correctly"))
        return None

    if expiry_time < time.time():
        print("\n"+_("Your token has expired, please get a new token before making request"))
        return None
        
    if not certificate: # Workaround for defining errors
        certificate = NXclientPath

    global fw
    if fw != '6.2.0-1.0':
        fw = '6.2.0-1.0' # Hard coded to the latest Switch firmware version
    
    reqHd = {'X-Nintendo-DenebEdgeToken': edgeToken,
             'User-Agent': 'NintendoSDK Firmware/%s (platform:NX; did:%s; eid:%s)' % (fw, did, env),
             'Accept-Encoding': 'gzip, deflate',
             'Accept': '*/*',
             'Connection': 'keep-alive'}
    reqHd.update(hdArgs)
    
    r = requests.request(method, url, cert=certificate, headers=reqHd, verify=False, stream=True)
    
    if r.status_code == 403:
        print("\n\n" + _('Request rejected by server! You may need a new cert.'))
        return None
    if r.status_code == 404:
        print('Error, File doesn\'t exist on the CDN')
        return None
    
    return r
    
def print_info(tid):
    print('\n%s:' % tid)
    if tid.endswith('000'):
        basetid   = tid
        updatetid = '%016x' % (int(tid, 16) + 0x800)
    elif tid.endswith('800'):
        basetid   = '%016x' % (int(tid, 16) & 0xFFFFFFFFFFFFF000)
        updatetid = tid
    else:
        basetid     = '%016x' % (int(tid, 16) - 0x1000 & 0xFFFFFFFFFFFFF000)
        updatetid   = basetid[:-3] + '800'
        
    try:
        _, name, size = get_info(tid=basetid)
    except requests.exceptions.SSLError:
        print('\tCould not get info from Shogun')
        name = ''
        size = 0
        
    if name:
        print('\tName: %s' % name)
    if size:
        print('\tSize: %s' % bytes2human(size))
        
    if tid != basetid:    
        versions = get_versions(tid)
        print('\n\tAvailable versions for %s:' % tid)
        print('\t\tv' + ' v'.join(versions))
    
    print('\n\tBase TID:   %s' % basetid)
    versions = get_versions(basetid)
    print('\tAvailable versions for %s:' % basetid)
    print('\t\tv' + ' v'.join(versions))
    
    print('\n\tUpdate TID: %s' % updatetid)
    versions = get_versions(updatetid)
    print('\tAvailable versions for %s:' % updatetid)
    if versions:
        print('\t\tv' + ' v'.join(versions))
    else:
        print('\t\t%s has no version available' % updatetid)
    
def get_info(tid='', freeword=''):
    print(tid)
    if tid:
        url = 'https://bugyo.hac.%s.eshop.nintendo.net/shogun/v1/contents/ids?shop_id=4&lang=en&country=%s&type=title&title_ids=%s'\
            % (env, reg, tid)
        r = make_request('GET', url, certificate=ShopNPath)
        j = r.json()
        nsuid = j['id_pairs'][0]['id']
        print(nsuid)
    elif freeword:
        url = 'https://bugyo.hac.lp1.eshop.nintendo.net/shogun/v1/titles?shop_id=4&lang=en&offset=0&country=%s&limit=25&sort=popular&freeword=%s'\
               % (reg, freeword)
        r = make_request('GET', url, certificate=ShopNPath)
        j = r.json()
        nsuid = j['contents'][0]['id']
    
    try:
        url = 'https://bugyo.hac.%s.eshop.nintendo.net/shogun/v1/titles/%s?shop_id=4&lang=en&country=%s' % (env, nsuid, reg)
        r = make_request('GET', url, certificate=ShopNPath)
        j = r.json()
        
        if freeword:
            try:
                tid = j['applications'][0]['id']
            except KeyError:
                print('Found no result for %s on Shogun' % freeword)
                raise
        
        try:
            name = j['formal_name']
            name = safe_name(name)
        except IndexError:
            name = ''
        
        try:
            size = j['total_rom_size']
        except IndexError:
            size = 0
            
    except IndexError:
        print('\tTitleID not found on Shogun!')
        
    return tid, name, size
    
def get_versions(tid):
##    url = 'https://tagaya.hac.%s.eshop.nintendo.net/tagaya/hac_versionlist' % env
    url = 'https://superfly.hac.%s.d4c.nintendo.net/v1/t/%s/dv' % (env,tid)
    r = make_request('GET', url)
    try:
        j = r.json()
    except AttributeError:
        return "none"
    print(j)

    try:
        if j['error']:
            return ['none']
    except Exception as e:
        pass
    try:
        lastestVer = j['version']
        if lastestVer < 65536:
            return ['%s' % lastestVer]
        else:
            versionList = ('%s' % "-".join(str(i) for i in range(0x10000, lastestVer+1, 0x10000))).split('-')
            return versionList
    except Exception as e:
        return ['none']
       
def check_versions(fPath):
    f = open(fPath, 'r')

    old = {}
    for line in f.readlines():
            tid, ver = line.strip().split('-')
            old[tid] = ver
    
    n = 1
    new = {}
    for tid in old:
        sys.stdout.write('\rChecking for updates for title %s of %s...' % (n, len(old)))
        sys.stdout.flush()
        n += 1
        
        latestVer = get_versions(tid)[-1]
        
        if latestVer and int(latestVer) > int(old[tid]):
            new[tid] = latestVer

        if tid.endswith('000'):
            updatetid = '%016x'.lower() % (int(tid, 16) + 0x800)
            if updatetid not in old:
                updateVer = get_versions(updatetid)
                if updateVer:
                    new[updatetid] = updateVer[-1]
    sys.stdout.write('\r\033[F')
    
    if new:
        for tid in new:
            print('New update available for %s: v%s' % (tid, new[tid]))
        
        dl = input('\nType anything to download the new updates: ')
        if dl:
            for tid in new:
                download_game(tid, new[tid], nspRepack=True, verify=True)
    else:
        print('No new update was found for any of the downloaded titles!')
            
    f.close()

def download_file(url, fPath, fSize=0):
    fName = os.path.basename(fPath).split()[0]
    
    if os.path.exists(fPath) and fSize != 0:
        dlded = os.path.getsize(fPath)
            
        if dlded == fSize:
            print('\t\tDownload is already complete, skipping!')
            return fPath
        elif dlded < fSize:
            print('\t\tResuming download...')
            r = make_request('GET', url, hdArgs={'Range': 'bytes=%s-' % dlded})
            f = open(fPath, 'ab')
        else:
            print('\t\tExisting file is bigger than expected (%s/%s), restarting download...' % (dlded, fSize))
            dlded = 0
            r = make_request('GET', url)
            f = open(fPath, "wb")
    else:
        dlded = 0
        r = make_request('GET', url)
        fSize = int(r.headers.get('Content-Length'))
        f = open(fPath, 'wb')
        
    if fSize >= 10000:
        t = tqdm(initial=dlded, total=int(fSize), desc=fName, unit='B', unit_scale=True, leave=False, mininterval=0.5)
        global downloading
        downloading = True
        for chunk in r.iter_content(4096):
            f.write(chunk)
            dlded += len(chunk)
            t.update(len(chunk))
            if pause_download:
                while pause_download:
                    time.sleep(.5)
        downloading = False
        t.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('\r\t\tSaved to %s!' % os.path.basename(f.name))
    return fPath
    
def download_cetk(rightsID, fPath):
    url = 'https://atum.hac.%s.d4c.nintendo.net/r/t/%s?device_id=%s' % (env, rightsID, did)
    r = make_request('HEAD', url)
    id = r.headers.get('X-Nintendo-Content-ID')
    
    url = 'https://atum.hac.%s.d4c.nintendo.net/c/t/%s?device_id=%s' % (env, id, did)
    cetk = download_file(url, fPath, fSize=2496)
    
    return cetk
        
def download_title(gameDir, tid, ver, tkey='', nspRepack=False, verify=False, n=''):
    print('\n%s v%s:' % (tid, ver))
    if len(tid) != 16:
        tid = (16-len(tid)) * '0' + tid
    
    url = 'https://atum%s.hac.%s.d4c.nintendo.net/t/a/%s/%s?device_id=%s' % (n, env, tid, ver, did)
    r = make_request('HEAD', url)
    if r == None:
        return (None, "")
    CNMTid = r.headers.get('X-Nintendo-Content-ID')
    
    print('\tDownloading CNMT (%s.cnmt.nca)...' % CNMTid)
    url = 'https://atum%s.hac.%s.d4c.nintendo.net/c/a/%s?device_id=%s' % (n, env, CNMTid, did)
    fPath = os.path.join(gameDir, CNMTid + '.cnmt.nca')
    cnmtNCA = download_file(url, fPath)
    
    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:
        outf = os.path.join(gameDir, '%s.xml' % os.path.basename(cnmtNCA).strip('.nca'))
        cnmtXML = CNMT.gen_xml(cnmtNCA, outf)
        
        rightsID = '%032x' % ((int(tid, 16) << 64) + int(CNMT.mkeyrev))
        
        tikPath  = os.path.join(gameDir, rightsID+'.tik')
        certPath = os.path.join(gameDir, rightsID+'.cert')
        if CNMT.type == 'Application' or CNMT.type == 'AddOnContent':
            gen_cert(certPath)
            gen_tik(tikPath, rightsID, tkey, CNMT.mkeyrev)
            
            print('\t\tGenerated %s and %s!' % (os.path.basename(certPath), os.path.basename(tikPath)))
        elif CNMT.type == 'Patch':
            print('\tDownloading CETK...')
            
            with open(download_cetk(rightsID, os.path.join(gameDir, rightsID+'.cetk')), 'rb') as cetk:
                cetk.seek(0x180)
                tkey = hx(cetk.read(0x10)).decode()
                print('\t\t\tTitlekey: %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('\t\tExtracted %s and %s from CETK!' % (os.path.basename(certPath), os.path.basename(tikPath)))
            
    NCAs = {
        0: [],
        1: [],
        2: [],
        3: [],
        4: [],
        5: [],
        6: [],
    }
    
    name = ''
    for type in [0, 3, 4, 5, 1, 2, 6]: # Download smaller files first
        list = CNMT.parse(CNMT.contentTypes[type])
        for ncaID in list:
            print('\tDownloading %s entry (%s.nca)...' % (CNMT.contentTypes[type], ncaID))
            url = 'https://atum%s.hac.%s.d4c.nintendo.net/c/c/%s?device_id=%s' % (n, env, ncaID, did)
            fPath = os.path.join(gameDir, ncaID + '.nca')
            fSize = list[ncaID][1]
            
            NCAs[type].append(download_file(url, fPath, fSize))
            print(list[ncaID][2])
            if verify:
                print('\t\tVerifying file...')
                if sha256_file(fPath) == list[ncaID][2]:
                    print('\t\t\tHashes match, file is correct!')
                else:
                    print('\t\t\t%s is corrupted, hashes don\'t match!' % os.path.basename(fPath))
           
            if type == 3:
                name = get_name_from_nacp(NCAs[type][-1])
    
    if not name:
        name = ''
        
    if nspRepack:
        files = []
        if tkey:
            files.append(certPath)
            files.append(tikPath)
        for key in [1, 5, 2, 4, 6]:
            if NCAs[key]:
                files.extend(NCAs[key])
        files.append(cnmtNCA)
        files.append(cnmtXML)
        if NCAs[3]:
            files.extend(NCAs[3])
        
        return files, name
    else:
        return gameDir, name

def download_title_tinfoil(gameDir, tid, ver, tkey='', nspRepack=False, n='', verify=False):
    print('\n%s v%s:' % (tid, ver))
    tid = tid.lower();
    tkey = tkey.lower();
    if len(tid) != 16:
        tid = (16 - len(tid)) * '0' + tid

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

    if CNMTid == None:
        print('title not available on CDN')
        return None

    print('\nDownloading CNMT (%s.cnmt.nca)...' % CNMTid)
    url = 'https://atum%s.hac.%s.d4c.nintendo.net/c/a/%s?device_id=%s' % (n, env, CNMTid, did)
    fPath = os.path.join(gameDir, CNMTid + '.cnmt.nca')
    cnmtNCA = download_file(url, fPath)
    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_tinfoil(cnmtNCA, outf)

        rightsID = '%s%s%s' % (tid, (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[0x286] = int(CNMT.mkeyrev)
                    data[0x2A0:0x2B0] = uhx(rightsID)

                    with open(tikPath, 'wb') as outtik:
                        outtik.write(data)
                print('\nGenerated %s and %s!' % (os.path.basename(certPath), os.path.basename(tikPath)))
            else:
                print('\nGenerated %s!' % os.path.basename(certPath))
        elif CNMT.type == 'Patch':
            print('\nDownloading 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('\nTitlekey: %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('\nExtracted %s and %s from cetk!' % (os.path.basename(certPath), os.path.basename(tikPath)))

    NCAs = {}
    for type in [3]:  # Download smaller files first
        for ncaID in CNMT.parse(CNMT.ncaTypes[type]):
            print('\nDownloading %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, did)
            fPath = os.path.join(gameDir, ncaID + '.nca')
            NCAs.update({type: download_file(url, fPath)})
            if verify:
                if calc_sha256(fPath) != CNMT.parse(CNMT.ncaTypes[type])[ncaID][2]:
                    print('\n\n%s is corrupted, hashes don\'t match!' % os.path.basename(fPath))
                else:
                    print('\nVerified %s...' % os.path.basename(fPath))

    if nspRepack == True:
        files = []
        files.append(certPath)
        if tkey != '':
            files.append(tikPath)
        files.append(cnmtXML)
        try:
            files.append(NCAs[3])
        except KeyError:
            pass

        return files
    
def download_game(tid, ver, tkey='', nspRepack=False, verify=False, clean=False, path_Dir="", nsp_dir=""):
    read_installed()
    if tid in installed_global:
        try:
            existing_ver = installed_global[tid]
            existing_ver = int(existing_ver)
        except:
            existing_ver = 0
        if int(ver) <= existing_ver:
            print("\nAlready have version: {} for this TID: {}".format(ver, tid))
            return None

    nsp_dir = nsp_location
    name = get_name(tid)
    status_label.config(text=_("Downloading: ")+name)
    global titlekey_check
    gameType = ''
    basetid = ''
    
    if ver == "none":
        ver = 0
    
    if name == 'Unknown Title':
        temp = "[" + tid + "]"
    else:
        temp = name + " [" + tid + "]"

    if tid.endswith('000'):  # Base game
        gameType = 'BASE'
    elif tid.endswith('800'):  # Update
        basetid = '%s000' % tid[:-3]
        gameType = 'UPD'
    else:  # DLC
        basetid = '%s%s000' % (tid[:-4], str(int(tid[-4], 16) - 1))
        gameType = 'DLC'
        
    if path_Dir == "":
        path_Dir = os.path.join(os.path.dirname(__file__), "_NSPOUT")

    if nsp_dir == "":
        nsp_dir = os.path.join(os.path.dirname(__file__), "_NSPOUT")
    
    gameDir = os.path.join(path_Dir, tid)

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

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

   
    if name != "":
        if gameType == "DLC":
            outf = os.path.join(outputDir, '%s [%s][v%s]' % (name,tid,ver))
        elif gameType == 'BASE':
            outf = os.path.join(outputDir, '%s [%s][v%s]' % (name,tid,ver))
        else:
            outf = os.path.join(outputDir, '%s [%s][%s][v%s]' % (name,gameType,tid,ver))
    else:
        if gameType == "DLC":
            outf = os.path.join(outputDir, '%s [%s][v%s]' % (name,tid,ver))
        elif gameType == 'BASE':
            outf = os.path.join(outputDir, '%s [v%s]' % (tid,ver))
        else:
            outf = os.path.join(outputDir, '%s [%s][v%s]' % (tid,gameType,ver))
    outname = outf.split(outputDir)[1][1:]

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

    if tinfoil:
        outf = outf + '[tf]'

    outf = outf + '.nsp'
    
    if current_mode == "Nut":
        if not tid.endswith("800"):
            if tkey == "00000000000000000000000000000000":
                outf = outf[0:-4] + '.nsx'
                tkey = "00000000000000000000000000000000"

    for item in os.listdir(outputDir):
        if item.find('%s' % tid) != -1:
            if item.find('v%s' % ver) != -1:
                if not tinfoil:
                    print('%s already exists, skipping download' % outf)
                    shutil.rmtree(gameDir)
                    return
    os.makedirs(gameDir, exist_ok=True)
    
    if tid.endswith('800'):
        basetid = '%016x' % (int(tid, 16) & 0xFFFFFFFFFFFFF000)
    elif not tid.endswith('000'):
        basetid = '%016x' % (int(tid, 16) - 0x1000 & 0xFFFFFFFFFFFFF000)
    else:
        basetid = tid
    if tinfoil:
        files = download_title_tinfoil(gameDir, tid, ver, tkey, nspRepack, verify=verify)
    else:
        files, name = download_title(gameDir, tid, ver, tkey, nspRepack, verify)

    if files == None:
        shutil.rmtree(gameDir)
        return
    
    if nspRepack:
        os.makedirs(path_Dir, exist_ok=True)
        NSP = nsp(outf, files)
        NSP.repack()
        shutil.rmtree(gameDir, ignore_errors=True)

    add_to_installed(tid, ver)
    status_label.config(text=_("Download finished!"))
    return gameDir
    
def download_sysupdate(ver):
    if ver == 'LTST':
        url = 'https://sun.hac.%s.d4c.nintendo.net/v1/system_update_meta?device_id=%s' % (env, did)
        r = make_request('GET', url)
        j = r.json()
        ver = str(j['system_update_metas'][0]['title_version'])
    
    sysupdateDir = os.path.join(os.path.dirname(__file__), '0100000000000816-SysUpdate', ver)
    os.makedirs(sysupdateDir, exist_ok=True)
    
    url = 'https://atumn.hac.%s.d4c.nintendo.net/t/s/0100000000000816/%s?device_id=%s' % (env, ver, did)
    r = make_request('HEAD', url)
    cnmtID = r.headers.get('X-Nintendo-Content-ID')
    
    print('\nDownloading CNMT (%s)...' % cnmtID)
    url = 'https://atumn.hac.%s.d4c.nintendo.net/c/s/%s?device_id=%s' % (env, cnmtID, did)
    fPath = os.path.join(sysupdateDir, '%s.cnmt.nca' % cnmtID)
    cnmtNCA = download_file(url, fPath)
    
    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'))
    
    titles = CNMT.parse()
    for title in titles:
        dir = os.path.join(sysupdateDir, title)
        os.makedirs(dir, exist_ok=True)
        download_title(dir, title, titles[title][0], n='n')
        
    return sysupdateDir    
    
class cnmt:
    titleTypes = {
        0x1: 'SystemProgram',
        0x2: 'SystemData',
        0x3: 'SystemUpdate',
        0x4: 'BootImagePackage',
        0x5: 'BootImagePackageSafe',
        0x80:'Application',
        0x81:'Patch',
        0x82:'AddOnContent',
        0x83:'Delta'
    }
    contentTypes  = {
        0:'Meta', 
        1:'Program', 
        2:'Data', 
        3:'Control', 
        4:'HtmlDocument', 
        5:'LegalInformation', 
        6:'DeltaFragment'
    }

    def __init__(self, fPath, hdPath):                    
        f = open(fPath, 'rb')
        
        self.path     = fPath
        self.type     = self.titleTypes[read_u8(f, 0xC)]
        self.id       = '%016x' % read_u64(f, 0x0)
        self.ver      = str(read_u32(f, 0x8))
        self.sysver   = str(read_u64(f, 0x28))
        self.dlsysver = str(read_u64(f, 0x18))
        self.digest   = hx(read_at(f, f.seek(0, 2)-0x20, f.seek(0, 2))).decode()

        self.packTypes = {0x1: 'SystemProgram',
                          0x2: 'SystemData',
                          0x3: 'SystemUpdate',
                          0x4: 'BootImagePackage',
                          0x5: 'BootImagePackageSafe',
                          0x80: 'Application',
                          0x81: 'Patch',
                          0x82: 'AddOnContent',
                          0x83: 'Delta'}

        self.ncaTypes = {0: 'Meta', 1: 'Program', 2: 'Data', 3: 'Control',
                         4: 'HtmlDocument', 5: 'LegalInformation', 6: 'DeltaFragment'}

        
        with open(hdPath, 'rb') as ncaHd:
            self.mkeyrev = str(read_u8(ncaHd, 0x220))
        
        f.close()

    def parse(self, contentType=''):
        f = open(self.path, 'rb')
        
        data = {}
        if self.type == 'SystemUpdate':
            metaEntriesNB = read_u16(f, 0x12)
            for n in range(metaEntriesNB):
                offset = 0x20 + 0x10*n
                tid  = '%016x' % read_u64(f, offset)
                ver  = str(read_u32(f, offset+0x8))
                titleType = self.titleTypes[read_u8(f, offset+0xC)]
                
                data[tid] = ver, titleType
        else:
            tableOffset = read_u16(f,0xE)
            contentEntriesNB = read_u16(f, 0x10)
            for n in range(contentEntriesNB):
                offset = 0x20 + tableOffset + 0x38*n
                hash = hx(read_at(f, offset, 0x20)).decode()
                tid  = hx(read_at(f, offset+0x20, 0x10)).decode()
                size = read_u48(f, offset+0x30)
                type = self.contentTypes[read_u16(f, offset+0x36)]
                
                if type == contentType or contentType == '':
                    data[tid] = type, size, hash
    
        f.close()
        return data
     
    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 tid in data:
            locals()["Content"+str(n)] = ET.SubElement(ContentMeta, 'Content')
            ET.SubElement(locals()["Content"+str(n)], 'Type').text          = data[tid][0]
            ET.SubElement(locals()["Content"+str(n)], 'Id').text            = tid
            ET.SubElement(locals()["Content"+str(n)], 'Size').text          = str(data[tid][1])
            ET.SubElement(locals()["Content"+str(n)], 'Hash').text          = data[tid][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('\t\tGenerated %s!' % os.path.basename(outf))
        return outf

    def gen_xml_tinfoil(self, ncaPath, outf):
        data = self.parse()
        hdPath = os.path.join(os.path.dirname(ncaPath),
                              '%s.cnmt' % os.path.basename(ncaPath).split('.')[0], 'Header.bin')
        print(hdPath)
        with open(hdPath, 'rb') as ncaHd:
            mKeyRev = str(read_u8(ncaHd, 0x220))
        print(mKeyRev)
        ContentMeta = ET.Element('ContentMeta')

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

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

        # cnmt.nca itself
        hash = sha256()
        with open(ncaPath, 'rb') as nca:
            hash.update(nca.read())  # Buffer not needed
        ET.SubElement(ContentMeta, 'Digest').text = self.digest
        ET.SubElement(ContentMeta, 'KeyGenerationMin').text = self.mkeyrev
        global sysver0
        ET.SubElement(ContentMeta, 'RequiredSystemVersion').text = ('0' if sysver0 else self.sysver)
        if self.id.endswith('800'):
            ET.SubElement(ContentMeta, 'PatchId').text = '0x%s000' % self.id[:-3]
        else:
            ET.SubElement(ContentMeta, 'PatchId').text = '0x%s800' % self.id[:-3]
        string = ET.tostring(ContentMeta, encoding='utf-8')
        reparsed = minidom.parseString(string)
        with open(outf, 'w') as f:
            f.write(reparsed.toprettyxml(encoding='utf-8', indent='  ').decode()[:-1])

        print('\nGenerated %s!' % os.path.basename(outf))
        return outf

class nsp:
    def __init__(self, outf, files):
        self.path = outf
        self.files = files
        
    def repack(self):
        print('\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('\t\tRepack %s is already complete!' % self.path)
            return
            
        t = tqdm(total=totSize, unit='B', unit_scale=True, desc=os.path.basename(self.path), leave=False)
        
        t.write('\t\tWriting header...')
        outf = open(self.path, 'wb')
        outf.write(hd)
        t.update(len(hd))
        
        for file in self.files:
            t.write('\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()
        
        outf.close()
        print('\t\tRepacked to %s!' % outf.name)
        
    def _gen_header(self):
        filesNb = len(self.files)
        stringTable = '\x00'.join(os.path.basename(file) for file in self.files)
        headerSize = 0x10 + filesNb*0x18 + len(stringTable)
        remainder = 0x10 - headerSize%0x10
        headerSize += remainder
        
        fileSizes = [os.path.getsize(file) for file in self.files]
        fileOffsets = [sum(fileSizes[:n]) for n in range(filesNb)]
        
        fileNamesLengths = [len(os.path.basename(file))+1 for file in self.files] # +1 for the \x00
        stringTableOffsets = [sum(fileNamesLengths[:n]) for n in range(filesNb)]
        
        header =  b''
        header += b'PFS0'
        header += pk('<I', filesNb)
        header += pk('<I', len(stringTable)+remainder)
        header += b'\x00\x00\x00\x00'
        for n in range(filesNb):
            header += pk('<Q', fileOffsets[n])
            header += pk('<Q', fileSizes[n])
            header += pk('<I', stringTableOffsets[n])
            header += b'\x00\x00\x00\x00'
        header += stringTable.encode()
        header += remainder * b'\x00'
        
        return header

  
# End of CDNSP script
# --------------------------
# GUI code begins

def game_image(tid, ver, tkey="", nspRepack=False, n='',verify=False):
    import glob
    if not os.path.isdir("Images"):
        os.mkdir("Images")
    if not os.path.isdir("Images/{}".format(tid)):
        os.mkdir("Images/{}".format(tid))
    
    gameDir = "Images/{}".format(tid)
    
    if os.path.isdir(os.path.dirname(os.path.abspath(__file__))+'/Images/{}/section0/'.format(tid)):
        for fname in os.listdir(os.path.dirname(os.path.abspath(__file__))+'/Images/{}/section0/'.format(tid)):
            if fname.endswith('.jpg'):
                return (gameDir, "Exist")
        
    tid = tid.lower();
    tkey = tkey.lower();
    if len(tid) != 16:
        tid = (16-len(tid)) * '0' + tid
        
    url = 'https://atum%s.hac.%s.d4c.nintendo.net/t/a/%s/%s?device_id=%s' % (n, env, tid, ver, did)
    check = False
    try:
        r = make_request('HEAD', url)
        check = True
    except Exception as e:
        print(e)

    if r == None:
        return ("", "Error")
    
    if check:
        CNMTid = r.headers.get('X-Nintendo-Content-ID')

        if CNMTid is None:
            print("not a valid title")
            return ("", "Error")
            
        fPath = os.path.join(gameDir, CNMTid + '.cnmt.nca')
        cnmtNCA = download_file(url, fPath)
        cnmtDir = decrypt_NCA(cnmtNCA)
    ##    print(os.path.join(cnmtDir, 'section0', os.listdir(os.path.join(cnmtDir, 'section0'))[0]), 
    ##                os.path.join(cnmtDir, 'Header.bin'))
    ##    sys.exit()
        CNMT = cnmt(os.path.join(cnmtDir, 'section0', os.listdir(os.path.join(cnmtDir, 'section0'))[0]), 
                    os.path.join(cnmtDir, 'Header.bin'))
        
        NCAs = {
            0: [],
            1: [],
            2: [],
            3: [],
            4: [],
            5: [],
            6: [],
        }
        for type in [3]: # Download smaller files first
            list = CNMT.parse(CNMT.contentTypes[type])
            for ncaID in list:
                print('\tDownloading %s entry (%s.nca)...' % (CNMT.contentTypes[type], ncaID))
                url = 'https://atum%s.hac.%s.d4c.nintendo.net/c/c/%s?device_id=%s' % (n, env, ncaID, did)
                fPath = os.path.join(gameDir, "control" + '.nca')
                fSize = list[ncaID][1]
                
                NCAs[type].append(download_file(url, fPath, fSize))
                
                if verify:
                    print('\t\tVerifying file...')
                    if sha256_file(fPath) == list[ncaID][2]:
                        print('\t\t\tHashes match, file is correct!')
                    else:
                        print('\t\t\t%s is corrupted, hashes don\'t match!' % os.path.basename(fPath))
        return (gameDir, "N")
    else:
        print("UnboundLocalError: local variable 'r' referenced before assignment\nIncorrect TID? Skipping")
        return (gameDir, "Error")
    
def read_game_info():
    try:
        file = open("Config/Game_info.json", "r", encoding="utf8")
        global game_info_json
        game_info_json = json.load(file)
        file.close()
    except:
        print(_("Missing Game_info.json file in the Config folder, attempting to download one for you"))
        urllib.request.urlretrieve("https://raw.githubusercontent.com/Bob123a1/CDNSP-GUI-Files/master/Config/Game_info.json", "Config/Game_info.json")

        if os.path.isfile("Config/Game_info.json"):
            file = open("Config/Game_info.json", "r", encoding="utf8")
            game_info_json = json.load(file)
            file.close()
        else:
            file = open("Config/Game_info.json", "w", encoding="utf8")
            file.write("{}")
            file.close()
            read_game_info()

read_game_info()

def updateJsonFile(key, value, root=""):
    if os.path.isfile("CDNSP-GUI-config.json"):
        with open("CDNSP-GUI-config.json", "r") as jsonFile:
            data = json.load(jsonFile)

        data["Options"]["{}".format(key)] = value

        with open("CDNSP-GUI-config.json", "w") as jsonFile:
            json.dump(data, jsonFile, indent=4)
    else:
        print(_("Error!, Missing CDNSP-GUI-config.json file"))

    if key == "Language":
        root.destroy()
        set_lang(value)
        main()

def get_current_mode():
    try:
        file = open("CDNSP-GUI-config.json", "r", encoding="utf8")
        f = json.load(file)
        file.close()
        return f["Options"]["Mode"]
    except:
        print(_("There's a problem with your GUI config json file, try to delete it and restart the GUI"))

def GUI_config(fPath):
    if sys_locale != "zh_CN":
        main_win = "1076x684+100+100"
        queue_win = "620x300+1177+100"
        scan_win = "415x100+100+170"
        base64_win = "497x100+95+176"
    else:
        main_win = "1250x684+100+100"
        queue_win = "720x300+770+100"
        scan_win = "420x85+100+100"
        base64_win = "500x105+100+100"

    # Set the default settings for the CDNSP GUI config file
    config = {"Options": {
                "Download_location":  "",
                "NSP_location": "",
                "Game_location": "",
                "NSP_repack":         "True",
                "Mute":               "False",
                "Titlekey_check":     "True",
                "noaria":             "True",
                "Disable_game_image": "False",
                "Shorten": "False",
                "Tinfoil": "False",
                "SysVerZero": "False",
                "Main_win": main_win,
                "Queue_win": queue_win,
                "Update_win": "600x400+120+200",
                "Scan_win": scan_win,
                "Base64_win": base64_win,
                "Language": "en",
                "Mode": "CDNSP",
                "No_demo": "False",
                "No_japanese_games": "False",
                "Disable_description": "False"}}

    try:
        f = open(fPath, 'r')
    except FileNotFoundError:
        f = open(fPath, 'w')
        json.dump(config, f, indent=4)
        f.close()
        f = open(fPath, 'r')

    j = json.load(f)

    def str2bool(v):
        return v.lower() == "true"
    
    download_location  = j['Options']['Download_location']
    try:
        nsp_location = j['Options']['NSP_location']
    except KeyError:
        nsp_location = ""
    game_location = j['Options']['Game_location']
    repack  = str2bool(j['Options']['NSP_repack'])
    mute  = str2bool(j['Options']['Mute'])
    titlekey_check  = str2bool(j['Options']['Titlekey_check'])
    noaria  = str2bool(j['Options']['noaria'])
    disable_game_image  = str2bool(j['Options']['Disable_game_image'])
    shorten = str2bool(j['Options']['Shorten'])
    tinfoil = str2bool(j['Options']['Tinfoil'])
    sysver0 = str2bool(j['Options']['SysVerZero'])
    main_win  = j['Options']['Main_win']
    queue_win  = j['Options']['Queue_win']
    update_win  = j['Options']['Update_win']
    scan_win = j['Options']['Scan_win']
    base64_win = j['Options']['Base64_win']
    language = j['Options']['Language']
    mode = j['Options']['Mode']
    no_demo = str2bool(j['Options']['No_demo'])
    no_japanese_games = str2bool(j['Options']['No_japanese_games'])
    disable_description = str2bool(j['Options']['Disable_description'])
    
    if not os.path.exists(download_location): # If the custom download directory doesn't exist then use default path
        download_location = ""
        updateJsonFile("Download_location", download_location)
    return download_location, nsp_location, game_location, repack, mute, titlekey_check, noaria, \
           disable_game_image, shorten, tinfoil, sysver0, main_win, queue_win, update_win, \
           scan_win, base64_win, language, mode, no_demo, no_japanese_games, disable_description

class Application():

    def __init__(self, root, titleID, titleKey, title, dbURL, info_list=None):

        global main_win
        global queue_win
        global update_win_size
        global scan_win
        global base64_win
        global langauge
        global nsp_location
        
        configGUIPath = os.path.join(os.path.dirname(__file__), 'CDNSP-GUI-config.json') # Load config file
        self.path, nsp_location, self.game_location, self.repack, self.mute, self.titlekey_check, noaria_temp, \
                   self.game_image_disable, shorten_temp, tinfoil_temp, sysver0_temp, main_win, \
                   queue_win, update_win, scan_win, \
                   base64_win, language, self.current_mode, no_demo, \
                   no_japanese_games, self.game_desc_disable = GUI_config(configGUIPath) # Get config values

        
        update_win_size = update_win
        
        self.root = root
        self.root.geometry(main_win)
        self.titleID = titleID
        self.titleKey = titleKey
        self.title = title
        self.first_queue = True
        self.queue_list = []
        self.persistent_queue = []
        self.db_URL = dbURL
        self.current_status = []
        self.info_list = info_list
        self.auto_shutdown = False
        
        self.listWidth = 67
        global current_mode
        current_mode = self.current_mode
        global sys_name
        
##        global save_game_folder
##        save_game_folder = False -- To be worked on in the future
        
        global titlekey_check
        titlekey_check = self.titlekey_check

        if platform.system() == 'Linux':
            sys_name = "Linux"

        if platform.system() == 'Darwin':
            sys_name = "Mac"
            self.listWidth = 39
        self.sys_name = sys_name

        global noaria
        noaria = True

        global truncateName
        truncateName = shorten_temp
        
        global tinfoil
        tinfoil = tinfoil_temp

        global sysver0
        sysver0 = sysver0_temp

        # Top Menu bar
        self.menubar = Menu(self.root)

        # Download Menu Tab
        self.downloadMenu = Menu(self.menubar, tearoff=0)
        self.downloadMenu.add_command(label=_("Select Download Location"), command=self.change_dl_path)
        self.downloadMenu.add_command(label=_("Select NSP Repack Location"), command=self.change_nsp_path)
        self.downloadMenu.add_separator() # Add separator to the menu dropdown
        self.downloadMenu.add_command(label=_("Preload Game Images"), command=self.preload_images)
        self.downloadMenu.add_command(label=_("Update Version List"), command=self.update_ver_list)
##        self.downloadMenu.add_command(label=_("Preload Game Descriptions"), command=self.preload_desc)
        # Not if I want to preload game description, since some games can't be found on the CDN
        self.downloadMenu.add_separator() # Add separator to the menu dropdown
        self.downloadMenu.add_command(label=_("Load Saved Queue"), command=self.import_persistent_queue)
        self.downloadMenu.add_command(label=_("Save Queue"), command=self.export_persistent_queue)
        self.downloadMenu.add_separator() # Add separator to the menu dropdown
        self.downloadMenu.add_command(label=_("Download All"), command=self.download_all_games)

        # Options Menu Tab
        self.optionMenu = Menu(self.menubar, tearoff=0)
        self.optionMenu.add_command(label=_("DISABLE GAME DESCRIPTION"), command=self.disable_game_description)
        self.optionMenu.add_command(label=_("DISABLE GAME IMAGE"), command=self.disable_game_image)
        self.optionMenu.add_separator() # Add separator to the menu dropdown
        self.optionMenu.add_command(label=_("Mute All Pop-ups"), command=self.mute_all)
        self.optionMenu.add_command(label=_("Disable NSP Repack"), command=self.nsp_repack_option)
        self.optionMenu.add_command(label=_("Disable Titlekey check"), command=self.titlekey_check_option)
        self.optionMenu.add_separator() # Add separator to the menu dropdown
        self.optionMenu.add_command(label=_("Enable Shorten Name"), command=self.shorten)
        self.optionMenu.add_command(label=_("Enable Tinfoil Download"), command=self.tinfoil_change)
        self.optionMenu.add_command(label=_("Enable SysVer 0 Patch"), command=self.sysver_zero)
        self.optionMenu.add_separator() # Add separator to the menu dropdown
        self.optionMenu.add_command(label=_("Save Windows Location and Size"), command=self.window_save)

        
        # Tool Menu Tab
        self.toolMenu = Menu(self.menubar, tearoff=0)
        self.toolMenu.add_command(label=_("Scan for existing games"), command=self.my_game_GUI)
        self.toolMenu.add_separator() # Add separator to the menu dropdown
        self.toolMenu.add_command(label=_("Base64 Decoder"), command=self.base_64_GUI)
        self.toolMenu.add_command(label=_("Unlock NSX Files"), command=self.unlock_nsx_gui_func)
        self.toolMenu.add_command(label=_("Refresh List"), command=lambda: self.update_list(rebuild=True))
        self.toolMenu.add_command(label=_("Auto Shutdown: OFF"), command=self.shutdown_set)        
        self.toolMenu.add_separator() # Add separator to the menu dropdown
        self.toolMenu.add_command(label=_("Update GUI and Language Files"), command=self.download_update)
        

        # Language Menu Tab
        self.langMenu = Menu(self.menubar, tearoff=0)
        self.langMenu.add_command(label='Afrikaans (Afrikaans)', command=lambda: updateJsonFile('Language', 'af', root=self.root))
        self.langMenu.add_command(label='Arabic (العربية)', command=lambda: updateJsonFile('Language', 'ar', root=self.root))
        self.langMenu.add_command(label='Chinese Simplified (简体中文)', command=lambda: updateJsonFile('Language', 'zh-cn', root=self.root))
        self.langMenu.add_command(label='Chinese Traditional (繁體中文)', command=lambda: updateJsonFile('Language', 'zh-tw', root=self.root))
        self.langMenu.add_command(label='Czech (Čeština)', command=lambda: updateJsonFile('Language', 'cs', root=self.root))
        self.langMenu.add_command(label='Dutch (Nederlands)', command=lambda: updateJsonFile('Language', 'nl', root=self.root))
        self.langMenu.add_command(label='English', command=lambda: updateJsonFile('Language', 'en', root=self.root))
        self.langMenu.add_command(label='French (Français)', command=lambda: updateJsonFile('Language', 'fr', root=self.root))
        self.langMenu.add_command(label='German (Deutsch)', command=lambda: updateJsonFile('Language', 'de', root=self.root))
        self.langMenu.add_command(label='Greek (Ελληνικά)', command=lambda: updateJsonFile('Language', 'el', root=self.root))
        self.langMenu.add_command(label='Hebrew (עברית)', command=lambda: updateJsonFile('Language', 'he', root=self.root))
        self.langMenu.add_command(label='Hungarian (Magyar)', command=lambda: updateJsonFile('Language', 'hu', root=self.root))
        self.langMenu.add_command(label='Indonesian (Bahasa Indonesia)', command=lambda: updateJsonFile('Language', 'id', root=self.root))
        self.langMenu.add_command(label='Italian (Italiano)', command=lambda: updateJsonFile('Language', 'it', root=self.root))
        self.langMenu.add_command(label='Japanese (日本語)', command=lambda: updateJsonFile('Language', 'ja', root=self.root))
        self.langMenu.add_command(label='Korean (한국어)', command=lambda: updateJsonFile('Language', 'ko', root=self.root))
        self.langMenu.add_command(label='Malaysian (Bahasa Melayu)', command=lambda: updateJsonFile('Language', 'ms', root=self.root))
        self.langMenu.add_command(label='Persian (پارسی)', command=lambda: updateJsonFile('Language', 'fa', root=self.root))
        self.langMenu.add_command(label='Polish (Polski)', command=lambda: updateJsonFile('Language', 'pl', root=self.root))
        self.langMenu.add_command(label='Portuguese (Português)', command=lambda: updateJsonFile('Language', 'pt', root=self.root))
        self.langMenu.add_command(label='Russian (Русский)', command=lambda: updateJsonFile('Language', 'ru', root=self.root))
        self.langMenu.add_command(label='Spanish (Español)', command=lambda: updateJsonFile('Language', 'es', root=self.root))
        self.langMenu.add_command(label='Thai (ไทย)', command=lambda: updateJsonFile('Language', 'th', root=self.root))
        self.langMenu.add_command(label='Turkish (Türkçe)', command=lambda: updateJsonFile('Language', 'tr', root=self.root))
        self.langMenu.add_command(label='Vietnamese (Tiếng Việt)', command=lambda: updateJsonFile('Language', 'vi', root=self.root))

        # About Menu
        self.aboutMenu = Menu(self.menubar, tearoff=0)
        self.aboutMenu.add_command(label=_('Credits'), command=lambda: self.credit_gui())
        
        # Menubar config
        self.menubar.add_cascade(label=_("Download"), menu=self.downloadMenu)
        self.menubar.add_cascade(label=_("Options"), menu=self.optionMenu)
        self.menubar.add_cascade(label=_("Tools"), menu=self.toolMenu)
        self.menubar.add_cascade(label=_("Language"), menu=self.langMenu)
        self.menubar.add_cascade(label=_("About"), menu=self.aboutMenu)
        self.root.config(menu=self.menubar)

        # Change Menu Label Based on loaded values
        if self.game_desc_disable == True:
            self.optionMenu.entryconfig(0, label= _("ENABLE GAME DESCRIPTION"))
        else:
            self.optionMenu.entryconfig(0, label= _("DISABLE GAME DESCRIPTION"))
        if self.repack == True:
            self.optionMenu.entryconfig(4, label= _("Disable NSP Repack"))
        else:
            self.optionMenu.entryconfig(4, label= _("Enable NSP Repack"))
        if self.mute == True:
            self.optionMenu.entryconfig(3, label= _("Unmute All Pop-ups"))
        else:
            self.optionMenu.entryconfig(3, label= _("Mute All Pop-ups"))
        if self.titlekey_check == True:
            self.optionMenu.entryconfig(5, label= _("Disable Titlekey Check"))
        else:
            self.optionMenu.entryconfig(5, label= _("Enable Titlekey Check"))
        if self.game_image_disable == True:
            self.optionMenu.entryconfig(1, label= _("ENABLE GAME IMAGE"))
        else:
            self.optionMenu.entryconfig(1, label= _("DISABLE GAME IMAGE"))
        if truncateName == True:
            self.optionMenu.entryconfig(7, label= _("Disable Shorten Name"))
        else:
            self.optionMenu.entryconfig(7, label= _("Enable Shorten Name"))
        if tinfoil == True:
            self.optionMenu.entryconfig(8, label= _("Disable Tinfoil Download"))
        else:
            self.optionMenu.entryconfig(8, label= _("Enable Tinfoil Download"))
        if sysver0 == True:
            self.optionMenu.entryconfig(9, label= _("Disable SysVer 0 Patch"))
        else:
            self.optionMenu.entryconfig(9, label= _("Enable SysVer 0 Patch"))

        # Status Label
        global status_label
        
        self.status_label = Label(self.root, text=_("Status:"))
        self.status_label.grid(row=0, column=0, columnspan=2, sticky=NS)
        status_label = self.status_label
        
        # Game selection section
        self.search_var = StringVar()
        self.search_var.trace("w", lambda name, index, mode: self.update_list(True, label=""))

        game_selection_frame = Frame(self.root)
        game_selection_frame.grid(row=1, column=0, padx=20, pady=20, sticky=N)

        # Filter entrybox
        if sys_name != "Mac":
            entryWidth = self.listWidth + 3
        else:
            entryWidth = self.listWidth
        self.entry = Entry(game_selection_frame, textvariable=self.search_var, width=entryWidth)
        self.entry.grid(row=0, column=0, columnspan=1, sticky=N)

        # Setup Scrollbar
        self.scrollbar = Scrollbar(game_selection_frame)
##        self.scrollbar.grid(row=1, column=1, sticky=N+S+W)
        self.title_list = Listbox(game_selection_frame, exportselection = False,\
                                  yscrollcommand = self.scrollbar.set, width=self.listWidth, selectmode=EXTENDED)
        self.title_list.bind('<<ListboxSelect>>', self.game_info)
##        self.title_list.grid(row=1, column=0, sticky=W)
        self.scrollbar.config(command = self.title_list.yview)

        # Setup Treeview and Two Scrollbars
        container = ttk.Frame(game_selection_frame)
        container.grid(row=1, column=0, columnspan=2)
        self.tree = ttk.Treeview(columns=("num", "tid", "G", "S", "R"), show="headings", selectmode=EXTENDED)
        self.tree.bind('<<TreeviewSelect>>', self.game_info)
        self.tree.heading("num", text="#", command=lambda c="num": self.sortby(self.tree, c, 0))
        self.tree.column("num", width=50)
        self.tree.heading("tid", text=_("TitleID"), command=lambda c="tid": self.sortby(self.tree, c, 0))
        self.tree.column("tid", width=160)
        self.tree.heading("G", text=_("Game"), command=lambda c="G": self.sortby(self.tree, c, 0))
        self.tree.column("G", width=590)
        self.tree.heading("S", text=_("State"), command=lambda c="S": self.sortby(self.tree, c, 0))
        self.tree.column("S", width=130)
        self.tree.heading("R", text=_("Release Date"), command=lambda c="R": self.sortby(self.tree, c, 0))
        self.tree.column("R", width=130)
        vsb = ttk.Scrollbar(orient="vertical", command=self.tree.yview)
        hsb = ttk.Scrollbar(orient="horizontal", command=self.tree.xview)
        self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
        self.tree.grid(column=0, row=0, sticky='nsew', in_=container, columnspan=2)
        vsb.grid(column=2, row=0, sticky='ns', in_=container)
        hsb.grid(column=0, row=1, sticky='ew', in_=container)
        container.grid_columnconfigure(0, weight=1)
        container.grid_rowconfigure(0, weight=1)

        # Game image section
        self.image_text_label = Label(game_selection_frame, text=_("Game Image:"))
        self.image_text_label.grid(row=2, column=0, pady=(20, 0))

        self.image = Image.open("blank.jpg")
        self.photo = ImageTk.PhotoImage(self.image)
        global imageLabel_widget
        self.imageLabel = Label(game_selection_frame, image=self.photo, borderwidth=0, highlightthickness=0, cursor="hand2")
        imageLabel_widget = self.imageLabel
        self.imageLabel.bind("<Button-1>", self.eShop_link)
        self.imageLabel.image = self.photo # keep a reference!
        self.imageLabel.grid(row=3, column=0, sticky=N, pady=0)

        Label(game_selection_frame, text=_("Click the game image above \nto open the game in the eShop!")).grid(row=4, column=0)

        # Game info section
        Label(game_selection_frame, text=_("Game Info:")).grid(row=2, column=1, pady=(20, 0))
        game_text = Text(game_selection_frame, width=50, height=17, wrap=WORD)

        self.game_text = game_text
        game_text.grid(row=3, column=1, sticky=N)

        # GameName
        #self.gametitle_label = Label(game_selection_frame, text=_("GameName:"))
        #self.gametitle_label.grid(row=4, column=1)
        self.game_title = StringVar()
        self.gametitle_entry = Entry(game_selection_frame, textvariable=self.game_title)
        self.gametitle_entry.grid(row=0, column=1, columnspan=1, sticky=E+W, padx=(0,0))
        #-------------------------------------------

        # Game title info section

        game_info_frame = Frame(self.root)
        game_info_frame.grid(row=1, column=1, sticky=N)
        
        # Demo filter
        filter_frame = Frame(game_info_frame)
        filter_frame.grid(row=0, column=0, columnspan=2, pady=(0,0), sticky=NS)
        self.current_mode_text = Label(filter_frame, text=_("Current mode is: {}").format(self.current_mode))
        self.current_mode_text.grid(row=0, column=0, sticky=NS, pady=(0, 10))
        self.list_mode = Button(filter_frame, text=_("CDNSP Mode").replace("\n", ""), command=self.change_mode)

        if self.current_mode == "CDNSP":
            self.list_mode.config(text=_("Nut Mode").replace("\n", ""))
        elif self.current_mode == "Nut":
            self.list_mode.config(text=_("CDNSP Mode").replace("\n", ""))
        self.list_mode.grid(row=1, column=0, sticky=NS, pady=(0, 20))
            
        self.demo = IntVar()
        if no_demo:
            self.demo.set(1)
        else:
            self.demo.set(0)
        Checkbutton(filter_frame, text=_("No Demo"), \
                    variable=self.demo, command=self.filter_game)\
                    .grid(row=2, column=0, sticky=NS)
        self.jap = IntVar()
        if no_japanese_games:
            self.jap.set(1)
        else:
            self.jap.set(0)
        Checkbutton(filter_frame, text=_("No Japanese Game"), \
                    variable=self.jap, command=self.filter_game)\
                    .grid(row=3, column=0, pady=(5,0), sticky=NS)
        
        # Title ID info
        self.titleID_label = Label(game_info_frame, text=_("Title ID:"))
        self.titleID_label.grid(row=1, column=0, pady=(20,0), columnspan=2)

        self.game_titleID = StringVar()
        self.gameID_entry = Entry(game_info_frame, textvariable=self.game_titleID)
        self.gameID_entry.grid(row=2, column=0, columnspan=2)

        # Title Key info
        self.titleID_label = Label(game_info_frame, text=_("Title Key:"))
        self.titleID_label.grid(row=3, column=0, pady=(20,0), columnspan=2)

        self.game_titleKey = StringVar()
        self.gameKey_entry = Entry(game_info_frame, textvariable=self.game_titleKey)
        self.gameKey_entry.grid(row=4, column=0, columnspan=2)

        # Select update versions
        self.version_label = Label(game_info_frame, text=_("Select update version:"))
        self.version_label.grid(row=5, column=0, pady=(20,0), columnspan=2)

        self.version_option = StringVar()
        self.version_select = ttk.Combobox(game_info_frame, textvariable=self.version_option, state="readonly", postcommand=self.get_update_lastestVer)
        self.version_select["values"] = ([_('Latest')])
        self.version_select.set(_("Latest"))
        self.version_select.grid(row=6, column=0, columnspan=2)
        
        # Download options
        self.download_label = Label(game_info_frame, text=_("Download options:"))
        self.download_label.grid(row=7, column=0, pady=(20,0), columnspan=2)

        MODES = [
            (_("Base game + Update + DLC"), "B+U+D"),
            (_("Base game + Update"), "B+U"),
            (_("Update + DLC"), "U+D"),
            (_("Base game only"), "B"),
            (_("Update only"), "U"),
            (_("All DLC"), "D")
        ]

        self.updateOptions = StringVar()
        self.updateOptions.set("B+U+D")
        
        self.radio_btn_collection = []
        row_count = 8
        for index in range(len(MODES)):
            a = Radiobutton(game_info_frame, text=MODES[index][0],
                        variable=self.updateOptions, value=MODES[index][1])
            a.grid(row=row_count, column=0, sticky=W, columnspan=2)
            row_count += 1
            self.radio_btn_collection.append(a)

        # Queue and Download button
        queue_btn = Button(game_info_frame, text=_("Add to queue"), command=self.add_selected_items_to_queue)
        queue_btn.grid(row=50, column=0, pady=(20,0))
        if _("Download_bottom") != "Download_bottom":
            download_bottom_txt = _("Download_bottom")
        else:
            download_bottom_txt = _("Download")
        dl_btn = Button(game_info_frame, text=download_bottom_txt, command=self.download)
        dl_btn.grid(row=50, column=1, pady=(20,0), padx=(5,0))
        self.pause_btn = Button(game_info_frame, text=("Pause Download"), command=self.pause_download_command)
        self.pause_btn.grid(row=51, column=0, pady=(20,0), columnspan=2)

        update_btn = Button(game_info_frame, text=_("Update Titlekeys"), command=self.update_titlekeys)
        update_btn.grid(row=52, column=0, pady=(20, 0), columnspan=2)

        #-----------------------------------------
        # Setup GUI Functions
        url = 'https://raw.githubusercontent.com/Bob123a1/CDNSP-GUI-Files/master/Config/Version_info.json'
        file = os.path.join("Config", "Version_info.json")
        urllib.request.urlretrieve(url, file)
        self.my_game_scan(a_dir=self.path, silent=True)
        self.update_list(rebuild=True)
        self.filter_game()
        self.queue_menu_setup()
        self.load_persistent_queue() # only load the queue once the UI is initialized
        try:
            self.status_label.config(text=_("Status: Done!"))
        except:
            pass
        update_result = self.check_update()
        self.update_result = update_result
        display = True
        display_text = ""
        
        # G - Indicates an update for the GUI,
        # L - Indicates an update for the Language files
        if update_result == "GL":
            display_text = _("Status:")+ " " + _("New GUI version and Language Files available!")
        elif update_result == "G":
            display_text = _("Status:")+ " " + _("New GUI version available!")
        elif update_result == "L":
            display_text = _("Status:")+ " " + _("New Language Files available!")
        else:
            display = False
        if display:
            threading.Timer(3, lambda: self.done_status(display_text)).start()
            
        #-----------------------------------------

    def queue_menu_setup(self):
        # Queue Menu
        global queue_win
        self.queue_win = Toplevel(self.root)
        self.queue_win.title(_("Queue Menu"))
        self.queue_win.geometry(queue_win)
        self.queue_win.withdraw() # Hide queue window, will show later

        # Top Menu bar
        menubar = Menu(self.queue_win)

        # Download Menu Tab
        downloadMenu = Menu(menubar, tearoff=0)
        downloadMenu.add_command(label=_("Load Saved Queue"), command=self.import_persistent_queue)
        downloadMenu.add_command(label=_("Save Queue"), command=self.export_persistent_queue)
        
        # Menubar config
        menubar.add_cascade(label=_("Download"), menu=downloadMenu)
        self.queue_win.config(menu=menubar)

        # Queue GUI
        self.queue_scrollbar = Scrollbar(self.queue_win)
        self.queue_scrollbar.grid(row=0, column=3, sticky=N+S+W)
        
        if self.sys_name == "Mac":
            self.queue_width = self.listWidth+28
        else:
            self.queue_width = 100 # Windows 
        self.queue_title_list = Listbox(self.queue_win, yscrollcommand = self.queue_scrollbar.set, width=self.queue_width, selectmode=EXTENDED)
        self.queue_title_list.grid(row=0, column=0, sticky=W, columnspan=3)
        self.queue_scrollbar.config(command = self.queue_title_list.yview)

        Button(self.queue_win, text=_("Remove selected game"), command=self.remove_selected_items).grid(row=1, column=0, pady=(30,0))
        Button(self.queue_win, text=_("Remove all"), command=self.remove_all_and_dump).grid(row=1, column=1, pady=(30,0))
        Button(self.queue_win, text=_("Download all"), command=self.download_all).grid(row=1, column=2, pady=(30,0))
        self.stateLabel = Label(self.queue_win, text=_("Click download all to download all games in queue!"))
        self.stateLabel.grid(row=2, column=0, columnspan=3, pady=(20, 0))

    # Sorting function for the treeview widget
    def sortby(self, tree, col, descending):
        """sort tree contents when a column header is clicked on"""
        # grab values to sort
        data = [(self.tree.set(child, col), child) for child in self.tree.get_children('')]
        # if the data to be sorted is numeric change to float
        #data =  change_numeric(data)
        # now sort the data in place
        
        data.sort(reverse=descending)
        
        for ix, item in enumerate(data):
            self.tree.move(item[1], '', ix)
        # switch the heading so it will sort in the opposite direction
        self.tree.heading(col, command=lambda col=col: self.sortby(tree, col, \
            int(not descending)))

    def remove_all(self, dump_queue = False):
        self.queue_list = []
        self.persistent_queue = []
        self.queue_title_list.delete(0, "end")
        if dump_queue: self.dump_persistent_queue()

    def remove_all_and_dump(self):
        self.remove_all(True)

    def threaded_eShop_link(self, evt):
        tid = self.game_titleID.get()
        isDLC = False
        if not tid.endswith("00"):
            isDLC = True
            tid = "{}".format(tid[0:12])
            indices = [i for i, s in enumerate(self.titleID) if tid in s]
            if len(indices) >= 2:
                for t in indices:
                    if self.titleID[t].endswith("000"):
                        tid = self.titleID[t]
                        break
                if tid.endswith("000"):
                    isDLC = False
        if not isDLC:
            region = ["US", "EU", "GB", "FR", \
                      "JP", "RU", "DE", "BE", "NL",\
                      "ES", "IT", "AT", "PT", "CH",\
                      "ZA", "CA"]
            # Thanks to user szczuru#7105 for find them for me :)
    ##        https://ec.nintendo.com/apps/01000320000cc000/FR
            url = ""
            for c in region:
                r = requests.get("https://ec.nintendo.com/apps/{}/{}".format(tid, c))
                if r.status_code == 200:
                    url = "https://ec.nintendo.com/apps/{}/{}".format(tid, c)
                    break
            if url != "":
                webbrowser.open(url, new=0, autoraise=True)
        self.root.config(cursor="")
        self.imageLabel.config(cursor="hand2")
                
    def eShop_link(self, evt):
        if self.game_titleID.get() != "":
            thread = threading.Thread(target = lambda: self.threaded_eShop_link(evt))
            self.root.config(cursor="watch")
            self.imageLabel.config(cursor="watch")
            thread.start()
                                      
    def update_list(self, search=False, rebuild=False, label="Status:"):
        if label == "Status:":
            label = _(label)
        #_("Status: Getting game status... Please wait")
        self.root.config(cursor="watch")
        
        if rebuild: # Rebuild current_status.txt file
            print(_("\nBuilding the current state list... Please wait, this may take some time \
depending on how many games you have."))
            updates_tid = []
            installed = []
            new_tid = []
            global known_ver
            known_ver = {}

            # -> Read_file
            self.title_list.delete(0, END)
            
            self.titleID = []
            self.titleKey = []
            self.title = []
            self.info_list = []

            global titleID_list
            global titleKey_list
            global title_list
            global info_list

            titleID_list, titleKey_list, title_list, info_list = read_titlekey_list()

            self.titleID = titleID_list
            self.titleKey = titleKey_list
            self.title = title_list
            self.info_list = info_list


            if not os.path.isfile("Config/Version_info.json"):
                print(_("\nCan't find {} file!\n").format("Version_info.json"))
                print(_("Attempting to download the Version_info.json file for you"))
                urllib.request.urlretrieve("https://raw.githubusercontent.com/Bob123a1/CDNSP-GUI-Files/master/Config/Version_info.json", "Config/Version_info.json")
                
            if os.path.isfile("Config/Version_info.json"):
                ver_file = open("Config/Version_info.json", "r", encoding="utf8")
                known_ver = json.load(ver_file)
                ver_file.close()
            file_path = r"Config/installed.txt"
            if os.path.exists(file_path):
                file = open(file_path, "r")
                for line in file.readlines():
                    if line[-1] == "\n":
                        line = line[:-1].lower()
                    installed.append(line.lower())
                file.close()
                for info in installed:
                    tid = info.split(",")[0].strip()
                    ver = info.split(",")[1].strip()
                    if ver == "none":
                        ver = "0"
                    if tid in known_ver:
                        tid = tid.lower()
                        try:
                            known_ver_num = known_ver[tid]
                            if known_ver_num == "none":
                                known_ver_num = "0"
                            if int(known_ver_num) > int(ver):
                                if tid.endswith("00"):
                                    tid = "{}000".format(tid[0:13])
                                updates_tid.append(tid)
                        except Exception as e:
                            print("Tid: {} has caused an error, it has the version of {}.\nError: {}".format(tid, ver, e))
            else:
                file = open(file_path, "w")
                file.close()

            new_path = r"Config/new.txt"
            if os.path.isfile(new_path):
                file = open(new_path, "r")
                for line in file.readlines():
                    if line[-1] == "\n":
                        line = line[:-1]
                    new_tid.append(line[:16])
                file.close()
            installed = [install.split(",")[0] for install in installed]

##            status_file = open(r"Config/Current_status.txt", "w", encoding="utf-8") Not going to rely on writing to a text file anymore
            titles_dict = {}

            if os.path.isfile("Config/titles.json"):
                with open("Config/titles.json", "r", encoding="utf-8") as file:
                    file_json = json.load(file)
                    for key in file_json:
                        key_u = key.upper()
                        titles_dict[key_u] = file_json[key]["releaseDate"]
            else:
                print("\n" + _("Unable to find titles.json file. Check changelog for more information on how to download it.") + "\n")
                

            self.status_list = []

            for tid in self.titleID:
                tid = tid.lower()
                number = int(self.titleID.index(tid))+1
                game_name = self.title[number-1]
                if game_name[-1] == "\n":
                    game_name = game_name[:-1]
                state = ""

                if tid in new_tid:
                    state = "New"
                
                if tid in installed:
                    state = "Own"
                
                if tid in updates_tid:
                    state = "Update"

                tid_u = str(tid).upper()
                if tid_u in titles_dict:
                    if titles_dict[tid_u] == None or titles_dict[tid_u] == "":
                        release_date = "0000-00-00"
                    else:
                        release_date = str(titles_dict[tid_u])
                        release_date = release_date[:4]+"-"+release_date[4:6]+"-"+release_date[6:8]
                else:
                    release_date = "0000-00-00"
                # Thanks to Moko0815 for the solution to fill with leading 0s
                tree_row = (str(number).zfill(4), tid, game_name, state, release_date)
                self.status_list.append(str(tree_row))
##            threading.Timer(1, self.done_status).start()
            self.done_status()
            self.update_list()
            
##        elif search:
##            search_term = self.search_var.get()
##            self.tree.delete(*self.tree.get_children())
##            for game_status in self.current_status:
##                number = game_status[0].strip()
##                tid = game_status[1].strip()
##                game_name = game_status[2].strip()
##                state = game_status[3].strip()
##                release_date = game_status[4].strip()
##                
##                tree_row = (str(number).zfill(4), tid, game_name, state, release_date)
##                if search_term.lower().strip() in game_name.lower() or search_term.lower().strip() in tid.lower():
##                    self.tree.insert('', 'end', values=tree_row)
                    
        else:
            self.current_status = []
            if self.status_list:
                for line in self.status_list:
                    if line[-1] == "\n":
                        line = line[:-1]
                    # Thanks to user danch15 on Github
                    if "New" in line:
                        line = line.replace("'New'", "'" + _("New") + "'")
                    if "Own" in line:
                        line = line.replace("'Own'", "'" + _("Own") + "'")
                    if "Update" in line:
                        line = line.replace("'Update'", "'" + _("Update") + "'")
                    status_list = eval(line)
                    self.current_status.append(status_list)
                self.make_list()
                self.filter_game()
            else:
                print(_("Error, Current_status.txt doesn't exist"))
  
        self.tree.yview_moveto(0)
        # Reset the sorting back to default (descending)
        self.tree.heading("num", text="#", command=lambda c="num": self.sortby(self.tree, c, 1))
        self.tree.heading("tid", text=_("TitleID"), command=lambda c="tid": self.sortby(self.tree, c, 1))
        self.tree.heading("G", text=_("Game"), command=lambda c="G": self.sortby(self.tree, c, 1))
        self.tree.heading("S", text=_("State"), command=lambda c="S": self.sortby(self.tree, c, 1))

        # Reset cursor status
        self.root.config(cursor="")
        try:
            self.imageLabel.config(cursor="hand2")
        except:
            pass
        
    def done_status(self, display_text="Status: Done!"):
        display_text = _(display_text)
        try:
            self.status_label.config(text=display_text)
        except RuntimeError as e:
            print(e)
        print(display_text)
        
    def threaded_game_info(self, evt):
        selection=self.tree.selection()[0]
        selected = self.tree.item(selection,"value") # Returns the selected value as a dictionary
        
        if selection:
            w = evt.widget
            self.is_DLC = False
##            try:
            value = selected[2]
            if "[DLC]" in value:
                for radio in self.radio_btn_collection:
                    radio.configure(state = DISABLED)
                self.is_DLC=True
            else:
                for radio in self.radio_btn_collection:
                    radio.configure(state = "normal")
            value = int(selected[0])
            value -= 1
            self.game_titleID.set(self.titleID[value])
            self.game_titleKey.set(self.titleKey[value])
            self.game_title.set(self.title[value])
            self.get_update_lastestVer()
##            except:
##                pass

            tid = self.titleID[value]
            thread = threading.Thread(target = lambda: self.game_desc(tid))
            thread.start()
            
            isDLC = False
            if not tid.endswith("00"):
                isDLC = True
                tid = "{}".format(tid[0:12])
                indices = [i for i, s in enumerate(self.titleID) if tid in s]
                if len(indices) >= 2:
                    for t in indices:
                        if self.titleID[t].endswith("000"):
                            tid = self.titleID[t]
                            break
                    if tid.endswith("000"):
                        isDLC = False
            
            if edgeToken == None and not self.game_image_disable:
                image_name = "{}.jpg".format(tid.lower())

                if not os.path.isfile("Images/{}".format(image_name)):
                    url = "https://terannet.sirv.com/CDNSP/{}".format(image_name)
                    r = requests.get(url)

                    if r.status_code != 200:
                        isDLC = True
                        print("\n"+_("Unable to get game image"))
                    else:
                        file_name = os.path.join("Images", image_name)
                        urllib.request.urlretrieve(url, file_name)
                
            change_img = False
            if not self.game_image_disable and not isDLC:
                if not os.path.isfile("Images/{}.jpg".format(tid)):
                    base_ver = get_versions(tid)[-1]
                    result = game_image(tid, base_ver, self.titleKey[self.titleID.index(tid)])
                    if result[1] != "Error":
                        change_img = True
                        if result[1] != "Exist":
                            if self.sys_name == "Win":
                                subprocess.check_output("{0} -k keys.txt {1}\\control.nca --section0dir={1}\\section0".format(hactoolPath, result[0].replace("/", "\\")), shell=True)
                            else:
                                subprocess.check_output("{0} -k keys.txt '{1}/control.nca' --section0dir='{1}/section0'".format(hactoolPath, result[0]), shell=True)
                            icon_list = ["icon_AmericanEnglish.dat", "icon_BritishEnglish.dat",\
                                         "icon_CanadianFrench.dat", "icon_German.dat", \
                                         "icon_Italian.dat", "icon_Japanese.dat", \
                                         "icon_LatinAmericanSpanish.dat", "icon_Spanish.dat", \
                                         "icon_Korean.dat", "icon_TraditionalChinese.dat"]
                            file_name = ""
                            dir_content = os.listdir(os.path.dirname(os.path.abspath(__file__))+'/Images/{}/section0/'.format(tid))
                            for i in icon_list:
                                if i in dir_content:
                                    file_name = i.split(".")[0]
                                    break
                            try:
                                os.rename('{}/section0/{}.dat'.format(result[0], file_name), '{}/section0/{}.jpg'.format(result[0], file_name))
                                shutil.copyfile('{}/section0/{}.jpg'.format(result[0], file_name), 'Images/{}.jpg'.format(tid))
                                shutil.rmtree(os.path.dirname(os.path.abspath(__file__))+'/Images/{}'.format(tid))
                            except Exception as e:
                                print(_("An error has occured, click on the game image to try again" + "\n" + _("Error:") + " {}".format(e)))
                    else:
                        img2 = ImageTk.PhotoImage(Image.open('blank.jpg'))
                        self.imageLabel.configure(image=img2, text="")
                        self.imageLabel.image = img2
                else:
                    change_img = True
                if change_img:
                    img2 = ImageTk.PhotoImage(Image.open(os.path.dirname(os.path.abspath(__file__))+'/Images/{}.jpg'.format(tid)))
                    self.imageLabel.configure(image=img2, text="")
                    self.imageLabel.image = img2
                    # print("\nIt took {} seconds to get the image\n".format(end - start))
                
            else:
                img2 = ImageTk.PhotoImage(Image.open('blank.jpg'))
                self.imageLabel.configure(image=img2, text="")
                self.imageLabel.image = img2
##            
##            except:
##                pass
    def game_info(self, evt):
        self.imageLabel.config(image="", text=_("\n\n\nDownloading game image..."))
        thread = threading.Thread(target = lambda: self.threaded_game_info(evt))
        thread.start()

    def game_desc(self, tid):
        #self.infoLabel.config(image="", text=_("\n\n\nDownloading game info..."))
        if self.game_desc_disable == False:
            global game_info_json
            if not tid.endswith("00"):
                tid = "{}".format(tid[0:12])
                indices = [i for i, s in enumerate(self.titleID) if tid in s]
                if len(indices) >= 2:
                    for t in indices:
                        if self.titleID[t].endswith("000"):
                            tid = self.titleID[t]
                            break
            if tid in game_info_json:
                description = ""
                if game_info_json[tid]["intro"].replace("\n", "") == "":
                    description += "No intro\n"
                else:
                    description += "{}\n".format(game_info_json[tid]["intro"]\
                                                 .replace("\n\n", " ").replace("\n", "").strip())

                info_name = {"Game Description:": "description",
                             "Release Date:": "release_date_string",
                             "Publisher:": "publisher",
                             "Category:": "category",
                             "Rating:": "rating",
                             "Game Size:": "Game_size",
                             "Number of Players:": "number_of_players",
                             "eShop Game Price:": "US_price"
                             }

                for game_name, game_key in info_name.items():
                    if game_key == "description":
                        description += "\n{} {}\n\n".format(_(game_name), game_info_json[tid]["{}".format(game_key)]\
                                                           .replace("\n\n", " ").replace("\n", "").strip())
                    elif game_key == "Game_size":
                        try:
                            game_size_temp = bytes2human(float(game_info_json[tid]["{}".format(game_key)].replace("\n", "").strip()))
                            game_size_temp_size = float(game_size_temp[0:-3])
                            description += "{} {:.2f} {}\n".format(_(game_name), game_size_temp_size, game_size_temp[-2:])
                        except:
                            description += "{} {}\n".format(_(game_name), _("Unable to get game size"))
                    elif game_key == "US_price":
                        description += "{} ${}\n".format(_(game_name), game_info_json[tid]["{}".format(game_key)].replace("\n", "").strip())
                    else:
                        description += "{} {}\n".format(_(game_name), game_info_json[tid]["{}".format(game_key)].replace("\n", "").strip())
                                                                             
                self.game_text.delete("1.0", END)
                self.game_text.insert(INSERT, description)

                self.game_text.tag_add("All", "1.0", "end")
                self.game_text.tag_config("All", font=("Open Sans", 10))
                
                self.game_text.tag_add("Intro", "1.0", "1.end")
                self.game_text.tag_config("Intro", justify="center", font=("Open Sans", 10, "bold"))

                counter = 3
                for game_name, game_key in info_name.items():
                    self.game_text.tag_add("{}".format(_(game_name)), "{}.0".format(counter), "{}.{}".format(counter, len(_(game_name))))
                    self.game_text.tag_config("{}".format(_(game_name)), font=("Open Sans", 10, "bold"), spacing2="2", spacing3="3")
                    if counter == 3:
                        counter += 2
                    else:
                        counter += 1
            else:
                self.game_text.delete("1.0", END)
                self.game_text.insert(INSERT, _("\n\n\nDownloading game info..."))
                thread = threading.Thread(target = lambda: self.download_desc(tid))
                thread.start()
        else:
            self.game_text.delete("1.0", END)
            self.game_text.insert(INSERT, "")
            
    def download_desc(self, tid, silent=False):
        # Coded by Panda
        global game_info_json
        done = False
        if tid in game_info_json:
            done = True
        if done == False:
##            try:
            titleinfo = {}
            titleinfo['titleid'] = tid
            titleinfo['date_added'] = time.strftime('%Y%m%d')
            titleinfo['last_updated'] = time.strftime('%Y%m%d')

            #initialize empty values
            titleinfo["release_date_string"] = ""
            titleinfo["release_date_iso"] = ""
            titleinfo["title"] = ""
            titleinfo["nsuid"] = ""
            titleinfo["slug"] = ""
            titleinfo["game_code"] = ""
            titleinfo["category"] = ""
            titleinfo["rating_content"] = ""
            titleinfo["number_of_players"] = ""
            titleinfo["rating"] = ""
            titleinfo["amiibo_compatibility"] = ""
            titleinfo["developer"] = ""
            titleinfo["publisher"] = ""
            titleinfo["front_box_art"] = ""
            titleinfo["intro"] = ""
            titleinfo["description"] = ""
            titleinfo["dlc"] = ""
            titleinfo["US_price"] = ""
            titleinfo["Game_size"] = ""

            result = requests.get("https://ec.nintendo.com/apps/%s/US" % tid)
            # result.status_code == 200
            if result.status_code == 200:
                if result.url != 'https://www.nintendo.com/games/':
                    soup = BeautifulSoup(result.text, "html.parser")
                    if soup.find("meta", {"property": "og:url"}) != None:
                        slug = soup.find("meta", {"property": "og:url"})["content"].split('/')[-1]
                        infoJson = json.loads(requests.get("https://www.nintendo.com/json/content/get/game/%s" % slug).text)["game"]
                        if "release_date" in infoJson:
                            titleinfo["release_date_string"] = infoJson["release_date"]
                            titleinfo["release_date_iso"] = datetime.datetime.strftime(datetime.datetime.strptime(infoJson["release_date"], "%b %d, %Y"),'%Y%m%d')

                        if "title" in infoJson:
                            titleinfo["title"] = infoJson["title"]

                        if "nsuid" in infoJson:
                            titleinfo["nsuid"] = infoJson["nsuid"]

                        if "slug" in infoJson:
                            titleinfo["slug"] = infoJson["slug"]

                        if "game_code" in infoJson:
                            titleinfo["game_code"] = infoJson["game_code"]

                        catagories = []
                        if "game_category_ref" in infoJson:
                            catindex = 0
                            if "title" in infoJson["game_category_ref"]:
                                catagories.append(infoJson["game_category_ref"]["title"])
                            else:
                                for game_category in infoJson["game_category_ref"]:
                                    catagories.append(infoJson["game_category_ref"][catindex]["title"])
                                    catindex += 1
                            if len(catagories) > 0:
                                titleinfo["category"] = ','.join(catagories)

                        esrbcontent = []
                        if "esrb_content_descriptor_ref" in infoJson:
                            esrbindex = 0
                            if "title" in infoJson["esrb_content_descriptor_ref"]:
                                esrbcontent.append(infoJson["esrb_content_descriptor_ref"]["title"])
                            else:
                                for descriptor in infoJson["esrb_content_descriptor_ref"]:
                                    esrbcontent.append(infoJson["esrb_content_descriptor_ref"][esrbindex]["title"])
                                    esrbindex += 1
                            if len(esrbcontent) > 0:
                                titleinfo["content"] = ','.join(esrbcontent)

                        if "number_of_players" in infoJson:
                            titleinfo["number_of_players"] = infoJson["number_of_players"]

                        if "eshop_price" in infoJson:
                            titleinfo["US_price"] = infoJson["eshop_price"]

                        if "esrb_rating_ref" in infoJson:
                            if "title" in infoJson["esrb_rating_ref"]:
                                titleinfo["rating"] = infoJson["esrb_rating_ref"]["esrb_rating"]["short_description"]

                        if "amiibo_compatibility" in infoJson:
                            titleinfo["amiibo_compatibility"] = infoJson["amiibo_compatibility"]
 
                        if "dlc" in infoJson:
                            titleinfo["dlc"] = infoJson["dlc"]

                        if "developer_ref" in infoJson:
                            if "title" in infoJson["developer_ref"]:
                                titleinfo["developer"] = infoJson["developer_ref"]["title"]


                        if "publisher_ref" in infoJson:
                            if "title" in infoJson["publisher_ref"]:
                                titleinfo["publisher"] = infoJson["publisher_ref"]["title"]

                        if "front_box_art" in infoJson:
                            if "image" in infoJson["front_box_art"]:
                                if "image" in infoJson["front_box_art"]["image"]:
                                    if "url" in infoJson["front_box_art"]["image"]["image"]:
                                        titleinfo["front_box_art"] = infoJson["front_box_art"]["image"]["image"]["url"]

                        if "intro" in infoJson:
                            try:
                                details = BeautifulSoup(infoJson["intro"][0],"html.parser")
                                try:
                                    details = details.decode(formatter=None)
                                except:
                                    details = details.decode()
                                details = re.sub('<[^<]+?>', '', details).strip()
                                details = re.sub(' +', ' ', details)
                                details = re.sub('\n ', '\n', details)
                                details = re.sub('\n\n+', '\n\n', details)
                                titleinfo["intro"] = details
                            except Exception as e:
                                pass

                        if "game_overview_description" in infoJson:
                            details = BeautifulSoup(infoJson["game_overview_description"][0],"html.parser")
                            try:
                                details = details.decode(formatter=None)
                            except:
                                details = details.decode()
                            details = re.sub('<[^<]+?>', '', details).strip()
                            details = re.sub(' +', ' ', details)
                            details = re.sub('\n ', '\n', details)
                            details = re.sub('\n\n+', '\n\n', details)
                            titleinfo["description"] = details

                        result = requests.get("https://ec.nintendo.com/apps/%s/AU" % tid)
                        _json = ''
                        if result.status_code == 200:
                            _json = json.loads(result.text.split('NXSTORE.titleDetail.jsonData = ')[1].split('NXSTORE.titleDetail')[0].replace(';',''))
                        else:
                            result = requests.get("https://ec.nintendo.com/apps/%s/JP" % tid)
                            
                            if result.status_code == 200:
                                _json = json.loads(result.text.split('NXSTORE.titleDetail.jsonData = ')[1].split('NXSTORE.titleDetail')[0].replace(';',''))
                        if _json != '':
        ##                    print(_json["total_rom_size"])
        ##                    sys.exit()

                            if "total_rom_size" in _json:
                                titleinfo["Game_size"] = str(_json["total_rom_size"])

                            game_info_json[tid] = titleinfo

            if tid not in game_info_json:
                result = requests.get("https://ec.nintendo.com/apps/%s/AU" % tid)
                _json = ''
                if result.status_code == 200:
                    _json = json.loads(result.text.split('NXSTORE.titleDetail.jsonData = ')[1].split('NXSTORE.titleDetail')[0].replace(';',''))
                else:
                    result = requests.get("https://ec.nintendo.com/apps/%s/JP" % tid)
                    
                    if result.status_code == 200:
                        _json = json.loads(result.text.split('NXSTORE.titleDetail.jsonData = ')[1].split('NXSTORE.titleDetail')[0].replace(';',''))
                if _json != '':
##                    print(_json["total_rom_size"])
##                    sys.exit()

                    if "total_rom_size" in _json:
                        titleinfo["Game_size"] = str(_json["total_rom_size"])

                    if "release_date_on_eshop" in _json:
                        titleinfo["release_date_iso"] = _json["release_date_on_eshop"].replace('-','')
                        titleinfo["release_date_string"] = datetime.datetime.strftime(datetime.datetime.strptime(_json["release_date_on_eshop"].replace('-',''),'%Y%m%d' ),"%b %d, %Y")
                    
                    if "formal_name" in _json:
                        titleinfo["title"] = _json["formal_name"]
                    
                    if "id" in _json:
                        titleinfo["nsuid"] = "%s" % _json["id"]
                        
                    titleinfo["slug"] = ""
                    titleinfo["game_code"] = ""

                    if "genre" in _json:
                        titleinfo["category"] = _json["genre"].replace(' / ',',')

                    if "rating_info" in _json:
                        if "rating" in _json["rating_info"]:
                            rating = ''
                            if "name" in _json["rating_info"]['rating']:
                                rating = "Rated %s" % _json["rating_info"]['rating']['name']

                            if "age" in _json["rating_info"]['rating']:
                                rating = rating + " for ages %s and up" % _json["rating_info"]['rating']['age']

                            titleinfo["rating"] = rating

                        if "content_descriptors" in _json["rating_info"]:
                            content = []
                            for descriptor in  _json["rating_info"]["content_descriptors"]:
                                content.append(descriptor['name'])
                            titleinfo["rating_content"] = ','.join(content)

                
                    if "player_number" in _json:
                        if 'offline_max' in _json["player_number"]:
                            titleinfo["number_of_players"] = "up to %s players" % _json["player_number"]["offline_max"]

                        if 'local_max' in _json["player_number"]:
                            titleinfo["number_of_players"] = "up to %s players" % _json["player_number"]["local_max"]

                    titleinfo["amiibo_compatibility"] = ""

                    titleinfo["developer"] = ""

                    if "publisher" in _json:
                        titleinfo["publisher"] = _json["publisher"]["name"]

                    if "applications" in _json:
                        if "image_url" in _json["applications"][0]:
                            titleinfo["front_box_art"] = _json["applications"][0]['image_url']
                    
                    if "hero_banner_url" in _json:
                        titleinfo["front_box_art_alt"] = _json["hero_banner_url"]

                    if "catch_copy" in _json:
                        titleinfo["intro"] = _json["catch_copy"]

                    if "description" in _json:
                        titleinfo["description"] = _json["description"]

                    titleinfo["dlc"] = ""
                    game_info_json[tid] = titleinfo
                
                else:
                    f = open("Config/missing.txt", 'a', encoding="utf8")
                    f.write(tid+"|title doesn't exist at ec.nintendo.com"+'\n')
                    f.close()
                    if not silent:
                        self.game_text.delete("1.0", END)
                        self.game_text.insert(INSERT, _("\n\n\nUnable to find game info"))
                    done = True

##            except Exception as e:
##                #print(repr(e))
##                f = open("Config/missing.txt", 'a', encoding="utf8")
##                f.write(tid+'|'+ repr(e) +'\n')
##                f.close()
##                self.game_text.delete("1.0", END)
##                self.game_text.insert(INSERT, _("\n\n\nUnable to find game info"))
##                done = True

        if not done:
            with open("Config/Game_info.json", "w", encoding="utf8") as jsonFile:
                json.dump(game_info_json, jsonFile, indent=4)
            jsonFile.close()
            if not silent:
                self.game_desc(tid)
            

    
    def threaded_download(self):
        option = self.updateOptions.get()
##        try:
        tid = self.game_titleID.get()
        updateTid = tid
        tkey = self.game_titleKey.get()
        ver = self.version_option.get()
        
        if len(tkey) != 32 and self.titlekey_check:
            self.messages(_('Error'), _('Titlekey {} is not a 32-digits hexadecimal number!').format(tkey))                
        elif len(tid) != 16:
            self.messages(_('Error'), _('TitleID {} is not a 16-digits hexadecimal number!').format(tid))
        else:
            if _("Latest") in ver:
                updateTid = tid
                if tid.endswith('000'):
                    updateTid = '%s800' % tid[:-3]
                elif tid.endswith('800'):
                    baseTid = '%s000' % tid[:-3]
                    updateTid = tid
                ver = get_versions(updateTid)[-1]
            elif "none" in ver:
                ver == "none"
                    
            if tid.endswith('000'):
                updateTid = '%s800' % tid[:-3]
            elif tid.endswith('800'):
                baseTid = '%s000' % tid[:-3]
                updateTid = tid
                
            if option == "U" or self.is_DLC == True:
                if ver != "none":
                    self.messages("", _("Starting to download! It will take some time, please be patient. You can check the CMD (command prompt) at the back to see your download progress."))
                    download_game(updateTid, ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                    self.messages("", _("Download finished!"))
                else:
                    self.messages("", _("No updates available for the game"))
                    
            elif option == "B+U+D":
                base_tid = "{}000".format(tid[0:13])
                self.messages("", _("Starting to download! It will take some time, please be patient. You can check the CMD (command prompt) at the back to see your download progress."))
                base_ver = get_versions(base_tid)[-1]
                download_game(base_tid, base_ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                if ver != 'none':
                    updateTid = "{}800".format(tid[0:13])
                    download_game(updateTid, ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                DLC_titleID = []
                tid = "{}".format(tid[0:12])
                indices = [i for i, s in enumerate(self.titleID) if tid in s]
                for index in indices:
                    if not self.titleID[index].endswith("00"):
                        DLC_titleID.append(self.titleID[index])
                for DLC_ID in DLC_titleID:
                    DLC_ver = get_versions(DLC_ID)[-1]
                    download_game(DLC_ID, DLC_ver, self.titleKey[self.titleID.index(DLC_ID)], nspRepack=self.repack, path_Dir=self.path)
                self.messages("", _("Download finished!"))

            elif option == "U+D":
                if ver != "none":
                    updateTid = "{}800".format(tid[0:13])
                    self.messages("", _("Starting to download! It will take some time, please be patient. You can check the CMD (command prompt) at the back to see your download progress."))
                    download_game(updateTid, ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                DLC_titleID = []
                tid = "{}".format(tid[0:12])
                indices = [i for i, s in enumerate(self.titleID) if tid in s]
                for index in indices:
                    if not self.titleID[index].endswith("00"):
                        DLC_titleID.append(self.titleID[index])
                for DLC_ID in DLC_titleID:
                    DLC_ver = get_versions(DLC_ID)[-1]
                    download_game(DLC_ID, DLC_ver, self.titleKey[self.titleID.index(DLC_ID)], nspRepack=self.repack, path_Dir=self.path)
                self.messages("", _("Download finished!"))


            elif option == "D":
                self.messages("", _("Starting to download! It will take some time, please be patient. You can check the CMD (command prompt) at the back to see your download progress."))
                DLC_titleID = []
                tid = "{}".format(tid[0:12])
                indices = [i for i, s in enumerate(self.titleID) if tid in s]
                for index in indices:
                    if not self.titleID[index].endswith("00"):
                        DLC_titleID.append(self.titleID[index])
                for DLC_ID in DLC_titleID:
                    DLC_ver = get_versions(DLC_ID)[-1]
                    download_game(DLC_ID, DLC_ver, self.titleKey[self.titleID.index(DLC_ID)], nspRepack=self.repack, path_Dir=self.path)
                self.messages("", _("Download finished!"))

                
            elif option == "B":
                base_tid = "{}000".format(tid[0:13])
                self.messages("", _("Starting to download! It will take some time, please be patient. You can check the CMD (command prompt) at the back to see your download progress."))
                base_ver = get_versions(base_tid)[-1]
                download_game(base_tid, base_ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                self.messages("", _("Download finished!"))
                
            elif option == "B+U":
                base_tid = "{}000".format(tid[0:13])
                self.messages("", _("Starting to download! It will take some time, please be patient. You can check the CMD (command prompt) at the back to see your download progress."))
                base_ver = get_versions(base_tid)[-1]
                download_game(base_tid, base_ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                if ver != 'none':
                    updateTid = "{}800".format(tid[0:13])
                    download_game(updateTid, ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                    self.messages("", _("Download finished!"))
                else:
                    self.messages("", _("No updates available for the game, base game downloaded!"))
##        except:
##            print("Error downloading {}, note: if you're downloading a DLC then different versions of DLC may have different titlekeys".format(tid))
        return
    
    def download(self):
        thread = threading.Thread(target = self.threaded_download)
        thread.start()

    def pause_download_command(self):
        global pause_download
        global downloading
        if downloading:
            pause_download = not pause_download
            if pause_download:
                self.pause_btn["text"] = "Resume Download"
                print("\nDownload paused, waiting for the user resumes the download again")
            else:
                self.pause_btn["text"] = "Pause Download"
                print("\nDownload resumed")
        else:
            print("\n There is not a download in progress")

    def export_persistent_queue(self):
        self.dump_persistent_queue(self.normalize_file_path(filedialog.asksaveasfilename(initialdir = self.path, title = "Select file", filetypes = (("JSON files","*.json"),("all files","*.*")))))

    def import_persistent_queue(self):
        self.load_persistent_queue(self.normalize_file_path(filedialog.askopenfilename(initialdir = self.path, title = "Select file", filetypes = (("JSON files","*.json"),("all files","*.*")))))

    def dump_persistent_queue(self, path = r'Config/CDNSP_queue.json'):
        if path == "":
            print(_("\nYou didn't choose a location to save the file!"))
            return
        elif not path.endswith(".json"):
            path += ".json"
            
        # if self.persist_queue # check for user option here
        f = open(path, 'w')
        json.dump(self.persistent_queue, f)
        f.close()

    def load_persistent_queue(self, path = r'Config/CDNSP_queue.json'):
        # if self.persist_queue # check for user option here
        try:
            f = open(path, 'r')
            try:
                self.remove_all()
            except:
                pass
            for c, tid, ver, key, option in json.load(f):
                self.add_item_to_queue((tid, ver, key, option), True)
            f.close()
        except:
            print(_("Persistent queue not found, skipping..."))

    def get_index_in_queue(self, item):
        try:
            return self.queue_list.index(item)
        except:
            print(_("Item not found in queue"), item)

    def add_selected_items_to_queue(self):
        self.add_items_to_queue(self.tree.selection())

    def add_items_to_queue(self, indices):
##        try:
        for index in indices:
            index = int(self.tree.item(index,"value")[0])-1
##            index = int(self.temp_list[index].split(",")[0])-1
            tid = self.titleID[index]
            key = self.titleKey[index]
            ver = self.version_option.get()
            option = self.updateOptions.get()
            if len(key) != 32 and self.titlekey_check:
                self.messages(_('Error'), _('Titlekey {} is not a 32-digits hexadecimal number!').format(key))
            elif len(tid) != 16:
                self.messages(_('Error'), _('TitleID {} is not a 16-digits hexadecimal number!').format(tid))
            else:
                self.add_item_to_queue((tid, ver, key, option))

        self.dump_persistent_queue()
##        except:
##            messagebox.showerror("Error", "No game selected/entered to add to queue")

    def process_item_versions(self, tid, ver):
        if _("Latest") in ver or "Latest" in ver:
            if tid.endswith('000'):
                tid = '%s800' % tid[:-3]
            ver = get_versions(tid)[-1]
        elif ver != "0" and ver != "1":
            if tid.endswith('000'):
                tid = '%s800' % tid[:-3]
        elif "none" in ver:
            ver = "none"

        return (tid, ver)

    # takes an item with unformatted tid and ver
    def add_item_to_queue(self, item, dump_queue = False):
        tid, ver, key, option = item
        if len(tid) == 16:
            if not Toplevel.winfo_exists(self.queue_win):
                self.queue_menu_setup() #Fix for app crashing when close the queue menu and re-open
            self.queue_win.update()
            self.queue_win.deiconify()
            try:
                c = self.titleID.index(tid)
                c = self.title[c] # Name of the game
            except:
                print(_("Name for titleID not found in the list"), tid)
                c = "UNKNOWN NAME"

            formatted_tid, formatted_ver = self.process_item_versions(tid, ver)
            if "[DLC]" in c:
                option = "DLC"
            if c[-1] == "\n":
                c = c[:-1]
            if ver == _("Latest"):
                eng_ver = "Latest"
            else:
                eng_ver = ver
            self.queue_title_list.insert("end", "{}---{}---{}".format(c, _(ver), option))
            self.queue_list.append((formatted_tid, formatted_ver, key, option))
            self.persistent_queue.append((c, tid, eng_ver, key, option))
            if dump_queue: self.dump_persistent_queue()
        self.queue_title_list.yview(END) # Auto scroll the queue menu as requested

    def remove_selected_items(self):
        self.remove_items(self.queue_title_list.curselection())

    def remove_items(self, indices):
        counter = 0
        for index in indices:
            index = index - counter
            try:
                self.remove_item(index)
                counter += 1
            except:
                print(_("No game selected to remove!"))

        self.dump_persistent_queue()

    def remove_item(self, index, dump_queue = False):
        del self.queue_list[index]
        del self.persistent_queue[index]
        self.queue_title_list.delete(index)
        if dump_queue: self.dump_persistent_queue()
        
    def threaded_download_all(self):
        self.messages("", _("Download for all your queued games will now begin! You will be informed once all the download has completed, please wait and be patient!"))
        self.stateLabel.configure(text = _("Downloading games..."))
        download_list = self.queue_list.copy()
        for item in download_list:
            tid, ver, tkey, option = item
            ver = self.process_item_versions(tid, ver)[1]
##            try:
            if option == "U" or option == "DLC":
                if ver != "none":
                    if tid.endswith("00"):
                        tid = "{}800".format(tid[0:13])
                    download_game(tid, ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                else:
                    print(_("No updates available for titleID: {}").format(tid))
                    
            elif option == "B+U+D":
                base_tid = "{}000".format(tid[0:13])
                base_ver = get_versions(base_tid)[-1]
                download_game(base_tid, base_ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                if ver != 'none':
                    updateTid = "{}800".format(tid[0:13])
                    download_game(updateTid, ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                DLC_titleID = []
                tid = "{}".format(tid[0:12])
                indices = [i for i, s in enumerate(self.titleID) if tid in s]
                for index in indices:
                    if not self.titleID[index].endswith("00"):
                        DLC_titleID.append(self.titleID[index])
                for DLC_ID in DLC_titleID:
                    DLC_ver = get_versions(DLC_ID)[-1]
                    download_game(DLC_ID, DLC_ver, self.titleKey[self.titleID.index(DLC_ID)], nspRepack=self.repack, path_Dir=self.path)

            elif option == "U+D":
                if ver != "none":
                    updateTid = "{}800".format(tid[0:13])
                    download_game(updateTid, ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                DLC_titleID = []
                tid = "{}".format(tid[0:12])
                indices = [i for i, s in enumerate(self.titleID) if tid in s]
                for index in indices:
                    if not self.titleID[index].endswith("00"):
                        DLC_titleID.append(self.titleID[index])
                for DLC_ID in DLC_titleID:
                    DLC_ver = get_versions(DLC_ID)[-1]
                    download_game(DLC_ID, DLC_ver, self.titleKey[self.titleID.index(DLC_ID)], nspRepack=self.repack, path_Dir=self.path)


            elif option == "D":
                DLC_titleID = []
                tid = "{}".format(tid[0:12])
                indices = [i for i, s in enumerate(self.titleID) if tid in s]
                for index in indices:
                    if not self.titleID[index].endswith("00"):
                        DLC_titleID.append(self.titleID[index])
                for DLC_ID in DLC_titleID:
                    DLC_ver = get_versions(DLC_ID)[-1]
                    download_game(DLC_ID, DLC_ver, self.titleKey[self.titleID.index(DLC_ID)], nspRepack=self.repack, path_Dir=self.path)

                
            elif option == "B":
                base_tid = "{}000".format(tid[0:13])
                base_ver = get_versions(base_tid)[-1]
                download_game(base_tid, base_ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                
            elif option == "B+U":
                base_tid = "{}000".format(tid[0:13])
                base_ver = get_versions(base_tid)[-1]
                download_game(base_tid, base_ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                if ver != 'none':
                    updateTid = "{}800".format(tid[0:13])
                    download_game(updateTid, ver, tkey, nspRepack=self.repack, path_Dir=self.path)
                else:
                    print(_("No updates available for titleID: {}, base game downloaded!").format(tid))

            index = self.get_index_in_queue(item)
            self.remove_item(index, True)
##            except:
##                print("Error downloading {}, note: if you're downloading a DLC then different versions of DLC may have different titlekeys".format(tid))
        self.messages("", _("Download finished!"))
        self.stateLabel["text"] = _("Download finished!")
        # self.remove_all(dump_queue = True)
        if self.auto_shutdown == True:
            self.messages("", _("Computer will now auto shutdown"))
            threading.Timer(2, self.shutdown).start() 
        

    def download_all(self):
        thread = threading.Thread(target = self.threaded_download_all)
        thread.start()

##    def download_check(self, tid, ver):
##        if ver != "0" or ver != "1": # Is an update game
##            if any(tid_list in tid for tid_list in self.installed):
##                print(tid, self.installed.index(tid))
##        else:
##            if any(tid_list in tid for tid_list in self.installed):                      

    def normalize_file_path(self, file_path):
        if self.sys_name == "Win":
            return file_path.replace("/", "\\")
        else:
            return file_path

    def change_dl_path(self):
        self.path = self.normalize_file_path(filedialog.askdirectory())

        updateJsonFile("Download_location", self.path)
        print("\nDownload Location:{}".format(self.path))

    def change_nsp_path(self):
        global nsp_location
        path = self.normalize_file_path(filedialog.askdirectory())
        nsp_location = path
        
        updateJsonFile("NSP_location", path)
        print("\nNSP Location: {}".format(path))

    def nsp_repack_option(self):
        if self.repack == True:
            self.optionMenu.entryconfig(4, label= _("Enable NSP Repack"))
            self.repack = False
        elif self.repack == False:
            self.optionMenu.entryconfig(4, label= _("Disable NSP Repack"))
            self.repack = True
        updateJsonFile("NSP_repack", str(self.repack))

    def threaded_update_titlekeys(self):
        # Set default values for CDNSP mode
        titlekey_file_name = "titlekeys.txt"
        titlekey_url = self.db_URL
        new_text_name = "new.txt"

        # Change the values if on Nut mode
        if self.current_mode == "Nut":
            titlekey_file_name = "Nut_titlekeys.txt"
            titlekey_url = "{}".format(base64.b64decode("aHR0cDovL3NuaXAubGkvbnV0ZGI=").decode("ascii"))
            new_text_name = "Nut_new.txt"
        
        self.status_label.config(text=_("Status: Updating titlekeys"))
        print(titlekey_url)

##        try:
        r = requests.get(titlekey_url, allow_redirects=True)

        if "snip" in titlekey_url:
            try:
                result = re.search(r"<a href=\"(.*?)\">Proceed", r.text)
                if result:
                    titlekey_url = result.group(1)
                    r = requests.get(titlekey_url, allow_redirects=True, verify=True)
            except:
                pass
            
        if str(r.status_code) == "200":
            r.encoding = "utf-8"
            newdb = r.text.replace("\r", "").split('\n')
            if newdb[-1] == "":
                newdb = newdb[:-1]
            if os.path.isfile(titlekey_file_name):
                with open(titlekey_file_name, encoding="utf8") as f:
                    currdb = f.read().split('\n')
                    if currdb[-1] == "":
                        currdb = currdb[:-1]
                    currdb = [x.strip() for x in currdb]
                    counter = 0
                    info = ''
                    new_tid = []

                    if self.current_mode == "Nut":
                        found_line = False
                        # Find the header info
                        for line in newdb:
                            if line[0] != "#" and line[:2] == "id" and current_mode_global == "Nut":
                                line = line.strip()
                                found_line = True
                                header_list = line.split("|")
                                index_tid = find_index(header_list, "id")
                                index_title = find_index(header_list, "name")
                                break
                    

                        if not found_line:
                            print("\n"+"Error: Header is not found in the Nut_titlekeys.txt file, please double check you have the header")
                            sys.exit()
            
                    for line in newdb:
                        if line[0:2] == "01":
                            if line.strip() not in currdb:
                                if line.strip() != newdb[0].strip():
                                    new_tid.append(line.strip().split('|')[0])
                                    if current_mode_global == "Nut":
                                        _name = line.strip().split('|')[index_title] + '\n'
                                        if _name not in info:
                                            info += _name
                                        else:
                                            continue
                                    else:
                                        info += (line.strip()).rsplit('|',1)[1] + '\n'
                                    counter += 1
                    if len(new_tid) != 0:
                        file_path = open(r"Config/{}".format(new_text_name), "w")
                        text = ""
                        for new in new_tid:
                            text += "{}\n".format(new[:16])
                        file_path.write(text)
                        file_path.close()
                    if counter:
                        update_win = Toplevel(self.root) #https://stackoverflow.com/questions/13832720/how-to-attach-a-scrollbar-to-a-text-widget
                        self.update_window = update_win
                        global update_win_size
                        update_win.title(_("Finished update!"))
                        if update_win_size != "":
                            update_win.geometry(update_win_size)
                        else:
                            update_win.geometry("600x400+120+200")
                        # create a Frame for the Text and Scrollbar
                        txt_frm = Frame(update_win, width=600, height=400)
                        txt_frm.pack(fill="both", expand=True)
                        # ensure a consistent GUI size
                        txt_frm.grid_propagate(False)
                        # implement stretchability
                        txt_frm.grid_rowconfigure(0, weight=1)
                        txt_frm.grid_columnconfigure(0, weight=3)

                        # create a Text widget
                        txt = Text(txt_frm, borderwidth=3, relief="sunken")
                        txt.config(font=("Open Sans", 12), undo=True, wrap='word')
                        txt.grid(row=0, column=0, sticky="nsew", padx=2, pady=2, columnspan=2)
                        txt.insert("1.0", info)

                        # create a Scrollbar and associate it with txt
                        scrollb = Scrollbar(txt_frm, command=txt.yview, width=20)
                        scrollb.grid(row=0, column=3, sticky='nsew')
                        txt['yscrollcommand'] = scrollb.set


                        # info on total games added and an exit button
                        Label(txt_frm, text=_("Total of new games added: {}").format(counter)).grid(row=1, column=0)
                        Button(txt_frm, text=_("Close"), height=2, command=lambda: update_win.destroy()).grid(row=1, column=1)
    ##                    Label(txt_frm, text="   ").grid(row=1, column=3)
                        
##                        try:
                        # print('\nSaving new database...')
                        f = open(titlekey_file_name,'w',encoding="utf-8")
                        for line in newdb:
                            if line[-1] != "\n":
                                line += "\n"
                            f.write(str(line))
                        f.close()
                        self.current_status = []
                        self.update_list(rebuild=True, label=build_text)
                    else:
                        self.status_label.config(text=_('Status: Finished update, There were no new games to update!'))
                        print(_('\nStatus: Finished update, There were no new games to update!'))
                        self.root.config(cursor="")
                        try:
                            self.imageLabel.config(cursor="hand2")
                        except:
                            pass
                        self.done_status()
            else:
                try:
                    # print('\nSaving new database...')
                    f = open(titlekey_file_name,'w',encoding="utf-8")
                    self.title = []
                    self.titleID = []
                    self.titleKey = []
                    for line in newdb:
                        if line.strip():
                            self.title.append(line.strip().split("|")[2])
                            self.titleID.append(line.strip().split("|")[0][:16])
                            self.titleKey.append(line.strip().split("|")[1])
                            f.write(line.strip() + '\n')
                    f.close()
                    self.current_status = []
                    self.update_list(rebuild=True, label=_('Status: Finished update, Database rebuilt from scratch'))
                except Exception as e:
                    print(e)
        else:
            self.messages(_("Error"), _("The database server {} might be down or unavailable").format(titlekey_url))

    def update_titlekeys(self):
        self.root.config(cursor="watch")
        self.imageLabel.config(cursor="watch")
        thread = threading.Thread(target = self.threaded_update_titlekeys)
        thread.start()

    def mute_all(self):
        if self.mute == False:
            self.optionMenu.entryconfig(3, label= _("Unmute All Pop-ups"))
            self.mute = True
        elif self.mute == True:
            self.optionMenu.entryconfig(3, label= _("Mute All Pop-ups"))
            self.mute = False
        updateJsonFile("Mute", str(self.mute))

    def messages(self, title, text):
        if self.mute != True:
            messagebox.showinfo(title, text)
        else:
            print(_("\n{}\n").format(text))

    def titlekey_check_option(self):
        global titlekey_check
        if self.titlekey_check == True:
            self.titlekey_check = False
            titlekey_check = self.titlekey_check
            self.optionMenu.entryconfig(5, label= _("Enable Titlekey Check")) # Automatically disable repacking as well
            self.repack = False
            self.optionMenu.entryconfig(4, label= _("Enable NSP Repack"))
        elif self.titlekey_check == False:
            self.titlekey_check = True
            titlekey_check = self.titlekey_check
            self.optionMenu.entryconfig(5, label= _("Disable Titlekey Check"))
            self.repack = True
            self.optionMenu.entryconfig(4, label= _("Disable NSP Repack"))
        updateJsonFile("Titlekey_check", str(self.titlekey_check))
        
    def disable_aria2c(self):
        pass

    def disable_game_image(self):
        if self.game_image_disable == False:
            self.game_image_disable = True
            self.optionMenu.entryconfig(1, label= _("ENABLE GAME IMAGE"))
        elif self.game_image_disable == True:
            self.game_image_disable = False
            self.optionMenu.entryconfig(1, label= _("DISABLE GAME IMAGE"))
        updateJsonFile("Disable_game_image", str(self.game_image_disable))

    def disable_game_description(self):
        if self.game_desc_disable == False:
            self.game_desc_disable = True
            self.optionMenu.entryconfig(0, label= _("ENABLE GAME DESCRIPTION"))
        elif self.game_desc_disable == True:
            self.game_desc_disable = False
            self.optionMenu.entryconfig(0, label= _("DISABLE GAME DESCRIPTION"))
        updateJsonFile("Disable_description", str(self.game_desc_disable))

    def threaded_preload_images(self):
##        try:
        start = time.time()
        for k in self.titleID:
            if k != "":
                tid = k
                isDLC = False
                if not tid.endswith("00"):
                    isDLC = True
                    tid = "{}".format(tid[0:12])
                    indices = [i for i, s in enumerate(self.titleID) if tid in s]
                    if len(indices) >= 2:
                        for t in indices:
                            if self.titleID[t].endswith("000"):
                                tid = self.titleID[t]
                                break
                        if tid.endswith("000"):
                            isDLC = False
                print(_("Currently downloading the image for TID: {}").format(tid))
                if edgeToken == None:
                    image_name = "{}.jpg".format(tid.lower())

                    if not os.path.isfile("Images/{}".format(image_name)):
                        print("don't have")
                        url = "https://terannet.sirv.com/CDNSP/{}".format(image_name)
                        r = requests.get(url)

                        if r.status_code != 200:
                            isDLC = True
                        else:
                            file_name = os.path.join("Images", image_name)
                            urllib.request.urlretrieve(url, file_name)
                else:
                    if not self.game_image_disable and not isDLC:
                        if not os.path.isfile("Images/{}.jpg".format(tid)):
                            base_ver = get_versions(tid)[-1]
                            result = game_image(tid, base_ver, self.titleKey[self.titleID.index(tid)])
                            if result[1] != "Error":
                                if result[1] != "Exist":
                                    if self.sys_name == "Win":
                                        subprocess.check_output("{0} -k keys.txt {1}\\control.nca --section0dir={1}\\section0".format(hactoolPath, result[0].replace("/", "\\")), shell=True)
                                    else:
                                        subprocess.check_output("{0} -k keys.txt '{1}/control.nca' --section0dir='{1}/section0'".format(hactoolPath, result[0]), shell=True)
                                    icon_list = ["icon_AmericanEnglish.dat", "icon_BritishEnglish.dat",\
                                             "icon_CanadianFrench.dat", "icon_German.dat", \
                                             "icon_Italian.dat", "icon_Japanese.dat", \
                                             "icon_LatinAmericanSpanish.dat", "icon_Spanish.dat", \
                                             "icon_Korean.dat", "icon_TraditionalChinese.dat"]
                                    file_name = ""
                                    dir_content = os.listdir(os.path.dirname(os.path.abspath(__file__))+'/Images/{}/section0/'.format(tid))
                                    for i in icon_list:
                                        if i in dir_content:
                                            file_name = i.split(".")[0]
                                            break
                                    os.rename('{}/section0/{}.dat'.format(result[0], file_name), '{}/section0/{}.jpg'.format(result[0], file_name))
                                    shutil.copyfile('{}/section0/{}.jpg'.format(result[0], file_name), 'Images/{}.jpg'.format(tid))
                                    shutil.rmtree(os.path.dirname(os.path.abspath(__file__))+'/Images/{}'.format(tid))
        end = time.time()
        print(_("\nIt took {} seconds for you to get all images!\n").format(end - start))
        self.messages("", _("Done getting all game images!"))
##        except:
##            print("Error getting game images")

    def preload_images(self):
        thread = threading.Thread(target = self.threaded_preload_images)
        thread.start()
    
    def get_update_ver(self):
        if edgeToken == None:
            tid = self.game_titleID.get().lower()
            if tid in known_ver:
                ver = known_ver[tid]
                update_list = []
                if ver == "none":
                    update_list.append("none")
                else:
                    ver = int(ver)
                    
                    while ver != 0:
                        update_list.append(str(ver))
                        ver -= 65536
                    
                if not tid.endswith("00"):
                    update_list.append("0")
                update_list = update_list[::-1]
                update_list.insert(0, _("Latest"))
                self.version_select["values"] = update_list
                self.version_select.set(_("Latest"))
            else:
                print("Unable to get the latest version for this TID")
##                update_list.insert(0, _("Latest"))
                self.version_select["values"] = [_("Latest")]
                self.version_select.set(_("Latest"))
            
        else:
            tid = self.game_titleID.get()
            if tid != "" and len(tid) == 16:
                value = self.titleID.index(tid)
                print(tid)
                try:
                    isDLC = False
                    tid = self.titleID[value]
                    updateTid = tid
                    if tid.endswith('000'):
                        updateTid = '%s800' % tid[:-3]
                    elif tid.endswith('800'):
                        baseTid = '%s000' % tid[:-3]
                        updateTid = tid
                    elif not tid.endswith('00'):
                        isDLC = True
                    update_list = []
                    for i in get_versions(updateTid):
                        update_list.append(i)
                    if isDLC:
                        if update_list[0] != "0":
                            update_list.insert(0, "0")
                    if update_list[0] == 'none':
                        update_list[0] = "0"
                    print(update_list)
                    update_list.insert(0, _("Latest"))
                    self.version_select["values"] = update_list
                    self.version_select.set(_("Latest"))
                except:
                    print(_("Failed to get version"))
            else:
                print(_("No TitleID or TitleID not 16 characters!"))

    def get_update_lastestVer(self):
        if edgeToken == None:
            tid = self.game_titleID.get().lower()
            if tid in known_ver:
                ver = known_ver[tid]
                update_list = []
                if ver == "none":
                    update_list.append("none")
                else:
                    ver = int(ver)

                    while ver != 0:
                        update_list.append(str(ver))
                        ver -= 65536

                if not tid.endswith("00"):
                    update_list.append("0")
                update_list = update_list[::-1]
                update_list.insert(0, _("Latest"))
                self.version_select["values"] = update_list
                self.version_select.set(update_list[-1])
            else:
                print("Unable to get the latest version for this TID")
##                update_list.insert(0, _("Latest"))
                self.version_select["values"] = [_("Latest")]
                self.version_select.set(_("Latest"))

        else:
            tid = self.game_titleID.get()
            if tid != "" and len(tid) == 16:
                value = self.titleID.index(tid)
                print(tid)
                try:
                    isDLC = False
                    tid = self.titleID[value]
                    updateTid = tid
                    if tid.endswith('000'):
                        updateTid = '%s800' % tid[:-3]
                    elif tid.endswith('800'):
                        baseTid = '%s000' % tid[:-3]
                        updateTid = tid
                    elif not tid.endswith('00'):
                        isDLC = True
                    update_list = []
                    for i in get_versions(updateTid):
                        update_list.append(i)
                    if isDLC:
                        if update_list[0] != "0":
                            update_list.insert(0, "0")
                    if update_list[0] == 'none':
                        update_list[0] = "0"
                    print(update_list)
                    update_list.insert(0, _("Latest"))
                    self.version_select["values"] = update_list
                    self.version_select.set(update_list[-1])
                except:
                    print(_("Failed to get version"))
            else:
                print(_("No TitleID or TitleID not 16 characters!"))


    def shorten(self):
        global truncateName
        if truncateName == False:
            truncateName = True
            self.optionMenu.entryconfig(7, label= _("Disable Shorten Name"))
        elif truncateName == True:
            truncateName = False
            self.optionMenu.entryconfig(7, label= _("Enable Shorten Name"))
        updateJsonFile("Shorten", str(truncateName))
            
    def tinfoil_change(self):
        global tinfoil
        if tinfoil == False:
            tinfoil = True
            self.optionMenu.entryconfig(8, label= _("Disable Tinfoil Download"))
        elif tinfoil == True:
            tinfoil = False
            self.optionMenu.entryconfig(8, label= _("Enable Tinfoil Download"))
        updateJsonFile("Tinfoil", str(tinfoil))

    def window_info(self, window):
        x = window.winfo_x()
        y = window.winfo_y()
        w = window.winfo_width()
        h = window.winfo_height()

        size_pos = "{}x{}+{}+{}".format(w, h, x, y)
        return size_pos

    def window_save(self):
        if Toplevel.winfo_exists(self.root):
            updateJsonFile("Main_win", self.window_info(self.root))
        try:
            if Toplevel.winfo_exists(self.queue_win):
                updateJsonFile("Queue_win", self.window_info(self.queue_win))
                global queue_win
                queue_win = self.window_info(self.queue_win)
        except:
            pass
        try:
            if Toplevel.winfo_exists(self.update_window):
                updateJsonFile("Update_win", self.window_info(self.update_window))
                global update_win
                update_win = self.window_info(self.update_window)
        except:
            pass
        try:
            if Toplevel.winfo_exists(self.my_game):
                updateJsonFile("Scan_win", self.window_info(self.my_game))
                global scan_win
                scan_win = self.window_info(self.my_game)
        except:
            pass
        try:
            if Toplevel.winfo_exists(self.base_64):
                updateJsonFile("Base64_win", self.window_info(self.base_64))
                global base64_win
                base64_win = self.window_info(self.base_64)
        except:
            pass
        self.messages("", _("Windows size and position saved!"))

    def my_game_GUI(self):
        global scan_win
        my_game = Toplevel(self.root)
        self.my_game = my_game
        my_game.title(_("Search for existing games"))
        my_game.geometry(scan_win)
        entry_w = 50
        if sys_name == "Mac":
            entry_w = 32
        dir_text = ""
        dir_entry = Entry(my_game, width=entry_w, text=dir_text)
        if self.game_location != "":
            dir_entry.delete(0, END)
            dir_entry.insert(0, self.game_location)
        self.dir_entry = dir_entry
        dir_entry.grid(row=0, column=0, padx=(10,0), pady=10)
        browse_btn = Button(my_game, text=_("Browse"), command=self.my_game_directory)
        browse_btn.grid(row=0, column=1, padx=10, pady=10)
        scan_btn = Button(my_game, text=_("Scan"), command=self.my_game_scan)
        scan_btn.grid(row=1, column=0, columnspan=2, sticky=N, padx=10)

    def my_game_directory(self):
        path = self.normalize_file_path(filedialog.askdirectory())
        if path != "":
            self.game_location = path
            updateJsonFile("Game_location", str(self.game_location))
        self.my_game.lift()
        self.my_game.update()
        self.dir_entry.delete(0, END)
        self.dir_entry.insert(0, path)

    def my_game_scan(self, a_dir=None, silent=False):
        import re
        
        if silent == True:
            a_dir = "_NSPOUT"
            
        if a_dir == None:
            a_dir = self.dir_entry.get()
        
        if a_dir == "":
            if not silent:
                self.messages(_("Error"), _("You didn't choose a directory!"))
                self.my_game.lift()
        elif not os.path.isdir(a_dir):
            if not silent:
                self.messages(_("Error"), _("The chosen directory doesn't exist!"))
                self.my_game.lift()
        else:
            game_list = []
            
            for root, dirs, files in os.walk(a_dir):
                for basename in files:
                    if basename.endswith(".nsp") or basename.endswith(".xci"):
                        game_list.append(basename)
            if not os.path.isdir("Config"):
                os.mkdir("Config")
            tid_exist = [] # Tid that's already in the installed.txt
            ver_exist = []

            if os.path.isfile(r"Config/installed.txt"):
                file = open(r"Config/installed.txt", "r")
                for game in file.readlines():
                    tid_exist.append("{}".format(game.split(",")[0].strip().lower()))
                    ver_exist.append("{}".format(game.split(",")[1].strip().lower()))
                file.close()

            file = open(r"Config/installed.txt", "w")
            for game in game_list:
                title = re.search(r".*[0][0-9a-zA-Z]{15}.*", game)
                if title:
                    try:
                        tid_check = re.compile(r"[0][0-9a-zA-Z]{15}")
                        tid_result = tid_check.findall(game)[0]
                        tid_result = tid_result.lower()
                        if tid_result.endswith("800"):
                            tid_result = "{}000".format(tid_result[:13])
                    except:
                        tid_result = "0"
                    
                    try:
                        ver_check = re.compile(r"[v][0-9]+")
                        ver_result = ver_check.findall(game)[0]
                        ver_result = ver_result.split("v")[1]
                    except:
                        ver_result = "0"
                    if tid_result != "0":
                        if tid_result in tid_exist: # Check if the tid is already in installed.txt
                            if int(ver_result) > int(ver_exist[tid_exist.index(tid_result)]):
                                ver_exist[tid_exist.index(tid_result)] = ver_result
                        else:
                            tid_exist.append(tid_result.lower())
                            ver_exist.append(ver_result)

            for i in range(len(tid_exist)):
                file.write("{}, {}\n".format(tid_exist[i], ver_exist[i]))

            file.close()
            if not silent:
                self.update_list(rebuild=True, label=build_text)
                self.my_game.destroy()
            
    def base_64_GUI(self):
        global base64_win
        base_64 = Toplevel(self.root)
        self.base_64 = base_64
        base_64.title(_("Base64 Decoder"))
        base_64.geometry(base64_win)
        entry_w = 50
        if sys_name == "Mac":
            entry_w = 28
        base_64_label = Label(base_64, text=_("Base64 text:"))
        base_64_label.grid(row=0, column=0)
        base_64_entry = Entry(base_64, width=entry_w)
        self.base_64_entry = base_64_entry
        base_64_entry.grid(row=0, column=1, padx=(10,0), pady=10)
        browse_btn = Button(base_64, text=_("Decode"), command=self.decode_64)
        browse_btn.grid(row=0, column=2, padx=10, pady=10)

        decoded_label = Label(base_64, text=_("Decoded text:"))
        decoded_label.grid(row=1, column=0)
        decoded_entry = Entry(base_64, width=entry_w)
        self.decoded_entry = decoded_entry
        decoded_entry.grid(row=1, column=1, padx=(10,0), pady=10)
        scan_btn = Button(base_64, text=_("Open"), command=self.base64_open)
        scan_btn.grid(row=1, column=2, sticky=N, padx=10, pady=10)

    def decode_64(self):
        base64_text = self.base_64_entry.get()
        self.decoded_entry.delete(0, END)
        self.decoded_entry.insert(0, base64.b64decode(base64_text))

    def base64_open(self):
        url = self.decoded_entry.get()
        webbrowser.open(url, new=0, autoraise=True)

    def make_list(self):
        # Create list in advance
        self.full_list = self.current_status
        
        if current_mode_global == "CDNSP":
            self.no_demo_list = [] # No demo list
            for game in self.full_list:
                if not "demo" in game[2].strip().lower() and not "体験版" in game[2].strip().lower():
                    self.no_demo_list.append(game)
                    
            self.no_jap_list = []
            for game in self.full_list:
                try:
                    game[2].strip().lower().encode(encoding='utf-8').decode('ascii')
                except UnicodeDecodeError:
                    pass
                else:
                    self.no_jap_list.append(game)

            self.no_demo_jap_list = []
            for game in self.full_list:
                if not "demo" in game[2].strip().lower() and not "体験版" in game[2].strip().lower():
                    try:
                        game[2].strip().lower().encode(encoding='utf-8').decode('ascii')
                    except UnicodeDecodeError:
                        pass
                    else:
                        self.no_demo_jap_list.append(game)
                        
        elif current_mode_global == "Nut":
            self.no_demo_list = [] # No demo list
            for game in self.full_list:
                try:
                    if self.info_list[int(game[0])-1][0] == "0":
                        self.no_demo_list.append(game)
                except IndexError:
                    pass
                    
            self.no_jap_list = []
            for game in self.full_list:
                try:
                    if self.info_list[int(game[0])-1][1] != "JP":
                        self.no_jap_list.append(game)
                except IndexError:
                    pass

            self.no_demo_jap_list = []
            for game in self.full_list:
                try:
                    if self.info_list[int(game[0])-1][0] == "0":
                        if self.info_list[int(game[0])-1][1] != "JP":
                            self.no_demo_jap_list.append(game)
                except IndexError:
                    pass
        
        
    def filter_game(self):        
        demo_off = self.demo.get()
        no_jap = self.jap.get()

        if demo_off:
            updateJsonFile("No_demo", "True")
        else:
            updateJsonFile("No_demo", "False")

        if no_jap:
            updateJsonFile("No_japanese_games", "True")
        else:
            updateJsonFile("No_japanese_games", "False")

        if demo_off and no_jap:
            self.current_status = self.no_demo_jap_list
        elif demo_off:
            self.current_status = self.no_demo_list
        elif no_jap:
            self.current_status = self.no_jap_list
        else:
            self.current_status = self.full_list
            
        try:
            search_term = self.search_var.get()
        except:
            search_term = ""
        self.tree.delete(*self.tree.get_children())
        for game_status in self.current_status:
            number = game_status[0].strip()
            tid = game_status[1].strip()
            game_name = game_status[2].strip()
            state = game_status[3].strip()
            release_date = game_status[4].strip()
            
            tree_row = (str(number).zfill(4), tid, game_name, state, release_date)
            if search_term.lower().strip() in game_name.lower() or search_term.lower().strip() in tid.lower():
                self.tree.insert('', 'end', values=tree_row)
                
        # Reset the sorting back to default (descending)
        self.tree.heading("num", text="#", command=lambda c="num": self.sortby(self.tree, c, 1))
        self.tree.heading("tid", text=_("TitleID"), command=lambda c="tid": self.sortby(self.tree, c, 1))
        self.tree.heading("G", text=_("Game"), command=lambda c="G": self.sortby(self.tree, c, 1))
        self.tree.heading("S", text=_("State"), command=lambda c="S": self.sortby(self.tree, c, 1))
                    
    def sysver_zero(self):
        global sysver0
        if sysver0 == False:
            sysver0 = True
            self.optionMenu.entryconfig(9, label= _("Disable SysVer 0 Patch"))
        elif sysver0 == True:
            sysver0 = False
            self.optionMenu.entryconfig(9, label= _("Enable SysVer 0 Patch"))
        updateJsonFile("SysVerZero", str(sysver0))

##    def threaded_preload_desc(self):
##        self.status_label.config(text=_("Status: Downloading all game descriptions"))
##        
##        global game_info_json
##        for tid in self.titleID:
##            if not tid.endswith("00"):
##                tid = "{}".format(tid[0:12])
##                indices = [i for i, s in enumerate(self.titleID) if tid in s]
##                if len(indices) >= 2:
##                    for t in indices:
##                        if self.titleID[t].endswith("000"):
##                            tid = self.titleID[t]
##                            break
##            if tid not in game_info_json:
##                if len(tid) == 16:
##                    print(tid)
##                    self.download_desc(tid, silent=True)
##                
##        print(_("Done preloading all game descriptions!"))
##        thread = threading.Thread(target = lambda: self.done_status())
##        thread.start()
##
##    def preload_desc(self):
##        thread = threading.Thread(target = lambda: self.threaded_preload_desc())
##        thread.start()

    def credit_gui(self):
        credit_win = Toplevel(self.root)
##        credit_win.geometry("600x500+100+100")
        self.credit_win = credit_win
        credit_win.title(_("Credits"))

        credit_text = """This GUI was originally a project started by Bob, and eventually grew thanks to the overwhelming support from the community!

We now support 24 different languages!
I would like to thank the following users for translating:

German: Jojo#1234
Portuguese: KazumaKiryu#7300
Chinese Simplified: Dolur#7867
Chinese Traditional: Maruku#7128
CZech: -Spider86-#7530
Japanese: Jinoshi(ジノシ)#4416
Korean: RoutineFree#4012
Spanish: pordeciralgo#3603
French: cdndave#1833
Russian: JHI_UA#8876
Arabic: mk#6387
Turkish: arkham_knight#9797
Dutch: Soundwave#2606
Italian: Vinczenon#7788
Hebrew: dinoster#0218
Persian: SirArmazd#8283
Thai: Pacmasaurus#4158
Greek: ioann1s#3498
Indonesian: Damar#0799
Polish: szczuru#7105
Afrikaans: twitchRSA#5765
Vietnamese: gurucuku#4629
Hungarian: Quince#2831
Malaysian: fadzly#4390"""

        credit = Text(credit_win, wrap=WORD)
        credit.grid(row=0, column=0)
        credit.insert(INSERT, credit_text)
        scrollb = Scrollbar(credit_win, command=credit.yview)
        scrollb.grid(row=0, column=1, sticky='nsew')
        credit['yscrollcommand'] = scrollb.set

    def threaded_update_ver_list(self):
        self.status_label.config(text=_("Status: Updating version list..."))
        global known_ver
        known_ver = {}

        if not os.path.isfile("Config/Version_info.json"):
            print(_("\nCan't find {} file!\n").format("Version_info.json"))
            print(_("Attempting to download the Version_info.json file for you"))
            urllib.request.urlretrieve("https://raw.githubusercontent.com/Bob123a1/CDNSP-GUI-Files/master/Config/Version_info.json", "Config/Version_info.json")
                
        if os.path.isfile("Config/Version_info.json"):
            ver_file = open("Config/Version_info.json", "r", encoding="utf8")
            known_ver = json.load(ver_file)
            ver_file.close()

            installed = []
            file_path = r"Config/installed.txt"
            if os.path.exists(file_path):
                file = open(file_path, "r")
                for line in file.readlines():
                    if line[-1] == "\n":
                        line = line[:-1]
                    installed.append(line.split(",")[0].strip())
                file.close()
            print("\nUpdating version list...")
            for tid in installed:
                if tid.endswith("00"):
                    updateTid = "{}800".format(tid[:13])
                else:
                    updateTid = tid
                latest_ver = str(get_versions(updateTid)[-1])

                print("Tid: {}, latest version: {}".format(updateTid, latest_ver))
                known_ver[tid] = latest_ver
                
            ver_file = open("Config/Version_info.json", "w", encoding="utf8")
            json.dump(known_ver, ver_file, indent=4)
            ver_file.close()
        else:
            print(_("Unable to find Version_info.json file inside your Config folder"))
        self.update_list(rebuild=True)
        
    def update_ver_list(self):
        thread = threading.Thread(target=self.threaded_update_ver_list)
        thread.start()

    def change_mode(self):
        if self.current_mode == "CDNSP":
            self.current_mode = "Nut"
            self.list_mode.config(text=_("CDNSP Mode").replace("\n", ""))
        elif self.current_mode == "Nut":
            self.current_mode = "CDNSP"
            self.list_mode.config(text=_("Nut Mode").replace("\n", ""))
        updateJsonFile("Mode", self.current_mode)        
        self.current_mode_text.config(text=_("Current mode is: {}").format(self.current_mode))
        self.root.destroy()
        main()

    # Thanks vertigo for your kindness to help me with the unlocking part of the GUI 
    def unlock_nsx_gui_func(self):
        unlock_nsx_gui = Toplevel(self.root)
        self.unlock_nsx_gui = unlock_nsx_gui
        unlock_nsx_gui.title(_("Unlock NSX Files"))
        entry_w = 50
        if sys_name == "Mac":
            entry_w = 32
        dir_text = ""

        # Input (Entry) Bar
        Label(unlock_nsx_gui, text=_("Select the folder that contains your NSX Files\n and the program will unlock the NSX files that\n we have the titlekeys for.")).grid(row=0, column=0, padx=(10,0), pady=10, columnspan=2) 
        dir_entry = Entry(unlock_nsx_gui, width=entry_w, text=dir_text)
        self.dir_entry_nsx = dir_entry
        dir_entry.grid(row=1, column=0, padx=(10,0), pady=10)
        browse_btn = Button(unlock_nsx_gui, text=_("Browse"), command=self.unlock_nsx)
        browse_btn.grid(row=1, column=1, padx=10, pady=10)
        scan_btn = Button(unlock_nsx_gui, text=_("Unlock NSX"), command=self.unlock_nsx_scan)
        scan_btn.grid(row=2, column=0, columnspan=2, sticky=N, padx=10, pady=(0, 20))

    def unlock_nsx(self):
        path = self.normalize_file_path(filedialog.askdirectory())
        self.unlock_nsx_gui.lift()
        self.unlock_nsx_gui.update()
        self.dir_entry_nsx.delete(0, END)
        self.dir_entry_nsx.insert(0, path)

    def unlock_nsx_scan(self):
        import re

        a_dir = self.dir_entry_nsx.get()

        if a_dir == "":
            self.messages(_("Error"), _("You didn't choose a directory!"))
            self.unlock_nsx_gui.lift()
        elif not os.path.isdir(a_dir):
            self.messages(_("Error"), _("The chosen directory doesn't exist!"))
            self.unlock_nsx_gui.lift()
        else:
            game_list = []
            
            for root, dirs, files in os.walk(a_dir):
                for basename in files:
                    if basename.lower().endswith(".nsx"):
                        game_list.append(os.path.join(root, basename))
                print("\n\n")

            
            if len(game_list) != 0:
                # Temporary load the titlekeys.txt file to check if 
                # we have the titlekey for the corresponding titleID

                temp_tid = []
                temp_tkey = []
                notified = False
                with open("titlekeys.txt", "r", encoding="utf-8") as file:
                    for line in file.readlines():
                        line = line.strip()
                        try:
                            titleID, titleKey, title = line.split("|")
                        except:
                            if not notified:
                                print(_("Check if there's extra spaces at the bottom of your titlekeys.txt file! Delete if you do!"))
                                notified = True
                        if len(titleID) == 16 or len(titleID) == 32:
                            if titleID != "":
                                temp_tid.append(titleID[:16].lower())
                                temp_tkey.append(titleKey)
                unlocked_a_game = False # Check if a game has been unlocked
                for game in game_list:
                    game_basename = os.path.basename(game)
                    tid_result, ver_result = self.get_tid_get_ver(game_basename, ".nsx")
                    if tid_result != "0":
                        print()
                        print(_("Attempting to unlock: {}").format(game_basename))
                        if tid_result in temp_tid:
                            print(_("Found the titlekey for {}, unlocking the game now").format(game_basename))
                            tkey_block = self.find_tkey_block(game)
                            if tkey_block != 0:
                                self.write_tkey_block(game, tkey_block, temp_tkey[temp_tid.index(tid_result)])
                                print(_("Successfully unlocked {}").format(game_basename))
                                unlocked_a_game = True
                            else:
                                print(_("Unable to find the titlekey block"))
                        else:
                            print(_("Unable to find the titlekey for {}").format(game_basename))
                if unlocked_a_game:
                    self.messages("", _("Done unlocking nsx files"))
                else:
                    self.messages("", _("Couldn't find nsx files that needs to be unlocked"))
            else:
                self.messages("", _("Couldn't find nsx files that needs to be unlocked"))
            self.unlock_nsx_gui.destroy()

    def get_tid_get_ver(self, game, extension='.nsp'):
        r_expression = r".*[0][0-9a-zA-Z]{15}.*[" + extension +"]"
        title = re.search(r_expression, game)
        if title:
            try:
                tid_check = re.compile(r"[0][0-9a-zA-Z]{15}")
                tid_result = tid_check.findall(game)[0]
                tid_result = tid_result.lower()
                if tid_result.endswith("800"):
                    tid_result = "{}000".format(tid_result[:13])
            except:
                tid_result = "0"
            
            try:
                ver_check = re.compile(r"[v][0-9]+")
                ver_result = ver_check.findall(game)[0]
                ver_result = ver_result.split("v")[1]
            except:
                ver_result = "0"
            return (tid_result, ver_result)
        return ("0", "0")

    def find_tkey_block(self, file):
        f = open(file, "rb")

        tkey_index = 0
        # Find titlekey block location
        for chunk in iter(lambda: f.read(16), ""):
            start = time.time()
            if chunk == b'Root-CA00000003-':
                root_index = f.tell()-16 # Index of Root-CA00000003-
                tkey_index = root_index + 4*16 # Index of tkey block 
                break
        ##D1D20486 21CDF338 37F1797E 217CD3B7
        f.close()
        return tkey_index

    def write_tkey_block(self, file, tkey_block, title_key):
        f = open(file, "r+b")
        f.seek(tkey_block)
        f.write(uhx(title_key))
        f.close()
        os.rename(file, file[:-3]+"nsp")

    def shutdown_set(self):
        if self.auto_shutdown == False:
            self.auto_shutdown = True
            self.toolMenu.entryconfig(5, label= _("Auto Shutdown: ON"))
        elif self.auto_shutdown == True:
            self.auto_shutdown = False
            self.toolMenu.entryconfig(5, label= _("Auto Shutdown: OFF"))
        
    def shutdown(self):
        if platform.system()=="Windows":
            os.system("shutdown -s -t 0")
     
        else:
            os.system("shutdown -h now")

    def threaded_download_all_games(self):
        self.messages("", _("Downloading all games"))
        for game in self.current_status:
            tid = game[1]
            self.download_option_B_U_D(tid)
            print("\n" + _("Finished downloading") + " " + tid)
            
    def download_all_games(self):
        thread = threading.Thread(target=self.threaded_download_all_games)
        thread.start()

    def download_option_B_U_D(self, tid):
        print(_("Downloading") + " " + tid)

        # Download Base Game
        base_tid = "{}000".format(tid[0:13])
        tkey = self.titleKey[self.titleID.index(tid)]
        base_ver = get_versions(base_tid)[-1]
        download_game(base_tid, base_ver, tkey, nspRepack=self.repack, path_Dir=self.path)

        # Download Update
        updateTid = "{}800".format(tid[0:13])
        ver = get_versions(updateTid)[-1]
        if ver == "none":
            ver = 0
        if int(ver) > 0:
            download_game(updateTid, ver, tkey, nspRepack=self.repack, path_Dir=self.path)

##        # Download DLC
##        DLC_titleID = []
##        tid = "{}".format(tid[0:12])
##        indices = [i for i, s in enumerate(self.titleID) if tid in s]
##        for index in indices:
##            if not self.titleID[index].endswith("00"):
##                DLC_titleID.append(self.titleID[index])
##        for DLC_ID in DLC_titleID:
##            DLC_ver = get_versions(DLC_ID)[-1]
##            download_game(DLC_ID, DLC_ver, self.titleKey[self.titleID.index(DLC_ID)], nspRepack=self.repack, path_Dir=self.path)

    def check_update(self):
        output_text = ""
        update = ""
        if not os.path.isfile("Config/version.txt"):
            file = open("Config/version.txt", "w")
            file.write("{}\n{}".format(__gui_version__, __lang_version__))
            file.close()

        file = open("Config/version.txt", "r")
        ver_list = file.readlines()
        file.close()
        
        if len(ver_list) != 2:
            file = open("Config/version.txt", "w")
            file.write("{}\n{}".format(__gui_version__, __lang_version__))
            file.close()
            
            file = open("Config/version.txt", "r")
            ver_list = file.readlines()
            file.close()
            
        
        gui_ver, lang_ver = ver_list
        gui_ver = __gui_version__
        
        gui_ver = gui_ver.strip()
        lang_ver = lang_ver.strip()
        
        print("\n"+_("GUI Version: {}").format(__gui_version__))
        print(_("Language Files Version: {}").format(lang_ver))
        
        self.gui_ver = __gui_version__
        self.lang_ver = lang_ver
                
        new_cdnsp_ver = requests.get("https://\
        raw.githubusercontent.com/Bob123a1/\
        CDNSP-GUI-Files/master/gui_version.txt".replace(" ", "")).text.replace("\n", "")

        new_lang_ver = requests.get("https://\
        raw.githubusercontent.com/Bob123a1/\
        CDNSP-GUI-Files/master/language_version.txt".replace(" ", "")).text.replace("\n", "")
        
        self.new_cdnsp_ver = new_cdnsp_ver
        self.new_lang_ver = new_lang_ver
        
        if StrV(new_cdnsp_ver) > StrV(gui_ver):
            output_text += "New GUI verison available"
            update = "G" # Indicates that the GUI has an update

        if StrV(new_lang_ver) > StrV(lang_ver):
            if output_text == "":
                output_text += "New language files available"
                update = "L"
            else:
                output_text = "New GUI version and language files available"
                update = "GL"

        if output_text == "":
            output_text = "No new updates"
        return update

    def download_update(self):
        gui_file, lang_file = requests.get("https://\
        raw.githubusercontent.com/Bob123a1/\
        CDNSP-GUI-Files/master/download_location.txt".replace(" ", "")).text.strip().split("\n")

        update_result = self.update_result

        updated = True
        
        if update_result == "GL":
            urllib.request.urlretrieve(gui_file, gui_file.split("/")[-1])
            urllib.request.urlretrieve(lang_file, "lang.zip")
            self.messages("", _("New GUI version and Language Files downloaded!" + "\n" + _("Please restart your GUI")))
            text = "{}\n{}".format(self.new_cdnsp_ver, self.new_lang_ver)
        elif update_result == "G":
            urllib.request.urlretrieve(gui_file, gui_file.split("/")[-1])
            self.messages("", _("New GUI version downloaded!" + "\n" + "\n" + _("Please restart your GUI")))
            text = "{}\n{}".format(self.new_cdnsp_ver, self.lang_ver)
        elif update_result == "L":
            urllib.request.urlretrieve(lang_file, "lang.zip")
            self.messages("", _("New Language Files downloaded!" + "\n" + _("Please restart your GUI")))
            text = "{}\n{}".format(__gui_version__, self.new_lang_ver)
        else:
            updated = False

        if "L" in update_result:
            if os.path.isfile("lang.zip"):
                import zipfile

                zip_file = zipfile.ZipFile("lang.zip", "r")
                zip_file.extractall()
                zip_file.close()
            else:
                print(_("Couldn't find the download lang.zip file in your GUI folder"))

        if updated:
            file = open("Config/version.txt", "w")
            file.write(text)
            file.close()
            self.status_label.config(text=_("Status: Done!"))
        else:
            self.messages("", _("No new update available"))

        
# ------------------------
# Main Section

def find_index(header_list, text):
    if text in header_list:
        return header_list.index(text)
    else:
        print("Couldn't match the header text: {} in the header provided".format(text))
        sys.exit()

def read_installed():
    if os.path.isfile("Config/installed.txt"):
        global installed_global
        installed_global = {}
        with open("Config/installed.txt", "r", encoding="utf-8") as file:
            for line in file.readlines():
                tid = line.split(",")[0].strip()
                ver = line.split(",")[1].strip()
                if tid not in installed_global:
                    installed_global[tid] = ver
                else:
                    if ver > installed_global[tid]:
                        installed_global[tid] = ver

def read_titlekey_list():
    titleID_list = []
    titleKey_list = []
    title_list = []
    info_list = []
    # Read titlekeys.txt file
    if current_mode_global == "CDNSP":
        f = open("titlekeys.txt", "r", encoding="utf8")
        content = f.readlines()
        notified = False
        for i in range(len(content)):
            titleID = ""
            try:
                titleID, titleKey, title = content[i].split("|")
            except:
                if not notified:
                    print(_("Check if there's extra spaces at the bottom of your titlekeys.txt file! Delete if you do!"))
                    notified = True
            if len(titleID) == 16 or len(titleID) == 32:
                if titleID != "":
                    titleID_list.append(titleID[:16].lower())
                    titleKey_list.append(titleKey)
                    if title[:-1] == "\n":
                        title_list.append(title[:-1])
                    else:
                        title_list.append(title)
        f.close()
    elif current_mode_global == "Nut":
        unique_tid_list = []
        info_list = []
        f = open("Nut_titlekeys.txt", "r", encoding="utf8")
        content = f.readlines()

        found_line = False
        # Find the header info
        for line in content:
            if line[0] != "#" and line[:2] == "id":
                line = line.strip()
                found_line = True
                header_list = line.split("|")
                index_tid = find_index(header_list, "id")
                index_key = find_index(header_list, "key")
                index_title = find_index(header_list, "name")
                index_isDemo = find_index(header_list, "isDemo")
                index_region = find_index(header_list, "region")
                break
                

        if not found_line:
            print("\n"+"Error: Header is not found in the Nut_titlekeys.txt file, please double check you have the header")
            sys.exit()
            
        notified = False
        for i in range(len(content)):
            titleID = ""
            if content[i][0:2] == "01":
                try:
                    content_row = content[i].split("|")
                except:
                    if not notified:
                        print(_("Check if there's extra spaces at the bottom of your Nut_titlekeys.txt file! Delete if you do!"))
                        notified = True
                titleID = content_row[index_tid].lower()
                if len(titleID) == 32:
                    titleID = titleID[:16]
                if len(titleID) == 16 or len(titleID) == 32:
                    if titleID.endswith("800"):
                        titleID = "{}000".format(titleID[:13])
                    if titleID not in unique_tid_list:
                        unique_tid_list.append(titleID)
                        if titleID != "":
                            titleKey = content_row[index_key]
                            if len(titleKey) == 32:
                                title = content_row[index_title]
                                isDemo = content_row[index_isDemo]
                                if not titleID.endswith("00"):
                                    title = "[DLC] " + title
                                if isDemo == "1":
                                    title += " Demo"
                                titleID_list.append(titleID[:16].lower())
                                titleKey_list.append(titleKey)
                                if title[:-1] == "\n":
                                    title_list.append(title[:-1])
                                else:
                                    title_list.append(title)
                                region = content_row[index_region].strip()
                                info_list.append((isDemo, region))
        f.close()

    if os.path.isfile("titlekeys_overwrite.txt"):
        t_overwrite = open("titlekeys_overwrite.txt", "r", encoding="utf8")
        for line in t_overwrite.readlines():
            line = line.strip()
            if "#" not in line and line != "":
                if line[-1] == "\n":
                    line = line[:-1]
                item = line.split("|")
                if len(item) == 2:
                    _tid = item[0][:16]
                    _tkey = item[1][:32]
                    _name = "Unknown Title"
                elif len(item) == 3:
                    _tid = item[0][:16]
                    _tkey = item[1][:32]
                    _name = item[2]
                
                if len(_tid) == 16:
                    _tid = _tid.lower()
                    if len(_tkey) == 32:
                        if _tid in titleID_list:
                            titleKey_list[titleID_list.index(_tid)] = _tkey
                        else:

                            titleID_list.append(_tid)
                            titleKey_list.append(_tkey)
                            title_list.append(_name)
    return (titleID_list, titleKey_list, title_list, info_list)

def main():
    urllib3.disable_warnings()       
    configPath = os.path.join(os.path.dirname(__file__), 'CDNSPconfig.json')
    global hactoolPath, keysPath, NXclientPath, ShopNPath, reg, fw, did, env, dbURL, nspout
    hactoolPath, keysPath, NXclientPath, ShopNPath, reg, fw, did, env, dbURL, nspout = load_config(configPath)

    global current_mode_global
    current_mode_global = get_current_mode()
    
    spam_spec = util.find_spec("tqdm")
    found = spam_spec is not None
    global tqdmProgBar
    if found:
        tqdmProgBar = True
    else:
        tqdmProgBar = False
        print('Install the tqdm library for better-looking progress bars! (pip install tqdm)')
    global keysArg
    if keysPath != '':
        keysArg = ' -k "%s"' % keysPath
    else:
        keysArg = ''

    # Create the list of TID, TKEY, and Game name to be passed to the Application class
    global titleID_list
    global titleKey_list
    global title_list
    global info_list

    titleID_list = []
    titleKey_list = []
    title_list = []
    info_list = []

    global edgeToken
    
    edgeToken = None
    if os.path.isfile("edge_token.txt"):
        with open("edge_token.txt", encoding="utf-8") as file:
            edgeToken = file.read()
            edgeToken = edgeToken.strip()
            if edgeToken == "":
                edgeToken = None
    print("\nToken: ", end="")
    print(edgeToken)
    root = Tk()
    root.title("CDNSP GUI - Bobv"+__gui_version__)
    Application(root, titleID_list, titleKey_list, title_list, dbURL, info_list=info_list)

    root.mainloop()

if __name__ == '__main__':
    main()


================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like
Download .txt
gitextract_45569mqu/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── CDNSP-GUI-Bob.py
├── LICENSE
├── README.md
└── locales/
    ├── af/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── ar/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── de/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── el/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── en/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── es/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── fa/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── fr/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── he/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── hu/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── id/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       ├── language.po
    │       └── language_old.po
    ├── it/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       ├── language.po
    │       └── language_old.po
    ├── ja/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       ├── language.po
    │       └── language_.po
    ├── ko/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── ms/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── nl/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── pl/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── pt/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       ├── language.po
    │       ├── language_old.mo
    │       └── language_old.po
    ├── ru/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── th/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── tr/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── vi/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    ├── zh-cn/
    │   └── LC_MESSAGES/
    │       ├── language.mo
    │       └── language.po
    └── zh-tw/
        └── LC_MESSAGES/
            ├── language.mo
            └── language.po
Download .txt
SYMBOL INDEX (129 symbols across 1 files)

FILE: CDNSP-GUI-Bob.py
  function set_lang (line 97) | def set_lang(default_lang = "en"):
  function check_req_file (line 137) | def check_req_file(file):
  function install_module (line 142) | def install_module(module):
  function add_to_installed (line 148) | def add_to_installed(tid, ver):
  function read_at (line 277) | def read_at(f, off, len):
  function read_u8 (line 281) | def read_u8(f, off):
  function read_u16 (line 284) | def read_u16(f, off):
  function read_u32 (line 287) | def read_u32(f, off):
  function read_u48 (line 290) | def read_u48(f, off):
  function read_u64 (line 294) | def read_u64(f, off):
  function sha256_file (line 297) | def sha256_file(fPath):
  function bytes2human (line 316) | def bytes2human(n, f='%(value).3f %(symbol)s'):
  function get_name (line 330) | def get_name(tid):
  function safe_name (line 358) | def safe_name(name):
  function safe_filename (line 361) | def safe_filename(safe_name):
  function check_tid (line 364) | def check_tid(tid):
  function check_tkey (line 367) | def check_tkey(tkey):
  function load_config (line 370) | def load_config(fPath):
  function gen_tik (line 416) | def gen_tik(fPath, rightsID, tkey, mkeyrev):
  function gen_cert (line 441) | def gen_cert(fPath):
  function decrypt_NCA (line 559) | def decrypt_NCA(fPath, outDir=''):
  function get_name_from_nacp (line 584) | def get_name_from_nacp(fPath):
  function cert_dead (line 595) | def cert_dead():
  function make_request (line 607) | def make_request(method, url, certificate='', hdArgs={}):
  function print_info (line 647) | def print_info(tid):
  function get_info (line 689) | def get_info(tid='', freeword=''):
  function get_versions (line 733) | def get_versions(tid):
  function check_versions (line 758) | def check_versions(fPath):
  function download_file (line 799) | def download_file(url, fPath, fSize=0):
  function download_cetk (line 847) | def download_cetk(rightsID, fPath):
  function download_title (line 857) | def download_title(gameDir, tid, ver, tkey='', nspRepack=False, verify=F...
  function download_title_tinfoil (line 959) | def download_title_tinfoil(gameDir, tid, ver, tkey='', nspRepack=False, ...
  function download_game (line 1054) | def download_game(tid, ver, tkey='', nspRepack=False, verify=False, clea...
  function download_sysupdate (line 1172) | def download_sysupdate(ver):
  class cnmt (line 1203) | class cnmt:
    method __init__ (line 1225) | def __init__(self, fPath, hdPath):
    method parse (line 1255) | def parse(self, contentType=''):
    method gen_xml (line 1284) | def gen_xml(self, ncaPath, outf):
    method gen_xml_tinfoil (line 1330) | def gen_xml_tinfoil(self, ncaPath, outf):
  class nsp (line 1376) | class nsp:
    method __init__ (line 1377) | def __init__(self, outf, files):
    method repack (line 1381) | def repack(self):
    method _gen_header (line 1412) | def _gen_header(self):
  function game_image (line 1445) | def game_image(tid, ver, tkey="", nspRepack=False, n='',verify=False):
  function read_game_info (line 1521) | def read_game_info():
  function updateJsonFile (line 1543) | def updateJsonFile(key, value, root=""):
  function get_current_mode (line 1560) | def get_current_mode():
  function GUI_config (line 1569) | def GUI_config(fPath):
  class Application (line 1650) | class Application():
    method __init__ (line 1652) | def __init__(self, root, titleID, titleKey, title, dbURL, info_list=No...
    method queue_menu_setup (line 2056) | def queue_menu_setup(self):
    method sortby (line 2095) | def sortby(self, tree, col, descending):
    method remove_all (line 2111) | def remove_all(self, dump_queue = False):
    method remove_all_and_dump (line 2117) | def remove_all_and_dump(self):
    method threaded_eShop_link (line 2120) | def threaded_eShop_link(self, evt):
    method eShop_link (line 2152) | def eShop_link(self, evt):
    method update_list (line 2159) | def update_list(self, search=False, rebuild=False, label="Status:"):
    method done_status (line 2339) | def done_status(self, display_text="Status: Done!"):
    method threaded_game_info (line 2347) | def threaded_game_info(self, evt):
    method game_info (line 2451) | def game_info(self, evt):
    method game_desc (line 2456) | def game_desc(self, tid):
    method download_desc (line 2528) | def download_desc(self, tid, silent=False):
    method threaded_download (line 2794) | def threaded_download(self):
    method download (line 2904) | def download(self):
    method pause_download_command (line 2908) | def pause_download_command(self):
    method export_persistent_queue (line 2922) | def export_persistent_queue(self):
    method import_persistent_queue (line 2925) | def import_persistent_queue(self):
    method dump_persistent_queue (line 2928) | def dump_persistent_queue(self, path = r'Config/CDNSP_queue.json'):
    method load_persistent_queue (line 2940) | def load_persistent_queue(self, path = r'Config/CDNSP_queue.json'):
    method get_index_in_queue (line 2954) | def get_index_in_queue(self, item):
    method add_selected_items_to_queue (line 2960) | def add_selected_items_to_queue(self):
    method add_items_to_queue (line 2963) | def add_items_to_queue(self, indices):
    method process_item_versions (line 2983) | def process_item_versions(self, tid, ver):
    method add_item_to_queue (line 2997) | def add_item_to_queue(self, item, dump_queue = False):
    method remove_selected_items (line 3026) | def remove_selected_items(self):
    method remove_items (line 3029) | def remove_items(self, indices):
    method remove_item (line 3041) | def remove_item(self, index, dump_queue = False):
    method threaded_download_all (line 3047) | def threaded_download_all(self):
    method download_all (line 3134) | def download_all(self):
    method normalize_file_path (line 3145) | def normalize_file_path(self, file_path):
    method change_dl_path (line 3151) | def change_dl_path(self):
    method change_nsp_path (line 3157) | def change_nsp_path(self):
    method nsp_repack_option (line 3165) | def nsp_repack_option(self):
    method threaded_update_titlekeys (line 3174) | def threaded_update_titlekeys(self):
    method update_titlekeys (line 3329) | def update_titlekeys(self):
    method mute_all (line 3335) | def mute_all(self):
    method messages (line 3344) | def messages(self, title, text):
    method titlekey_check_option (line 3350) | def titlekey_check_option(self):
    method disable_aria2c (line 3366) | def disable_aria2c(self):
    method disable_game_image (line 3369) | def disable_game_image(self):
    method disable_game_description (line 3378) | def disable_game_description(self):
    method threaded_preload_images (line 3387) | def threaded_preload_images(self):
    method preload_images (line 3450) | def preload_images(self):
    method get_update_ver (line 3454) | def get_update_ver(self):
    method get_update_lastestVer (line 3514) | def get_update_lastestVer(self):
    method shorten (line 3575) | def shorten(self):
    method tinfoil_change (line 3585) | def tinfoil_change(self):
    method window_info (line 3595) | def window_info(self, window):
    method window_save (line 3604) | def window_save(self):
    method my_game_GUI (line 3637) | def my_game_GUI(self):
    method my_game_directory (line 3658) | def my_game_directory(self):
    method my_game_scan (line 3668) | def my_game_scan(self, a_dir=None, silent=False):
    method base_64_GUI (line 3739) | def base_64_GUI(self):
    method decode_64 (line 3764) | def decode_64(self):
    method base64_open (line 3769) | def base64_open(self):
    method make_list (line 3773) | def make_list(self):
    method filter_game (line 3829) | def filter_game(self):
    method sysver_zero (line 3874) | def sysver_zero(self):
    method credit_gui (line 3910) | def credit_gui(self):
    method threaded_update_ver_list (line 3953) | def threaded_update_ver_list(self):
    method update_ver_list (line 3995) | def update_ver_list(self):
    method change_mode (line 3999) | def change_mode(self):
    method unlock_nsx_gui_func (line 4012) | def unlock_nsx_gui_func(self):
    method unlock_nsx (line 4031) | def unlock_nsx(self):
    method unlock_nsx_scan (line 4038) | def unlock_nsx_scan(self):
    method get_tid_get_ver (line 4105) | def get_tid_get_ver(self, game, extension='.nsp'):
    method find_tkey_block (line 4127) | def find_tkey_block(self, file):
    method write_tkey_block (line 4142) | def write_tkey_block(self, file, tkey_block, title_key):
    method shutdown_set (line 4149) | def shutdown_set(self):
    method shutdown (line 4157) | def shutdown(self):
    method threaded_download_all_games (line 4164) | def threaded_download_all_games(self):
    method download_all_games (line 4171) | def download_all_games(self):
    method download_option_B_U_D (line 4175) | def download_option_B_U_D(self, tid):
    method check_update (line 4203) | def check_update(self):
    method download_update (line 4264) | def download_update(self):
  function find_index (line 4311) | def find_index(header_list, text):
  function read_installed (line 4318) | def read_installed():
  function read_titlekey_list (line 4332) | def read_titlekey_list():
  function main (line 4450) | def main():
Condensed preview — 59 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,129K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 799,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise descriptio"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 560,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? "
  },
  {
    "path": ".gitignore",
    "chars": 78,
    "preview": "# IDE\n/.idea/\n/*.iml\n\n# language.mo files\n#/locales/*/LC_MESSAGES/language.mo\n"
  },
  {
    "path": "CDNSP-GUI-Bob.py",
    "chars": 194118,
    "preview": "#!/usr/bin/env python3\r\n# -*- coding: utf-8 -*-\r\n# Credit:\r\n# Thanks to Zotan (DB and script), Big Poppa Panda, AnalogMa"
  },
  {
    "path": "LICENSE",
    "chars": 35147,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "README.md",
    "chars": 1954,
    "preview": "# CDNSP GUI Bob\nCheck [here](https://github.com/Bob123a1/CDNSP-GUI/releases) for the latest releases!\n\nPeople with probl"
  },
  {
    "path": "locales/af/LC_MESSAGES/language.po",
    "chars": 27470,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/ar/LC_MESSAGES/language.po",
    "chars": 26830,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/de/LC_MESSAGES/language.po",
    "chars": 28348,
    "preview": "# REALLY DESCRIPTIVE TITLE.\r\n# Copyright (C) 2018 CDNSP GUI Translation Team\r\n# Jojo, 2018.\r\n#\r\nmsgid \"\"\r\nmsgstr \"\"\r\n\"Pr"
  },
  {
    "path": "locales/el/LC_MESSAGES/language.po",
    "chars": 28176,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/en/LC_MESSAGES/language.po",
    "chars": 23675,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/es/LC_MESSAGES/language.po",
    "chars": 28020,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/fa/LC_MESSAGES/language.po",
    "chars": 27121,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/fr/LC_MESSAGES/language.po",
    "chars": 29005,
    "preview": "# French language file for CDNSP Gui by Bob.\r\n# Copyright (C) 2018 Bob\r\n# cdndave, etraga<etraga@gmx.com>, 2018.\r\n#\r\nmsg"
  },
  {
    "path": "locales/he/LC_MESSAGES/language.po",
    "chars": 25693,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/hu/LC_MESSAGES/language.po",
    "chars": 27715,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/id/LC_MESSAGES/language.po",
    "chars": 27571,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/id/LC_MESSAGES/language_old.po",
    "chars": 27449,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/it/LC_MESSAGES/language.po",
    "chars": 27162,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/it/LC_MESSAGES/language_old.po",
    "chars": 27770,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/ja/LC_MESSAGES/language.po",
    "chars": 26117,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/ja/LC_MESSAGES/language_.po",
    "chars": 25958,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/ko/LC_MESSAGES/language.po",
    "chars": 25950,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/ms/LC_MESSAGES/language.po",
    "chars": 27411,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/nl/LC_MESSAGES/language.po",
    "chars": 27713,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/pl/LC_MESSAGES/language.po",
    "chars": 27592,
    "preview": "# Polish language file for CDNSP Gui by Bob.\r\n# Copyright (C) 2018 Bob\r\n# szczuru <nope@no.pe>, 2018.\r\n#\r\nmsgid \"\"\r\nmsgs"
  },
  {
    "path": "locales/pt/LC_MESSAGES/language.po",
    "chars": 27595,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/pt/LC_MESSAGES/language_old.po",
    "chars": 27460,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/ru/LC_MESSAGES/language.po",
    "chars": 27716,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/th/LC_MESSAGES/language.po",
    "chars": 27094,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/tr/LC_MESSAGES/language.po",
    "chars": 27238,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/vi/LC_MESSAGES/language.po",
    "chars": 27030,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/zh-cn/LC_MESSAGES/language.po",
    "chars": 25192,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  },
  {
    "path": "locales/zh-tw/LC_MESSAGES/language.po",
    "chars": 25432,
    "preview": "# SOME DESCRIPTIVE TITLE.\r\n# Copyright (C) YEAR ORGANIZATION\r\n# Maruku <cpalm.org@gmail.com>, 2018.\r\n#\r\nmsgid \"\"\r\nmsgstr"
  }
]

// ... and 25 more files (download for full content)

About this extraction

This page contains the full source code of the Bob123a1/CDNSP-GUI GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 59 files (967.0 KB), approximately 379.5k tokens, and a symbol index with 129 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!