#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""""
"Cute Penguin" was designed by fourminute. The font
used in the "Fluffy" logo is 100% royalty free.

Fluffy(this program) and the "Cute Penguin"
design is Copyright (c) 2019 fourminute
(https://github.com/fourminute)

Fluffy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Fluffy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""""
# Imports
import signal
import time
import socket
import base64
import os
import sys
import threading
import struct
import random
import re
import configparser
try:
    if "win" in sys.platform[:3].lower():
        initial_dir = os.getcwd() + "/"
    elif "linux" in sys.platform.lower():
        if not os.path.exists(os.path.expanduser('~') + "/.fluffy"):
            os.makedirs(os.path.expanduser('~') + "/.fluffy")
        initial_dir = os.path.expanduser('~') + "/.fluffy/"
    else: # MacOS. A little help here would be great.
        initial_dir = os.getcwd() + "/"
except:
    initial_dir = os.getcwd() + "/"
    pass
try:
    import logging
    if os.path.isfile(initial_dir + 'fluffy.log'):
        os.remove(initial_dir + 'fluffy.log')
    LOG_FILENAME = initial_dir + 'fluffy.log'
    logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)
    logging.debug("Fluffy Log: If you see nothing here. Good!")
    is_logging = True
except:
    is_logging = False
    print('Error: Logging not possible. Possible permission issue.')
    pass
try:
    from tkinter import filedialog
    import tkinter as tk
    root = tk.Tk()
    root.withdraw()
except Exception as e:
    if is_logging:
        logging.error(e, exc_info=True)
        logging.debug("Error: Failed to import Tkinter.")
    print('Error: Failed to import Tkinter.')
    print(str(e))
    sys.exit()
try:
    from SimpleHTTPServer import SimpleHTTPRequestHandler
    from SocketServer import TCPServer
    from urllib import quote
except ImportError:
    from http.server import SimpleHTTPRequestHandler
    from socketserver import TCPServer
    from urllib.parse import quote
try:
    from PyQt5 import QtWidgets
    from PyQt5.QtCore import Qt, QThread, QByteArray
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import QIcon, QPixmap, QColor, QImage
    app = QtWidgets.QApplication(sys.argv)
    window = QMainWindow()
except Exception as e:
    if is_logging:
        logging.error(e, exc_info=True)
        logging.debug("Error: Failed to import PyQt5.")
    print('Error: Failed to import PyQt5.')
    print(str(e))
    sys.exit()
try:
    import usb.core
    import usb.util
except:
    logging.debug("Error: Failed to import modules required for USB install. Defaulting to Network Mode.")
    print('Error: Failed to import modules required for USB install. Defaulting to Network Mode.')
    usb_success = False
    pass

# Variables
VERSION = "2.9.2"
MONERO_ADDRESS = "4APPsi7nnAs4ZjGC58V5CjVnceEvnZbY1WCBSjmcQsKhGPWLL2EaoUDU2RVFnuLEnASRA2ECXD4YvQ8hyVyZg1raJ482yei"
thanks = ["DDinghoya", "YoyPa","Sev73n","LoOkYe","wendyliga","TheZoc","TheLastZombie","friedkeenan","danypava", "DavidOliM","TorpedoXL","gut5"]
GREEN = "QLabel {color: #09A603;}"
BLUE = "QLabel {color: #00A2FF;}"
RED = "QLabel {color: #cc2249;}"
PURPLE = "QLabel {color: #7F0CE8;}"
goldpixmap = QPixmap()
iconpixmap = QPixmap()
inlaypixmap = QPixmap()
dinlaypixmap = QPixmap()
aboutpixmap = QPixmap()
transfer_rate = 0
is_installing = False
last_error = "NA"
is_done = False
is_network = False
is_goldleaf = False 
selected_dir = None
selected_files = None
sent_header = False
start_time = time.time()
cur_transfer_rate = 0
last_transfer_rate = 0
cur_progress = 0
end_progress = 0
cur_nsp_count = 1
total_nsp = 0
cur_nsp_name = "NA"
switch_ip = "0.0.0.0"
host_ip = "0.0.0.0"
language = 0
qresponse = False
needresponse = False
qrespnum = 0
haveresponse = False
allow_access_non_nsp = 0
ignore_warning_prompt = 0
global_dev = None
global_in = None
global_out = None
task_canceled = False
usb_success = False

# Load Settings
if os.path.isfile(initial_dir + 'fluffy.conf'):
    try:
        with open(initial_dir + 'fluffy.conf') as cfgfile:
            configp = configparser.ConfigParser()
            configp.read_file(cfgfile)
            switch_ip = configp.get('DEFAULT', 'switch_ip')
            dark_mode = int(configp.get('DEFAULT', 'dark_mode'))
            language = int(configp.get('DEFAULT', 'language'))
            allow_access_non_nsp = int(configp.get('DEFAULT', 'allow_access_non_nsp'))
            ignore_warning_prompt = int(configp.get('DEFAULT', 'ignore_warning_prompt'))           
            print("Successfully loaded config: \'" + str(initial_dir) + "fluffy.conf\'")
    except:
        print("Config not found: \'" + str(initial_dir) + "fluffy.conf\'")
        switch_ip = "0.0.0.0"
        dark_mode = 0
        language = 0
        allow_access_non_nsp = 0
        ignore_warning_prompt = 0
        pass
else:
    print("Config not found: \'" + str(initial_dir) + "fluffy.conf\'")
    switch_ip = "0.0.0.0"
    dark_mode = 0
    language = 0
    allow_access_non_nsp = 0
    ignore_warning_prompt = 0
    


ABOUT_DATA = b'[encoded data, 58508 characters]'
GOLD_DATA = b'[encoded data, 66336 characters]'
ICON_DATA = b'[encoded data, 20684 characters]'
DARK_DONUT_DATA = b'[encoded data, 34776 characters]'
DONUT_DATA = DARK_DONUT_DATA
class TransferRates:
    Safe = 0x1F4
    Normal = 0x100000
TransferRateDict  = {0: TransferRates.Safe,
                     1: TransferRates.Normal}

# "Language!" -Cap, May 1st 2015
def set_language(v):
    global language
    language = v
    if v == 0:
        Language.CurrentDict = Language.EnglishDict
    elif v == 1:
        Language.CurrentDict = Language.ChineseDict
    elif v == 2:
        Language.CurrentDict = Language.VietDict
    elif v == 3:
        Language.CurrentDict = Language.TurkishDict
    elif v == 4:
        Language.CurrentDict = Language.BrazilianDict
    elif v == 5:
        Language.CurrentDict = Language.ItalianDict
    elif v == 6:
        Language.CurrentDict = Language.FrenchDict
    elif v == 7:
        Language.CurrentDict = Language.SpanishDict
    elif v == 8:
        Language.CurrentDict = Language.GermanDict
    elif v == 9:
        Language.CurrentDict = Language.IndonesiaDict
    elif v == 10:
        Language.CurrentDict = Language.KoreanDict
        
class Language:
    CurrentDict = None
    EnglishDict = {0: "Fluffy",
                  1: "Begin Transfer",
                  2: "Switch IP",
                  3: "This Computer's IP",
                  4: "USB Transfer Mode",
                  5: "Normal Mode",
                  6: "Safe Mode",
                  7: "Current NSP",
                  8: "Successfully Installed",
                  9: "Awaiting Selection",
                  10: "Switch Not Detected",
                  11: "Switch Detected",
                  12: "Network Mode",
                  13: "NSP Selection",
                  14: "NSP(s) Selected",
                  15: "Awaiting Connection Request",
                  16: "Cancel",
                  17: "Error: Goldleaf threw an exception.",
                  18: "Error: Tinfoil threw an exception.",
                  19: "Error: Network threw an exception.",
                  20: "Dark Mode",
                  21: "Options",
                  22: "Language",
                  23: "Github",
                  24: "Network",
                  25: "Headers Sent",
                  26: "NSP(s) in Queue",
                  27: "Installing",
                  28: "Transfer Rate",
                  29: "Current NCA",
                  30: "About",
                  31: "Special Thanks",
                  32: "Donate",
                   }
    
    ChineseDict = {0: "Fluffy 卷卷安装器",
                  1: "开始传输",
                  2: "Switch的IP地址",
                  3: "此电脑的IP地址",
                  4: "USB传输模式",
                  5: "正常模式",
                  6: "安全模式",
                  7: "当前的NSP游戏文件",
                  8: "成功安装",
                  9: "等待选择",
                  10: "Switch没有连接",
                  11: "Switch已连接",
                  12: "网络模式",
                  13: "选择NSP游戏文件",
                  14: "个NSP游戏文件 已选择",
                  15: "等待连接",
                  16: "取消",
                  17: "错误: Goldleaf 反馈了一个异常.",
                  18: "错误: Tinfoil 反馈了一个异常.",
                  19: "错误: 网络状态 反馈了一个异常.",
                  20: "黑暗模式",
                  21: "选项",
                  22: "语言切换",
                  23: "Github主页地址",
                  24: "网络",
                  25: "发送NSP头文件",
                  26: "个NSP游戏文件 在队列中",
                  27: "NSP游戏文件 安装中",
                  28: "传输速率",
                  29: "当前的NCA游戏文件包",
                  30: "学分",
                  31: "谢谢",
                  32: "捐赠给我",
                   }

    VietDict = {  0: "Fluffy",
                  1: "Bắt Đầu Chuyển",
                  2: "IP Của Switch",
                  3: "IP Của Máy Vi Tính",
                  4: "Tốc Độ USB",
                  5: "Tốc Độ Bình Thường",
                  6: "Tốc Độ Chậm",
                  7: "Đang Chuyển NSP",
                  8: "Tải Xông",
                  9: "Hãy Chọn NSP Của Bạn",
                  10: "Không Thể Tìm Thấy Switch Của Bạn",
                  11: "Tìm Được Switch Của Bạn",
                  12: "Bạn Đã Chọn Chuyển Bằng Wi-Fi",
                  13: "Xin Vui Lòng Chọn NSP",
                  14: "Cái NSP(s) Đã Được Chọn",
                  15: "Đang Chờ Yêu Cầu Kết Nối",
                  16: "Hủy Bỏ",
                  17: "Error: Goldleaf ngừng hoạt động.",
                  18: "Error: Tinfoil ngừng hoạt động.",
                  19: "Error: Network ngừng hoạt động.",
                  20: "Hình Tối",
                  21: "Sự Lựa Chọn",
                  22: "Ngôn Ngữ",
                  23: "Github",
                  24: "Network",
                  25: "Danh Sách NSP Đã Gởi Cho Bạn",
                  26: "Đang Chờ Chuyển NSP(s)",
                  27: "Đang Tải",
                  28: "Tốc Độ",
                  29: "Đang Chuyển NCA",
                  30: "Thông Tin",
                  31: "Cảm ơn bạn",
                  32: "Tặng Tôi",
                   }
    
    BrazilianDict = {0: "Fluffy",
                  1: "INICIAR TRANSFERÊNCIA",
                  2: "IP do Switch",
                  3: "IP deste computador",
                  4: "Modo de transferência USB",
                  5: "Modo normal",
                  6: "Modo seguro",
                  7: "NSP atual",
                  8: "Instalado com sucesso",
                  9: "Aguardando seleção",
                  10: "Switch não detectado",
                  11: "Switch detectado",
                  12: "Modo de rede",
                  13: "Selecione o NSP",
                  14: "NSP(s) selecionados",
                  15: "Aguardando pedido de conexão",
                  16: "CANCELAR",
                  17: "Erro: Problema reportado pelo Goldleaf.",
                  18: "Erro: Problema reportado pelo Tinfoil.",
                  19: "Erro: Problema reportado pela rede.",
                  20: "Tema escuro",
                  21: "Opções",
                  22: "Língua",
                  23: "Github",
                  24: "Rede",
                  25: "Cabeçalho enviado",
                  26: "NSP(s) em fila",
                  27: "Instalando",
                  28: "Velocidade de transferência",
                  29: "NCA atual",
                  30: "Informação",
                  31: "Obrigado",
                  32: "Doe Para Mim",
                   }
    
    ItalianDict = {0: "Fluffy",
                  1: "Inizia Trasferimento",
                  2: "IP della Switch",
                  3: "IP di questo Computer",
                  4: "Trasferimento USB",
                  5: "Modalità Normale",
                  6: "Modalità Sicura",
                  7: "NSP Corrente",
                  8: "Installazione Avvenuta con Successo",
                  9: "In attesa di selezione",
                  10: "Switch Non Rilevata",
                  11: "Switch Rilevata",
                  12: "Modalità Network",
                  13: "Seleziona NSP",
                  14: "NSP(s) Selezionato(i)",
                  15: "In Attesa di Richiesta di Connessione",
                  16: "Annulla",
                  17: "Errore: Goldleaf ha lanciato una eccezione.",
                  18: "Errore: Tinfoil ha lanciato una eccezione.",
                  19: "Errore: eccezione nella Modalità Network.",
                  20: "Dark Mode",
                  21: "Opzioni",
                  22: "Lingua",
                  23: "Github",
                  24: "Network",
                  25: "Headers Inviati",
                  26: "NSP(s) in Coda",
                  27: "Installazione in Corso",
                  28: "Velocità di Trasferimento",
                  29: "NCA Corrente",
                  30: "Informazione",
                  31: "Grazie",
                  32: "Dona A Me",
                  }


    TurkishDict = {0: "Fluffy",
                  1: "Aktarmaya Başla",
                  2: "Switch IP 'si",
                  3: "Bu bilgisayarın IP 'si",
                  4: "USB Transfer Modu",
                  5: "Normal Mod",
                  6: "Güvenli Mod",
                  7: "Şu anki NSP",
                  8: "Başarıyla Yüklendi",
                  9: "Seçim Bekleniyor",
                  10: "Switch Algılanamadı",
                  11: "Switch Algılandı",
                  12: "Ağ Modu",
                  13: "NSP Seçimi",
                  14: "NSP(ler) Seçildi",
                  15: "Bağlantı İsteği Bekleniyor",
                  16: "İptal",
                  17: "Hata: Goldleaf 'te sıradışı durum oluştu.",
                  18: "Hata: Tinfoil 'de sıradışı durum oluştu.",
                  19: "Hata: Ağda sıradışı durum oluştu.",
                  20: "Karanlık Mod",
                  21: "Ayarlar",
                  22: "Dil",
                  23: "Github",
                  24: "Ağ",
                  25: "Başlık Gönderildi",
                  26: "Sıradaki NSP(ler)",
                  27: "Yükleniyor",
                  28: "Aktarma Hızı",
                  29: "Şu anki NCA",
                  30: "Bilgi",
                  31: "Teşekkür Ederim",
                  32: "Bağış",
                   }
    
    FrenchDict = {0: "Fluffy",
                  1: "Démarrer le Transfert",
                  2: "IP de la Switch",
                  3: "IP de l'Ordinateur",
                  4: "Mode de Transfert USB",
                  5: "Mode Normal",
                  6: "Mode Sûr",
                  7: "NSP Actuel",
                  8: "Installé Avec Succès",
                  9: "En Attente de Sélection",
                  10: "Switch Non Détecté",
                  11: "Switch Détecté",
                  12: "Mode Réseau",
                  13: "Ajouter NSP",
                  14: "NSP(s) Sélectionné(s)",
                  15: "En Attente de la Demande de Connection",
                  16: "Annuler",
                  17: "Erreur: Goldleaf a généré une exception.",
                  18: "Erreur: Tinfoil a généré une exception.",
                  19: "Erreur: Le réseau a généré une exception.",
                  20: "Mode Sombre",
                  21: "Options",
                  22: "Langue",
                  23: "Github",
                  24: "Réseau",
                  25: "En-têtes Envoyées",
                  26: "NSP(s) en File d'Attente",
                  27: "En Cours d'Installation",
                  28: "Débit",
                  29: "NCA Actuel",
                  30: "À Propos",
                  31: "Remerciements",
                  32: "Faire Un Don",
                   }
    
    SpanishDict = {0: "Fluffy",
                  1: "Iniciar la Transferencia",
                  2: "IP de la Switch",
                  3: "IP del Ordenador",
                  4: "Modo de Transferencia USB",
                  5: "Modo Normal",
                  6: "Modo Seguro",
                  7: "NSP Actual",
                  8: "Instalado Con Éxito",
                  9: "Esperando Selección",
                  10: "Switch No Detectada",
                  11: "Switch Detectada",
                  12: "Modo Red",
                  13: "Añadir NSP",
                  14: "NSP(s) Seleccionado(s)",
                  15: "Esperando la Solicitud de Conexión",
                  16: "Cancelar",
                  17: "Error: Goldleaf ha generado una excepción.",
                  18: "Error: Tinfoil ha generado une excepción.",
                  19: "Error: El red ha generado une excepción.",
                  20: "Modo Oscuro",
                  21: "Opciones",
                  22: "Idioma",
                  23: "Github",
                  24: "Red",
                  25: "Cabeceras Enviadas",
                  26: "NSP(s) en la Cola",
                  27: "Instalando",
                  28: "Velocidad",
                  29: "NCA Actual",
                  30: "Sobre Fluffy",
                  31: "Muchas Gracias",
                  32: "Hacer Una Donación",
                   }

    GermanDict = {0: "Fluffy",
                  1: "Übertragung starten",
                  2: "Switch-IP-Adresse",
                  3: "Computer-IP-Adresse",
                  4: "USB-Übertragungsmodus",
                  5: "Normaler Modus",
                  6: "Sicherer Modus",
                  7: "Momentane NSP",
                  8: "Erfolgreich installiert",
                  9: "Warte auf Auswahl",
                  10: "Keine Switch erkannt",
                  11: "Switch erkannt",
                  12: "Netzwerk-Modus",
                  13: "NSP-Auswahl",
                  14: "NSP(s) ausgewählt",
                  15: "Warte auf Verbindung",
                  16: "Abbrechen",
                  17: "Goldleaf-Fehler!",
                  18: "Tinfoil-Fehler!",
                  19: "Netzwerk-Fehler!",
                  20: "Dunkles Design",
                  21: "Einstellungen",
                  22: "Sprache",
                  23: "GitHub",
                  24: "Netzwerk",
                  25: "Header gesendet",
                  26: "NSP(s) in Warteschlange",
                  27: "Installiere",
                  28: "Transferrate",
                  29: "Aktuelle NCA",
                  30: "Über",
                  31: "Besonderer Dank",
                  32: "Spenden",
                   }

    IndonesiaDict = {0: "Fluffy",
                  1: "Memulai Transfer",
                  2: "Ganti IP",
                  3: "IP Komputer Ini",
                  4: "Mode Transfer melalui USB",
                  5: "Mode Normal",
                  6: "Mode Aman",
                  7: "NSP Saat Ini",
                  8: "Penerapaan Berhasil",
                  9: "Menunggu Pilihan",
                  10: "Switch tidak terdeteksi",
                  11: "Switch terdeteksi",
                  12: "Mode Jaringan",
                  13: "Pemilihan NSP",
                  14: "NSP(s) Terpilih",
                  15: "Menunggu Request Koneksi",
                  16: "Batal",
                  17: "Error: Goldleaf Melemparkan Pengecualian.",
                  18: "Error: Tinfoil Melemparkan Pengecualian.",
                  19: "Error: Jaringan Melemparkan Pengecualian.",
                  20: "Mode Gelap",
                  21: "Opsi",
                  22: "Bahasa",
                  23: "Github",
                  24: "Jaringan",
                  25: "Headers Terkirim",
                  26: "NSP dalam Antrian",
                  27: "Menerapkan",
                  28: "Kecepatan Transfer",
                  29: "NCA Saat Ini",
                  30: "Tentang",
                  31: "Terima Kasih",
                  32: "Donasi Untukku",
                  }

    KoreanDict = {0: "Fluffy",
                  1: "전송 시작",
                  2: "스위치 IP",
                  3: "이 컴퓨터의 IP",
                  4: "USB 전송 모드",
                  5: "일반 모드",
                  6: "안전 모드",
                  7: "현재 NSP",
                  8: "성공적으로 설치되었습니다",
                  9: "선택을 기다리고 있습니다",
                  10: "스위치가 감지되지 않았습니다",
                  11: "스위치가 감지되었습니다",
                  12: "네트워크 모드",
                  13: "NSP 선택",
                  14: "선택된 NSP",
                  15: "연결 요청 대기 중",
                  16: "취소",
                  17: "오류: Goldleaf에서 예외가 발생했습니다.",
                  18: "오류: Tinfoil에서 예외가 발생했습니다.",
                  19: "오류: 네트워크에서 예외가 발생했습니다.",
                  20: "어두운 모드",
                  21: "옵션",
                  22: "언어",
                  23: "Github",
                  24: "네트워크",
                  25: "헤더 보내기",
                  26: "대기열의 NSP",
                  27: "설치",
                  28: "전송율",
                  29: "현재 NCA",
                  30: "소개",
                  31: "특별 감사",
                  32: "기부",
                   }


                   
				   
set_language(language)
# End Language

# Setters
def set_dark_mode(v):
    global dark_mode
    if v == 1:
        import qdarkstyle
        app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
        dark_mode = 1
        l_github.setStyleSheet("QLabel { color: rgba(255, 255, 255, 50%) }")
        pixmap = QPixmap(dinlaypixmap)
        gold_res = QPixmap(goldpixmap)
        about_res = QPixmap(aboutpixmap)
        screen = app.primaryScreen()
        if screen.size().width() <= 2560:
            if screen.size().width() <= 1920:
                if screen.size().width() <= 1366:
                    lowresfix = pixmap.scaled(230, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    lowresfix2 = gold_res.scaled(230, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    lowresfix3 = about_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    img_label.setPixmap(lowresfix)
                    gold_img_label.setPixmap(lowresfix2)
                    about_img_label.setPixmap(lowresfix3)
                else:
                    lowresfix = pixmap.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    lowresfix2 = gold_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    lowresfix3 = about_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    img_label.setPixmap(lowresfix)
                    gold_img_label.setPixmap(lowresfix2)
                    about_img_label.setPixmap(lowresfix3)
            else:
                lowresfix = pixmap.scaled(350, 240, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                lowresfix2 = gold_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                lowresfix3 = about_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                img_label.setPixmap(lowresfix)
                gold_img_label.setPixmap(lowresfix2)
                about_img_label.setPixmap(lowresfix3)
        else:
            img_label.setPixmap(pixmap)
            gold_img_label.setPixmap(gold_res)
            about_img_label.setPixmap(about_res)
    else:
        dark_mode = 0
        pixmap = QPixmap(inlaypixmap)
        gold_res = QPixmap(goldpixmap)
        about_res = QPixmap(aboutpixmap)
        screen = app.primaryScreen()
        app.setStyleSheet("")
        l_github.setStyleSheet("QLabel { color: rgba(0, 0, 0, 50%) }")
        if screen.size().width() <= 2560:
            if screen.size().width() <= 1920:
                if screen.size().width() <= 1366:
                    lowresfix = pixmap.scaled(230, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    lowresfix2 = gold_res.scaled(230, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    lowresfix3 = about_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    img_label.setPixmap(lowresfix)
                    gold_img_label.setPixmap(lowresfix2)
                    about_img_label.setPixmap(lowresfix3)
                else:
                    lowresfix = pixmap.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    lowresfix2 = gold_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    lowresfix3 = about_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                    img_label.setPixmap(lowresfix)
                    gold_img_label.setPixmap(lowresfix2)
                    about_img_label.setPixmap(lowresfix3)
            else:
                lowresfix = pixmap.scaled(350, 240, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                lowresfix2 = gold_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                lowresfix3 = about_res.scaled(270, 270, Qt.KeepAspectRatio, Qt.SmoothTransformation)
                img_label.setPixmap(lowresfix)
                gold_img_label.setPixmap(lowresfix2)
                about_img_label.setPixmap(lowresfix3)
        else:
            img_label.setPixmap(pixmap)
            gold_img_label.setPixmap(gold_res)
            about_img_label.setPixmap(about_res)
            

def turn_off_logging():
    global is_logging
    is_logging = False
    
def set_nca_name(v):
    global cur_nca_name
    cur_nca_name = v

def set_start_time():
    global start_time
    start_time = time.time()

def set_canceled(x):
    global task_canceled
    task_canceled = x
    
def set_cur_transfer_rate(v):
    global cur_transfer_rate
    cur_transfer_rate = v

def set_sent_header():
    global sent_header
    sent_header = True

def set_last_transfer_rate(v):
    global last_transfer_rate
    last_transfer_rate = v

def detach_switch():
    global global_dev
    global global_out
    global global_in
    try:
        usb.util.dispose_resources(global_dev)
        global_dev.reset()
    except:
        pass
    global_in = None
    global_out = None
    global_dev = None
        
def connect_switch():
    global global_dev
    global global_out
    global global_in
    global_dev = usb.core.find(idVendor=0x057E, idProduct=0x3000)
    if global_dev is not None:
        try:
            global_dev.set_configuration()
            intf = global_dev.get_active_configuration()[(0,0)]
            global_out = usb.util.find_descriptor(intf,custom_match=lambda e:usb.util.endpoint_direction(e.bEndpointAddress)==usb.util.ENDPOINT_OUT)
            global_in = usb.util.find_descriptor(intf,custom_match=lambda e:usb.util.endpoint_direction(e.bEndpointAddress)==usb.util.ENDPOINT_IN)
            return True
        except:
            return False
            pass
    else:
        return False

def save_config():
    try:
        configp = configparser.ConfigParser()
        configp['DEFAULT'] = {'switch_ip': switch_ip,
                              'language': language,
                              'dark_mode': dark_mode,
                              'allow_access_non_nsp': allow_access_non_nsp,
                              'ignore_warning_prompt': ignore_warning_prompt}
        with open(initial_dir + 'fluffy.conf', 'w') as cfgfile:
            configp.write(cfgfile)
    except:
        pass
                
def set_transfer_rate(v):
    global transfer_rate
    transfer_rate = TransferRateDict[v]

def get_response_qmessage(e):
    global needresponse
    global qrespnum
    needresponse = True
    qrespnum = e
    
def set_response_qmessage(x):
    global qresponse
    global haveresponse
    haveresponse = True
    qresponse = x

    
def reset_response():
    global needresponse
    global qresponse
    global haveresponse
    needresponse = False
    qresponse = False
    haveresponse = False
    

def set_dir(d):
    global selected_dir
    selected_dir = d
   
def set_selected_files(f):
    global selected_files
    selected_files = f
    
def set_progress(c, e):
    global cur_progress
    global end_progress
    end_progress = e
    cur_progress = c

def set_cur_nsp(nsp):
    global cur_nsp_name
    global cur_nsp_count
    if cur_nsp_name != nsp:
        if cur_nsp_name == "NA":
            cur_nsp_name = nsp
        else:
            cur_nsp_count += 1
            cur_nsp_name = nsp
            set_start_time()
            last_progress = 0
            
def cancel_task():
    set_canceled(True)
    reset_install()

def set_total_nsp(n):
    global total_nsp
    total_nsp = n
    
def complete_install():
    global is_done
    is_done = True

def complete_goldleaf_transfer():
    global is_installing
    is_installing = False
    
def reset_install():
    global is_installing
    global sent_header
    global is_done
    global cur_progress
    global end_progress
    global cur_nsp_name
    global cur_transfer_rate
    global last_transfer_rate
    global selected_files
    global selected_dir
    global total_nsp
    global cur_nsp_count
    list_nsp.clear()
    btn_header.setEnabled(True)
    btn_nsp.setEnabled(True)
    combo.setEnabled(True)
    txt_ip.setEnabled(True)
    txt_ip2.setEnabled(True)
    net_radio.setEnabled(True)
    usb_radio.setEnabled(True)
    txt_port.setEnabled(True)
    tin_radio.setEnabled(True)
    gold_radio.setEnabled(True)
    l_nsp.setText("")
    l_nsp.setStyleSheet("")
    l_switch.setText("")
    l_switch.setStyleSheet("")
    l_status.setStyleSheet("")
    if is_goldleaf:
        l_status.setText('')
    progressbar.setValue(0)
    cur_nsp_count = 1
    total_nsp = 0
    selected_files = None
    selected_dir = None
    cur_nsp_name = "NA"
    cur_transfer_rate = 0
    last_transfer_rate = 0
    is_done = False
    is_installing = False
    sent_header = False
    cur_progress = 0
    end_progress = 100
    UI.init_language()
    window.menuBar().setEnabled(True)
    if is_network:
        UI.net_radio_cmd()
    else:
        UI.usb_radio_cmd()

def throw_error(_type):
    global last_error
    if _type == 0:
        last_error = Language.CurrentDict[17] # Goldleaf
    elif _type == 1:
        last_error = Language.CurrentDict[19] # Network
    elif _type == 2:
        last_error = Language.CurrentDict[18] # Tinfoil

def reset_last_error():
    global last_error
    last_error = "NA"
    
def complete_loading():
    global is_installing
    is_installing = True

def set_network(v):
    global is_network
    is_network = v
    
def set_ip(v, n):
    global switch_ip
    global host_ip
    if n == 0:
        switch_ip = v
    else:
        host_ip = v

def set_goldleaf(v):
    global is_goldleaf
    is_goldleaf = v

def set_usb_success(v):
    global usb_success
    usb_success = v
    
# Goldleaf            
class GoldleafCommandId:
    ListSystemDrives = 0
    GetEnvironmentPaths = 1
    GetPathType = 2
    ListDirectories = 3
    ListFiles = 4
    GetFileSize = 5
    FileRead = 6
    FileWrite = 7
    CreateFile = 8
    CreateDirectory = 9
    DeleteFile = 10
    DeleteDirectory = 11
    RenameFile = 12
    RenameDirectory = 13
    GetDriveTotalSpace = 14
    GetDriveFreeSpace = 15
    GetNSPContents = 16
    Max = 17

class GoldleafCommandReadResult:
    Success = 0
    InvalidMagic = 1
    InvalidGoldleafCommandId = 2

class Goldleaf:
    GLUC         = b"GLUC"
    magic        = b"GLUC"
    cmd_id       = 0
    drives       = {}
    FW_DENIED = 0
    FW_ACCEPTED = 1
    FW_NOSTATUS = 2
    fw_status = FW_NOSTATUS
    
    def init(self):
        try:
            detach_switch()
            connect_switch()
            self.goldleaf_usb()
        except Exception as e:
            if is_logging:
                logging.error(e, exc_info=True)
            throw_error(0)
            sys.exit()
        
    def write(self,buffer):
        try:
            global_out.write(buffer,timeout=3000)
        except:
            pass

    def read(self,length):
        return global_in.read(length,timeout=0).tobytes()
        
    def write_u32(self,x):
        try:
            global_out.write(struct.pack("<I", x))
        except:
            pass
        
    def write_u64(self,x):
        try:
            global_out.write(struct.pack("<Q", x))
        except:
            pass
        
    def write_string(self,x):
        try:
            self.write_u32(len(x))
            self.write(x.encode())
        except:
            pass
        
    def read_u32(self):
        return struct.unpack("<I", self.read(4))[0]
    
    def read_u64(self):
        return struct.unpack("<Q", self.read(8))[0]
    
    def read_string(self):
        return self.read(self.read_u32() + 1)[:-1].decode()
    
    def magic_ok(self):
        return self.GLUC == self.magic
        
    def is_id(self,a_cmd):
        return a_cmd == self.cmd_id
    
    def read_cmd(self):
        try:
            self.magic = self.read(4)
            self.cmd_id = self.read_u32()
        except:
            pass
        
    def write_cmd(self,a_cmd):
        try:
            self.write(self.magic)
            self.write_u32(a_cmd)
        except:
            pass

    def read_path(self):
        path = self.read_string()
        drive = path.split(":", 1)[0]
        try:
            path = path.replace(drive + ":", self.drives[drive])
        except KeyError:
            pass
        return path
    
    def goldleaf_usb(self):
        while global_dev is not None and not task_canceled:
            self.read_cmd()
            if self.magic_ok():
                if self.is_id(GoldleafCommandId.ListSystemDrives):
                    drive_labels = {}
                    if "win" in sys.platform[:3].lower():
                        import string
                        import ctypes
                        kernel32 = ctypes.windll.kernel32
                        bitmask = kernel32.GetLogicalDrives()
                        for letter in string.ascii_uppercase:
                            if bitmask & 1:
                                self.drives[letter] = letter + ":/"
                                label_buf = ctypes.create_unicode_buffer(1024)
                                kernel32.GetVolumeInformationW(
                                    ctypes.c_wchar_p(letter + ":\\"),
                                    label_buf,
                                    ctypes.sizeof(label_buf),
                                    None,
                                    None,
                                    None,
                                    None,
                                    0
                                    )
                                if label_buf.value:
                                    drive_labels[letter] = label_buf.value
                            bitmask >>= 1
                    else:
                        self.drives["ROOT"] = "/"
                    self.write_u32(len(self.drives))
                    for d in self.drives:
                        try:
                            self.write_string(drive_labels[d])
                        except KeyError:
                            self.write_string(d)
                        self.write_string(d)
                elif self.is_id(GoldleafCommandId.GetEnvironmentPaths):
                    env_paths = {x:os.path.expanduser("~/"+x) for x in ["Desktop", "Documents"]}
                    for arg in sys.argv[1:]:
                        folder = os.path.abspath(arg)
                        if os.path.isfile(folder):
                            folder = os.path.dirname(folder)
                        env_paths[os.path.basename(folder)] = folder
                    env_paths = {x:env_paths[x] for x in env_paths if os.path.exists(env_paths[x])}
                    self.write_u32(len(env_paths))
                    for env in env_paths:
                        env_paths[env] = env_paths[env].replace("\\", "/")
                        self.write_string(env)
                        if env_paths[env][1:3] != ":/":
                            env_paths[env] = "ROOT:" + env_paths[env]
                        self.write_string(env_paths[env])
                elif self.is_id(GoldleafCommandId.GetPathType):
                    ptype = 0
                    path = self.read_path()
                    if os.path.isfile(path):
                        ptype = 1
                    elif os.path.isdir(path):
                        ptype = 2
                    self.write_u32(ptype)
                elif self.is_id(GoldleafCommandId.ListDirectories):
                    path = self.read_path()
                    ents = [x for x in os.listdir(path) if os.path.isdir(os.path.join(path, x))]
                    n_ents = []
                    for e in ents:
                        try:
                            test = os.listdir(os.path.join(path, e))
                            n_ents.append(e)
                        except:
                            pass
                    self.write_u32(len(n_ents))
                    for name in n_ents:
                        self.write_string(name)
                elif self.is_id(GoldleafCommandId.ListFiles):
                    self.fw_status = self.FW_NOSTATUS
                    if is_installing:
                        complete_goldleaf_transfer()
                    path = self.read_path()
                    ents = [x for x in os.listdir(path) if os.path.isfile(os.path.join(path, x))]
                    if not allow_access_non_nsp:
                        len_nsps = 0
                        for f in ents:
                            if f.lower().endswith('.nsp'):
                                len_nsps = len_nsps+1
                        self.write_u32(len_nsps)
                        for name in ents:
                            if name.lower().endswith('.nsp'):
                                self.write_string(name)
                    else:
                        self.write_u32(len(ents))
                        for name in ents:
                            self.write_string(name)
                elif self.is_id(GoldleafCommandId.GetFileSize):
                    path = self.read_path()
                    self.write_u64(os.path.getsize(path))
                elif self.is_id(GoldleafCommandId.FileRead):
                    can_read = True
                    offset = self.read_u64()
                    size = self.read_u64()
                    path = self.read_path()
                    if not os.path.basename(path).lower().endswith('.nsp'):
                        if allow_access_non_nsp:
                            can_read = True
                        else:
                            can_read = False
                    if can_read:
                        with open(path, "rb") as f:
                            f.seek(offset)
                            data = f.read(size)
                        self.write_u64(len(data))
                        self.write(data)
                        try:
                            if self.fw_status != self.FW_DENIED:
                                complete_loading()
                                set_cur_nsp(str(os.path.basename(path)))
                                set_progress(int(offset), int(os.path.getsize(path)))
                                elapsed_time = time.time() - start_time
                                if elapsed_time >= 1:
                                    set_cur_transfer_rate(int(offset) - last_transfer_rate)
                                    set_last_transfer_rate(int(offset))
                                    set_start_time()
                            else:
                                complete_goldleaf_transfer()
                        except:
                            pass
                    else:
                        logging.debug("Error: Access denied. \nReason: Goldleaf tried to access a non .NSP file(to bypass this default restriction, change \'allow_access_non_nsp\' to 1 in fluffy.conf).")
                        print("Error: Access denied. \nReason: Goldleaf tried to access a non .NSP file(to bypass this default restriction, change \'allow_access_non_nsp\' to 1 in fluffy.conf).")
                        cancel_task()
                        sys.exit()
                elif self.is_id(GoldleafCommandId.FileWrite):
                    offset = self.read_u64()
                    size = self.read_u64()
                    path = self.read_path()
                    data = self.read(size)
                    can_write = False
                    if self.fw_status == self.FW_NOSTATUS:
                        get_response_qmessage(1)
                        while not haveresponse and global_dev is not None:                    
                            time.sleep(1)
                        if qresponse:
                            self.fw_status = self.FW_ACCEPTED
                            can_write = True
                        else:
                            self.fw_status = self.FW_DENIED
                    elif self.fw_status == self.FW_ACCEPTED:
                        can_write = True
                    if can_write:
                        cont = bytearray()
                        try:
                            with open(path, "rb") as f:
                                cont=bytearray(f.read())
                        except FileNotFoundError:
                            pass
                        cont[offset:offset + size] = data
                        with open(path, "wb") as f:
                            f.write(cont)
                    reset_response()
                elif self.is_id(GoldleafCommandId.CreateFile):
                    path = self.read_path()
                    get_response_qmessage(2)
                    while not haveresponse and global_dev is not None:                    
                        time.sleep(1)
                    if qresponse:
                        open(path, "a").close()
                    reset_response()
                elif self.is_id(GoldleafCommandId.CreateDirectory):
                    path = self.read_path()
                    get_response_qmessage(3)
                    while not haveresponse and global_dev is not None:                    
                        time.sleep(1)
                    if qresponse:
                        try:
                            os.mkdir(path)
                        except os.FileExistsError:
                            pass
                    reset_response()
                elif self.is_id(GoldleafCommandId.DeleteFile):
                    path = self.read_path()
                    get_response_qmessage(4)
                    while not haveresponse and global_dev is not None:                    
                        time.sleep(1)
                    if qresponse:
                        os.remove(path)
                    reset_response()
                elif self.is_id(GoldleafCommandId.DeleteDirectory):
                    path = self.read_path()
                    get_response_qmessage(5)
                    while not haveresponse and global_dev is not None:                    
                        time.sleep(1)
                    if qresponse:
                        shutil.rmtree(path)
                    reset_response()
                elif self.is_id(GoldleafCommandId.RenameFile):
                    path = self.read_path()
                    new_name = self.read_string()
                    get_response_qmessage(6)
                    while not haveresponse and global_dev is not None:                    
                        time.sleep(1)
                    if qresponse:
                        os.rename(path, new_name)
                    reset_response()
                elif self.is_id(GoldleafCommandId.RenameDirectory):
                    path = self.read_path()
                    new_name = self.read_path()
                    get_response_qmessage(6)
                    while not haveresponse and global_dev is not None:                    
                        time.sleep(1)
                    if qresponse:
                        os.rename(path, new_name)
                    reset_response()
                elif self.is_id(GoldleafCommandId.GetDriveTotalSpace):
                    path = self.read_path()
                    disk = os.statvfs(path)
                    totalBytes = float(disk.f_bsize*disk.f_blocks)
                    self.write_u64(int(totalspace))
                elif self.is_id(GoldleafCommandId.GetDriveFreeSpace):
                    path = self.read_path()
                    disk = os.statvfs(path)
                    totalFreeSpace = float(disk.f_bsize*disk.f_bfree)
                    self.write_u64(int(totalFreeSpace))
        sys.exit()

# Tinfoil Network
netrlist = []
def reset_netrlist():
    global netrlist
    netrlist = None
    netrlist = []
def append_netrlist(v, v2):
    global netrlist
    netrlist.append((v, v2))
class TinfoilNetwork:
    def init(self):
        reset_netrlist()
        accepted_extension = ('.nsp')
        hostPort = random.randint(26490,26999)
        target_ip = switch_ip
        hostIp = host_ip
        target_path = str(selected_dir).strip()
        baseUrl = hostIp + ':' + str(hostPort) + '/'
        directory = target_path
        file_list_payload = ''  
        for file in [file for file in next(os.walk(target_path))[2] if file.endswith(accepted_extension)]:
            for y in selected_files:
                if str(file).find(os.path.basename(y)) != -1:
                    n = random.randint(1,10000000)
                    fake_file = str(n) + ".nsp"
                    append_netrlist(fake_file, str(y))
                    file_list_payload += baseUrl + fake_file + '\n'
        file_list_payloadBytes = file_list_payload.encode('ascii')
        if directory and directory != '.':
            os.chdir(directory)
        server = TinfoilServer((host_ip, hostPort), TinfoilHTTPHandler)
        thread = threading.Thread(target=server.serve_forever)
        thread.daemon = True
        thread.start()
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.connect((target_ip, 2000))
            sock.sendall(struct.pack('!L', len(file_list_payloadBytes)) + file_list_payloadBytes)
            while len(sock.recv(1)) < 1:
                if task_canceled:
                    server.force_stop()
                    sys.exit()
                time.sleep(0.1)
            sock.close()
        except Exception as e:
            if is_logging:
                logging.error(e, exc_info=True)
            server.force_stop()
            throw_error(1)
            sys.exit()
        complete_install()
        server.force_stop()
        sys.exit()
class TinfoilHTTPHandler(SimpleHTTPRequestHandler):
    def send_head(self):
        for s in range(len(netrlist)):
            if netrlist[s][0] == str(self.path)[1:]:
                path = netrlist[s][1]
        ctype = self.guess_type(path)
        if os.path.isdir(path):
            return SimpleHTTPRequestHandler.send_head(self)
        if not os.path.exists(path):
            return self.send_error(404, self.responses.get(404)[0])
        f = open(path, 'rb')
        fs = os.fstat(f.fileno())
        size = fs[6]
        start, end = 0, size - 1
        if 'Range' in self.headers:
            start, end = self.headers.get('Range').strip().strip('bytes=')\
                .split('-')
        if start == "":
            try:
                end = int(end)
            except ValueError as e:
                self.send_error(400, 'invalid range')
            start = size - end
        else:
            try:
                start = int(start)
            except ValueError as e:
                self.send_error(400, 'invalid range')
            if start >= size:
                self.send_error(416, self.responses.get(416)[0])
            if end == "":
                end = size - 1
            else:
                try:
                    end = int(end)
                except ValueError as e:
                    self.send_error(400, 'invalid range')

        start = max(start, 0)
        end = min(end, size - 1)
        self.range = (start, end)
        cont_length = end - start + 1
        if 'Range' in self.headers:
            self.send_response(206)
        else:
            self.send_response(200)
        self.send_header('Content-type', ctype)
        self.send_header('Accept-Ranges', 'bytes')
        self.send_header('Content-Range','bytes %s-%s/%s' % (start, end, size))
        self.send_header('Content-Length', str(cont_length))
        self.send_header('Last-Modified', self.date_time_string(fs.st_mtime))
        self.end_headers()
        return f

    def copyfile(self, infile, outfile):
        if 'Range' not in self.headers:
            SimpleHTTPRequestHandler.copyfile(self, infile, outfile)
            return
        complete_loading()
        set_cur_nsp(str(os.path.basename(infile.name)))
        start, end = self.range
        infile.seek(start)
        bufsize = 64 * 1024  # 64KB
        while True:
            if task_canceled: sys.exit()
            buf = infile.read(bufsize)
            if not buf:
                break
            try:
                outfile.write(buf)
                try:
                    set_progress(int(infile.tell()), int(end))
                    elapsed_time = time.time() - start_time
                    if elapsed_time >= 1:
                        set_cur_transfer_rate(int(infile.tell()) - last_transfer_rate)
                        set_last_transfer_rate(int(infile.tell()))
                        set_start_time()
                except:
                    pass
            except BrokenPipeError:
                pass    
class TinfoilServer(TCPServer):
    stopped = False
    def server_bind(self):
        import socket
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)
    def serve_forever(self):
        while not self.stopped:
            if task_canceled: sys.exit()
            self.handle_request()
        sys.exit()
    def force_stop(self):
        self.server_close()
        self.stopped = True
        sys.exit()
        
    
# Tinfoil USB
class Tinfoil:
    CMD_ID_EXIT = 0
    CMD_ID_FILE_RANGE = 1
    CMD_TYPE_RESPONSE = 1
    
    def init(self):
        try:
            detach_switch()
            connect_switch()
            self.send_nsp_list()
            self.poll_commands()
            complete_install()
            sys.exit()
        except Exception as e:
            if is_logging:
                logging.error(e, exc_info=True)
            throw_error(2)
            sys.exit()
        
    def send_response_header(self, cmd_id, data_size):
        global_out.write(b'TUC0')
        global_out.write(struct.pack('<B', self.CMD_TYPE_RESPONSE))
        global_out.write(b'\x00' * 3)
        global_out.write(struct.pack('<I', cmd_id))
        global_out.write(struct.pack('<Q', data_size))
        global_out.write(b'\x00' * 0xC)
        
    def file_range_cmd(self, data_size):
        file_range_header = global_in.read(0x20)
        range_size = struct.unpack('<Q', file_range_header[:8])[0]
        range_offset = struct.unpack('<Q', file_range_header[8:16])[0]
        nsp_name_len = struct.unpack('<Q', file_range_header[16:24])[0]
        nsp_name = bytes(global_in.read(nsp_name_len)).decode('utf-8')
        set_cur_nsp(str(os.path.basename(nsp_name)))
        self.send_response_header(self.CMD_ID_FILE_RANGE, range_size)
        with open(nsp_name, 'rb') as f:
            complete_loading()
            f.seek(range_offset)
            curr_off = 0x0
            end_off = range_size
            read_size = transfer_rate
            while curr_off < end_off:
                if task_canceled: sys.exit()
                if curr_off + read_size >= end_off:
                    read_size = end_off - curr_off
                    try:
                        set_progress(int(end_off), int(end_off))
                    except:
                        pass
                buf = f.read(read_size)
                global_out.write(data=buf, timeout=0)
                curr_off += read_size
                try:
                    set_progress(int(curr_off), int(end_off))
                    elapsed_time = time.time() - start_time
                    if elapsed_time >= 1:
                        set_cur_transfer_rate(curr_off - last_transfer_rate)
                        set_last_transfer_rate(curr_off)
                        set_start_time()
                except:
                    pass
                
    def poll_commands(self):
        while True:
            if task_canceled: sys.exit()
            cmd_header = bytes(global_in.read(0x20, timeout=0))
            magic = cmd_header[:4]
            if magic != b'TUC0': 
                continue
            cmd_type = struct.unpack('<B', cmd_header[4:5])[0]
            cmd_id = struct.unpack('<I', cmd_header[8:12])[0]
            data_size = struct.unpack('<Q', cmd_header[12:20])[0]
            if cmd_id == self.CMD_ID_EXIT:
                complete_install()
                sys.exit()
            elif cmd_id == self.CMD_ID_FILE_RANGE:
                self.file_range_cmd(data_size)

    def send_nsp_list(self):
        nsp_path_list = list()
        nsp_path_list_len = 0
        for nsp_path in os.listdir(selected_dir):
            if nsp_path.endswith(".nsp"):
                for y in selected_files:
                    if str(nsp_path).find(os.path.basename(y)) != -1:
                        print(str(nsp_path))
                        nsp_path_list.append(selected_dir + "/" + nsp_path.__str__() + '\n')
                        nsp_path_list_len += len(selected_dir + "/" + nsp_path.__str__()) + 1
        global_out.write(b'TUL0')
        global_out.write(struct.pack('<I', nsp_path_list_len))
        global_out.write(b'\x00' * 0x8) 
        for nsp_path in nsp_path_list:
            global_out.write(nsp_path)
    
# UI
class UI:
    @staticmethod
    def send_header_cmd():
        if not sent_header:
            set_canceled(False)
            btn_header.setEnabled(False)
            btn_nsp.setEnabled(False)
            combo.setEnabled(False)
            txt_ip.setEnabled(False)
            txt_ip2.setEnabled(False)
            net_radio.setEnabled(False)
            usb_radio.setEnabled(False)
            txt_port.setEnabled(False)
            tin_radio.setEnabled(False)
            gold_radio.setEnabled(False)
            window.menuBar().setEnabled(False)
            if combo.currentText() == Language.CurrentDict[5]:
                set_transfer_rate(1)
            elif combo.currentText() == Language.CurrentDict[6]:
                set_transfer_rate(0)
            if is_network:
                set_ip(txt_ip.text(), 0)
                set_ip(txt_ip2.text(), 1)
                set_sent_header()
                set_start_time()
                tinnet = TinfoilNetwork()
                thread = threading.Thread(target=tinnet.init)
                thread.daemon = True
                thread.start()
            else:
                if is_goldleaf:
                    set_sent_header()
                    set_start_time()
                    gold = Goldleaf()
                    thread = threading.Thread(target=gold.init)
                    thread.daemon = True
                    thread.start()

                else:
                    set_sent_header()
                    set_start_time()
                    tin = Tinfoil()
                    thread = threading.Thread(target=tin.init)
                    thread.daemon = True
                    thread.start()
        else:
            cancel_task()
    @staticmethod
    def nsp_file_dialog():
        try:
            d = filedialog.askopenfilenames(parent=root,title=Language.CurrentDict[13],filetypes=[("NSP files", "*.nsp")])
            tmp = list()
            list_nsp.clear()
            i = 0
            if not is_goldleaf:
                file_list = list(d)
                for f in file_list:
                    if str(f).endswith(".nsp"):
                        i += 1
                        list_nsp.addItem(os.path.basename(str(f)))
                        tmp.append(f)
            else:
                tmp.append(str(d))
                list_nsp.addItem(os.path.basename(str(d)))
                i+=1
            if i > 0:
                btn_header.setEnabled(True)
                set_total_nsp(i)
                set_dir(os.path.dirname(tmp[0]))
                set_selected_files(tmp)
                l_status.setText(str(total_nsp) + " " + Language.CurrentDict[14])
            else:
                btn_header.setEnabled(False)
                l_status.setText(Language.CurrentDict[9])
        except Exception as e:
            if is_logging:
                logging.error(e, exc_info=True)
            pass
    @staticmethod
    def dark_mode_cmd():
        if dark_check.isChecked():
            try:
                set_dark_mode(1)
            except Exception as e:
                if is_logging:
                    logging.error(e, exc_info=True)
                    logging.debug('Error: Failed to set Dark Mode')
                print('Error: Failed to set Dark Mode')
                print(str(e))
                pass
        else:
            set_dark_mode(0)
            
    @staticmethod
    def tin_radio_cmd():
        l_nsp.setVisible(True)
        combo.setVisible(True)
        l_rate.setVisible(True)
        txt_ip.setEnabled(False)
        txt_ip2.setEnabled(False)
        txt_port.setEnabled(False)
        net_radio.setChecked(False)
        usb_radio.setChecked(True)
        net_radio.setVisible(True)
        set_goldleaf(False)
        split_check.setEnabled(True)
        l_status.setText(Language.CurrentDict[9])
        gold_img_label.setVisible(False)
        btn_header.setEnabled(False)
        btn_nsp.setEnabled(True)
        btn_nsp.setVisible(True)
        l_ip.setVisible(True)
        txt_ip.setVisible(True)
        list_nsp.setVisible(True)
        txt_ip2.setVisible(True)
        l_host.setVisible(True)
        usb_radio.setVisible(True)
        window.adjustSize()
        
    @staticmethod
    def gold_radio_cmd():
        l_nsp.setVisible(False)
        combo.setVisible(False)
        l_rate.setVisible(False)
        txt_ip.setEnabled(False)
        txt_ip2.setEnabled(False)
        txt_port.setEnabled(False)
        net_radio.setChecked(False)
        usb_radio.setChecked(True)
        net_radio.setVisible(False)
        set_network(False)
        set_goldleaf(True)
        split_check.setCheckState(False)
        split_check.setEnabled(False)
        list_nsp.clear()
        l_status.setText('')
        btn_nsp.setVisible(False)
        l_ip.setVisible(False)
        txt_ip.setVisible(False)
        list_nsp.setVisible(False)
        txt_ip2.setVisible(False)
        l_host.setVisible(False)
        usb_radio.setVisible(False)
        gold_img_label.setVisible(True)
        window.adjustSize() 
        
        
    @staticmethod
    def usb_radio_cmd():
        txt_ip.setEnabled(False)
        txt_ip2.setEnabled(False)
        combo.setEnabled(True)
        set_network(False)
        txt_port.setEnabled(False)
        split_check.setEnabled(True)
        
    @staticmethod
    def net_radio_cmd():
        txt_ip.setEnabled(True)
        txt_ip2.setEnabled(True)
        combo.setEnabled(False)
        set_network(True)
        txt_port.setEnabled(True)
        split_check.setCheckState(False)
        split_check.setEnabled(False)
        
    @staticmethod 
    def set_done_text():
        tmp_string = str(total_nsp)
        if not is_goldleaf:
            reset_install()
            l_nsp.setText(Language.CurrentDict[8] + " " + tmp_string + " NSP(s)!")
            
    @staticmethod
    def set_loading_text():
        l_nsp.setText("")
        l_status.setText("")
        if not is_goldleaf:
            l_switch.setText(str(total_nsp) + " " + Language.CurrentDict[26] + ".")
            l_switch.setStyleSheet(PURPLE)
        else:
            l_switch.setText(Language.CurrentDict[9] + ".")
            l_switch.setStyleSheet(BLUE)
            progressbar.setValue(0)
    @staticmethod
    def set_progress_text():
        v = (int(cur_progress) / int(end_progress)) * 100
        progressbar.setValue(v)
        n_rate = round((cur_transfer_rate /1000000),2)
        if n_rate < 0:
            n_rate = 0.0
        if not is_goldleaf:
            l_status.setText(Language.CurrentDict[27] + " " + str(cur_nsp_count) + " / " + str(total_nsp) + " NSP(s).")
        l_switch.setText(Language.CurrentDict[28] + ": " + str(n_rate) + "MB/s.")
        l_switch.setStyleSheet(GREEN)
        l_status.setStyleSheet(GREEN)
        if len(cur_nsp_name) > 13:
            if is_goldleaf:
                l_status.setText("\"" + cur_nsp_name[:13] + "...\"")
            else:
                l_nsp.setText(Language.CurrentDict[7] + ": \"" + cur_nsp_name[:13] + "...\"")
        else:
            if is_goldleaf:
                l_status.setText("\"" + cur_nsp_name + "\"")
            else:
                l_nsp.setText(Language.CurrentDict[7] + ": \"" + cur_nsp_name + "\"")
    @staticmethod
    def set_switch_text():
        try:
            if connect_switch():
                set_usb_success(True)
                l_switch.setText(Language.CurrentDict[11]+"!")
                l_switch.setStyleSheet(GREEN)
                if not is_goldleaf:
                    if list_nsp.count() > 0:
                        btn_header.setEnabled(True)
                    else:
                        btn_header.setEnabled(False)
                else:
                    btn_header.setEnabled(True)
            else:
                l_switch.setText(Language.CurrentDict[10]+"!")
                btn_header.setEnabled(False)
                l_switch.setStyleSheet(RED)
                try:
                    detach_switch()
                except:
                    pass
        except Exception as e:
            if is_logging:
                logging.error(e, exc_info=True)
            set_usb_success(False)
            UI.check_usb_success()
            pass
        
    @staticmethod
    def init_language():
        l_nsp.setText("")
        if not is_goldleaf:
            if list_nsp.count() > 0:
                l_status.setText(str(total_nsp) + " " + Language.CurrentDict[14])
            else:
                l_status.setText(Language.CurrentDict[9])
        l_switch.setText(Language.CurrentDict[10]+"!")
        l_ip.setText(Language.CurrentDict[2]+":")
        dark_check.setText(Language.CurrentDict[20])
        net_radio.setText(Language.CurrentDict[24])
        btn_nsp.setText(Language.CurrentDict[13])
        btn_header.setText(Language.CurrentDict[1])
        l_rate.setText(Language.CurrentDict[4])
        combo.clear()
        combo.SelectedIndex = 0
        combo.addItem(Language.CurrentDict[5])
        combo.addItem(Language.CurrentDict[6])
        l_host.setText(Language.CurrentDict[3]+":")
        lang_menu.setTitle(Language.CurrentDict[22])
        window.setWindowTitle(Language.CurrentDict[0])
        about.setWindowTitle(Language.CurrentDict[30])
        l_thanks.setText(Language.CurrentDict[31])
        l_donate.setText(Language.CurrentDict[32])
        about_menu.setText(Language.CurrentDict[30])
        

    @staticmethod
    def lang_menu_cmd():
        new_lang = None
        ai = 0
        for action in lang_menu.actions():
            if action.isChecked():
                if ai != language:
                        set_language(ai)
                        UI.init_language()
            ai+=1

    @staticmethod
    def about_menu_cmd():
        try:
            random.shuffle(thanks)
            credit_list.clear()
            for a in thanks:
                credit_list.addItem(a)
            about.show()
            about.setFixedSize(about.size().width(),about.size().height())
        except Exception as e:
            print(str(e))
            
    @staticmethod
    def check_usb_success():
        try:
            connect_switch()
            set_usb_success(True)
        except Exception as e:
            if is_logging:
                logging.error(e, exc_info=True)
            set_usb_success(False)
            pass
        if not usb_success:
            UI.net_radio_cmd()
            net_radio.setChecked(True)
            usb_radio.setVisible(False)
            l_rate.setVisible(False)
            combo.setVisible(False)
            gold_radio.setVisible(False)
            l_switch.setText(Language.CurrentDict[12])
            l_switch.setStyleSheet(BLUE)
# Main
try:
    # Load Images
    aboutpixmap.loadFromData(base64.b64decode(ABOUT_DATA))
    goldpixmap.loadFromData(base64.b64decode(GOLD_DATA))
    iconpixmap.loadFromData(base64.b64decode(ICON_DATA))
    inlaypixmap.loadFromData(base64.b64decode(DONUT_DATA))
    dinlaypixmap.loadFromData(base64.b64decode(DARK_DONUT_DATA))
    
    #Init Widgets
    l_host = QtWidgets.QLabel(Language.CurrentDict[3]+":")
    txt_ip2 = QtWidgets.QLineEdit("0.0.0.0")
    l_nsp = QtWidgets.QLabel("")
    l_ip = QtWidgets.QLabel(Language.CurrentDict[2]+":")
    l_port = QtWidgets.QLabel("Port:")
    txt_ip = QtWidgets.QLineEdit("0.0.0.0")
    tin_radio = QtWidgets.QRadioButton("Tinfoil")
    gold_radio = QtWidgets.QRadioButton("Goldleaf")
    split_check = QtWidgets.QCheckBox("Use Split NSP")
    dark_check = QtWidgets.QCheckBox(Language.CurrentDict[20])
    usb_radio = QtWidgets.QRadioButton("USB")
    net_radio = QtWidgets.QRadioButton(Language.CurrentDict[24])
    btn_nsp = QtWidgets.QPushButton(Language.CurrentDict[13])
    btn_header = QtWidgets.QPushButton(Language.CurrentDict[1])
    l_rate = QtWidgets.QLabel(Language.CurrentDict[4])
    l_github = QtWidgets.QLabel("v" + VERSION)
    l_status = QtWidgets.QLabel(Language.CurrentDict[9])
    l_switch = QtWidgets.QLabel(Language.CurrentDict[10]+"!")
    list_nsp = QtWidgets.QListWidget()
    combo = QComboBox()
    h_box = QtWidgets.QHBoxLayout()
    h2_box = QtWidgets.QHBoxLayout()
    h3_box = QtWidgets.QHBoxLayout()
    h_group = QtWidgets.QButtonGroup()
    v_box = QtWidgets.QVBoxLayout()
    img_label = QLabel()
    progressbar = QProgressBar()
    gold_img_label = QLabel()
    about = QMainWindow()
    about_v_box = QtWidgets.QVBoxLayout()
    credit_list = QtWidgets.QListWidget()
    monero_list = QtWidgets.QListWidget()
    about_img_label = QLabel()
    l_thanks = QtWidgets.QLabel(Language.CurrentDict[31])
    l_donate = QtWidgets.QLabel(Language.CurrentDict[32])
    
    #Set Widgets
    try:
        fill = [(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]
        txt_ip2.setText(str(fill))
    except:
        pass
    try:
        txt_ip.setText(switch_ip)
    except:
        txt_ip.setText("0.0.0.0")
        pass
    txt_ip.setEnabled(False)
    txt_ip2.setEnabled(False)
    txt_port = QtWidgets.QLineEdit("2000")
    txt_port.setEnabled(False)
    h3_box.addWidget(dark_check)
    h3_box.addStretch()
    h3_box.addWidget(l_github)
    combo.addItem(Language.CurrentDict[6])
    combo.addItem(Language.CurrentDict[5])
    combo.setCurrentIndex(1)
    tin_radio.setChecked(True)
    tin_radio.toggled.connect(UI.tin_radio_cmd)
    gold_radio.setChecked(False)
    gold_radio.toggled.connect(UI.gold_radio_cmd)
    h_group.addButton(tin_radio)
    h_group.addButton(gold_radio)
    h2_box.addWidget(tin_radio)
    h2_box.addWidget(gold_radio)
    dark_check.stateChanged.connect(UI.dark_mode_cmd)
    usb_radio.setChecked(True)
    usb_radio.toggled.connect(UI.usb_radio_cmd)
    h_box.addWidget(usb_radio)
    net_radio.toggled.connect(UI.net_radio_cmd)
    h_box.addWidget(net_radio)
    btn_header.setEnabled(False)
    progressbar.setAlignment(Qt.AlignVCenter)
    progressbar.setMaximum(100)
    img_label.setAlignment(Qt.AlignCenter)
    gold_img_label.setAlignment(Qt.AlignCenter)
    gold_img_label.setPixmap(goldpixmap)
    
    # About Window
    about_v_box.setContentsMargins(0,0,0,0)
    about_img_label.setAlignment(Qt.AlignCenter)
    l_thanks.setAlignment(Qt.AlignCenter)
    l_donate.setAlignment(Qt.AlignCenter)
    about_img_label.setPixmap(aboutpixmap)
    about_v_box.addWidget(about_img_label)
    about_v_box.addWidget(l_thanks)
    about_v_box.addWidget(credit_list)
    about_v_box.addWidget(l_donate)
    about_v_box.addWidget(monero_list)
    about.setCentralWidget(QWidget(about))
    about.centralWidget().setLayout(about_v_box)
    about.setWindowTitle(Language.CurrentDict[30])
    about.setWindowIcon(QIcon(iconpixmap))
    for a in thanks:
        credit_list.addItem(a)
    monero_list.addItem("Monero(XMR)")
    monero_list.addItem(MONERO_ADDRESS)

    # Menu Bar
    lang_menu = window.menuBar().addMenu(Language.CurrentDict[22])
    about_menu = window.menuBar().addAction(Language.CurrentDict[30])
    lang_group = QActionGroup(lang_menu)
    #lang_group.setExclusive(True)
    lang_group.addAction(QAction('English',lang_group,checkable=True))
    lang_group.addAction(QAction('中文',lang_group,checkable=True))
    lang_group.addAction(QAction('Tiếng Việt',lang_group,checkable=True))
    lang_group.addAction(QAction('Türkçe',lang_group,checkable=True))
    lang_group.addAction(QAction('Português Brasileiro',lang_group,checkable=True))
    lang_group.addAction(QAction('Italiano',lang_group,checkable=True))
    lang_group.addAction(QAction('Français',lang_group,checkable=True))
    lang_group.addAction(QAction('Español',lang_group,checkable=True))
    lang_group.addAction(QAction('Deutsch',lang_group,checkable=True))
    lang_group.addAction(QAction('Bahasa Indonesia',lang_group,checkable=True))
    lang_group.addAction(QAction('한국어',lang_group,checkable=True))
    lang_menu.addActions(lang_group.actions())
    lang_group.triggered.connect(UI.lang_menu_cmd)
    about_menu.triggered.connect(UI.about_menu_cmd)
    window.menuBar().setNativeMenuBar(False) # Possible MacOS Fix

    # "And for gosh sake watch your language!" -Tony, May 1st 2015
    aix = 0
    for action in lang_menu.actions():
        if aix == language:
            action.setChecked(True)
        aix+=1
    UI.init_language()
                
    
    # Occupy VBOX
    v_box.addLayout(h2_box)
    v_box.addWidget(img_label)
    v_box.addStretch()
    v_box.addWidget(gold_img_label)
    v_box.addStretch()
    v_box.addLayout(h_box)
    v_box.addWidget(l_ip)
    v_box.addWidget(txt_ip)
    v_box.addWidget(l_host)
    v_box.addWidget(txt_ip2)
    v_box.addWidget(l_rate)
    v_box.addWidget(combo)
    v_box.addWidget(btn_nsp)
    v_box.addWidget(btn_header)
    v_box.addWidget(l_nsp)
    v_box.addWidget(l_status)
    v_box.addWidget(l_switch)
    v_box.addWidget(progressbar)
    v_box.addWidget(list_nsp)
    v_box.addLayout(h3_box)
    window.setCentralWidget(QWidget(window))
    window.centralWidget().setLayout(v_box)
    window.setWindowTitle(Language.CurrentDict[0])
    btn_nsp.clicked.connect(UI.nsp_file_dialog)
    btn_header.clicked.connect(UI.send_header_cmd)
    window.setWindowIcon(QIcon(iconpixmap))
    gold_img_label.setVisible(False)
    window.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
    window.show()

    # Revert to network mode
    UI.check_usb_success()

    # Checkbox for Dark Mode
    if dark_mode == 1:
        try:
            set_dark_mode(1)
            dark_check.setChecked(True)
        except:
            set_dark_mode(0)
            dark_check.setChecked(False)
            pass
    else:
        set_dark_mode(0)
        dark_check.setChecked(False)
    
    # Main loop
    while True:
        QApplication.processEvents()
        # QMessage Response
        if needresponse:
            if ignore_warning_prompt == 0:
                print("To ignore future prompts, change \'ignore_warning_prompt\' to 1 in fluffy.conf.")
                if qrespnum == 0:
                    re = QMessageBox.warning(window, 'Warning!', "Goldleaf wants to read a file that isn't an NSP.\nLet Goldleaf read this file?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                    if re == QMessageBox.No:
                        set_response_qmessage(False)
                    elif re == QMessageBox.Yes:
                        set_response_qmessage(True)
                elif qrespnum == 1:
                    re = QMessageBox.warning(window, 'Warning!', "Goldleaf wants to write a file.\nConfirm file write?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                    if re == QMessageBox.No:
                        set_response_qmessage(False)
                    elif re == QMessageBox.Yes:
                        set_response_qmessage(True)
                elif qrespnum == 2:
                    re = QMessageBox.warning(window, 'Warning!', "Goldleaf wants to create a file.\nConfirm creation?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                    if re == QMessageBox.No:
                        set_response_qmessage(False)
                    elif re == QMessageBox.Yes:
                        set_response_qmessage(True)
                elif qrespnum == 3:
                    re = QMessageBox.warning(window, 'Warning!', "Goldleaf wants to create a directory.\nConfirm creation?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                    if re == QMessageBox.No:
                        set_response_qmessage(False)
                    elif re == QMessageBox.Yes:
                        set_response_qmessage(True)
                elif qrespnum == 4:
                    re = QMessageBox.warning(window, 'Warning!', "Goldleaf wants to delete a file.\nConfirm deletion?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                    if re == QMessageBox.No:
                        set_response_qmessage(False)
                    elif re == QMessageBox.Yes:
                        set_response_qmessage(True)
                elif qrespnum == 5:
                    re = QMessageBox.warning(window, 'Warning!', "Goldleaf wants to delete a directory.\nConfirm deletion?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                    if re == QMessageBox.No:
                        set_response_qmessage(False)
                    elif re == QMessageBox.Yes:
                        set_response_qmessage(True)
                elif qrespnum == 6:
                    re = QMessageBox.warning(window, 'Warning!', "Goldleaf wants to rename a file or directory.\nConfirm rename?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
                    if re == QMessageBox.No:
                        set_response_qmessage(False)
                    elif re == QMessageBox.Yes:
                        set_response_qmessage(True)
            else:
                set_response_qmessage(True)
            while haveresponse:
                time.sleep(1)
                
        # Check If Any Errors
        if last_error != "NA":
            if not task_canceled:
                msg_box = QMessageBox.critical(window, 'Error', last_error, QMessageBox.Ok)
            reset_last_error()
            cancel_task()
            
        # Check Log Size
        if is_logging:
            if os.path.isfile(initial_dir + 'fluffy.log'):
                if os.path.getsize(initial_dir + 'fluffy.log') > 250000:
                    logging.debug("Error: Log size reached, turning off logging.")
                    turn_off_logging()

        # Fix Dark Mode CheckBox
        if dark_mode == 0 and dark_check.isChecked():
            dark_check.setChecked(False)
            
        # Save config and close
        if not window.isVisible():
            try:
                switch_ip = txt_ip.text()
            except:
                pass
            save_config()
            cancel_task()
            sys.exit()
            
                
        # Switch Indicator
        if not is_installing and not is_network and usb_success and not sent_header:
            UI.set_switch_text()
                    
        # Tinfoil Network Mode
        if not sent_header and not is_installing and is_network:
            l_switch.setText(Language.CurrentDict[12])
            l_switch.setStyleSheet(BLUE)
            if list_nsp.count() > 0:
                btn_header.setEnabled(True)
            else:
                btn_header.setEnabled(False)
                
        # Network Header Sent
        if sent_header and is_network:
            try:
                if is_done:
                    UI.set_done_text()
                else:
                    if is_installing:
                        UI.set_progress_text()
                    else:
                        l_status.setText(Language.CurrentDict[25])
                        l_switch.setText(Language.CurrentDict[15])
                        l_switch.setStyleSheet(PURPLE)
            except:
                pass
            
        # Cancel Button
        if sent_header and not is_done:
            btn_header.setEnabled(True)
            btn_header.setText(Language.CurrentDict[16])

        # Installation in progress disable cancel
        #if sent_header and is_installing and not is_done:
            #btn_header.setEnabled(False)
            
        # Goldleaf & Tinfoil USB Mode
        if sent_header and not is_network:
            try:
                if is_done:
                    UI.set_done_text()
                else:
                    if is_installing:
                        UI.set_progress_text()
                    else:
                        UI.set_loading_text()
            except:
                pass
            
except Exception as e:
    if is_logging:
        logging.error(e, exc_info=True)
    save_config()
    sys.exit()
