From a1bfcd5f8bbafed462b27efde379c1fd586442e2 Mon Sep 17 00:00:00 2001 From: LNLenost Date: Tue, 20 May 2025 14:52:37 +0200 Subject: [PATCH 01/10] Update Italian translations (#31) * Delete resources/translations/nusget_it.ts * new translations --- resources/translations/nusget_it.ts | 102 +++++++++++++++------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/resources/translations/nusget_it.ts b/resources/translations/nusget_it.ts index 94600c2..a2e6ef0 100644 --- a/resources/translations/nusget_it.ts +++ b/resources/translations/nusget_it.ts @@ -6,72 +6,72 @@ About NUSGet - + Info su NUSGet NUSGet - + NUSGet Version {nusget_version} - + Versione {nusget_version} Using libWiiPy {libwiipy_version} & libTWLPy {libtwlpy_version} - + Versione libWiiPy {libwiipy_version} & libTWLPy {libtwlpy_version} © 2024-2025 NinjaCheetah & Contributors - + © 2024-2025 NinjaCheetah & Contributori View Project on GitHub - + Vedi il progetto su GitHub Translations - + Traduzioni French (Français): <a href=https://github.com/rougets style='color: #4a86e8; text-decoration: none;'><b>rougets</b></a> - + French (Français): <a href=https://github.com/rougets style='color: #4a86e8; text-decoration: none;'><b>rougets</b></a> German (Deutsch): <a href=https://github.com/yeah-its-gloria style='color: #4a86e8; text-decoration: none;'><b>yeah-its-gloria</b></a> - + German (Deutsch): <a href=https://github.com/yeah-its-gloria style='color: #4a86e8; text-decoration: none;'><b>yeah-its-gloria</b></a> Italian (Italiano): <a href=https://github.com/LNLenost style='color: #4a86e8; text-decoration: none;'><b>LNLenost</b></a> - + Italian (Italiano): <a href=https://github.com/LNLenost style='color: #4a86e8; text-decoration: none;'><b>LNLenost</b></a> Korean (한국어): <a href=https://github.com/DDinghoya style='color: #4a86e8; text-decoration: none;'><b>DDinghoya</b></a> - + Korean (한국어): <a href=https://github.com/DDinghoya style='color: #4a86e8; text-decoration: none;'><b>DDinghoya</b></a> Norwegian (Norsk): <a href=https://github.com/rolfiee style='color: #4a86e8; text-decoration: none;'><b>rolfiee</b></a> - + Norwegian (Norsk): <a href=https://github.com/rolfiee style='color: #4a86e8; text-decoration: none;'><b>rolfiee</b></a> Romanian (Română): <a href=https://github.com/NotImplementedLife style='color: #4a86e8; text-decoration: none;'><b>NotImplementedLife</b></a> - + Romanian (Română): <a href=https://github.com/NotImplementedLife style='color: #4a86e8; text-decoration: none;'><b>NotImplementedLife</b></a> Spanish (Español): <a href=https://github.com/DarkMatterCore style='color: #4a86e8; text-decoration: none;'><b>DarkMatterCore</b></a> - + Spanish (Español): <a href=https://github.com/DarkMatterCore style='color: #4a86e8; text-decoration: none;'><b>DarkMatterCore</b></a> @@ -148,7 +148,7 @@ About NUSGet - + Info su NUSGet @@ -192,22 +192,22 @@ App Settings - + Impostazioni app Check for updates on startup - + Controlla aggiornamenti all'avvio Use a custom download directory - + Usa una cartella di download personalizzata Select... - + Seleziona... @@ -219,22 +219,29 @@ li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> Help - + Aiuto About Qt - + Info su Qt Output Path - + Cartella output <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -295,17 +302,21 @@ I titoli verranno scaricati nella cartella "NUSGet Downloads" all&apos Titles marked with a checkmark are free and have a ticket available, and can be decrypted and/or packed into a WAD or TAD. Titles with an X do not have a ticket, and only their encrypted contents can be saved. By default, titles will be downloaded to a folder named "NUSGet Downloads" inside your downloads folder. - + Scegli un titolo dalla lista a sinistra o inserisci un ID Titolo per iniziare. + +I titoli marcati da una spunta sono disponibili e hanno un ticket libero, e possono essere decriptati e/o convertiti in WAD o TAD. I titoli con una X non hanno un ticket e solo il contenuto criptato può essere salvato. + +Per impostazione predefinita, i titoli verranno scaricati nella cartella "NUSGet Downloads" all'interno della cartella Download. Use the Wii U NUS (faster, only affects Wii/vWii) - + Usa il NUS di Wii U (più veloce, influisce solo su Wii/vWii) <b>There's a newer version of NUSGet available!</b> - + <b>È disponibile una nuova versione di NUSGet!</b> @@ -326,17 +337,17 @@ By default, titles will be downloaded to a folder named "NUSGet Downloads&q Invalid Download Directory - + Cartella di download non valida The specified download directory does not exist! - + La cartella di download specificata non esiste! Please make sure the specified download directory exists, and that you have permission to access it. - + Assicurati che la cartella di download specificata esista e che tu abbia i permessi per accedervi. @@ -346,52 +357,47 @@ By default, titles will be downloaded to a folder named "NUSGet Downloads&q <b>The Title ID you have entered is not in a valid format!</b> - + <b>L'ID Titolo che hai inserito non è in un formato valido!</b> <b>No title with the provided Title ID or version could be found!</b> - + <b>Non è stato trovato alcun titolo con l'ID Titolo o la versione forniti!</b> <b>Content decryption was not successful! Decrypted contents could not be created.</b> - + <b>La decriptazione dei contenuti non è riuscita! Non è stato possibile creare i contenuti decriptati.</b> <b>No Ticket is Available for the Requested Title!</b> - + <b>Nessun ticket disponibile per il titolo richiesto!</b> <b>An Unknown Error has Occurred!</b> - + <b>Si è verificato un errore sconosciuto!</b> <b>Some issues occurred while running the download script.</b> - + <b>Si sono verificati alcuni problemi durante l'esecuzione dello script di download.</b> <b>An error occurred while parsing the script file!</b> - + <b>Si è verificato un errore durante l'analisi del file script!</b> <b>An error occurred while parsing Title IDs!</b> - + <b>Si è verificato un errore durante l'analisi degli ID Titolo!</b> The Title ID you have entered is not in a valid format! L' ID Titolo che hai inserito non è in un formato valido! - - - Title IDs must be 16 digit strings of numbers and letters. Please enter a correctly formatted Title ID, or select one from the menu on the left. - Gli ID Titolo sono un codice di 16 caratteri tra numeri e lettere. Per favore inserisci in ID Titolo formattato correttamente, o scegline uno dal menù a sinistra. - Title ID/Version Not Found @@ -470,7 +476,7 @@ By default, titles will be downloaded to a folder named "NUSGet Downloads&q You enabled "Create decrypted contents" or "Pack installable archive", but the following titles in the script do not have tickets available. If enabled, encrypted contents were still downloaded. - You enabled "Create decrypted contents" or "Pack installable archive", but the following titles in the script do not have tickets available. If enabled, encrypted contents were still downloaded. + Hai abilitato "Crea contenuto decriptato" o "Archivio installabile", ma i seguenti titoli nello script non hanno ticket disponibili. Se abilitati, i contenuti criptati sono stati comunque scaricati. @@ -494,7 +500,7 @@ By default, titles will be downloaded to a folder named "NUSGet Downloads&q Error encountered at line {lineno}, column {colno}. Please double-check the script and try again. - Error encountered at line {lineno}, column {colno}. Please double-check the script and try again. + Errore riscontrato alla riga {lineno}, colonna {colno}. Controlla nuovamente lo script e riprova. An error occurred while parsing Title IDs! @@ -503,22 +509,22 @@ By default, titles will be downloaded to a folder named "NUSGet Downloads&q The title at index {index} does not have a Title ID! - The title at index {index} does not have a Title ID! + Il titolo all'indice {index} non ha un ID Titolo! Open Directory - + Apri cartella <b>The specified download directory does not exist!</b> - + <b>La cartella di download specificata non esiste!</b> Please make sure the download directory you want to use exists, and that you have permission to access it. - + Assicurati che la cartella di download che desideri utilizzare esista e che tu abbia i permessi per accedervi. Open NUS script From 9b4addc8a530f1aad85cab553432665059b248e7 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 20 May 2025 09:08:07 -0400 Subject: [PATCH 02/10] Remove illegal characters from WAD names on Windows --- NUSGet.py | 4 +++ resources/translations/nusget_de.ts | 14 ++++----- resources/translations/nusget_es.ts | 12 ++++---- resources/translations/nusget_fr.ts | 12 ++++---- resources/translations/nusget_it.ts | 47 ++++++++++++++++------------- resources/translations/nusget_ko.ts | 14 ++++----- resources/translations/nusget_nb.ts | 12 ++++---- resources/translations/nusget_no.ts | 12 ++++---- resources/translations/nusget_ro.ts | 12 ++++---- 9 files changed, 74 insertions(+), 65 deletions(-) diff --git a/NUSGet.py b/NUSGet.py index 888c8e0..783a4b6 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -279,6 +279,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): if selected_title.category.find("System") != -1 or selected_title.category == "IOS": archive_name += "-Wii" archive_name += ".wad" + # On Windows, we need to strip characters that aren't allowed on NTFS. APFS and Linux filesystems aren't nearly + # as picky, but some titles contain characters that NTFS dislikes which will break downloads. + if os.name == "nt": + archive_name = archive_name.translate({ord(c): None for c in '/\\:*"?<>|'}) self.ui.archive_file_entry.setText(archive_name) danger_text = selected_title.danger # Add warning text to the log if the selected title has no ticket. diff --git a/resources/translations/nusget_de.ts b/resources/translations/nusget_de.ts index c9e419e..781a3db 100644 --- a/resources/translations/nusget_de.ts +++ b/resources/translations/nusget_de.ts @@ -439,17 +439,17 @@ Sie nutzen bereits die neuste Version von NUSGet. App-Einstellungen - + Output Path Downloads-Ordner - + Select... Auswählen... - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -468,17 +468,17 @@ li.checked::marker { content: "\2612"; } <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> - + Help Hilfe - + About NUSGet - Über NUSGet + Über NUSGet - + About Qt Über Qt diff --git a/resources/translations/nusget_es.ts b/resources/translations/nusget_es.ts index 4c4c2b1..34151b4 100644 --- a/resources/translations/nusget_es.ts +++ b/resources/translations/nusget_es.ts @@ -147,7 +147,7 @@ Configuración general - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -159,7 +159,7 @@ li.checked::marker { content: "\2612"; } - + About NUSGet Acerca de NUSGet @@ -214,22 +214,22 @@ li.checked::marker { content: "\2612"; } Usar ruta de descarga personalizada - + Select... Seleccionar... - + Help Ayuda - + About Qt Acerca de Qt - + Output Path Ruta de descarga diff --git a/resources/translations/nusget_fr.ts b/resources/translations/nusget_fr.ts index 78e8b28..6dbf8b6 100644 --- a/resources/translations/nusget_fr.ts +++ b/resources/translations/nusget_fr.ts @@ -410,7 +410,7 @@ Les titres seront téléchargés dans un dossier "NUSGet Downloads", Configuration - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -422,7 +422,7 @@ li.checked::marker { content: "\2612"; } - + About NUSGet À propos de NUSGet @@ -486,22 +486,22 @@ li.checked::marker { content: "\2612"; } Utiliser un dossier de téléchargement différent - + Select... Choisir - + Help Aide - + About Qt À propos de Qt - + Output Path Dossier de téléchargement diff --git a/resources/translations/nusget_it.ts b/resources/translations/nusget_it.ts index a2e6ef0..0d294ef 100644 --- a/resources/translations/nusget_it.ts +++ b/resources/translations/nusget_it.ts @@ -41,37 +41,37 @@ French (Français): <a href=https://github.com/rougets style='color: #4a86e8; text-decoration: none;'><b>rougets</b></a> - French (Français): <a href=https://github.com/rougets style='color: #4a86e8; text-decoration: none;'><b>rougets</b></a> + Francese (Français): <a href=https://github.com/rougets style='color: #4a86e8; text-decoration: none;'><b>rougets</b></a> German (Deutsch): <a href=https://github.com/yeah-its-gloria style='color: #4a86e8; text-decoration: none;'><b>yeah-its-gloria</b></a> - German (Deutsch): <a href=https://github.com/yeah-its-gloria style='color: #4a86e8; text-decoration: none;'><b>yeah-its-gloria</b></a> + Tedesco (Deutsch): <a href=https://github.com/yeah-its-gloria style='color: #4a86e8; text-decoration: none;'><b>yeah-its-gloria</b></a> Italian (Italiano): <a href=https://github.com/LNLenost style='color: #4a86e8; text-decoration: none;'><b>LNLenost</b></a> - Italian (Italiano): <a href=https://github.com/LNLenost style='color: #4a86e8; text-decoration: none;'><b>LNLenost</b></a> + Italiano: <a href=https://github.com/LNLenost style='color: #4a86e8; text-decoration: none;'><b>LNLenost</b></a> Korean (한국어): <a href=https://github.com/DDinghoya style='color: #4a86e8; text-decoration: none;'><b>DDinghoya</b></a> - Korean (한국어): <a href=https://github.com/DDinghoya style='color: #4a86e8; text-decoration: none;'><b>DDinghoya</b></a> + Coreano (한국어): <a href=https://github.com/DDinghoya style='color: #4a86e8; text-decoration: none;'><b>DDinghoya</b></a> Norwegian (Norsk): <a href=https://github.com/rolfiee style='color: #4a86e8; text-decoration: none;'><b>rolfiee</b></a> - Norwegian (Norsk): <a href=https://github.com/rolfiee style='color: #4a86e8; text-decoration: none;'><b>rolfiee</b></a> + Norvegese (Norsk): <a href=https://github.com/rolfiee style='color: #4a86e8; text-decoration: none;'><b>rolfiee</b></a> Romanian (Română): <a href=https://github.com/NotImplementedLife style='color: #4a86e8; text-decoration: none;'><b>NotImplementedLife</b></a> - Romanian (Română): <a href=https://github.com/NotImplementedLife style='color: #4a86e8; text-decoration: none;'><b>NotImplementedLife</b></a> + Rumeno (Română): <a href=https://github.com/NotImplementedLife style='color: #4a86e8; text-decoration: none;'><b>NotImplementedLife</b></a> Spanish (Español): <a href=https://github.com/DarkMatterCore style='color: #4a86e8; text-decoration: none;'><b>DarkMatterCore</b></a> - Spanish (Español): <a href=https://github.com/DarkMatterCore style='color: #4a86e8; text-decoration: none;'><b>DarkMatterCore</b></a> + Spagnolo (Español): <a href=https://github.com/DarkMatterCore style='color: #4a86e8; text-decoration: none;'><b>DarkMatterCore</b></a> @@ -146,7 +146,7 @@ Impostazioni generali - + About NUSGet Info su NUSGet @@ -205,12 +205,12 @@ Usa una cartella di download personalizzata - + Select... Seleziona... - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -229,17 +229,17 @@ li.checked::marker { content: "\2612"; } <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> - + Help Aiuto - + About Qt Info su Qt - + Output Path Cartella output @@ -306,7 +306,7 @@ By default, titles will be downloaded to a folder named "NUSGet Downloads&q I titoli marcati da una spunta sono disponibili e hanno un ticket libero, e possono essere decriptati e/o convertiti in WAD o TAD. I titoli con una X non hanno un ticket e solo il contenuto criptato può essere salvato. -Per impostazione predefinita, i titoli verranno scaricati nella cartella "NUSGet Downloads" all'interno della cartella Download. +Per impostazione predefinita, i titoli verranno scaricati nella cartella "NUSGet Downloads" all'interno della cartella Download. @@ -357,12 +357,17 @@ Per impostazione predefinita, i titoli verranno scaricati nella cartella "NUSGet <b>The Title ID you have entered is not in a valid format!</b> - <b>L'ID Titolo che hai inserito non è in un formato valido!</b> + <b>L'ID Titolo che hai inserito non è in un formato valido!</b> + + + + Title IDs must be 16 digit strings of numbers and letters. Please enter a correctly formatted Title ID, or select one from the menu on the left. + <b>No title with the provided Title ID or version could be found!</b> - <b>Non è stato trovato alcun titolo con l'ID Titolo o la versione forniti!</b> + <b>Non è stato trovato alcun titolo con l'ID Titolo o la versione forniti!</b> @@ -382,17 +387,17 @@ Per impostazione predefinita, i titoli verranno scaricati nella cartella "NUSGet <b>Some issues occurred while running the download script.</b> - <b>Si sono verificati alcuni problemi durante l'esecuzione dello script di download.</b> + <b>Si sono verificati alcuni problemi durante l'esecuzione dello script di download.</b> <b>An error occurred while parsing the script file!</b> - <b>Si è verificato un errore durante l'analisi del file script!</b> + <b>Si è verificato un errore durante l'analisi del file script!</b> <b>An error occurred while parsing Title IDs!</b> - <b>Si è verificato un errore durante l'analisi degli ID Titolo!</b> + <b>Si è verificato un errore durante l'analisi degli ID Titolo!</b> The Title ID you have entered is not in a valid format! @@ -476,7 +481,7 @@ Per impostazione predefinita, i titoli verranno scaricati nella cartella "NUSGet You enabled "Create decrypted contents" or "Pack installable archive", but the following titles in the script do not have tickets available. If enabled, encrypted contents were still downloaded. - Hai abilitato "Crea contenuto decriptato" o "Archivio installabile", ma i seguenti titoli nello script non hanno ticket disponibili. Se abilitati, i contenuti criptati sono stati comunque scaricati. + Hai abilitato "Crea contenuto decriptato" o "Archivio installabile", ma i seguenti titoli nello script non hanno ticket disponibili. Se abilitati, i contenuti criptati sono stati comunque scaricati. @@ -509,7 +514,7 @@ Per impostazione predefinita, i titoli verranno scaricati nella cartella "NUSGet The title at index {index} does not have a Title ID! - Il titolo all'indice {index} non ha un ID Titolo! + Il titolo all'indice {index} non ha un ID Titolo! diff --git a/resources/translations/nusget_ko.ts b/resources/translations/nusget_ko.ts index ee40843..34c666e 100644 --- a/resources/translations/nusget_ko.ts +++ b/resources/translations/nusget_ko.ts @@ -146,9 +146,9 @@ 일반 설정 - + About NUSGet - NUSGet 정보 + NUSGet 정보 @@ -205,12 +205,12 @@ 커스텀 다운로드 디렉터리 사용 - + Select... 선택... - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -222,7 +222,7 @@ li.checked::marker { content: "\2612"; } - + Help 도움말 @@ -231,12 +231,12 @@ li.checked::marker { content: "\2612"; } 정보 - + About Qt Qt 정보 - + Output Path 출력 경로 diff --git a/resources/translations/nusget_nb.ts b/resources/translations/nusget_nb.ts index 480f799..a286499 100644 --- a/resources/translations/nusget_nb.ts +++ b/resources/translations/nusget_nb.ts @@ -146,7 +146,7 @@ Generelle Instillinger - + About NUSGet Om NUSGet @@ -205,12 +205,12 @@ Bruke en egendefinert nedlastingsmappe - + Select... Velg... - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -229,17 +229,17 @@ li.checked::marker { content: "\2612"; } <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> - + Help Hjelp - + About Qt Om Qt - + Output Path Utgangsbane diff --git a/resources/translations/nusget_no.ts b/resources/translations/nusget_no.ts index 480f799..a286499 100644 --- a/resources/translations/nusget_no.ts +++ b/resources/translations/nusget_no.ts @@ -146,7 +146,7 @@ Generelle Instillinger - + About NUSGet Om NUSGet @@ -205,12 +205,12 @@ Bruke en egendefinert nedlastingsmappe - + Select... Velg... - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -229,17 +229,17 @@ li.checked::marker { content: "\2612"; } <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> - + Help Hjelp - + About Qt Om Qt - + Output Path Utgangsbane diff --git a/resources/translations/nusget_ro.ts b/resources/translations/nusget_ro.ts index bc37607..063cf09 100644 --- a/resources/translations/nusget_ro.ts +++ b/resources/translations/nusget_ro.ts @@ -447,7 +447,7 @@ Implicit, titlurile vor fi descărcate într-un folder numit „NUSGet Downloads Setări Generale - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -459,7 +459,7 @@ li.checked::marker { content: "\2612"; } - + About NUSGet Despre NUSGet @@ -523,22 +523,22 @@ li.checked::marker { content: "\2612"; } Folosiți un director de descărcare propriu - + Select... Selectează... - + Help Ajutor - + About Qt Despre Qt - + Output Path Cale de ieșire From 07579d7361297a0c5d8427cfd227759f36c59748 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 20 May 2025 13:54:27 -0400 Subject: [PATCH 03/10] Improve illegal character removal Now removes the NTFS-forbidden characters on all platforms, as some may still cause issues depending on your OS and FS. These characters are also now removed during the actual download process, so they'll still appear in the WAD name box, but will be stripped out before the file is created. --- NUSGet.py | 4 ---- modules/download_dsi.py | 4 ++++ modules/download_wii.py | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/NUSGet.py b/NUSGet.py index 783a4b6..888c8e0 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -279,10 +279,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): if selected_title.category.find("System") != -1 or selected_title.category == "IOS": archive_name += "-Wii" archive_name += ".wad" - # On Windows, we need to strip characters that aren't allowed on NTFS. APFS and Linux filesystems aren't nearly - # as picky, but some titles contain characters that NTFS dislikes which will break downloads. - if os.name == "nt": - archive_name = archive_name.translate({ord(c): None for c in '/\\:*"?<>|'}) self.ui.archive_file_entry.setText(archive_name) danger_text = selected_title.danger # Add warning text to the log if the selected title has no ticket. diff --git a/modules/download_dsi.py b/modules/download_dsi.py index c0d8c98..91054de 100644 --- a/modules/download_dsi.py +++ b/modules/download_dsi.py @@ -104,6 +104,10 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_ tad_file_name += ".tad" else: tad_file_name = f"{tid}-v{title_version}.tad" + # Certain special characters are prone to breaking things, so strip them from the file name before actually + # opening the file for writing. On some platforms (like macOS), invalid characters get replaced automatically, + # but on Windows the file will just fail to be written out at all. + tad_file_name = tad_file_name.translate({ord(c): None for c in '/\\:*"?<>|'}) # Have libTWLPy dump the TAD, and write that data out. version_dir.joinpath(tad_file_name).write_bytes(title.dump_tad()) progress_callback.emit("Download complete!") diff --git a/modules/download_wii.py b/modules/download_wii.py index 6064da3..cf94ed5 100644 --- a/modules/download_wii.py +++ b/modules/download_wii.py @@ -134,6 +134,10 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_ title = ios_patcher.dump() # Append "-PATCHED" to the end of the WAD file name to make it clear that it was modified. wad_file_name = wad_file_name[:-4] + "-PATCHED" + wad_file_name[-4:] + # Certain special characters are prone to breaking things, so strip them from the file name before actually + # opening the file for writing. On some platforms (like macOS), invalid characters get replaced automatically, + # but on Windows the file will just fail to be written out at all. + wad_file_name = wad_file_name.translate({ord(c): None for c in '/\\:*"?<>|'}) # Have libWiiPy dump the WAD, and write that data out. version_dir.joinpath(wad_file_name).write_bytes(title.dump_wad()) progress_callback.emit("Download complete!") From 7caa7775ffbbfcce2c27fd0547727a7d5c4cf46f Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 20 May 2025 21:11:29 -0400 Subject: [PATCH 04/10] Force correct coloring so that text is always legible on all system themes Since NUSGet only supports a dark theme right now, but your system may use a light theme, the OS may recolor text on screen to be black on black, which is obviously bad for readability. All text will now be white, so you can always read it. And hey, maybe there will be a proper light theme soon! --- resources/right_arrow.svg | 59 +++++++++++++++++++++++++++++++++++++++ resources/style.qss | 16 +++++++++++ 2 files changed, 75 insertions(+) create mode 100644 resources/right_arrow.svg diff --git a/resources/right_arrow.svg b/resources/right_arrow.svg new file mode 100644 index 0000000..4ab9fbd --- /dev/null +++ b/resources/right_arrow.svg @@ -0,0 +1,59 @@ + + + + + + + + + + diff --git a/resources/style.qss b/resources/style.qss index 805f8b9..5b4aaaa 100644 --- a/resources/style.qss +++ b/resources/style.qss @@ -145,6 +145,7 @@ QTreeView { } QTreeView QHeaderView::section { + color: white; background-color: #2b2b2b; border: 0; font-weight: 500; @@ -170,7 +171,18 @@ QTreeView QScrollBar:vertical { margin-top: 16px; } +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + image: url("{IMAGE_PREFIX}/right_arrow.svg"); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + image: url("{IMAGE_PREFIX}/down_arrow.svg"); +} + QTextBrowser { + color: white; background-color: #1a1a1a; selection-background-color: #1a73e8; } @@ -326,6 +338,10 @@ QScrollBar::sub-line:horizontal { subcontrol-origin: margin; } +QMessageBox QLabel { + color: white; +} + WrapCheckboxWidget { show-decoration-selected: 1; outline: 0; From a4679be04304c7d8fda2b14ec733d9d6d8e79c39 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 20 May 2025 22:26:11 -0400 Subject: [PATCH 05/10] Added an experimental light theme and automatic system theme detection --- NUSGet.py | 12 +- modules/theme.py | 52 +++ qt/py/ui_AboutDialog.py | 27 -- resources/down_arrow_black.svg | 59 +++ .../{down_arrow.svg => down_arrow_white.svg} | 0 resources/right_arrow_black.svg | 59 +++ ...{right_arrow.svg => right_arrow_white.svg} | 0 resources/{style.qss => style_dark.qss} | 6 +- resources/style_light.qss | 398 ++++++++++++++++++ 9 files changed, 581 insertions(+), 32 deletions(-) create mode 100644 modules/theme.py create mode 100644 resources/down_arrow_black.svg rename resources/{down_arrow.svg => down_arrow_white.svg} (100%) create mode 100644 resources/right_arrow_black.svg rename resources/{right_arrow.svg => right_arrow_white.svg} (100%) rename resources/{style.qss => style_dark.qss} (97%) create mode 100644 resources/style_light.qss diff --git a/NUSGet.py b/NUSGet.py index 888c8e0..06e7170 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -30,6 +30,7 @@ from qt.py.ui_AboutDialog import AboutNUSGet from qt.py.ui_MainMenu import Ui_MainWindow from modules.core import * +from modules.theme import is_dark_theme from modules.tree import NUSGetTreeModel, TIDFilterProxyModel from modules.download_batch import run_nus_download_batch from modules.download_wii import run_nus_download_wii @@ -154,10 +155,14 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.trees[tree].collapsed.connect(lambda: self.resize_tree(self.ui.platform_tabs.currentIndex())) # This stylesheet patch allows me to add the correct padding above the scrollbar so that it doesn't overlap # the QTreeView's header. + if is_dark_theme(): + bg_color = "#2b2b2b" + else: + bg_color = "#e3e3e3" self.trees[tree].setStyleSheet(self.trees[tree].styleSheet() + f""" QTreeView QScrollBar::sub-line:vertical {{ border: 0; - background: #2b2b2b; + background: {bg_color}; height: {self.trees[tree].header().sizeHint().height()}px; }}""") # Prevent resizing. @@ -650,7 +655,10 @@ if __name__ == "__main__": # Load Fusion because that's objectively the best base theme, and then load the fancy stylesheet on top to make # NUSGet look nice and pretty. app.setStyle("fusion") - stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", "style.qss")).read() + if is_dark_theme(): + stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", "style_dark.qss")).read() + else: + stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", "style_light.qss")).read() image_path_prefix = pathlib.Path(os.path.join(os.path.dirname(__file__), "resources")).resolve().as_posix() stylesheet = stylesheet.replace("{IMAGE_PREFIX}", image_path_prefix) app.setStyleSheet(stylesheet) diff --git a/modules/theme.py b/modules/theme.py new file mode 100644 index 0000000..1906b9a --- /dev/null +++ b/modules/theme.py @@ -0,0 +1,52 @@ +# "modules/theme.py", licensed under the MIT license +# Copyright 2024-2025 NinjaCheetah & Contributors + +import platform +import subprocess + +def is_dark_theme_windows(): + # This has to be here so that Python doesn't try to import it on non-Windows. + import winreg + try: + registry = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) + key = winreg.OpenKey(registry, r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize") + # This value is "AppsUseLightTheme" so a "1" is light and a "0" is dark. Side note: I hate the Windows registry. + value, _ = winreg.QueryValueEx(key, "AppsUseLightTheme") + return value == 0 + except Exception: + return False + +def is_dark_theme_macos(): + # macOS is weird. If the dark theme is on, then `defaults read -g AppleInterfaceStyle` returns "Dark". If the light + # theme is on, then trying to read this key fails and returns an error instead. + try: + result = subprocess.run( + ["defaults", "read", "-g", "AppleInterfaceStyle"], + capture_output=True, text=True + ) + return "Dark" in result.stdout + except Exception: + return False + +def is_dark_theme_linux(): + try: + import subprocess + result = subprocess.run( + ["gsettings", "get", "org.gnome.desktop.interface", "gtk-theme"], + capture_output=True, text=True + ) + # Looking for *not* "Light", because I want any theme that isn't light to be dark. An example of this is my own + # KDE Plasma setup on my desktop, where I use the "Breeze" GTK theme and want dark NUSGet to be used in that + # case. + return not "light" in result.stdout.lower() + except Exception: + return False + +def is_dark_theme(): + system = platform.system() + if system == "Windows": + return is_dark_theme_windows() + elif system == "Darwin": + return is_dark_theme_macos() + else: + return is_dark_theme_linux() diff --git a/qt/py/ui_AboutDialog.py b/qt/py/ui_AboutDialog.py index 2da906b..8b1bb01 100644 --- a/qt/py/ui_AboutDialog.py +++ b/qt/py/ui_AboutDialog.py @@ -19,17 +19,9 @@ class AboutNUSGet(QDialog): # Set background color to match main app self.setStyleSheet(""" - Credits { - background-color: #222222; - color: #ffffff; - } - QLabel { - color: #ffffff; - } QLabel[class="title"] { font-size: 20px; font-weight: bold; - color: #ffffff; } QLabel[class="version"] { font-size: 13px; @@ -45,25 +37,6 @@ class AboutNUSGet(QDialog): border-bottom: 1px solid #444444; padding-bottom: 4px; margin-top: 8px; - } - QPushButton { - background-color: transparent; - border: 1px solid rgba(70, 70, 70, 1); - border-radius: 8px; - padding: 8px 12px; - margin: 4px 0px; - font-size: 13px; - font-weight: 500; - color: #ffffff; - text-align: center; - } - QPushButton:hover { - background-color: rgba(60, 60, 60, 1); - border-color: #4a86e8; - } - QPushButton:pressed { - background-color: rgba(26, 115, 232, 0.15); - border: 1px solid #1a73e8; }""") # Create main layout diff --git a/resources/down_arrow_black.svg b/resources/down_arrow_black.svg new file mode 100644 index 0000000..2f1a769 --- /dev/null +++ b/resources/down_arrow_black.svg @@ -0,0 +1,59 @@ + + + + + + + + + + diff --git a/resources/down_arrow.svg b/resources/down_arrow_white.svg similarity index 100% rename from resources/down_arrow.svg rename to resources/down_arrow_white.svg diff --git a/resources/right_arrow_black.svg b/resources/right_arrow_black.svg new file mode 100644 index 0000000..34720f0 --- /dev/null +++ b/resources/right_arrow_black.svg @@ -0,0 +1,59 @@ + + + + + + + + + + diff --git a/resources/right_arrow.svg b/resources/right_arrow_white.svg similarity index 100% rename from resources/right_arrow.svg rename to resources/right_arrow_white.svg diff --git a/resources/style.qss b/resources/style_dark.qss similarity index 97% rename from resources/style.qss rename to resources/style_dark.qss index 5b4aaaa..897ed36 100644 --- a/resources/style.qss +++ b/resources/style_dark.qss @@ -173,12 +173,12 @@ QTreeView QScrollBar:vertical { QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { - image: url("{IMAGE_PREFIX}/right_arrow.svg"); + image: url("{IMAGE_PREFIX}/right_arrow_white.svg"); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { - image: url("{IMAGE_PREFIX}/down_arrow.svg"); + image: url("{IMAGE_PREFIX}/down_arrow_white.svg"); } QTextBrowser { @@ -254,7 +254,7 @@ QComboBox::drop-down { } QComboBox::down-arrow { - image: url("{IMAGE_PREFIX}/down_arrow.svg"); + image: url("{IMAGE_PREFIX}/down_arrow_white.svg"); } QComboBox QAbstractItemView { diff --git a/resources/style_light.qss b/resources/style_light.qss new file mode 100644 index 0000000..d333062 --- /dev/null +++ b/resources/style_light.qss @@ -0,0 +1,398 @@ +/* "resources/style.qss" from NUSGet by NinjaCheetah & Contributors */ +/* Much of this QSS was written by Alex (https://github.com/Humanoidear) */ +/* from WiiLink for the fancy new WiiLink Patcher GUI. Used with permission. */ + +QMainWindow, QDialog { + background-color: #ffffff; +} + +QMainWindow QLabel { + color: #000000; +} + +QMenuBar { + background-color: #ffffff; +} + +QMenuBar::item:selected { + background-color: rgba(60, 60, 60, 1); + color: white; +} + +QMenuBar::item:pressed { + background-color: #1a73e8; + color: #000000; +} + +QMenu { + background-color: #ffffff; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 6px 2px; + margin: 4px 0; + color: #000000; +} + +QMenu::item { + padding: 6px 2px; + margin: 2px; + border-radius: 4px; + background-color: transparent; +} + +QMenu::item:selected { + background-color: #1a73e8; + color: #000000; +} + +QMenu::icon { + padding: 4px; +} + +QRadioButton { + background-color: transparent; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 8px 10px; + font-size: 13px; + font-weight: 500; + color: #ffffff; +} + +QRadioButton:hover { + background-color: rgba(60, 60, 60, 1); + border-color: #4a86e8; +} + +QRadioButton:checked { + background-color: rgba(26, 115, 232, 0.08); + border: 1px solid #1a73e8; + color: #1a73e8; +} + +QRadioButton::indicator { + width: 18px; + height: 18px; + border-radius: 5px; + border: 1px solid #5f6368; + margin-right: 8px; + subcontrol-position: left center; +} + +QRadioButton::indicator:checked { + background-color: #1a73e8; + border: 1px solid #1a73e8; + image: url("{IMAGE_PREFIX}/rounded_square.svg"); +} + +QRadioButton::indicator:hover { + border-color: #1a73e8; +} + +QLineEdit { + background-color: transparent; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 6px 10px; + margin: 4px 0px; + font-size: 13px; + color: #000000; + selection-background-color: #1a73e8; +} + +QLineEdit:focus { + border-color: #1a73e8; +} + +QLineEdit:disabled { + background-color: rgba(182, 182, 182, 0.5); + border: 1px solid rgba(100, 100, 100, 0.3); + color: rgba(255, 255, 255, 0.3); +} + +QTabWidget::pane { + border: 1px solid rgb(163, 163, 163); + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + border-bottom-left-radius: 8px; + background-color: #e3e3e3; + top: -1px; +} + +QTabBar::tab { + background-color: transparent; + border-top: 1px solid rgb(163, 163, 163); + border-left: 1px solid rgb(163, 163, 163); + border-right: 1px solid rgb(163, 163, 163); + border-top-left-radius: 6px; + border-top-right-radius: 6px; + padding: 6px 10px; + font-size: 13px; + font-weight: 500; + color: #000000; +} + +QTabBar::tab:selected, QTabBar::tab:hover { + background-color: #e3e3e3; +} + +QTreeView { + show-decoration-selected: 1; + outline: 0; + background-color: #ffffff; + border: 0; + border-radius: 8px; +} + +QTreeView QHeaderView::section { + color: #000000; + background-color: #e3e3e3; + border: 0; + font-weight: 500; +} + +QTreeView::item { + color: #000000; +} + +QTreeView::item:hover { + background-color: rgb(195, 195, 195); +} + +QTreeView::item:focus { + background-color: rgba(26, 115, 232, 0.08); +} + +QTreeView::item:selected { + background-color: rgb(127, 182, 255); +} + +QTreeView::branch:selected { + background-color: rgb(127, 182, 255); +} + +QTreeView QScrollBar:vertical { + margin-top: 16px; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + image: url("{IMAGE_PREFIX}/right_arrow_black.svg"); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + image: url("{IMAGE_PREFIX}/down_arrow_black.svg"); +} + +QTextBrowser { + color: #000000; + background-color: #ececec; + selection-background-color: #1a73e8; + selection-color: #ffffff; +} + +QPushButton { + outline: 0; + show-decoration-selected: 1; + background-color: transparent; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 6px 10px; + margin: 4px 0px; + font-size: 13px; + font-weight: 500; + color: #000000; +} + +QPushButton:hover { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +QPushButton:focus { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +QPushButton:pressed { + background-color: rgba(26, 115, 232, 0.15); + border: 1px solid #1a73e8; +} + +QPushButton:disabled { + background-color: rgba(182, 182, 182, 0.5); + border: 1px solid rgba(100, 100, 100, 0.3); + color: rgba(255, 255, 255, 0.3); +} + +QComboBox { + background-color: transparent; + combobox-popup: 0; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 6px 10px; + margin: 4px 0px; + font-size: 13px; + font-weight: 500; + color: #000000; +} + +QComboBox:on { + background-color: rgba(26, 115, 232, 0.15); + border: 1px solid #1a73e8; +} + +QComboBox:hover { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +QComboBox:focus { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +QComboBox::drop-down { + border: 0; + width: 24px; +} + +QComboBox::down-arrow { + image: url("{IMAGE_PREFIX}/down_arrow_black.svg"); +} + +QComboBox QAbstractItemView { + background-color: #ffffff; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 4px; + outline: none; +} + +QComboBox QAbstractItemView::item { + height: 25px; + border-radius: 4px; + padding: 4px 8px; + margin: 2px 0px; + color: #000000; +} + +QComboBox QAbstractItemView::item:hover { + background-color: #1a73e8; + color: #ffffff; +} + +QScrollBar:vertical { + border: 0; + border-radius: 8px; + padding: 2px 0 2px 0; + background-color: #f1f1f1; +} + +QScrollBar::handle:vertical { + background-color: #e3e3e3; + margin: 0 2px 0 2px; + width: 10px; + border: 1px solid rgb(163, 163, 163); + border-radius: 4px; +} + +QScrollBar::handle:vertical:hover { + background-color: rgba(26, 115, 232, 0.4); +} + +QScrollBar::add-line:vertical { + height: 0; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical { + height: 0; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar:horizontal { + border: 0; + border-radius: 8px; + padding: 2px 0 2px 0; + background-color: #f1f1f1; +} + +QScrollBar::handle:horizontal { + background-color: #e3e3e3; + border: 1px solid rgb(163, 163, 163); + margin: 0px 2px 0px 2px; + border: 1px solid rgb(163, 163, 163); + border-radius: 4px; +} + +QScrollBar::handle:horizontal:hover { + background-color: rgba(26, 115, 232, 0.4); +} + +QScrollBar::add-line:horizontal { + height: 0; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal { + height: 0; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QMessageBox QLabel { + color: #000000; +} + +WrapCheckboxWidget { + show-decoration-selected: 1; + outline: 0; + background-color: transparent; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 12px 10px; + font-size: 13px; + font-weight: 500; + color: #000000; +} + +WrapCheckboxWidget:hover { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +WrapCheckboxWidget:disabled { + background-color: rgba(182, 182, 182, 0.5); + border: 1px solid rgba(100, 100, 100, 0.3); + color: rgba(255, 255, 255, 0.3); +} + +WrapCheckboxWidget QLabel:disabled { + color: #919191; +} + +WrapCheckboxWidget QCheckBox::indicator { + width: 16px; + height: 16px; + border-radius: 4px; + border: 1px solid #5f6368; +} + +WrapCheckboxWidget QCheckBox::indicator:checked { + background-color: #1a73e8; + border: 1px solid #1a73e8; + image: url("{IMAGE_PREFIX}/check.svg"); +} + +WrapCheckboxWidget QCheckBox::indicator:hover { + border-color: #1a73e8; +} + +WrapCheckboxWidget QCheckBox:checked { + color: #1a73e8; +} From 1af938decdec01a7370531452ae75157b82f5669 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 20 May 2025 23:07:24 -0400 Subject: [PATCH 06/10] Improved light theme based on testing on Linux/Windows --- NUSGet.py | 13 +++--- qt/py/ui_AboutDialog.py | 22 --------- resources/information_black.svg | 46 +++++++++++++++++++ ...{information.svg => information_white.svg} | 0 resources/style_dark.qss | 27 +++++++++++ resources/style_light.qss | 40 +++++++++++++--- 6 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 resources/information_black.svg rename resources/{information.svg => information_white.svg} (100%) diff --git a/NUSGet.py b/NUSGet.py index 06e7170..afed5e8 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -130,8 +130,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.ui.menuHelp.setWindowFlags(self.ui.menuHelp.windowFlags() | Qt.FramelessWindowHint) self.ui.menuHelp.setWindowFlags(self.ui.menuHelp.windowFlags() | Qt.NoDropShadowWindowHint) self.ui.menuHelp.setAttribute(Qt.WA_TranslucentBackground) - # Load the custom information icon. - icon = QIcon(os.path.join(os.path.dirname(__file__), "resources", "information.svg")) + # Save some light/dark theme values for later, including the appropriately colored info icon. + if is_dark_theme(): + bg_color = "#2b2b2b" + icon = QIcon(os.path.join(os.path.dirname(__file__), "resources", "information_white.svg")) + else: + bg_color = "#e3e3e3" + icon = QIcon(os.path.join(os.path.dirname(__file__), "resources", "information_black.svg")) self.ui.actionAbout.setIcon(icon) self.ui.actionAbout_Qt.setIcon(icon) # Title tree loading code. Now powered by Models:tm: @@ -155,10 +160,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.trees[tree].collapsed.connect(lambda: self.resize_tree(self.ui.platform_tabs.currentIndex())) # This stylesheet patch allows me to add the correct padding above the scrollbar so that it doesn't overlap # the QTreeView's header. - if is_dark_theme(): - bg_color = "#2b2b2b" - else: - bg_color = "#e3e3e3" self.trees[tree].setStyleSheet(self.trees[tree].styleSheet() + f""" QTreeView QScrollBar::sub-line:vertical {{ border: 0; diff --git a/qt/py/ui_AboutDialog.py b/qt/py/ui_AboutDialog.py index 8b1bb01..679213b 100644 --- a/qt/py/ui_AboutDialog.py +++ b/qt/py/ui_AboutDialog.py @@ -17,28 +17,6 @@ class AboutNUSGet(QDialog): self.setFixedWidth(450) self.setFixedHeight(500) - # Set background color to match main app - self.setStyleSheet(""" - QLabel[class="title"] { - font-size: 20px; - font-weight: bold; - } - QLabel[class="version"] { - font-size: 13px; - color: #aaaaaa; - } - QLabel[class="copyright"] { - font-size: 12px; - color: #888888; - } - QLabel[class="header"] { - font-size: 14px; - font-weight: bold; - border-bottom: 1px solid #444444; - padding-bottom: 4px; - margin-top: 8px; - }""") - # Create main layout self.layout = QVBoxLayout() self.layout.setSpacing(4) diff --git a/resources/information_black.svg b/resources/information_black.svg new file mode 100644 index 0000000..4a3b2ef --- /dev/null +++ b/resources/information_black.svg @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/resources/information.svg b/resources/information_white.svg similarity index 100% rename from resources/information.svg rename to resources/information_white.svg diff --git a/resources/style_dark.qss b/resources/style_dark.qss index 897ed36..da8c99f 100644 --- a/resources/style_dark.qss +++ b/resources/style_dark.qss @@ -10,6 +10,33 @@ QMainWindow QLabel { color: #ffffff; } +QDialog QLabel { + color: #ffffff; +} + +QDialog QLabel[class="title"] { + font-size: 20px; + font-weight: bold; +} + +QDialog QLabel[class="version"] { + font-size: 13px; + color: #aaaaaa; +} + +QDialog QLabel[class="copyright"] { + font-size: 12px; + color: #888888; +} + +QDialog QLabel[class="header"] { + font-size: 14px; + font-weight: bold; + border-bottom: 1px solid #444444; + padding-bottom: 4px; + margin-top: 8px; +} + QMenuBar { background-color: #2b2b2b; } diff --git a/resources/style_light.qss b/resources/style_light.qss index d333062..a1dcaf5 100644 --- a/resources/style_light.qss +++ b/resources/style_light.qss @@ -10,18 +10,46 @@ QMainWindow QLabel { color: #000000; } +QDialog QLabel { + color: #000000; +} + +QDialog QLabel[class="title"] { + font-size: 20px; + font-weight: bold; +} + +QDialog QLabel[class="version"] { + font-size: 13px; + color: #777777; +} + +QDialog QLabel[class="copyright"] { + font-size: 12px; + color: #444444; +} + +QDialog QLabel[class="header"] { + font-size: 14px; + font-weight: bold; + border-bottom: 1px solid #111111; + padding-bottom: 4px; + margin-top: 8px; +} + QMenuBar { - background-color: #ffffff; + background-color: #e3e3e3; + color: #000000; } QMenuBar::item:selected { - background-color: rgba(60, 60, 60, 1); - color: white; + background-color: rgb(195, 195, 195); + color: #000000; } QMenuBar::item:pressed { background-color: #1a73e8; - color: #000000; + color: #ffffff; } QMenu { @@ -42,7 +70,7 @@ QMenu::item { QMenu::item:selected { background-color: #1a73e8; - color: #000000; + color: #ffffff; } QMenu::icon { @@ -107,7 +135,7 @@ QLineEdit:focus { QLineEdit:disabled { background-color: rgba(182, 182, 182, 0.5); border: 1px solid rgba(100, 100, 100, 0.3); - color: rgba(255, 255, 255, 0.3); + color: rgba(143, 143, 143, 0.3); } QTabWidget::pane { From f29964be53499cd2601e4e7200dbe592e098927c Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 20 May 2025 23:16:01 -0400 Subject: [PATCH 07/10] Another quick style fix for light theme on Windows --- resources/style_light.qss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/style_light.qss b/resources/style_light.qss index a1dcaf5..bc20184 100644 --- a/resources/style_light.qss +++ b/resources/style_light.qss @@ -251,7 +251,7 @@ QPushButton:pressed { QPushButton:disabled { background-color: rgba(182, 182, 182, 0.5); border: 1px solid rgba(100, 100, 100, 0.3); - color: rgba(255, 255, 255, 0.3); + color: rgba(143, 143, 143, 0.3); } QComboBox { @@ -401,7 +401,7 @@ WrapCheckboxWidget:disabled { } WrapCheckboxWidget QLabel:disabled { - color: #919191; + color: rgba(143, 143, 143, 0.3); } WrapCheckboxWidget QCheckBox::indicator { From 4a08bd47cd7f27e21ba10e36105d99fadd410aaa Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Wed, 21 May 2025 12:53:32 -0400 Subject: [PATCH 08/10] Allow overriding the color scheme with the THEME environment variable --- NUSGet.py | 16 ++++++++++++---- modules/core.py | 5 +++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/NUSGet.py b/NUSGet.py index afed5e8..c3f5208 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -656,10 +656,18 @@ if __name__ == "__main__": # Load Fusion because that's objectively the best base theme, and then load the fancy stylesheet on top to make # NUSGet look nice and pretty. app.setStyle("fusion") - if is_dark_theme(): - stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", "style_dark.qss")).read() - else: - stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", "style_light.qss")).read() + theme_sheet = "style_dark.qss" + try: + # Check for an environment variable overriding the theme. This is mostly for theme testing but would also allow + # you to force a theme. + if os.environ["THEME"].lower() == "light": + theme_sheet = "style_light.qss" + except KeyError: + if is_dark_theme(): + theme_sheet = "style_dark.qss" + else: + theme_sheet = "style_light.qss" + stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", theme_sheet)).read() image_path_prefix = pathlib.Path(os.path.join(os.path.dirname(__file__), "resources")).resolve().as_posix() stylesheet = stylesheet.replace("{IMAGE_PREFIX}", image_path_prefix) app.setStyleSheet(stylesheet) diff --git a/modules/core.py b/modules/core.py index e579075..d149293 100644 --- a/modules/core.py +++ b/modules/core.py @@ -54,6 +54,7 @@ def connect_label_to_checkbox(label, checkbox): checkbox.toggle() label.mousePressEvent = toggle_checkbox + def connect_is_enabled_to_checkbox(items, chkbox): for item in items: if chkbox.isChecked(): @@ -61,6 +62,7 @@ def connect_is_enabled_to_checkbox(items, chkbox): else: item.setEnabled(False) + def check_nusget_updates(app, current_version: str, progress_callback=None) -> str | None: # Simple function to make a request to the GitHub API and then check if the latest available version is newer. gh_api_request = requests.get(url="https://api.github.com/repos/NinjaCheetah/NUSGet/releases/latest", stream=True) @@ -80,6 +82,7 @@ def check_nusget_updates(app, current_version: str, progress_callback=None) -> s progress_callback.emit(app.translate("MainWindow", "\n\nYou're running the latest release of NUSGet.")) return None + def get_config_file() -> pathlib.Path: config_dir = pathlib.Path(os.path.join( os.environ.get('APPDATA') or @@ -90,11 +93,13 @@ def get_config_file() -> pathlib.Path: config_dir.mkdir(exist_ok=True) return config_dir.joinpath("config.json") + def save_config(config_data: dict) -> None: config_file = get_config_file() print(f"writing data: {config_data}") open(config_file, "w").write(json.dumps(config_data)) + def update_setting(config_data: dict, setting: str, value: any) -> None: config_data[setting] = value save_config(config_data) From 811e2ef01fefd7261b11fd616c79c9533da50e12 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Sun, 25 May 2025 00:58:55 -0400 Subject: [PATCH 09/10] Added progress bar to show download progress --- NUSGet.py | 35 ++++++++++++++++++------------- modules/download_batch.py | 2 +- modules/download_dsi.py | 26 +++++++++++------------ modules/download_wii.py | 44 ++++++++++++++++++++------------------- modules/theme.py | 12 +++++++++++ qt/py/ui_MainMenu.py | 20 ++++++++++++------ qt/ui/MainMenu.ui | 25 +++++++++++++++++++--- requirements.txt | 4 ++-- resources/style_dark.qss | 18 ++++++++++++++++ resources/style_light.qss | 18 ++++++++++++++++ 10 files changed, 144 insertions(+), 60 deletions(-) diff --git a/NUSGet.py b/NUSGet.py index c3f5208..a1ab2cf 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -36,7 +36,7 @@ from modules.download_batch import run_nus_download_batch from modules.download_wii import run_nus_download_wii from modules.download_dsi import run_nus_download_dsi -nusget_version = "1.4.0" +nusget_version = "1.4.1" regions = {"World": ["41"], "USA/NTSC": ["45"], "Europe/PAL": ["50"], "Japan": ["4A"], "Korea": ["4B"], "China": ["43"], "Australia/NZ": ["55"]} @@ -45,7 +45,7 @@ regions = {"World": ["41"], "USA/NTSC": ["45"], "Europe/PAL": ["50"], "Japan": [ # Signals needed for the worker used for threading the downloads. class WorkerSignals(QObject): result = Signal(object) - progress = Signal(str) + progress = Signal(int, int, str) # Worker class used to thread the downloads. @@ -81,6 +81,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.ui.download_btn.clicked.connect(self.download_btn_pressed) self.ui.script_btn.clicked.connect(self.script_btn_pressed) self.ui.custom_out_dir_btn.clicked.connect(self.choose_output_dir) + self.ui.progress_bar.setRange(0, 0) # About and About Qt Buttons self.ui.actionAbout.triggered.connect(self.about_nusget) self.ui.actionAbout_Qt.triggered.connect(lambda: QMessageBox.aboutQt(self)) @@ -230,6 +231,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): return self.ui.patch_ios_checkbox.setEnabled(False) + def download_progress_update(self, done, total, log_text): + if done == 0 and total == 0: + self.ui.progress_bar.setRange(0, 0) + elif done == -1 and total == -1: + pass + else: + self.ui.progress_bar.setRange(0, total) + self.ui.progress_bar.setValue(done) + # Pass the text on to the log text updater, if it was provided. + if log_text: + self.update_log_text(log_text) + def update_log_text(self, new_text): # This method primarily exists to be the handler for the progress signal emitted by the worker thread. self.log_text += new_text + "\n" @@ -385,7 +398,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.ui.pack_vwii_mode_checkbox.isChecked(), self.ui.patch_ios_checkbox.isChecked(), self.ui.archive_file_entry.text()) worker.signals.result.connect(self.check_download_result) - worker.signals.progress.connect(self.update_log_text) + worker.signals.progress.connect(self.download_progress_update) self.threadpool.start(worker) def check_download_result(self, result): @@ -552,7 +565,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.ui.use_wiiu_nus_checkbox.isChecked(), self.ui.use_local_checkbox.isChecked(), self.ui.pack_vwii_mode_checkbox.isChecked(), self.ui.patch_ios_checkbox.isChecked()) worker.signals.result.connect(self.check_batch_result) - worker.signals.progress.connect(self.update_log_text) + worker.signals.progress.connect(self.download_progress_update) self.threadpool.start(worker) def choose_output_dir(self): @@ -657,16 +670,10 @@ if __name__ == "__main__": # NUSGet look nice and pretty. app.setStyle("fusion") theme_sheet = "style_dark.qss" - try: - # Check for an environment variable overriding the theme. This is mostly for theme testing but would also allow - # you to force a theme. - if os.environ["THEME"].lower() == "light": - theme_sheet = "style_light.qss" - except KeyError: - if is_dark_theme(): - theme_sheet = "style_dark.qss" - else: - theme_sheet = "style_light.qss" + if is_dark_theme(): + theme_sheet = "style_dark.qss" + else: + theme_sheet = "style_light.qss" stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", theme_sheet)).read() image_path_prefix = pathlib.Path(os.path.join(os.path.dirname(__file__), "resources")).resolve().as_posix() stylesheet = stylesheet.replace("{IMAGE_PREFIX}", image_path_prefix) diff --git a/modules/download_batch.py b/modules/download_batch.py index bc86208..e76ace4 100644 --- a/modules/download_batch.py +++ b/modules/download_batch.py @@ -53,5 +53,5 @@ def run_nus_download_batch(out_folder: pathlib.Path, titles: List[BatchTitleData # failed title. result = 1 failed_titles.append(title.tid) - progress_callback.emit(f"Batch download finished.") + progress_callback.emit(0, 1, f"Batch download finished.") return BatchResults(result, warning_titles, failed_titles) diff --git a/modules/download_dsi.py b/modules/download_dsi.py index 91054de..8c2bd66 100644 --- a/modules/download_dsi.py +++ b/modules/download_dsi.py @@ -29,10 +29,10 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_ title_dir.mkdir(exist_ok=True) # Announce the title being downloaded, and the version if applicable. if title_version is not None: - progress_callback.emit(f"Downloading title {tid} v{title_version}, please wait...") + progress_callback.emit(0, 0, f"Downloading title {tid} v{title_version}, please wait...") else: - progress_callback.emit(f"Downloading title {tid} vLatest, please wait...") - progress_callback.emit(" - Downloading and parsing TMD...") + progress_callback.emit(0, 0, f"Downloading title {tid} vLatest, please wait...") + progress_callback.emit(-1, -1, " - Downloading and parsing TMD...") # Download a specific TMD version if a version was specified, otherwise just download the latest TMD. try: if title_version is not None: @@ -50,17 +50,17 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_ version_dir.joinpath(f"tmd.{title_version}").write_bytes(title.tmd.dump()) # Use a local ticket, if one exists and "use local files" is enabled. if use_local_chkbox and version_dir.joinpath("tik").exists(): - progress_callback.emit(" - Parsing local copy of Ticket...") + progress_callback.emit(-1, -1, " - Parsing local copy of Ticket...") title.load_ticket(version_dir.joinpath("tik").read_bytes()) else: - progress_callback.emit(" - Downloading and parsing Ticket...") + progress_callback.emit(-1, -1, " - Downloading and parsing Ticket...") try: title.load_ticket(libTWLPy.download_ticket(tid)) version_dir.joinpath("tik").write_bytes(title.ticket.dump()) except ValueError: # If libTWLPy returns an error, then no ticket is available. Log this, and disable options requiring a # ticket so that they aren't attempted later. - progress_callback.emit(" - No Ticket is available!") + progress_callback.emit(-1, -1, " - No Ticket is available!") pack_tad_enabled = False decrypt_contents_enabled = False # Load the content record from the TMD, and download the content it lists. DSi titles only have one content. @@ -68,13 +68,13 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_ content_file_name = f"{title.tmd.content_record.content_id:08X}" # Check for a local copy of the current content if "use local files" is enabled, and use it. if use_local_chkbox and version_dir.joinpath(content_file_name).exists(): - progress_callback.emit(" - Using local copy of content") + progress_callback.emit(-1, -1, " - Using local copy of content") content = version_dir.joinpath(content_file_name).read_bytes() else: - progress_callback.emit(f" - Downloading content (Content ID: {title.tmd.content_record.content_id}, Size: " + progress_callback.emit(-1, -1, f" - Downloading content (Content ID: {title.tmd.content_record.content_id}, Size: " f"{title.tmd.content_record.content_size} bytes)...") content = libTWLPy.download_content(tid, title.tmd.content_record.content_id) - progress_callback.emit(" - Done!") + progress_callback.emit(-1, -1, " - Done!") # If keep encrypted contents is on, write out the content after its downloaded. if keep_enc_chkbox is True: version_dir.joinpath(content_file_name).write_bytes(content) @@ -82,7 +82,7 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_ # If decrypt local contents is still true, decrypt the content and write out the decrypted file. if decrypt_contents_enabled is True: try: - progress_callback.emit(f" - Decrypting content (Content ID: {title.tmd.content_record.content_id})...") + progress_callback.emit(-1, -1, f" - Decrypting content (Content ID: {title.tmd.content_record.content_id})...") dec_content = title.get_content() content_file_name = f"{title.tmd.content_record.content_id:08X}.app" version_dir.joinpath(content_file_name).write_bytes(dec_content) @@ -93,10 +93,10 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_ # If pack TAD is still true, pack the TMD, ticket, and content into a TAD. if pack_tad_enabled is True: # Get the TAD certificate chain, courtesy of libTWLPy. - progress_callback.emit(" - Building certificate...") + progress_callback.emit(-1, -1, " - Building certificate...") title.tad.set_cert_data(libTWLPy.download_cert()) # Use a typed TAD name if there is one, and auto generate one based on the TID and version if there isn't. - progress_callback.emit("Packing TAD...") + progress_callback.emit(-1, -1, "Packing TAD...") if tad_file_name != "" and tad_file_name is not None: # Batch downloads may insert -vLatest, so if it did we can fill in the real number now. tad_file_name = tad_file_name.replace("-vLatest", f"-v{title_version}") @@ -110,7 +110,7 @@ def run_nus_download_dsi(out_folder: pathlib.Path, tid: str, version: str, pack_ tad_file_name = tad_file_name.translate({ord(c): None for c in '/\\:*"?<>|'}) # Have libTWLPy dump the TAD, and write that data out. version_dir.joinpath(tad_file_name).write_bytes(title.dump_tad()) - progress_callback.emit("Download complete!") + progress_callback.emit(0, 1, "Download complete!") # This is where the variables come in. If the state of these variables doesn't match the user's choice by this # point, it means that they enabled decryption or TAD packing for a title that doesn't have a ticket. Return # code 1 so that a warning popup is shown informing them of this. diff --git a/modules/download_wii.py b/modules/download_wii.py index cf94ed5..e2d1be0 100644 --- a/modules/download_wii.py +++ b/modules/download_wii.py @@ -9,6 +9,8 @@ import libWiiPy def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_wad_chkbox: bool, keep_enc_chkbox: bool, decrypt_contents_chkbox: bool, wiiu_nus_chkbox: bool, use_local_chkbox: bool, repack_vwii_chkbox: bool, patch_ios: bool, wad_file_name: str, progress_callback=None): + def progress_update(done, total): + progress_callback.emit(done, total, None) # Actual NUS download function that runs in a separate thread. # Immediately knock out any invalidly formatted Title IDs. if len(tid) != 16: @@ -31,16 +33,16 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_ title_dir.mkdir(exist_ok=True) # Announce the title being downloaded, and the version if applicable. if title_version is not None: - progress_callback.emit(f"Downloading title {tid} v{title_version}, please wait...") + progress_callback.emit(0, 0, f"Downloading title {tid} v{title_version}, please wait...") else: - progress_callback.emit(f"Downloading title {tid} vLatest, please wait...") - progress_callback.emit(" - Downloading and parsing TMD...") + progress_callback.emit(-1, -1, f"Downloading title {tid} vLatest, please wait...") + progress_callback.emit(-1, -1, " - Downloading and parsing TMD...") # Download a specific TMD version if a version was specified, otherwise just download the latest TMD. try: if title_version is not None: - title.load_tmd(libWiiPy.title.download_tmd(tid, title_version, wiiu_endpoint=wiiu_nus_enabled)) + title.load_tmd(libWiiPy.title.download_tmd(tid, title_version, wiiu_endpoint=wiiu_nus_enabled, progress=progress_update)) else: - title.load_tmd(libWiiPy.title.download_tmd(tid, wiiu_endpoint=wiiu_nus_enabled)) + title.load_tmd(libWiiPy.title.download_tmd(tid, wiiu_endpoint=wiiu_nus_enabled, progress=progress_update)) title_version = title.tmd.title_version # If libWiiPy returns an error, that means that either the TID or version doesn't exist, so return code -2. except ValueError: @@ -52,17 +54,17 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_ version_dir.joinpath(f"tmd.{title_version}").write_bytes(title.tmd.dump()) # Use a local ticket, if one exists and "use local files" is enabled. if use_local_chkbox and version_dir.joinpath("tik").exists(): - progress_callback.emit(" - Parsing local copy of Ticket...") + progress_callback.emit(-1, -1, " - Parsing local copy of Ticket...") title.load_ticket(version_dir.joinpath("tik").read_bytes()) else: - progress_callback.emit(" - Downloading and parsing Ticket...") + progress_callback.emit(-1, -1, " - Downloading and parsing Ticket...") try: - title.load_ticket(libWiiPy.title.download_ticket(tid, wiiu_endpoint=wiiu_nus_enabled)) + title.load_ticket(libWiiPy.title.download_ticket(tid, wiiu_endpoint=wiiu_nus_enabled, progress=progress_update)) version_dir.joinpath("tik").write_bytes(title.ticket.dump()) except ValueError: # If libWiiPy returns an error, then no ticket is available. Log this, and disable options requiring a # ticket so that they aren't attempted later. - progress_callback.emit(" - No Ticket is available!") + progress_callback.emit(0, 0, " - No Ticket is available!") pack_wad_enabled = False decrypt_contents_enabled = False # Load the content records from the TMD, and begin iterating over the records. @@ -73,15 +75,15 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_ content_file_name = f"{title.tmd.content_records[content].content_id:08X}" # Check for a local copy of the current content if "use local files" is enabled, and use it. if use_local_chkbox is True and version_dir.joinpath(content_file_name).exists(): - progress_callback.emit(f" - Using local copy of content {content + 1} of {len(title.tmd.content_records)}") + progress_callback.emit(-1, -1, f" - Using local copy of content {content + 1} of {len(title.tmd.content_records)}") content_list.append(version_dir.joinpath(content_file_name).read_bytes()) else: - progress_callback.emit(f" - Downloading content {content + 1} of {len(title.tmd.content_records)} " + progress_callback.emit(0, 0, f" - Downloading content {content + 1} of {len(title.tmd.content_records)} " f"(Content ID: {title.tmd.content_records[content].content_id}, Size: " f"{title.tmd.content_records[content].content_size} bytes)...") content_list.append(libWiiPy.title.download_content(tid, title.tmd.content_records[content].content_id, - wiiu_endpoint=wiiu_nus_enabled)) - progress_callback.emit(" - Done!") + wiiu_endpoint=wiiu_nus_enabled, progress=progress_update)) + progress_callback.emit(-1, -1, " - Done!") # If keep encrypted contents is on, write out each content after its downloaded. if keep_enc_chkbox is True: version_dir.joinpath(content_file_name).write_bytes(content_list[content]) @@ -90,7 +92,7 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_ if decrypt_contents_enabled is True: try: for content in range(len(title.tmd.content_records)): - progress_callback.emit(f" - Decrypting content {content + 1} of {len(title.tmd.content_records)} " + progress_callback.emit(-1, -1, f" - Decrypting content {content + 1} of {len(title.tmd.content_records)} " f"(Content ID: {title.tmd.content_records[content].content_id})...") dec_content = title.get_content_by_index(content) content_file_name = f"{title.tmd.content_records[content].content_id:08X}.app" @@ -105,15 +107,15 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_ # re-encrypted with the common key instead of the vWii key, so that the title can be installed from within # vWii mode. (vWii mode does not have access to the vWii key, only Wii U mode has that.) if repack_vwii_chkbox is True and (tid[3] == "7" or tid[7] == "7"): - progress_callback.emit(" - Re-encrypting Title Key with the common key...") + progress_callback.emit(-1, -1, " - Re-encrypting Title Key with the common key...") title_key_common = libWiiPy.title.encrypt_title_key(title.ticket.get_title_key(), 0, title.tmd.title_id) title.ticket.common_key_index = 0 title.ticket.title_key_enc = title_key_common # Get the WAD certificate chain, courtesy of libWiiPy. - progress_callback.emit(" - Building certificate...") + progress_callback.emit(-1, -1, " - Building certificate...") title.load_cert_chain(libWiiPy.title.download_cert_chain(wiiu_endpoint=wiiu_nus_enabled)) # Use a typed WAD name if there is one, and auto generate one based on the TID and version if there isn't. - progress_callback.emit(" - Packing WAD...") + progress_callback.emit(-1, -1, " - Packing WAD...") if wad_file_name != "" and wad_file_name is not None: # Batch downloads may insert -vLatest, so if it did we can fill in the real number now. wad_file_name = wad_file_name.replace("-vLatest", f"-v{title_version}") @@ -123,14 +125,14 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_ wad_file_name = f"{tid}-v{title_version}.wad" # If enabled (after we make sure it's an IOS), apply all main IOS patches. if patch_ios and (tid[:8] == "00000001" and int(tid[-2:], 16) > 2): - progress_callback.emit(" - Patching IOS...") + progress_callback.emit(-1, -1, " - Patching IOS...") ios_patcher = libWiiPy.title.IOSPatcher() ios_patcher.load(title) patch_count = ios_patcher.patch_all() if patch_count > 0: - progress_callback.emit(f" - Applied {patch_count} patches!") + progress_callback.emit(-1, -1, f" - Applied {patch_count} patches!") else: - progress_callback.emit(" - No patches could be applied! Is this a stub IOS?") + progress_callback.emit(-1, -1, " - No patches could be applied! Is this a stub IOS?") title = ios_patcher.dump() # Append "-PATCHED" to the end of the WAD file name to make it clear that it was modified. wad_file_name = wad_file_name[:-4] + "-PATCHED" + wad_file_name[-4:] @@ -140,7 +142,7 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_ wad_file_name = wad_file_name.translate({ord(c): None for c in '/\\:*"?<>|'}) # Have libWiiPy dump the WAD, and write that data out. version_dir.joinpath(wad_file_name).write_bytes(title.dump_wad()) - progress_callback.emit("Download complete!") + progress_callback.emit(0, 1, "Download complete!") # This is where the variables come in. If the state of these variables doesn't match the user's choice by this # point, it means that they enabled decryption or WAD packing for a title that doesn't have a ticket. Return # code 1 so that a warning popup is shown informing them of this. diff --git a/modules/theme.py b/modules/theme.py index 1906b9a..b210fac 100644 --- a/modules/theme.py +++ b/modules/theme.py @@ -1,6 +1,7 @@ # "modules/theme.py", licensed under the MIT license # Copyright 2024-2025 NinjaCheetah & Contributors +import os import platform import subprocess @@ -43,6 +44,17 @@ def is_dark_theme_linux(): return False def is_dark_theme(): + # First, check for an environment variable overriding the theme, and use that if it exists. + try: + if os.environ["THEME"].lower() == "light": + return False + elif os.environ["THEME"].lower() == "dark": + return True + else: + print(f"Unknown theme specified: \"{os.environ['THEME']}\"") + except KeyError: + pass + # If the theme wasn't overridden, then check the current system theme. system = platform.system() if system == "Windows": return is_dark_theme_windows() diff --git a/qt/py/ui_MainMenu.py b/qt/py/ui_MainMenu.py index e2a4915..09a3bc8 100644 --- a/qt/py/ui_MainMenu.py +++ b/qt/py/ui_MainMenu.py @@ -18,9 +18,9 @@ from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient, QTransform) from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QHeaderView, QLabel, QLayout, QLineEdit, QMainWindow, - QMenu, QMenuBar, QPushButton, QSizePolicy, - QSpacerItem, QTabWidget, QTextBrowser, QTreeView, - QVBoxLayout, QWidget) + QMenu, QMenuBar, QProgressBar, QPushButton, + QSizePolicy, QSpacerItem, QTabWidget, QTextBrowser, + QTreeView, QVBoxLayout, QWidget) from qt.py.ui_WrapCheckboxWidget import WrapCheckboxWidget @@ -308,17 +308,25 @@ class Ui_MainWindow(object): self.log_text_browser = QTextBrowser(self.centralwidget) self.log_text_browser.setObjectName(u"log_text_browser") - self.log_text_browser.setMinimumSize(QSize(0, 247)) + self.log_text_browser.setMinimumSize(QSize(0, 222)) self.vertical_layout_controls.addWidget(self.log_text_browser) + self.progress_bar = QProgressBar(self.centralwidget) + self.progress_bar.setObjectName(u"progress_bar") + self.progress_bar.setMinimumSize(QSize(0, 25)) + self.progress_bar.setMaximumSize(QSize(16777215, 30)) + self.progress_bar.setValue(0) + + self.vertical_layout_controls.addWidget(self.progress_bar) + self.horizontalLayout_3.addLayout(self.vertical_layout_controls) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(MainWindow) self.menubar.setObjectName(u"menubar") - self.menubar.setGeometry(QRect(0, 0, 1010, 21)) + self.menubar.setGeometry(QRect(0, 0, 1010, 30)) self.menuHelp = QMenu(self.menubar) self.menuHelp.setObjectName(u"menuHelp") MainWindow.setMenuBar(self.menubar) @@ -367,7 +375,7 @@ class Ui_MainWindow(object): "hr { height: 1px; border-width: 0; }\n" "li.unchecked::marker { content: \"\\2610\"; }\n" "li.checked::marker { content: \"\\2612\"; }\n" -"\n" +"\n" "


", None)) self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) # retranslateUi diff --git a/qt/ui/MainMenu.ui b/qt/ui/MainMenu.ui index bf613a5..40bef00 100755 --- a/qt/ui/MainMenu.ui +++ b/qt/ui/MainMenu.ui @@ -422,7 +422,7 @@ 0 - 247 + 222 @@ -435,11 +435,30 @@ p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } -</style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> + + + + + 0 + 25 + + + + + 16777215 + 30 + + + + 0 + + + @@ -450,7 +469,7 @@ li.checked::marker { content: "\2612"; } 0 0 1010 - 21 + 30 diff --git a/requirements.txt b/requirements.txt index 2066228..3446304 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ pyside6 nuitka~=2.6.0 -libWiiPy +git+https://github.com/NinjaCheetah/libWiiPy libTWLPy zstandard requests -imageio \ No newline at end of file +imageio diff --git a/resources/style_dark.qss b/resources/style_dark.qss index da8c99f..f2bc95f 100644 --- a/resources/style_dark.qss +++ b/resources/style_dark.qss @@ -369,6 +369,24 @@ QMessageBox QLabel { color: white; } +QProgressBar { + border: 1px solid rgba(70, 70, 70, 1); + border-radius: 8px; + background-color: #1a1a1a; + text-align: center; + color: white; + padding-left: 1px; +} + +QProgressBar::chunk { + background-color: qlineargradient( + x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0 #1a73e8, stop: 1 #5596f4 + ); + border-radius: 5px; + margin: 0.5px; +} + WrapCheckboxWidget { show-decoration-selected: 1; outline: 0; diff --git a/resources/style_light.qss b/resources/style_light.qss index bc20184..b13d52e 100644 --- a/resources/style_light.qss +++ b/resources/style_light.qss @@ -377,6 +377,24 @@ QMessageBox QLabel { color: #000000; } +QProgressBar { + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + background-color: #ececec; + text-align: center; + padding: 1px; + color: black; +} + +QProgressBar::chunk { + background-color: qlineargradient( + x1: 0, y1: 0, x2: 1, y2: 0, + stop: 0 #1a73e8, stop: 1 #5596f4 + ); + border-radius: 5px; + margin: 0.5px; +} + WrapCheckboxWidget { show-decoration-selected: 1; outline: 0; From e8d6a19d034e727097599bd69f853a5261316be2 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Sun, 25 May 2025 01:02:57 -0400 Subject: [PATCH 10/10] Disable debug line that was setting progress bar to busy on launch --- NUSGet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/NUSGet.py b/NUSGet.py index a1ab2cf..f8620a4 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -81,7 +81,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.ui.download_btn.clicked.connect(self.download_btn_pressed) self.ui.script_btn.clicked.connect(self.script_btn_pressed) self.ui.custom_out_dir_btn.clicked.connect(self.choose_output_dir) - self.ui.progress_bar.setRange(0, 0) # About and About Qt Buttons self.ui.actionAbout.triggered.connect(self.about_nusget) self.ui.actionAbout_Qt.triggered.connect(lambda: QMessageBox.aboutQt(self))