Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e002aa9ec2 | ||
|
|
0aaf460dbf | ||
|
|
76c8b806d0 | ||
|
|
61783bc530 | ||
|
|
a3a2a04991 | ||
|
|
b6304fca75 | ||
|
|
5612ae5691 | ||
|
|
657c160599 | ||
|
|
f66494aeb5 | ||
|
|
650e7812e5 | ||
|
|
cca54340a2 | ||
|
|
8161b52e7b | ||
|
|
9390bd3865 | ||
|
|
483be133a5 | ||
|
|
e2022eac4c | ||
|
|
977331c3b2 | ||
|
|
64a40ae672 |
@@ -4,6 +4,8 @@ A homebrew menu for the switch.
|
||||
|
||||
[See the gbatemp thread for more details / discussion](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/).
|
||||
|
||||
[We have now have a Discord server!](https://discord.gg/8vZBsrprEc). Please use the issues tab to report bugs, as it is much easier for me to track.
|
||||
|
||||
## showcase
|
||||
|
||||
| | |
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"Details": "Details",
|
||||
"Update": "Update",
|
||||
"Remove": "Entfernen",
|
||||
"Restore": "",
|
||||
"Restore": "Wiederherstellen",
|
||||
"Download": "Download",
|
||||
"Next Page": "Nächste Seite",
|
||||
"Prev Page": "Vorherige Seite",
|
||||
@@ -27,9 +27,12 @@
|
||||
"Star": "Favorit",
|
||||
"System memory": "System-Speicher",
|
||||
"microSD card": "microSD-Karte",
|
||||
"Sd": "",
|
||||
"Image System memory": "",
|
||||
"Image microSD card": "",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "System-Speicher Bild",
|
||||
"Image microSD card": "microSD-Karten Bild",
|
||||
"Slow": "Langsam",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Schnell",
|
||||
"Yes": "Ja",
|
||||
"No": "Nein",
|
||||
"Enabled": "Aktiviert",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "Alphabetisch (Favoriten)",
|
||||
"Likes": "Likes",
|
||||
"ID": "ID",
|
||||
"Decending": "Absteigend",
|
||||
"Descending": "Absteigend",
|
||||
"Descending (down)": "Absteigend",
|
||||
"Desc": "Abst.",
|
||||
"Ascending": "Aufsteigend",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "hbmenu beim Beenden ersetzen",
|
||||
"Misc": "Sonstiges",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "Forwarder installieren",
|
||||
"Install location": "Installationsort",
|
||||
"Show install warning": "Installationswarnung anzeigen",
|
||||
"Text scroll speed": "Textlaufgeschwindigkeit",
|
||||
|
||||
"FileBrowser": "Datei-Browser",
|
||||
"%zd files": "%zd Dateien",
|
||||
@@ -117,8 +122,8 @@
|
||||
"Create Folder": "Ordner erstellen",
|
||||
"Set Folder Name": "Ordnernamen eingeben",
|
||||
"View as text (unfinished)": "Als Text anzeigen (Beta)",
|
||||
"Ignore read only": "",
|
||||
"Mount": "",
|
||||
"Ignore read only": "Schreibschutz ignorieren",
|
||||
"Mount": "Einbinden",
|
||||
"Empty...": "Leer...",
|
||||
"Open with DayBreak?": "Mit DayBreak öffnen?",
|
||||
"Launch ": "Starten ",
|
||||
@@ -206,9 +211,9 @@
|
||||
"Bad Page": "Ungültige Seite",
|
||||
"Download theme?": "Theme herunterladen?",
|
||||
|
||||
"GitHub": "",
|
||||
"Downloading json": "",
|
||||
"Select asset to download for ": "",
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Lade JSON herunter",
|
||||
"Select asset to download for ": "Wähle Asset zum Download für ",
|
||||
|
||||
"Installing ": "Installiere ",
|
||||
"Uninstalling ": "Deinstalliere ",
|
||||
@@ -222,8 +227,8 @@
|
||||
"Copying ": "Kopiere ",
|
||||
"Trying to load ": "Lade ",
|
||||
"Downloading ": "Lade herunter ",
|
||||
"Downloaded ": "",
|
||||
"Removed ": "",
|
||||
"Downloaded ": "Heruntergeladen ",
|
||||
"Removed ": "Entfernt ",
|
||||
"Checking MD5": "Prüfe MD5",
|
||||
"Loading...": "Lade...",
|
||||
"Loading": "Lade",
|
||||
@@ -233,14 +238,15 @@
|
||||
"Update avaliable: ": "Update verfügbar: ",
|
||||
"Download update: ": "Update herunterladen: ",
|
||||
"Updated to ": "Aktualisiert auf ",
|
||||
"Press OK to restart Sphaira": "OK drücken um Sphaira neuzustarten",
|
||||
"Restart Sphaira?": "Sphaira neustarten?",
|
||||
"Failed to download update": "Update-Download fehlgeschlagen",
|
||||
"Restore hbmenu?": "",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "",
|
||||
"Failed to restore hbmenu, using sphaira instead": "",
|
||||
"Restored hbmenu, closing sphaira": "",
|
||||
"Restored hbmenu": "",
|
||||
"Restore hbmenu?": "hbmenu wiederherstellen?",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Konnte /switch/hbmenu.nro nicht finden\nBitte hbmenu über den AppStore neu installieren",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Wiederherstellung fehlgeschlagen, bitte hbmenu neu herunterladen",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Wiederherstellung fehlgeschlagen, verwende stattdessen Sphaira",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu wiederhergestellt, Sphaira wird beendet",
|
||||
"Restored hbmenu": "hbmenu wiederhergestellt",
|
||||
"Delete Selected files?": "Ausgewählte Dateien löschen?",
|
||||
"Completely remove ": "Vollständig entfernen ",
|
||||
"Are you sure you want to delete ": "Wirklich löschen ",
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Image System memory",
|
||||
"Image microSD card": "Image microSD card",
|
||||
"Slow": "Slow",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Fast",
|
||||
"Yes": "Yes",
|
||||
"No": "No",
|
||||
"Enabled": "Enabled",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "Alphabetical (Star)",
|
||||
"Likes": "Likes",
|
||||
"ID": "ID",
|
||||
"Decending": "Decending",
|
||||
"Descending": "Descending",
|
||||
"Descending (down)": "Descending (down)",
|
||||
"Desc": "Desc",
|
||||
"Ascending": "Ascending",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "Replace hbmenu on exit",
|
||||
"Misc": "Misc",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "Install forwarders",
|
||||
"Install location": "Install location",
|
||||
"Show install warning": "Show install warning",
|
||||
"Text scroll speed": "Text scroll speed",
|
||||
|
||||
"FileBrowser": "FileBrowser",
|
||||
"%zd files": "%zd files",
|
||||
@@ -233,6 +238,7 @@
|
||||
"Update avaliable: ": "Update avaliable: ",
|
||||
"Download update: ": "Download update: ",
|
||||
"Updated to ": "Updated to ",
|
||||
"Press OK to restart Sphaira": "Press OK to restart Sphaira",
|
||||
"Restart Sphaira?": "Restart Sphaira?",
|
||||
"Failed to download update": "Failed to download update",
|
||||
"Restore hbmenu?": "Restore hbmenu?",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"Details": "Detalles",
|
||||
"Update": "Actualizar",
|
||||
"Remove": "Borrar",
|
||||
"Restore": "",
|
||||
"Restore": "Restaurar",
|
||||
"Download": "Descargar",
|
||||
"Next Page": "Página siguiente",
|
||||
"Prev Page": "Página anterior",
|
||||
@@ -27,9 +27,12 @@
|
||||
"Star": "Favorito",
|
||||
"System memory": "Memoria de sistema",
|
||||
"microSD card": "microSD",
|
||||
"Sd": "",
|
||||
"Image System memory": "",
|
||||
"Image microSD card": "",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Imagen memoria interna",
|
||||
"Image microSD card": "Imagen tarjeta microSD",
|
||||
"Slow": "Lento",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rápido",
|
||||
"Yes": "Sí",
|
||||
"No": "No",
|
||||
"Enabled": "Activado",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "Alfabético (favorito)",
|
||||
"Likes": "Me Gusta",
|
||||
"ID": "ID",
|
||||
"Decending": "Descendente",
|
||||
"Descending": "Descendente",
|
||||
"Descending (down)": "Descendente (abajo)",
|
||||
"Desc": "Descendente",
|
||||
"Ascending": "Ascendente",
|
||||
@@ -87,18 +90,20 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Logging": "Registros",
|
||||
"Replace hbmenu on exit": "Reemplazar hbmenu al salir",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Registro",
|
||||
"Replace hbmenu on exit": "Reemplazar hbmenu",
|
||||
"Misc": "Varios",
|
||||
"Misc Options": "Opciones varias",
|
||||
"Web": "Web",
|
||||
"Install forwarders": "Instalar forwarders",
|
||||
"Install location": "Ruta de instalación ",
|
||||
"Show install warning": "Mostrar precaución de instalación",
|
||||
"Install location": "Dispositivo de instalación",
|
||||
"Show install warning": "Precaución de instalación",
|
||||
"Text scroll speed": "Velocidad de scroll",
|
||||
|
||||
"FileBrowser": "Explorador de archivos",
|
||||
"%zd files": "%zd files",
|
||||
"%zd dirs": "%zd dirs",
|
||||
"%zd files": "%zd archivos",
|
||||
"%zd dirs": "%zd carpetas",
|
||||
"File Options": "Opciones de archivo",
|
||||
"Show Hidden": "Mostrar archivos ocultos",
|
||||
"Folders First": "Carpetas primero",
|
||||
@@ -117,15 +122,15 @@
|
||||
"Create Folder": "Crear carpeta",
|
||||
"Set Folder Name": "Establecer nombre de carpeta",
|
||||
"View as text (unfinished)": "Ver como texto (sin terminar)",
|
||||
"Ignore read only": "",
|
||||
"Mount": "",
|
||||
"Ignore read only": "Ignorar sólo lectura",
|
||||
"Mount": "Montar",
|
||||
"Empty...": "Vacío...",
|
||||
"Open with DayBreak?": "¿Abrir con DayBreak?",
|
||||
"Launch ": "Abrir ",
|
||||
"Launch option for: ": "Opción de abrir con: ",
|
||||
"Select launcher for: ": "Seleccionar abrir con: ",
|
||||
|
||||
"Homebrew": "Honebrew",
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Opciones de Homebrew",
|
||||
"Hide Sphaira": "Ocultar Sphaira",
|
||||
"Install Forwarder": "Instalar Forwarder",
|
||||
@@ -162,16 +167,16 @@
|
||||
"Irs": "IRS",
|
||||
"Ambient Noise Level: ": "Nivel de Ruido Ambiente",
|
||||
"Controller": "Control",
|
||||
"Pad ": "Almohadilla ",
|
||||
"Pad ": "GamePad ",
|
||||
" (Available)": " (Disponible)",
|
||||
" (Unsupported)": "(No Compatible)",
|
||||
" (Unconnected)": " (Desconectado)",
|
||||
"HandHeld": "Portátil",
|
||||
"Rotation": "Rotación",
|
||||
"0 (Sideways)": "0 (De lado)",
|
||||
"90 (Flat)": "90 (Plano)",
|
||||
"180 (-Sideways)": "180 (-De lado)",
|
||||
"270 (Upside down)": "270 (Al revés)",
|
||||
"0 (Sideways)": "0° (De lado)",
|
||||
"90 (Flat)": "90° (Plano)",
|
||||
"180 (-Sideways)": "180° (De lado)",
|
||||
"270 (Upside down)": "270° (Al revés)",
|
||||
"Colour": "Color",
|
||||
"Grey": "Gris",
|
||||
"Ironbow": "Paleta térmica",
|
||||
@@ -206,9 +211,9 @@
|
||||
"Bad Page": "Página Errónea",
|
||||
"Download theme?": "¿Descargar Tema?",
|
||||
|
||||
"GitHub": "",
|
||||
"Downloading json": "",
|
||||
"Select asset to download for ": "",
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Descargando json",
|
||||
"Select asset to download for ": "Seleccionar recurso a descargar para ",
|
||||
|
||||
"Installing ": "Instalando ",
|
||||
"Uninstalling ": "Desinstalando ",
|
||||
@@ -220,10 +225,10 @@
|
||||
"Scanning ": "Escaneando ",
|
||||
"Creating ": "Creando ",
|
||||
"Copying ": "Copiando ",
|
||||
"Trying to load ": "Intentando cargar",
|
||||
"Trying to load ": "Intentando cargar ",
|
||||
"Downloading ": "Descargando ",
|
||||
"Downloaded ": "",
|
||||
"Removed ": "",
|
||||
"Downloaded ": "Descargado ",
|
||||
"Removed ": "Removido ",
|
||||
"Checking MD5": "Chequeando MD5",
|
||||
"Loading...": "Cargando...",
|
||||
"Loading": "Cargando",
|
||||
@@ -233,17 +238,18 @@
|
||||
"Update avaliable: ": "Actualización disponible: ",
|
||||
"Download update: ": "Descargar actualización: ",
|
||||
"Updated to ": "Actualizado a ",
|
||||
"Restart Sphaira?": "¿Reiniciar Sphaira?",
|
||||
"Press OK to restart Sphaira": "Presiona OK para reiniciar sphaira",
|
||||
"Restart Sphaira?": "¿Reiniciar sphaira?",
|
||||
"Failed to download update": "Fallo al descargar actualización",
|
||||
"Restore hbmenu?": "",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "",
|
||||
"Failed to restore hbmenu, using sphaira instead": "",
|
||||
"Restored hbmenu, closing sphaira": "",
|
||||
"Restored hbmenu": "",
|
||||
"Restore hbmenu?": "¿Restaurar hbmenu?",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Fallo al encontrar /switch/hbmenu.nro\nUsar la Tienda para reinstalar hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Fallo al restaurar hbmenu, por favor volver a descargar hbmenu",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Fallo al restaurar hbmenu, se usará sphaira",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu restaurado, cerrando sphaira",
|
||||
"Restored hbmenu": "hbmenu restaurado",
|
||||
"Delete Selected files?": "¿Eliminar archivos seleccionados?",
|
||||
"Completely remove ": "Eliminar completamente",
|
||||
"Are you sure you want to delete ": "¿Estás seguro que quieres eliminar? ",
|
||||
"Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?",
|
||||
"If this message appears repeatedly, please open an issue.": "Si este mensaje aparece repetidamente, por favor abrir un 'issue'."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Image de la mémoire System",
|
||||
"Image microSD card": "Image de la Carte microSD",
|
||||
"Slow": "Lent",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rapide",
|
||||
"Yes": "Oui",
|
||||
"No": "Non",
|
||||
"Enabled": "Activé(e)",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "Alphabétique (Favories)",
|
||||
"Likes": "Likes",
|
||||
"ID": "ID",
|
||||
"Decending": "Décroissant",
|
||||
"Descending": "Décroissant",
|
||||
"Descending (down)": "Décroissant",
|
||||
"Desc": "Décroissant",
|
||||
"Ascending": "Croissant",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Journalisation",
|
||||
"Replace hbmenu on exit": "Remplacer hbmenu quand quitté",
|
||||
"Misc": "Divers",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "Installer les Forwarders",
|
||||
"Install location": "Emplacement d'installation",
|
||||
"Show install warning": "Afficher l'avertissement d'installation",
|
||||
"Text scroll speed": "Vitesse de défilement du texte",
|
||||
|
||||
"FileBrowser": "Explorateur de Fichiers",
|
||||
"%zd files": "%zd fichiers",
|
||||
@@ -233,6 +238,7 @@
|
||||
"Update avaliable: ": "Mise à jour disponible: ",
|
||||
"Download update: ": "Télécharger la mise à jour: ",
|
||||
"Updated to ": "Mis à jour vers ",
|
||||
"Press OK to restart Sphaira": "Appuyez sur OK pour redémarrer Sphaira",
|
||||
"Restart Sphaira?": "Redémarrer Sphaira?",
|
||||
"Failed to download update": "Echec du téléchargement de la mise à jour",
|
||||
"Restore hbmenu?": "Restaurer hbmenu?",
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Immagine memoria di sistema",
|
||||
"Image microSD card": "Immagine scheda microSD",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Sì",
|
||||
"No": "No",
|
||||
"Enabled": "Abilitato",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "Alfabetico (Preferiti)",
|
||||
"Likes": "Mi Piace",
|
||||
"ID": "ID",
|
||||
"Decending": "Decrescente",
|
||||
"Descending": "Decrescente",
|
||||
"Descending (down)": "Decrescente",
|
||||
"Desc": "Decrescente",
|
||||
"Ascending": "Crescente",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "Sostituisci hbmenu all'uscita",
|
||||
"Misc": "Varie",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "Installa forwarder",
|
||||
"Install location": "Installa posizione",
|
||||
"Show install warning": "Mostra avvertimento installazione",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "FileBrowser",
|
||||
"%zd files": "%zd files",
|
||||
@@ -233,6 +238,7 @@
|
||||
"Update avaliable: ": "",
|
||||
"Download update: ": "",
|
||||
"Updated to ": "",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "",
|
||||
"Failed to download update": "",
|
||||
"Restore hbmenu?": "",
|
||||
@@ -246,4 +252,4 @@
|
||||
"Are you sure you want to delete ": "Sei sicuro di voler eliminare? ",
|
||||
"Are you sure you wish to cancel?": "",
|
||||
"If this message appears repeatedly, please open an issue.": ""
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "SDメモリーカード",
|
||||
"Image System memory": "システムメモリイメージ",
|
||||
"Image microSD card": "SDイメージ",
|
||||
"Slow": "遅い",
|
||||
"Normal": "普通",
|
||||
"Fast": "速い",
|
||||
"Yes": "はい",
|
||||
"No": "いいえ",
|
||||
"Enabled": "",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "アルファベット順(お気に入り)",
|
||||
"Likes": "いいね順",
|
||||
"ID": "デベロッパー順",
|
||||
"Decending": "降順",
|
||||
"Descending": "降順",
|
||||
"Descending (down)": "降順",
|
||||
"Desc": "降順",
|
||||
"Ascending": "上昇",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "ログの取得",
|
||||
"Replace hbmenu on exit": "終了時に hbmenu を置き換える",
|
||||
"Misc": "その他",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "Forwarderのインストール機能",
|
||||
"Install location": "インストール経路",
|
||||
"Show install warning": "警告文を示す",
|
||||
"Text scroll speed": "流れる文字の速さ",
|
||||
|
||||
"FileBrowser": "ファイルブラウザ",
|
||||
"%zd files": "%zd個のファイル",
|
||||
@@ -233,6 +238,7 @@
|
||||
"Update avaliable: ": "アップデート可能: ",
|
||||
"Download update: ": "アップデートをダウンロード: ",
|
||||
"Updated to ": "アップデート: ",
|
||||
"Press OK to restart Sphaira": "確認ボタンを押してSphairaを再起動",
|
||||
"Restart Sphaira?": "Sphairaを再起動しますか?",
|
||||
"Failed to download update": "アップデートのダウンロード失敗",
|
||||
"Restore hbmenu?": "hbmenuに戻しますか?",
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "SD 카드",
|
||||
"Image System memory": "낸드 이미지",
|
||||
"Image microSD card": "SD 이미지",
|
||||
"Slow": "느림",
|
||||
"Normal": "보통",
|
||||
"Fast": "빠름",
|
||||
"Yes": "예",
|
||||
"No": "아니요",
|
||||
"Enabled": "",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "알파벳순 (즐겨찾기)",
|
||||
"Likes": "좋아요순",
|
||||
"ID": "ID순",
|
||||
"Decending": "내림차순",
|
||||
"Descending": "내림차순",
|
||||
"Descending (down)": "내림차순",
|
||||
"Desc": "내림차순",
|
||||
"Ascending": "오름차순",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "로깅",
|
||||
"Replace hbmenu on exit": "종료 시 hbmenu 교체",
|
||||
"Misc": "기타",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "바로가기 설치",
|
||||
"Install location": "설치 위치",
|
||||
"Show install warning": "경고 메시지",
|
||||
"Text scroll speed": "흐르는 텍스트 속도",
|
||||
|
||||
"FileBrowser": "파일 탐색기",
|
||||
"%zd files": "%zd 개 파일",
|
||||
@@ -233,6 +238,7 @@
|
||||
"Update avaliable: ": "업데이트 가능: ",
|
||||
"Download update: ": "업데이트 다운로드: ",
|
||||
"Updated to ": "업데이트: ",
|
||||
"Press OK to restart Sphaira": "확인 버튼 입력하여 Sphaira 재시작",
|
||||
"Restart Sphaira?": "Sphaira를 재시작할까요?",
|
||||
"Failed to download update": "업데이트 다운로드 실패함",
|
||||
"Restore hbmenu?": "hbmenu로 교체할까요?",
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "",
|
||||
"Image System memory": "",
|
||||
"Image microSD card": "",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Ja",
|
||||
"No": "Nee",
|
||||
"Enabled": "Ingeschakeld",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "",
|
||||
"Likes": "",
|
||||
"ID": "",
|
||||
"Decending": "Aflopend",
|
||||
"Descending": "Aflopend",
|
||||
"Descending (down)": "Aflopend",
|
||||
"Desc": "Aflopend",
|
||||
"Ascending": "Oplopend",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Loggen",
|
||||
"Replace hbmenu on exit": "Vervang hbmenu bij afsluiten",
|
||||
"Misc": "Diversen",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "",
|
||||
"Install location": "",
|
||||
"Show install warning": "",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "Bestandsbrowser",
|
||||
"%zd files": "%zd files",
|
||||
@@ -233,6 +238,7 @@
|
||||
"Update avaliable: ": "",
|
||||
"Download update: ": "",
|
||||
"Updated to ": "",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "",
|
||||
"Failed to download update": "",
|
||||
"Restore hbmenu?": "",
|
||||
|
||||
@@ -28,40 +28,43 @@
|
||||
"System memory": "Memória do console",
|
||||
"microSD card": "Cartão microSD",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Imagem (Memória do console)",
|
||||
"Image microSD card": "Imagem (Cartão microSD)",
|
||||
"Image System memory": "Imagem (memória do console)",
|
||||
"Image microSD card": "Imagem (cartão microSD)",
|
||||
"Slow": "Lenta",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rápida",
|
||||
"Yes": "Sim",
|
||||
"No": "Não",
|
||||
"Enabled": "Habilitado",
|
||||
"Disabled": "Desabilitado",
|
||||
"Enabled": "Sim",
|
||||
"Disabled": "Não",
|
||||
|
||||
"Sort By": "Ordenar por",
|
||||
"Sort Options": "Opções de classificação",
|
||||
"Sort By": "Ordernar/Organizar",
|
||||
"Sort Options": "Ordernar/Organizar",
|
||||
"Filter": "Filtro",
|
||||
"Sort": "Organizar",
|
||||
"Sort": "Organizar por",
|
||||
"Order": "Ordem",
|
||||
"Search": "Procurar",
|
||||
"Search": "Buscar",
|
||||
"Updated": "Atualizado",
|
||||
"Updated (Star)": "Atualizado (Favoritos)",
|
||||
"Downloads": "Downloads",
|
||||
"Updated (Star)": "Atualizado (favoritos)",
|
||||
"Downloads": "Nº de downloads",
|
||||
"Size": "Tamanho",
|
||||
"Size (Star)": "Tamanho (Favoritos)",
|
||||
"Alphabetical": "Alfabético",
|
||||
"Alphabetical (Star)": "Alfabético (Favoritos)",
|
||||
"Likes": "Curtidas",
|
||||
"Size (Star)": "Tamanho (favoritos)",
|
||||
"Alphabetical": "Ordem alfabética",
|
||||
"Alphabetical (Star)": "Ordem alfabética (favoritos)",
|
||||
"Likes": "Nº de curtidas",
|
||||
"ID": "ID",
|
||||
"Decending": "Decrescente",
|
||||
"Descending (down)": "Decrescente (Baixo)",
|
||||
"Descending": "Decrescente",
|
||||
"Descending (down)": "Decrescente (baixo)",
|
||||
"Desc": "Decr.",
|
||||
"Ascending": "Ascendente",
|
||||
"Ascending (Up)": "Ascendente (Cima)",
|
||||
"Ascending (Up)": "Ascendente (cima)",
|
||||
"Asc": "Asc.",
|
||||
|
||||
"Menu Options": "Opções do menu",
|
||||
"Theme": "Tema",
|
||||
"Theme Options": "Opções de tema",
|
||||
"Select Theme": "Selecionar tema",
|
||||
"Shuffle": "Embaralhar",
|
||||
"Select Theme": "Tema atual",
|
||||
"Shuffle": "Embaralhar temas",
|
||||
"Music": "Música",
|
||||
"Network": "Rede",
|
||||
"Network Options": "Opções de rede",
|
||||
@@ -87,47 +90,49 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Registro de depuração",
|
||||
"Replace hbmenu on exit": "Substituir hbmenu ao sair",
|
||||
"Misc": "Diversos",
|
||||
"Misc Options": "Opções diversas",
|
||||
"Web": "Navegador web",
|
||||
"Install forwarders": "Instalar forwarder",
|
||||
"Web": "Navegador de internet",
|
||||
"Install forwarders": "Instalar forwarders",
|
||||
"Install location": "Local de instalação",
|
||||
"Show install warning": "Mostrar aviso de instalação",
|
||||
"Text scroll speed": "Rolagem do texto",
|
||||
|
||||
"FileBrowser": "Navegador de arquivos",
|
||||
"FileBrowser": "Arquivos",
|
||||
"%zd files": "%zd arquivo(s)",
|
||||
"%zd dirs": "%zd diretório(s)",
|
||||
"File Options": "Opções de arquivo",
|
||||
"Show Hidden": "Mostrar ocultos",
|
||||
"Folders First": "Pastas primeiro",
|
||||
"Hidden Last": "Ocultos por último",
|
||||
"Cut": "Cortar",
|
||||
"Cut": "Recortar",
|
||||
"Copy": "Copiar",
|
||||
"Paste": "Colar",
|
||||
"Paste ": "Colar",
|
||||
"Paste ": "Colar ",
|
||||
" file(s)?": " arquivo(s)?",
|
||||
"Rename": "Renomear",
|
||||
"Set New File Name": "Definir novo nome do arquivo",
|
||||
"Set New File Name": "Defina o nome do novo arquivo",
|
||||
"Advanced": "Avançado",
|
||||
"Advanced Options": "Opções avançadas",
|
||||
"Create File": "Criar arquivo",
|
||||
"Set File Name": "Definir nome do arquivo",
|
||||
"Set File Name": "Defina o nome do arquivo",
|
||||
"Create Folder": "Criar pasta",
|
||||
"Set Folder Name": "Definir novo nome da pasta",
|
||||
"Set Folder Name": "Defina o nome da pasta",
|
||||
"View as text (unfinished)": "Ver como texto (inacabado)",
|
||||
"Ignore read only": "Ignorar somente leitura",
|
||||
"Mount": "Montar",
|
||||
"Empty...": "Vazio...",
|
||||
"Open with DayBreak?": "Abrir com DayBreak?",
|
||||
"Launch ": "Iniciar",
|
||||
"Launch ": "Iniciar ",
|
||||
"Launch option for: ": "Opções de inicialização para: ",
|
||||
"Select launcher for: ": "Selecionar launcher para: ",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Opções do Homebrew",
|
||||
"Hide Sphaira": "Esconder Sphaira",
|
||||
"Homebrew": "Aplicativos",
|
||||
"Homebrew Options": "Opções do aplicativo",
|
||||
"Hide Sphaira": "Esconder sphaira",
|
||||
"Install Forwarder": "Instalar forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "AVISO: Instalar forwarders pode\nresultar em um banimento!",
|
||||
"Installing Forwarder": "Instalando forwarder",
|
||||
@@ -142,9 +147,9 @@
|
||||
"Unstarred ": "Desfavoritado ",
|
||||
"Starred ": "Favoritado ",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Organizar: %s | Ordem: %s",
|
||||
"AppStore Options": "Opções da AppStore",
|
||||
"AppStore": "Loja",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Por: %s | Ordem: %s",
|
||||
"AppStore Options": "Opções da loja",
|
||||
"All": "Todos",
|
||||
"Games": "Jogos",
|
||||
"Emulators": "Emuladores",
|
||||
@@ -156,29 +161,29 @@
|
||||
"category: %s": "categoria: %s",
|
||||
"extracted: %.2f MiB": "tam. extraído: %.2f MiB",
|
||||
"app_dls: %s": "downloads: %s",
|
||||
"More by Author": "Mais do autor",
|
||||
"More by Author": "Mais deste autor",
|
||||
"Leave Feedback": "Deixar um feedback",
|
||||
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "Nível de ruído ambiente",
|
||||
"Irs": "IRS",
|
||||
"Ambient Noise Level: ": "Nível de ruído ambiente: ",
|
||||
"Controller": "Controle",
|
||||
"Pad ": "Pad ",
|
||||
" (Available)": " (Disponível)",
|
||||
" (Unsupported)": "(Não suportado)",
|
||||
" (Unconnected)": " (Desconectado)",
|
||||
" (Available)": " (disponível)",
|
||||
" (Unsupported)": "(não suportado)",
|
||||
" (Unconnected)": " (desconectado)",
|
||||
"HandHeld": "Portátil",
|
||||
"Rotation": "Rotação",
|
||||
"0 (Sideways)": "0 (Lateralmente)",
|
||||
"0 (Sideways)": "0 (lateralmente)",
|
||||
"90 (Flat)": "90 (plano)",
|
||||
"180 (-Sideways)": "180 (-Lateralmente)",
|
||||
"270 (Upside down)": "270 (De cabeça para baixo)",
|
||||
"180 (-Sideways)": "180 (-lateralmente)",
|
||||
"270 (Upside down)": "270 (de cabeça para baixo)",
|
||||
"Colour": "Cor",
|
||||
"Grey": "Cinza",
|
||||
"Ironbow": "Arco de ferro",
|
||||
"Green": "Verde",
|
||||
"Red": "Vermelho",
|
||||
"Blue": "Azul",
|
||||
"Light Target": "Alvo leve",
|
||||
"Light Target": "Alvo de luz",
|
||||
"All leds": "Todos os LEDs",
|
||||
"Bright group": "Grupo claro",
|
||||
"Dim group": "Grupo escuro",
|
||||
@@ -199,10 +204,10 @@
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Opções do Themezer",
|
||||
"Nsfw": "NSFW",
|
||||
"Page": "Página",
|
||||
"Page %zu / %zu": "Page %zu / %zu",
|
||||
"Enter Page Number": "Digite o número da página",
|
||||
"Nsfw": "Temas 18+ (NSFW)",
|
||||
"Page": "Ir para página",
|
||||
"Page %zu / %zu": "Página %zu / %zu",
|
||||
"Enter Page Number": "Número da página",
|
||||
"Bad Page": "Página inválida",
|
||||
"Download theme?": "Baixar tema?",
|
||||
|
||||
@@ -212,8 +217,8 @@
|
||||
|
||||
"Installing ": "Instalando ",
|
||||
"Uninstalling ": "Desinstalando ",
|
||||
"Deleting ": "Deletando ",
|
||||
"Deleting": "Deletando ",
|
||||
"Deleting ": "Excluindo ",
|
||||
"Deleting": "Excluindo",
|
||||
"Pasting ": "Colando ",
|
||||
"Pasting": "Colando ",
|
||||
"Removing ": "Removendo ",
|
||||
@@ -222,18 +227,19 @@
|
||||
"Copying ": "Copiando ",
|
||||
"Trying to load ": "Tentando carregar ",
|
||||
"Downloading ": "Baixando ",
|
||||
"Downloaded ": "Baixado",
|
||||
"Removed ": "Removido",
|
||||
"Downloaded ": "Baixado ",
|
||||
"Removed ": "Removido ",
|
||||
"Checking MD5": "Checando MD5",
|
||||
"Loading...": "Carregando...",
|
||||
"Loading": "Carregando",
|
||||
"Empty!": "Vazio!",
|
||||
"Empty!": "Vazio",
|
||||
"Not Ready...": "Não está pronto...",
|
||||
"Error loading page!": "Erro ao carregar página!",
|
||||
"Update avaliable: ": "Atualização disponível: ",
|
||||
"Download update: ": "Baixar autalização: ",
|
||||
"Updated to ": "Atualizado para ",
|
||||
"Restart Sphaira?": "Reiniciar Sphaira?",
|
||||
"Press OK to restart Sphaira": "Selecione OK para reiniciar o sphaira",
|
||||
"Restart Sphaira?": "Reiniciar sphaira?",
|
||||
"Failed to download update": "Falha ao baixar a atualização",
|
||||
"Restore hbmenu?": "Restaurar hbmenu?",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Falha ao buscar /switch/hbmenu.nro\nUse a AppStore para reinstalar o hbmenu",
|
||||
@@ -241,9 +247,9 @@
|
||||
"Failed to restore hbmenu, using sphaira instead": "Falha ao restaurar hbmenu, usando sphaira",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu restaurado, fechando sphaira",
|
||||
"Restored hbmenu": "hbmenu restaurado",
|
||||
"Delete Selected files?": "Deletar arquivos selecionados?",
|
||||
"Delete Selected files?": "Excluir os arquivos selecionados?",
|
||||
"Completely remove ": "Remover completamente ",
|
||||
"Are you sure you want to delete ": "Você tem certeza que quer deletar ",
|
||||
"Are you sure you want to delete ": "Você tem certeza que quer excluir ",
|
||||
"Are you sure you wish to cancel?": "Você tem certeza que quer cancelar?",
|
||||
"If this message appears repeatedly, please open an issue.": "Se esta mensagem aparecer repetidamente, abra um issue."
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "",
|
||||
"Image System memory": "",
|
||||
"Image microSD card": "",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Да",
|
||||
"No": "Нет",
|
||||
"Enabled": "Включено",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "",
|
||||
"Likes": "",
|
||||
"ID": "",
|
||||
"Decending": "По убыванию",
|
||||
"Descending": "По убыванию",
|
||||
"Descending (down)": "По убыванию",
|
||||
"Desc": "По убыванию",
|
||||
"Ascending": "По возрастанию",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Журналирование",
|
||||
"Replace hbmenu on exit": "Заменить hbmenu при выходе",
|
||||
"Misc": "Прочее",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "",
|
||||
"Install location": "",
|
||||
"Show install warning": "",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "Файловый менеджер",
|
||||
"%zd files": "%zd files",
|
||||
@@ -233,6 +238,7 @@
|
||||
"Update avaliable: ": "",
|
||||
"Download update: ": "",
|
||||
"Updated to ": "",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "",
|
||||
"Failed to download update": "",
|
||||
"Restore hbmenu?": "",
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Avbild Systemminne",
|
||||
"Image microSD card": "Avbild microSD-kort",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Ja",
|
||||
"No": "Nej",
|
||||
"Enabled": "Aktiverad",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "Alfabetisk (Stjärna)",
|
||||
"Likes": "Gillar",
|
||||
"ID": "ID",
|
||||
"Decending": "Fallande",
|
||||
"Descending": "Fallande",
|
||||
"Descending (down)": "Fallande (nedåt)",
|
||||
"Desc": "Fall",
|
||||
"Ascending": "Stigande",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Portugisiska",
|
||||
"Russian": "Ryska",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Loggning",
|
||||
"Replace hbmenu on exit": "Ersätt hbmenu vid avslut",
|
||||
"Misc": "Övrigt",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "Installera genvägar",
|
||||
"Install location": "Installationsplats",
|
||||
"Show install warning": "Visa installationsvarning",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "Filbläddrare",
|
||||
"%zd files": "%zd filer",
|
||||
@@ -233,6 +238,7 @@
|
||||
"Update avaliable: ": "Uppdatering tillgänglig: ",
|
||||
"Download update: ": "Ladda ner uppdatering: ",
|
||||
"Updated to ": "Uppdaterad till ",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "Starta om Sphaira?",
|
||||
"Failed to download update": "Misslyckades att ladda ner uppdatering",
|
||||
"Restore hbmenu?": "Återställ hbmenu?",
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Bộ nhớ hệ thống hình ảnh",
|
||||
"Image microSD card": "Thẻ nhớ hệ thống hình ảnh",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Có",
|
||||
"No": "Không",
|
||||
"Enabled": "Bật",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "A-Z (Yêu thích)",
|
||||
"Likes": "Thích",
|
||||
"ID": "ID",
|
||||
"Decending": "Giảm dần",
|
||||
"Descending": "Giảm dần",
|
||||
"Descending (down)": "Giảm dần (xuống)",
|
||||
"Desc": "Giảm",
|
||||
"Ascending": "Tăng dần",
|
||||
@@ -76,7 +79,6 @@
|
||||
"Language": "Ngôn ngữ",
|
||||
"Auto": "Tự động",
|
||||
"English": "English",
|
||||
"Vietnamese": "Việt Nam",
|
||||
"Japanese": "日本語",
|
||||
"French": "Français",
|
||||
"German": "Deutsch",
|
||||
@@ -88,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Việt Nam",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "Thay thế hbmenu khi thoát",
|
||||
"Misc": "Tiện ích",
|
||||
@@ -96,6 +99,7 @@
|
||||
"Install forwarders": "Cài ra màn hình",
|
||||
"Install location": "Vị trí cài đặt",
|
||||
"Show install warning": "Hiển thị cảnh báo cài đặt",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "Duyệt tập tin",
|
||||
"%zd files": "%zd tập tin",
|
||||
@@ -234,6 +238,7 @@
|
||||
"Update avaliable: ": "Cập nhậc có sẵn: ",
|
||||
"Download update: ": "Tải cập nhật: ",
|
||||
"Updated to ": "Đã cập nhật ",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "Khởi động lại Sphaira?",
|
||||
"Failed to download update": "Cập nhật thất bại",
|
||||
"Restore hbmenu?": "Khôi phục hbmenu?",
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
"Sd": "SD卡",
|
||||
"Image System memory": "主机内存图像",
|
||||
"Image microSD card": "SD卡图像",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "是",
|
||||
"No": "否",
|
||||
"Enabled": "启用",
|
||||
@@ -50,7 +53,7 @@
|
||||
"Alphabetical (Star)": "按字母顺序(星标优先)",
|
||||
"Likes": "点赞量",
|
||||
"ID": "ID",
|
||||
"Decending": "降序",
|
||||
"Descending": "降序",
|
||||
"Descending (down)": "降序",
|
||||
"Desc": "降序",
|
||||
"Ascending": "升序",
|
||||
@@ -87,6 +90,7 @@
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "日志",
|
||||
"Replace hbmenu on exit": "退出后用Sphaira替换hbmenu",
|
||||
"Misc": "拓展",
|
||||
@@ -95,6 +99,7 @@
|
||||
"Install forwarders": "允许安装前端应用",
|
||||
"Install location": "安装位置",
|
||||
"Show install warning": "显示安装警告",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "文件浏览",
|
||||
"%zd files": "%zd 个文件",
|
||||
@@ -233,6 +238,7 @@
|
||||
"Update avaliable: ": "有可用更新!",
|
||||
"Download update: ": "下载更新:",
|
||||
"Updated to ": "更新至 ",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "重启 Sphaira?",
|
||||
"Failed to download update": "更新下载失败",
|
||||
"Restore hbmenu?": "恢复 hbmenu?",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(sphaira_VERSION 0.6.0)
|
||||
set(sphaira_VERSION 0.6.2)
|
||||
|
||||
project(sphaira
|
||||
VERSION ${sphaira_VERSION}
|
||||
@@ -81,12 +81,54 @@ target_compile_definitions(sphaira PRIVATE
|
||||
-DAPP_VERSION_HASH="${sphaira_VERSION_HASH}"
|
||||
)
|
||||
|
||||
target_compile_options(sphaira PRIVATE
|
||||
-Wall
|
||||
-Wextra
|
||||
|
||||
# unsure if it's a good idea to enable these by default as
|
||||
# it may cause breakage upon compiler updates.
|
||||
# -Werror
|
||||
# -Wfatal-errors
|
||||
|
||||
# disabled as nx uses s64 for size and offset, however stl uses size_t instead, thus
|
||||
# there being a lot of warnings.
|
||||
-Wno-sign-compare
|
||||
# disabled as many overriden methods don't use the params.
|
||||
-Wno-unused-parameter
|
||||
# pedantic warning, missing fields are set to 0.
|
||||
-Wno-missing-field-initializers
|
||||
# disabled as it warns for strcat 2 paths together, but it will never
|
||||
# overflow due to fs enforcing a max path len anyway.
|
||||
-Wno-format-truncation
|
||||
|
||||
# the below are taken from my gba emulator, they've served me well ;)
|
||||
-Wformat-overflow=2
|
||||
-Wundef
|
||||
-Wmissing-include-dirs
|
||||
-fstrict-aliasing
|
||||
-Wstrict-overflow=2
|
||||
-Walloca
|
||||
-Wduplicated-cond
|
||||
-Wwrite-strings
|
||||
-Wdate-time
|
||||
-Wlogical-op
|
||||
-Wpacked
|
||||
-Wcast-qual
|
||||
-Wcast-align
|
||||
-Wimplicit-fallthrough=5
|
||||
-Wsuggest-final-types
|
||||
-Wuninitialized
|
||||
-fimplicit-constexpr
|
||||
-Wmissing-requires
|
||||
)
|
||||
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_QUIET FALSE)
|
||||
|
||||
FetchContent_Declare(ftpsrv
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
|
||||
GIT_TAG 1.2.1
|
||||
GIT_TAG 1.2.2
|
||||
SOURCE_SUBDIR NONE
|
||||
)
|
||||
|
||||
FetchContent_Declare(libhaze
|
||||
@@ -101,7 +143,7 @@ FetchContent_Declare(libpulsar
|
||||
|
||||
FetchContent_Declare(nanovg
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/nanovg-deko3d.git
|
||||
GIT_TAG 1902b38
|
||||
GIT_TAG 845c9fc
|
||||
)
|
||||
|
||||
FetchContent_Declare(stb
|
||||
@@ -136,8 +178,6 @@ set(NANOVG_NO_GIF ON)
|
||||
set(NANOVG_NO_HDR ON)
|
||||
set(NANOVG_NO_PIC ON)
|
||||
set(NANOVG_NO_PNM ON)
|
||||
set(NANOVG_STBI_STATIC OFF)
|
||||
set(NANOVG_STBTT_STATIC ON)
|
||||
|
||||
set(YYJSON_DISABLE_READER OFF)
|
||||
set(YYJSON_DISABLE_WRITER OFF)
|
||||
@@ -148,7 +188,7 @@ set(YYJSON_DISABLE_UTF8_VALIDATION ON)
|
||||
set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF)
|
||||
|
||||
FetchContent_MakeAvailable(
|
||||
# ftpsrv
|
||||
ftpsrv
|
||||
libhaze
|
||||
libpulsar
|
||||
nanovg
|
||||
@@ -157,11 +197,6 @@ FetchContent_MakeAvailable(
|
||||
yyjson
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(ftpsrv)
|
||||
if (NOT ftpsrv_POPULATED)
|
||||
FetchContent_Populate(ftpsrv)
|
||||
endif()
|
||||
|
||||
set(FTPSRV_LIB_BUILD TRUE)
|
||||
set(FTPSRV_LIB_SOCK_UNISTD TRUE)
|
||||
set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
|
||||
@@ -178,6 +213,7 @@ set(FTPSRV_LIB_CUSTOM_DEFINES
|
||||
USE_VFS_SAVE=$<BOOL:TRUE>
|
||||
USE_VFS_STORAGE=$<BOOL:TRUE>
|
||||
USE_VFS_GC=$<BOOL:${USE_VFS_GC}>
|
||||
USE_VFS_USBHSFS=$<BOOL:FALSE>
|
||||
VFS_NX_BUFFER_IO=$<BOOL:TRUE>
|
||||
)
|
||||
|
||||
|
||||
@@ -119,6 +119,21 @@ public:
|
||||
return type == AppletType_Application || type == AppletType_SystemApplication;
|
||||
}
|
||||
|
||||
static auto IsApplet() -> bool {
|
||||
return !IsApplication();
|
||||
}
|
||||
|
||||
// returns true if launched in applet mode with a title suspended in the background.
|
||||
static auto IsAppletWithSuspendedApp() -> bool {
|
||||
R_UNLESS(IsApplet(), false);
|
||||
R_TRY_RESULT(pmdmntInitialize(), false);
|
||||
ON_SCOPE_EXIT(pmdmntExit());
|
||||
|
||||
u64 pid;
|
||||
return R_SUCCEEDED(pmdmntGetApplicationProcessId(&pid));
|
||||
}
|
||||
|
||||
|
||||
// private:
|
||||
static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini";
|
||||
static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini";
|
||||
@@ -166,7 +181,6 @@ public:
|
||||
// todo: move this into it's own menu
|
||||
option::OptionLong m_text_scroll_speed{"accessibility", "text_scroll_speed", 1}; // normal
|
||||
|
||||
PLSR_BFSAR m_qlaunch_bfsar{};
|
||||
PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
|
||||
|
||||
private: // from nanovg decko3d example by adubbz
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <stop_token>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::curl {
|
||||
@@ -29,6 +30,7 @@ struct ApiResult;
|
||||
using Path = fs::FsPath;
|
||||
using OnComplete = std::function<void(ApiResult& result)>;
|
||||
using OnProgress = std::function<bool(u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow)>;
|
||||
using StopToken = std::stop_token;
|
||||
|
||||
struct Url {
|
||||
Url() = default;
|
||||
@@ -71,6 +73,7 @@ struct ApiResult {
|
||||
struct DownloadEventData {
|
||||
OnComplete callback;
|
||||
ApiResult result;
|
||||
StopToken stoken;
|
||||
};
|
||||
|
||||
auto Init() -> bool;
|
||||
@@ -114,6 +117,7 @@ struct Api {
|
||||
auto ToMemory(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
|
||||
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
|
||||
Api::set_option(std::forward<Ts>(ts)...);
|
||||
return curl::ToMemory(*this);
|
||||
}
|
||||
@@ -122,6 +126,7 @@ struct Api {
|
||||
auto ToFile(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
|
||||
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
|
||||
Api::set_option(std::forward<Ts>(ts)...);
|
||||
return curl::ToFile(*this);
|
||||
}
|
||||
@@ -131,6 +136,7 @@ struct Api {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
|
||||
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
|
||||
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
|
||||
Api::set_option(std::forward<Ts>(ts)...);
|
||||
return curl::ToMemoryAsync(*this);
|
||||
}
|
||||
@@ -140,18 +146,38 @@ struct Api {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
|
||||
Api::set_option(std::forward<Ts>(ts)...);
|
||||
return curl::ToFileAsync(*this);
|
||||
}
|
||||
|
||||
Url m_url;
|
||||
Fields m_fields{};
|
||||
Header m_header{};
|
||||
Flags m_flags{};
|
||||
Path m_path{};
|
||||
OnComplete m_on_complete = nullptr;
|
||||
OnProgress m_on_progress = nullptr;
|
||||
Priority m_prio = Priority::High;
|
||||
auto& GetUrl() const {
|
||||
return m_url.m_str;
|
||||
}
|
||||
auto& GetFields() const {
|
||||
return m_fields.m_str;
|
||||
}
|
||||
auto& GetHeader() const {
|
||||
return m_header;
|
||||
}
|
||||
auto& GetFlags() const {
|
||||
return m_flags.m_flags;
|
||||
}
|
||||
auto& GetPath() const {
|
||||
return m_path;
|
||||
}
|
||||
auto& GetOnComplete() const {
|
||||
return m_on_complete;
|
||||
}
|
||||
auto& GetOnProgress() const {
|
||||
return m_on_progress;
|
||||
}
|
||||
auto& GetPriority() const {
|
||||
return m_prio;
|
||||
}
|
||||
auto& GetToken() const {
|
||||
return m_stoken;
|
||||
}
|
||||
|
||||
private:
|
||||
void SetOption(Url&& v) {
|
||||
@@ -178,6 +204,9 @@ private:
|
||||
void SetOption(Priority&& v) {
|
||||
m_prio = v;
|
||||
}
|
||||
void SetOption(StopToken&& v) {
|
||||
m_stoken = v;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void set_option(T&& t) {
|
||||
@@ -189,6 +218,18 @@ private:
|
||||
set_option(std::forward<T>(t));
|
||||
set_option(std::forward<Ts>(ts)...);
|
||||
}
|
||||
|
||||
private:
|
||||
Url m_url;
|
||||
Fields m_fields{};
|
||||
Header m_header{};
|
||||
Flags m_flags{};
|
||||
Path m_path{};
|
||||
OnComplete m_on_complete{nullptr};
|
||||
OnProgress m_on_progress{nullptr};
|
||||
Priority m_prio{Priority::High};
|
||||
std::stop_source m_stop_source{};
|
||||
StopToken m_stoken{m_stop_source.get_token()};
|
||||
};
|
||||
|
||||
} // namespace sphaira::curl
|
||||
|
||||
@@ -13,10 +13,10 @@ public:
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
private:
|
||||
Result m_code;
|
||||
std::string m_message;
|
||||
std::string m_module_str;
|
||||
std::string m_description_str;
|
||||
Result m_code{};
|
||||
std::string m_message{};
|
||||
std::string m_module_str{};
|
||||
std::string m_description_str{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -43,8 +43,8 @@ private:
|
||||
const s64 m_row;
|
||||
const s64 m_page;
|
||||
|
||||
Vec4 m_v;
|
||||
Vec2 m_pad;
|
||||
Vec4 m_v{};
|
||||
Vec2 m_pad{};
|
||||
|
||||
Vec4 m_scrollbar{};
|
||||
|
||||
|
||||
@@ -42,26 +42,26 @@ enum class EntryStatus {
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
std::string category; // todo: lable
|
||||
std::string binary; // optional, only valid for .nro
|
||||
std::string updated; // date of update
|
||||
std::string name;
|
||||
std::string license; // optional
|
||||
std::string title; // same as name but with spaces
|
||||
std::string url; // url of repo (optional?)
|
||||
std::string description;
|
||||
std::string author;
|
||||
std::string changelog; // optional
|
||||
u64 screens; // number of screenshots
|
||||
u64 extracted; // extracted size in KiB
|
||||
std::string version;
|
||||
u64 filesize; // compressed size in KiB
|
||||
std::string details;
|
||||
u64 app_dls;
|
||||
std::string md5; // md5 of the zip
|
||||
std::string category{}; // todo: lable
|
||||
std::string binary{}; // optional, only valid for .nro
|
||||
std::string updated{}; // date of update
|
||||
std::string name{};
|
||||
std::string license{}; // optional
|
||||
std::string title{}; // same as name but with spaces
|
||||
std::string url{}; // url of repo (optional?)
|
||||
std::string description{};
|
||||
std::string author{};
|
||||
std::string changelog{}; // optional
|
||||
u64 screens{}; // number of screenshots
|
||||
u64 extracted{}; // extracted size in KiB
|
||||
std::string version{};
|
||||
u64 filesize{}; // compressed size in KiB
|
||||
std::string details{};
|
||||
u64 app_dls{};
|
||||
std::string md5{}; // md5 of the zip
|
||||
|
||||
LazyImage image;
|
||||
u32 updated_num;
|
||||
LazyImage image{};
|
||||
u32 updated_num{};
|
||||
EntryStatus status{EntryStatus::Get};
|
||||
};
|
||||
|
||||
@@ -99,13 +99,13 @@ private:
|
||||
Menu& m_menu;
|
||||
|
||||
s64 m_index{}; // where i am in the array
|
||||
std::vector<Option> m_options;
|
||||
LazyImage m_banner;
|
||||
std::unique_ptr<List> m_list;
|
||||
std::vector<Option> m_options{};
|
||||
LazyImage m_banner{};
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
std::shared_ptr<ScrollableText> m_details;
|
||||
std::shared_ptr<ScrollableText> m_changelog;
|
||||
std::shared_ptr<ScrollableText> m_detail_changelog;
|
||||
std::shared_ptr<ScrollableText> m_details{};
|
||||
std::shared_ptr<ScrollableText> m_changelog{};
|
||||
std::shared_ptr<ScrollableText> m_detail_changelog{};
|
||||
|
||||
bool m_show_changlog{};
|
||||
};
|
||||
@@ -130,38 +130,10 @@ enum SortType {
|
||||
};
|
||||
|
||||
enum OrderType {
|
||||
OrderType_Decending,
|
||||
OrderType_Descending,
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
struct FeedbackEntry {
|
||||
u32 id;
|
||||
u64 time;
|
||||
std::string package; // name of package
|
||||
std::string content; // the feedback message that was sent
|
||||
std::string reply; // the reply, "" if no reply yet :)
|
||||
};
|
||||
|
||||
struct FeedbackMenu final : MenuBase {
|
||||
FeedbackMenu(const std::vector<Entry>& package_entries, LazyImage& default_image);
|
||||
~FeedbackMenu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(s64 index);
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
|
||||
private:
|
||||
const std::vector<Entry>& m_package_entries;
|
||||
LazyImage& m_default_image;
|
||||
std::vector<FeedbackEntry> m_entries;
|
||||
s64 m_index{}; // where i am in the array
|
||||
ImageDownloadState m_repo_download_state{ImageDownloadState::None};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(const std::vector<NroEntry>& nro_entries);
|
||||
~Menu();
|
||||
@@ -191,27 +163,27 @@ struct Menu final : MenuBase {
|
||||
|
||||
private:
|
||||
const std::vector<NroEntry>& m_nro_entries;
|
||||
std::vector<Entry> m_entries;
|
||||
std::vector<EntryMini> m_entries_index[Filter_MAX];
|
||||
std::vector<EntryMini> m_entries_index_author;
|
||||
std::vector<EntryMini> m_entries_index_search;
|
||||
std::span<EntryMini> m_entries_current;
|
||||
std::vector<Entry> m_entries{};
|
||||
std::vector<EntryMini> m_entries_index[Filter_MAX]{};
|
||||
std::vector<EntryMini> m_entries_index_author{};
|
||||
std::vector<EntryMini> m_entries_index_search{};
|
||||
std::span<EntryMini> m_entries_current{};
|
||||
|
||||
Filter m_filter{Filter::Filter_All};
|
||||
SortType m_sort{SortType::SortType_Updated};
|
||||
OrderType m_order{OrderType::OrderType_Decending};
|
||||
OrderType m_order{OrderType::OrderType_Descending};
|
||||
|
||||
s64 m_index{}; // where i am in the array
|
||||
LazyImage m_default_image;
|
||||
LazyImage m_update;
|
||||
LazyImage m_get;
|
||||
LazyImage m_local;
|
||||
LazyImage m_installed;
|
||||
LazyImage m_default_image{};
|
||||
LazyImage m_update{};
|
||||
LazyImage m_get{};
|
||||
LazyImage m_local{};
|
||||
LazyImage m_installed{};
|
||||
ImageDownloadState m_repo_download_state{ImageDownloadState::None};
|
||||
std::unique_ptr<List> m_list;
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
std::string m_search_term;
|
||||
std::string m_author_term;
|
||||
std::string m_search_term{};
|
||||
std::string m_author_term{};
|
||||
s64 m_entry_search_jump_back{};
|
||||
s64 m_entry_author_jump_back{};
|
||||
bool m_is_search{};
|
||||
|
||||
@@ -16,12 +16,12 @@ struct Menu final : MenuBase {
|
||||
|
||||
private:
|
||||
const fs::FsPath m_path;
|
||||
fs::FsNativeSd m_fs;
|
||||
FsFile m_file;
|
||||
fs::FsNativeSd m_fs{};
|
||||
FsFile m_file{};
|
||||
s64 m_file_size{};
|
||||
s64 m_file_offset{};
|
||||
|
||||
std::unique_ptr<ScrollableText> m_scroll_text;
|
||||
std::unique_ptr<ScrollableText> m_scroll_text{};
|
||||
|
||||
s64 m_start{};
|
||||
s64 m_index{}; // where i am in the array
|
||||
|
||||
@@ -29,7 +29,7 @@ enum SortType {
|
||||
};
|
||||
|
||||
enum OrderType {
|
||||
OrderType_Decending,
|
||||
OrderType_Descending,
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
@@ -86,23 +86,23 @@ struct FileEntry : FsDirectoryEntry {
|
||||
|
||||
struct FileAssocEntry {
|
||||
fs::FsPath path{}; // ini name
|
||||
std::string name; // ini name
|
||||
std::vector<std::string> ext; // list of ext
|
||||
std::vector<std::string> database; // list of systems
|
||||
std::string name{}; // ini name
|
||||
std::vector<std::string> ext{}; // list of ext
|
||||
std::vector<std::string> database{}; // list of systems
|
||||
};
|
||||
|
||||
struct LastFile {
|
||||
fs::FsPath name;
|
||||
s64 index;
|
||||
float offset;
|
||||
s64 entries_count;
|
||||
fs::FsPath name{};
|
||||
s64 index{};
|
||||
float offset{};
|
||||
s64 entries_count{};
|
||||
};
|
||||
|
||||
struct FsDirCollection {
|
||||
fs::FsPath path;
|
||||
fs::FsPath parent_name;
|
||||
std::vector<FsDirectoryEntry> files;
|
||||
std::vector<FsDirectoryEntry> dirs;
|
||||
fs::FsPath path{};
|
||||
fs::FsPath parent_name{};
|
||||
std::vector<FsDirectoryEntry> files{};
|
||||
std::vector<FsDirectoryEntry> dirs{};
|
||||
};
|
||||
|
||||
using FsDirCollections = std::vector<FsDirCollection>;
|
||||
@@ -231,38 +231,38 @@ private:
|
||||
static constexpr inline const char* INI_SECTION = "filebrowser";
|
||||
|
||||
const std::vector<NroEntry>& m_nro_entries;
|
||||
std::unique_ptr<fs::FsNative> m_fs;
|
||||
FsType m_fs_type;
|
||||
fs::FsPath m_path;
|
||||
std::vector<FileEntry> m_entries;
|
||||
std::vector<u32> m_entries_index; // files not including hidden
|
||||
std::vector<u32> m_entries_index_hidden; // includes hidden files
|
||||
std::vector<u32> m_entries_index_search; // files found via search
|
||||
std::span<u32> m_entries_current;
|
||||
std::unique_ptr<fs::FsNative> m_fs{};
|
||||
FsType m_fs_type{};
|
||||
fs::FsPath m_path{};
|
||||
std::vector<FileEntry> m_entries{};
|
||||
std::vector<u32> m_entries_index{}; // files not including hidden
|
||||
std::vector<u32> m_entries_index_hidden{}; // includes hidden files
|
||||
std::vector<u32> m_entries_index_search{}; // files found via search
|
||||
std::span<u32> m_entries_current{};
|
||||
|
||||
std::unique_ptr<List> m_list;
|
||||
std::optional<fs::FsPath> m_daybreak_path;
|
||||
std::unique_ptr<List> m_list{};
|
||||
std::optional<fs::FsPath> m_daybreak_path{};
|
||||
|
||||
// search options
|
||||
// show files [X]
|
||||
// show folders [X]
|
||||
// recursive (slow) [ ]
|
||||
|
||||
std::vector<FileAssocEntry> m_assoc_entries;
|
||||
std::vector<FileEntry> m_selected_files;
|
||||
std::vector<FileAssocEntry> m_assoc_entries{};
|
||||
std::vector<FileEntry> m_selected_files{};
|
||||
|
||||
// this keeps track of the highlighted file before opening a folder
|
||||
// if the user presses B to go back to the previous dir
|
||||
// this vector is popped, then, that entry is checked if it still exists
|
||||
// if it does, the index becomes that file.
|
||||
std::vector<LastFile> m_previous_highlighted_file;
|
||||
fs::FsPath m_selected_path;
|
||||
std::vector<LastFile> m_previous_highlighted_file{};
|
||||
fs::FsPath m_selected_path{};
|
||||
s64 m_index{};
|
||||
s64 m_selected_count{};
|
||||
SelectedType m_selected_type{SelectedType::None};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||
option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false};
|
||||
option::OptionBool m_folders_first{INI_SECTION, "folders_first", true};
|
||||
option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false};
|
||||
|
||||
@@ -10,35 +10,35 @@
|
||||
namespace sphaira::ui::menu::gh {
|
||||
|
||||
struct AssetEntry {
|
||||
std::string name;
|
||||
std::string path;
|
||||
std::string pre_install_message;
|
||||
std::string post_install_message;
|
||||
std::string name{};
|
||||
std::string path{};
|
||||
std::string pre_install_message{};
|
||||
std::string post_install_message{};
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
fs::FsPath json_path;
|
||||
std::string url;
|
||||
std::string owner;
|
||||
std::string repo;
|
||||
std::string tag;
|
||||
std::string pre_install_message;
|
||||
std::string post_install_message;
|
||||
std::vector<AssetEntry> assets;
|
||||
fs::FsPath json_path{};
|
||||
std::string url{};
|
||||
std::string owner{};
|
||||
std::string repo{};
|
||||
std::string tag{};
|
||||
std::string pre_install_message{};
|
||||
std::string post_install_message{};
|
||||
std::vector<AssetEntry> assets{};
|
||||
};
|
||||
|
||||
struct GhApiAsset {
|
||||
std::string name;
|
||||
std::string content_type;
|
||||
u64 size;
|
||||
u64 download_count;
|
||||
std::string browser_download_url;
|
||||
std::string name{};
|
||||
std::string content_type{};
|
||||
u64 size{};
|
||||
u64 download_count{};
|
||||
std::string browser_download_url{};
|
||||
};
|
||||
|
||||
struct GhApiEntry {
|
||||
std::string tag_name;
|
||||
std::string name;
|
||||
std::vector<GhApiAsset> assets;
|
||||
std::string tag_name{};
|
||||
std::string name{};
|
||||
std::vector<GhApiAsset> assets{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
@@ -66,9 +66,9 @@ private:
|
||||
void UpdateSubheading();
|
||||
|
||||
private:
|
||||
std::vector<Entry> m_entries;
|
||||
std::vector<Entry> m_entries{};
|
||||
s64 m_index{};
|
||||
std::unique_ptr<List> m_list;
|
||||
std::unique_ptr<List> m_list{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::gh
|
||||
|
||||
@@ -18,7 +18,7 @@ enum SortType {
|
||||
};
|
||||
|
||||
enum OrderType {
|
||||
OrderType_Decending,
|
||||
OrderType_Descending,
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
@@ -50,12 +50,12 @@ struct Menu final : MenuBase {
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "homebrew";
|
||||
|
||||
std::vector<NroEntry> m_entries;
|
||||
std::vector<NroEntry> m_entries{};
|
||||
s64 m_index{}; // where i am in the array
|
||||
std::unique_ptr<List> m_list;
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||
option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};
|
||||
};
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ private:
|
||||
void UpdateVars();
|
||||
|
||||
private:
|
||||
std::string m_title;
|
||||
std::string m_title_sub_heading;
|
||||
std::string m_sub_heading;
|
||||
std::string m_title{};
|
||||
std::string m_title_sub_heading{};
|
||||
std::string m_sub_heading{};
|
||||
|
||||
struct tm m_tm{};
|
||||
TimeStamp m_poll_timestamp{};
|
||||
|
||||
@@ -42,79 +42,50 @@ enum class PageLoadState {
|
||||
Error,
|
||||
};
|
||||
|
||||
// all commented out entries are those that we don't query for.
|
||||
// this saves time not only processing the json, but also the download
|
||||
// of said json.
|
||||
// by reducing the fields to only what we need, the size is 4-5x smaller.
|
||||
|
||||
struct Creator {
|
||||
std::string id;
|
||||
std::string display_name;
|
||||
std::string id{};
|
||||
std::string display_name{};
|
||||
};
|
||||
|
||||
struct Details {
|
||||
std::string name;
|
||||
// std::string description;
|
||||
std::string name{};
|
||||
};
|
||||
|
||||
struct Preview {
|
||||
// std::string original;
|
||||
std::string thumb;
|
||||
LazyImage lazy_image;
|
||||
std::string thumb{};
|
||||
LazyImage lazy_image{};
|
||||
};
|
||||
|
||||
struct DownloadPack {
|
||||
std::string filename;
|
||||
std::string url;
|
||||
std::string mimetype;
|
||||
std::string filename{};
|
||||
std::string url{};
|
||||
std::string mimetype{};
|
||||
};
|
||||
|
||||
using DownloadTheme = DownloadPack;
|
||||
|
||||
struct ThemeEntry {
|
||||
std::string id;
|
||||
// Creator creator;
|
||||
// Details details;
|
||||
// std::string last_updated;
|
||||
// u64 dl_count;
|
||||
// u64 like_count;
|
||||
// std::vector<std::string> categories;
|
||||
// std::string target;
|
||||
Preview preview;
|
||||
std::string id{};
|
||||
Preview preview{};
|
||||
};
|
||||
|
||||
// struct Pack {
|
||||
// std::string id;
|
||||
// Creator creator;
|
||||
// Details details;
|
||||
// std::string last_updated;
|
||||
// std::vector<std::string> categories;
|
||||
// u64 dl_count;
|
||||
// u64 like_count;
|
||||
// std::vector<ThemeEntry> themes;
|
||||
// };
|
||||
|
||||
struct PackListEntry {
|
||||
std::string id;
|
||||
Creator creator;
|
||||
Details details;
|
||||
// std::string last_updated;
|
||||
// std::vector<std::string> categories;
|
||||
// u64 dl_count;
|
||||
// u64 like_count;
|
||||
std::vector<ThemeEntry> themes;
|
||||
std::string id{};
|
||||
Creator creator{};
|
||||
Details details{};
|
||||
std::vector<ThemeEntry> themes{};
|
||||
};
|
||||
|
||||
struct Pagination {
|
||||
u64 page;
|
||||
u64 limit;
|
||||
u64 page_count;
|
||||
u64 item_count;
|
||||
u64 page{};
|
||||
u64 limit{};
|
||||
u64 page_count{};
|
||||
u64 item_count{};
|
||||
};
|
||||
|
||||
struct PackList {
|
||||
std::vector<PackListEntry> packList;
|
||||
Pagination pagination;
|
||||
std::vector<PackListEntry> packList{};
|
||||
Pagination pagination{};
|
||||
};
|
||||
|
||||
struct Config {
|
||||
@@ -123,10 +94,10 @@ struct Config {
|
||||
u32 sort_index{};
|
||||
u32 order_index{};
|
||||
// search query, if empty, its not used
|
||||
std::string query;
|
||||
std::string query{};
|
||||
// this is actually an array of creator ids, but we don't support that feature
|
||||
// if empty, its not used
|
||||
std::string creator;
|
||||
std::string creator{};
|
||||
// defaults
|
||||
u32 page{1};
|
||||
u32 limit{18};
|
||||
@@ -152,7 +123,7 @@ struct Config {
|
||||
struct Menu; // fwd
|
||||
|
||||
struct PageEntry {
|
||||
std::vector<PackListEntry> m_packList;
|
||||
std::vector<PackListEntry> m_packList{};
|
||||
Pagination m_pagination{};
|
||||
PageLoadState m_ready{PageLoadState::None};
|
||||
};
|
||||
@@ -172,9 +143,6 @@ struct Menu final : MenuBase {
|
||||
}
|
||||
}
|
||||
|
||||
// void SetSearch(const std::string& term);
|
||||
// void SetAuthor();
|
||||
|
||||
void InvalidateAllPages();
|
||||
void PackListDownload();
|
||||
void OnPackListDownload();
|
||||
@@ -183,14 +151,14 @@ private:
|
||||
static constexpr inline const char* INI_SECTION = "themezer";
|
||||
static constexpr inline u32 MAX_ON_PAGE = 16; // same as website
|
||||
|
||||
std::vector<PageEntry> m_pages;
|
||||
std::vector<PageEntry> m_pages{};
|
||||
s64 m_page_index{};
|
||||
s64 m_page_index_max{1};
|
||||
|
||||
std::string m_search{};
|
||||
|
||||
s64 m_index{}; // where i am in the array
|
||||
std::unique_ptr<List> m_list;
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
// options
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
||||
|
||||
@@ -22,9 +22,9 @@ private:
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
std::string m_text;
|
||||
std::string m_text{};
|
||||
std::size_t m_count{180}; // count down to zero
|
||||
Side m_side;
|
||||
Side m_side{};
|
||||
bool m_bounds_measured{};
|
||||
};
|
||||
|
||||
@@ -47,8 +47,8 @@ private:
|
||||
void Draw(NVGcontext* vg, Theme* theme, Entries& entries);
|
||||
|
||||
private:
|
||||
Entries m_entries_left;
|
||||
Entries m_entries_right;
|
||||
Entries m_entries_left{};
|
||||
Entries m_entries_right{};
|
||||
Mutex m_mutex{};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include <stop_token>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class Object {
|
||||
public:
|
||||
Object() = default;
|
||||
virtual ~Object() = default;
|
||||
virtual ~Object() {
|
||||
m_stop_source.request_stop();
|
||||
}
|
||||
|
||||
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void = 0;
|
||||
|
||||
@@ -71,8 +74,14 @@ public:
|
||||
m_hidden = value;
|
||||
}
|
||||
|
||||
auto GetToken() const {
|
||||
return m_stop_source.get_token();
|
||||
}
|
||||
|
||||
protected:
|
||||
Vec4 m_pos{};
|
||||
// used for lifetime management across threads.
|
||||
std::stop_source m_stop_source{};
|
||||
bool m_hidden{false};
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
private:
|
||||
|
||||
private:
|
||||
std::string m_text;
|
||||
std::string m_text{};
|
||||
Vec2 m_text_pos{};
|
||||
bool m_selected{false};
|
||||
};
|
||||
@@ -48,13 +48,13 @@ private:
|
||||
void SetIndex(s64 index);
|
||||
|
||||
private:
|
||||
std::string m_message;
|
||||
Callback m_callback;
|
||||
std::string m_message{};
|
||||
Callback m_callback{};
|
||||
|
||||
Vec4 m_spacer_line{};
|
||||
|
||||
s64 m_index{};
|
||||
std::vector<OptionBoxEntry> m_entries;
|
||||
std::vector<OptionBoxEntry> m_entries{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -32,12 +32,12 @@ private:
|
||||
static constexpr float m_text_xoffset{15.f};
|
||||
static constexpr float m_line_width{1220.f};
|
||||
|
||||
std::string m_title;
|
||||
Items m_items;
|
||||
Callback m_callback;
|
||||
s64 m_index; // index in list array
|
||||
std::string m_title{};
|
||||
Items m_items{};
|
||||
Callback m_callback{};
|
||||
s64 m_index{}; // index in list array
|
||||
|
||||
std::unique_ptr<List> m_list;
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
float m_yoff{};
|
||||
float m_line_top{};
|
||||
|
||||
@@ -42,9 +42,9 @@ struct ProgressBox final : Widget {
|
||||
|
||||
public:
|
||||
struct ThreadData {
|
||||
ProgressBox* pbox;
|
||||
ProgressBoxCallback callback;
|
||||
bool result;
|
||||
ProgressBox* pbox{};
|
||||
ProgressBoxCallback callback{};
|
||||
bool result{};
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -57,7 +57,6 @@ private:
|
||||
std::string m_transfer{};
|
||||
s64 m_size{};
|
||||
s64 m_offset{};
|
||||
bool m_exit_requested{};
|
||||
};
|
||||
|
||||
// this is a helper function that does many things.
|
||||
|
||||
@@ -14,15 +14,15 @@ struct ScrollableText final : Widget {
|
||||
// float m_y_off = m_y_off_base;
|
||||
// static constexpr float m_clip_y = 250.0F;
|
||||
|
||||
static constexpr inline float m_step = 30;
|
||||
const float m_font_size;
|
||||
const float m_y_off_base;
|
||||
float m_y_off;
|
||||
const float m_clip_y;
|
||||
const float m_end_w;
|
||||
static constexpr float m_step = 30;
|
||||
|
||||
int m_index = 0;
|
||||
const float m_font_size;
|
||||
float m_bounds[4];
|
||||
float m_y_off{};
|
||||
int m_index{};
|
||||
float m_bounds[4]{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -323,37 +323,36 @@ inline ActionType operator|(ActionType a, ActionType b) {
|
||||
}
|
||||
|
||||
struct Action final {
|
||||
using CallbackEmpty = std::function<void()>;
|
||||
using CallbackWithBool = std::function<void(bool)>;
|
||||
using Callback = std::variant<
|
||||
std::function<void()>,
|
||||
std::function<void(bool)>
|
||||
CallbackEmpty,
|
||||
CallbackWithBool
|
||||
>;
|
||||
|
||||
Action(Callback cb) : m_type{ActionType::DOWN}, m_hint{""}, m_callback{cb}, m_hidden{true} {}
|
||||
Action(std::string hint, Callback cb) : m_type{ActionType::DOWN}, m_hint{hint}, m_callback{cb} {}
|
||||
Action(u8 type, Callback cb) : m_type{type}, m_hint{""}, m_callback{cb}, m_hidden{true} {}
|
||||
Action(u8 type, std::string hint, Callback cb) : m_type{type}, m_hint{hint}, m_callback{cb} {}
|
||||
Action(Callback cb) : Action{ActionType::DOWN, "", cb} {}
|
||||
Action(std::string hint, Callback cb) : Action{ActionType::DOWN, hint, cb} {}
|
||||
Action(u8 type, Callback cb) : Action{type, "", cb} {}
|
||||
Action(u8 type, std::string hint, Callback cb) : m_type{type}, m_callback{cb}, m_hint{hint} {}
|
||||
|
||||
auto IsHidden() const noexcept { return m_hidden; }
|
||||
auto IsHidden() const noexcept { return m_hint.empty(); }
|
||||
|
||||
auto Invoke(bool down) const {
|
||||
// todo: make this a visit
|
||||
switch (m_callback.index()) {
|
||||
case 0:
|
||||
std::get<0>(m_callback)();
|
||||
break;
|
||||
case 1:
|
||||
std::get<1>(m_callback)(down);
|
||||
break;
|
||||
}
|
||||
// std::visit([down, this](auto& cb){
|
||||
// cb(down);
|
||||
// }), m_callback;
|
||||
std::visit([down](auto&& arg){
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr(std::is_same_v<T, CallbackEmpty>) {
|
||||
arg();
|
||||
} else if constexpr(std::is_same_v<T, CallbackWithBool>) {
|
||||
arg(down);
|
||||
} else {
|
||||
static_assert(false, "non-exhaustive visitor!");
|
||||
}
|
||||
}, m_callback);
|
||||
}
|
||||
|
||||
u8 m_type;
|
||||
std::string m_hint; // todo: make optional
|
||||
Callback m_callback;
|
||||
bool m_hidden{false}; // replace this optional text
|
||||
u8 m_type{};
|
||||
Callback m_callback{};
|
||||
std::string m_hint{};
|
||||
};
|
||||
|
||||
struct Controller {
|
||||
|
||||
@@ -78,7 +78,7 @@ struct Widget : public Object {
|
||||
|
||||
auto GetUiButtons() const -> uiButtons;
|
||||
|
||||
Actions m_actions;
|
||||
Actions m_actions{};
|
||||
Vec2 m_button_pos{1220, 675};
|
||||
bool m_focus{false};
|
||||
bool m_pop{false};
|
||||
|
||||
@@ -45,6 +45,11 @@ struct ThemeIdPair {
|
||||
ElementType type{ElementType::None};
|
||||
};
|
||||
|
||||
struct FrameBufferSize {
|
||||
Vec2 size;
|
||||
Vec2 scale;
|
||||
};
|
||||
|
||||
constexpr ThemeIdPair THEME_ENTRIES[] = {
|
||||
{ "background", ThemeEntryID_BACKGROUND },
|
||||
{ "grid", ThemeEntryID_GRID },
|
||||
@@ -223,6 +228,26 @@ void appplet_hook_calback(AppletHookType type, void *param) {
|
||||
}
|
||||
}
|
||||
|
||||
auto GetFrameBufferSize() -> FrameBufferSize {
|
||||
FrameBufferSize fb{};
|
||||
|
||||
switch (appletGetOperationMode()) {
|
||||
case AppletOperationMode_Handheld:
|
||||
fb.size.x = 1280;
|
||||
fb.size.y = 720;
|
||||
break;
|
||||
|
||||
case AppletOperationMode_Console:
|
||||
fb.size.x = 1920;
|
||||
fb.size.y = 1080;
|
||||
break;
|
||||
}
|
||||
|
||||
fb.scale.x = fb.size.x / SCREEN_WIDTH;
|
||||
fb.scale.y = fb.size.y / SCREEN_HEIGHT;
|
||||
return fb;
|
||||
}
|
||||
|
||||
// this will try to decompress the icon and then re-convert it to jpg
|
||||
// in order to strip exif data.
|
||||
// this doesn't take long at all, but it's very overkill.
|
||||
@@ -327,7 +352,7 @@ void LoadThemeInternal(ThemeMeta meta, ThemeData& theme_data, int inherit_level
|
||||
if (!ini_browse(cb, &theme_data, meta.ini_path)) {
|
||||
log_write("failed to open ini: %s\n", meta.ini_path.s);
|
||||
} else {
|
||||
log_write("opened ini: %s\n", meta.ini_path);
|
||||
log_write("opened ini: %s\n", meta.ini_path.s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,31 +446,21 @@ void App::Loop() {
|
||||
}
|
||||
} else if constexpr(std::is_same_v<T, curl::DownloadEventData>) {
|
||||
log_write("[DownloadEventData] got event\n");
|
||||
arg.callback(arg.result);
|
||||
if (arg.callback && !arg.stoken.stop_requested()) {
|
||||
arg.callback(arg.result);
|
||||
}
|
||||
} else {
|
||||
static_assert(false, "non-exhaustive visitor!");
|
||||
}
|
||||
}, event.value());
|
||||
}
|
||||
|
||||
u32 w{},h{};
|
||||
switch (appletGetOperationMode()) {
|
||||
case AppletOperationMode_Handheld:
|
||||
w = 1280;
|
||||
h = 720;
|
||||
break;
|
||||
|
||||
case AppletOperationMode_Console:
|
||||
w = 1920;
|
||||
h = 1080;
|
||||
break;
|
||||
}
|
||||
|
||||
if (w != s_width || h != s_height) {
|
||||
s_width = w;
|
||||
s_height = h;
|
||||
m_scale.x = (float)s_width / SCREEN_WIDTH;
|
||||
m_scale.y = (float)s_height / SCREEN_HEIGHT;
|
||||
const auto fb = GetFrameBufferSize();
|
||||
if (fb.size.x != s_width || fb.size.y != s_height) {
|
||||
s_width = fb.size.x;
|
||||
s_height = fb.size.y;
|
||||
m_scale = fb.scale;
|
||||
this->destroyFramebufferResources();
|
||||
this->createFramebufferResources();
|
||||
renderer->UpdateViewSize(s_width, s_height);
|
||||
}
|
||||
@@ -616,13 +631,11 @@ void App::SetReplaceHbmenuEnable(bool enable) {
|
||||
g_app->m_replace_hbmenu.Set(enable);
|
||||
if (!enable) {
|
||||
// check we have already replaced hbmenu with sphaira
|
||||
NacpStruct hbmenu_nacp;
|
||||
if (R_FAILED(nro_get_nacp("/hbmenu.nro", hbmenu_nacp))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(hbmenu_nacp.lang[0].name, "sphaira")) {
|
||||
return;
|
||||
NacpStruct hbmenu_nacp{};
|
||||
if (R_SUCCEEDED(nro_get_nacp("/hbmenu.nro", hbmenu_nacp))) {
|
||||
if (std::strcmp(hbmenu_nacp.lang[0].name, "sphaira")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ask user if they want to restore hbmenu
|
||||
@@ -664,7 +677,7 @@ void App::SetReplaceHbmenuEnable(bool enable) {
|
||||
if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
|
||||
if (std::strcmp(sphaira_nacp.display_version, hbmenu_nacp.display_version) < 0) {
|
||||
if (R_FAILED(rc = fs.copy_entire_file(sphaira_path, "/hbmenu.nro"))) {
|
||||
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path, rc, R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path.s, rc, R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
} else {
|
||||
log_write("success with updating hbmenu!\n");
|
||||
}
|
||||
@@ -870,23 +883,26 @@ void App::Poll() {
|
||||
}
|
||||
|
||||
auto gesture = gestures[i];
|
||||
if (gesture_count && gesture.type == HidGestureType_Swipe) {
|
||||
log_write("[SWIPE] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
if (gesture_count && gesture.type == HidGestureType_Touch) {
|
||||
log_write("[TOUCH] got gesture attr: %u direction: %u sampling_number: %zu context_number: %zu\n", gesture.attributes, gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
}
|
||||
else if (gesture_count && gesture.type == HidGestureType_Swipe) {
|
||||
log_write("[SWIPE] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
}
|
||||
else if (gesture_count && gesture.type == HidGestureType_Tap) {
|
||||
log_write("[TAP] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
log_write("[TAP] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
}
|
||||
else if (gesture_count && gesture.type == HidGestureType_Press) {
|
||||
log_write("[PRESS] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
log_write("[PRESS] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
}
|
||||
else if (gesture_count && gesture.type == HidGestureType_Cancel) {
|
||||
log_write("[CANCEL] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
log_write("[CANCEL] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
}
|
||||
else if (gesture_count && gesture.type == HidGestureType_Complete) {
|
||||
log_write("[COMPLETE] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
log_write("[COMPLETE] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
|
||||
}
|
||||
else if (gesture_count && gesture.type == HidGestureType_Pan) {
|
||||
log_write("[PAN] got gesture sampling_number: %d context_number: %d x: %d y: %d dx: %d dy: %d vx: %.2f vy: %.2f count: %d\n", gesture.sampling_number, gesture.context_number, gesture.x, gesture.y, gesture.delta_x, gesture.delta_y, gesture.velocity_x, gesture.velocity_y, gesture.point_count);
|
||||
log_write("[PAN] got gesture direction: %u sampling_number: %zu context_number: %zu x: %d y: %d dx: %d dy: %d vx: %.2f vy: %.2f count: %d\n", gesture.direction, gesture.sampling_number, gesture.context_number, gesture.x, gesture.y, gesture.delta_x, gesture.delta_y, gesture.velocity_x, gesture.velocity_y, gesture.point_count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1205,6 +1221,12 @@ App::App(const char* argv0) {
|
||||
|
||||
curl::Init();
|
||||
|
||||
// get current size of the framebuffer
|
||||
const auto fb = GetFrameBufferSize();
|
||||
s_width = fb.size.x;
|
||||
s_height = fb.size.y;
|
||||
m_scale = fb.scale;
|
||||
|
||||
// Create the deko3d device
|
||||
this->device = dk::DeviceMaker{}
|
||||
.setCbDebug(deko3d_error_cb)
|
||||
@@ -1228,7 +1250,7 @@ App::App(const char* argv0) {
|
||||
// Create the framebuffer resources
|
||||
this->createFramebufferResources();
|
||||
|
||||
this->renderer.emplace(SCREEN_WIDTH, SCREEN_HEIGHT, this->device, this->queue, *this->pool_images, *this->pool_code, *this->pool_data);
|
||||
this->renderer.emplace(s_width, s_height, this->device, this->queue, *this->pool_images, *this->pool_code, *this->pool_data);
|
||||
this->vg = nvgCreateDk(&*this->renderer, NVG_ANTIALIAS | NVG_STENCIL_STROKES);
|
||||
|
||||
i18n::init(GetLanguage());
|
||||
@@ -1260,22 +1282,31 @@ App::App(const char* argv0) {
|
||||
}
|
||||
}
|
||||
|
||||
// disable audio in applet mode with a suspended application due to audren fatal.
|
||||
// see: https://github.com/ITotalJustice/sphaira/issues/92
|
||||
if (IsAppletWithSuspendedApp()) {
|
||||
App::Notify("Audio disabled due to suspended game"_i18n);
|
||||
} else {
|
||||
plsrPlayerInit();
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(romfsMountDataStorageFromProgram(0x0100000000001000, "qlaunch"))) {
|
||||
ON_SCOPE_EXIT(romfsUnmount("qlaunch"));
|
||||
plsrPlayerInit();
|
||||
plsrBFSAROpen("qlaunch:/sound/qlaunch.bfsar", &m_qlaunch_bfsar);
|
||||
ON_SCOPE_EXIT(plsrBFSARClose(&m_qlaunch_bfsar));
|
||||
PLSR_BFSAR qlaunch_bfsar;
|
||||
if (R_SUCCEEDED(plsrBFSAROpen("qlaunch:/sound/qlaunch.bfsar", &qlaunch_bfsar))) {
|
||||
ON_SCOPE_EXIT(plsrBFSARClose(&qlaunch_bfsar));
|
||||
|
||||
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconFocus", &m_sound_ids[SoundEffect_Focus]);
|
||||
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconScroll", &m_sound_ids[SoundEffect_Scroll]);
|
||||
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconLimit", &m_sound_ids[SoundEffect_Limit]);
|
||||
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeStartupMenu_game", &m_sound_ids[SoundEffect_Startup]);
|
||||
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconAdd", &m_sound_ids[SoundEffect_Install]);
|
||||
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeInsertError", &m_sound_ids[SoundEffect_Error]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconFocus", &m_sound_ids[SoundEffect_Focus]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconScroll", &m_sound_ids[SoundEffect_Scroll]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconLimit", &m_sound_ids[SoundEffect_Limit]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeStartupMenu_game", &m_sound_ids[SoundEffect_Startup]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconAdd", &m_sound_ids[SoundEffect_Install]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeInsertError", &m_sound_ids[SoundEffect_Error]);
|
||||
|
||||
plsrPlayerSetVolume(m_sound_ids[SoundEffect_Limit], 2.0f);
|
||||
plsrPlayerSetVolume(m_sound_ids[SoundEffect_Focus], 0.5f);
|
||||
PlaySoundEffect(SoundEffect_Startup);
|
||||
plsrPlayerSetVolume(m_sound_ids[SoundEffect_Limit], 2.0f);
|
||||
plsrPlayerSetVolume(m_sound_ids[SoundEffect_Focus], 0.5f);
|
||||
PlaySoundEffect(SoundEffect_Startup);
|
||||
}
|
||||
} else {
|
||||
log_write("failed to mount romfs 0x0100000000001000\n");
|
||||
}
|
||||
@@ -1333,7 +1364,7 @@ App::App(const char* argv0) {
|
||||
log_write("launching from sphaira created forwarder\n");
|
||||
m_is_launched_via_sphaira_forwader = true;
|
||||
} else {
|
||||
log_write("launching from unknown forwader: %.*s size: %zu\n", loader_info_size, envGetLoaderInfo(), loader_info_size);
|
||||
log_write("launching from unknown forwader: %.*s size: %zu\n", (int)loader_info_size, envGetLoaderInfo(), loader_info_size);
|
||||
}
|
||||
} else {
|
||||
log_write("not launching from forwarder\n");
|
||||
@@ -1407,11 +1438,8 @@ App::~App() {
|
||||
}
|
||||
}
|
||||
|
||||
// Close the archive
|
||||
plsrBFSARClose(&m_qlaunch_bfsar);
|
||||
|
||||
// De-initialize our player
|
||||
plsrPlayerExit();
|
||||
plsrPlayerExit();
|
||||
|
||||
this->destroyFramebufferResources();
|
||||
nvgDeleteDk(this->vg);
|
||||
@@ -1433,7 +1461,7 @@ App::~App() {
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fs.copy_entire_file("/hbmenu.nro", GetExePath()))) {
|
||||
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", GetExePath(), rc, R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", GetExePath().s, rc, R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
} else {
|
||||
log_write("success with copying over root file!\n");
|
||||
}
|
||||
@@ -1458,7 +1486,7 @@ App::~App() {
|
||||
if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
|
||||
if (std::strcmp(hbmenu_nacp.display_version, sphaira_nacp.display_version) < 0) {
|
||||
if (R_FAILED(rc = fs.copy_entire_file(GetExePath(), sphaira_path))) {
|
||||
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path, rc, R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path.s, rc, R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
} else {
|
||||
log_write("success with updating hbmenu!\n");
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace {
|
||||
log_write("curl_share_setopt(%s, %s) msg: %s\n", #opt, #v, curl_share_strerror(r)); \
|
||||
} \
|
||||
|
||||
#define USE_THREAD_QUEUE 1
|
||||
constexpr auto API_AGENT = "ITotalJustice";
|
||||
constexpr u64 CHUNK_SIZE = 1024*1024;
|
||||
constexpr auto MAX_THREADS = 4;
|
||||
@@ -302,18 +301,19 @@ struct ThreadQueue {
|
||||
}
|
||||
|
||||
auto Add(const Api& api) -> bool {
|
||||
if (api.GetUrl().empty() || api.GetPath().empty() || !api.GetOnComplete()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
ThreadQueueEntry entry{};
|
||||
entry.api = api;
|
||||
|
||||
switch (api.m_prio) {
|
||||
switch (api.GetPriority()) {
|
||||
case Priority::Normal:
|
||||
m_entries.emplace_back(entry);
|
||||
m_entries.emplace_back(api);
|
||||
break;
|
||||
case Priority::High:
|
||||
m_entries.emplace_front(entry);
|
||||
m_entries.emplace_front(api);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -350,13 +350,13 @@ auto ProgressCallbackFunc1(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
|
||||
}
|
||||
|
||||
auto ProgressCallbackFunc2(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -> size_t {
|
||||
if (!g_running) {
|
||||
auto api = static_cast<Api*>(clientp);
|
||||
if (!g_running || api->GetToken().stop_requested()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// log_write("pcall called %u %u %u %u\n", dltotal, dlnow, ultotal, ulnow);
|
||||
auto callback = *static_cast<OnProgress*>(clientp);
|
||||
if (!callback(dltotal, dlnow, ultotal, ulnow)) {
|
||||
if (!api->GetOnProgress()(dltotal, dlnow, ultotal, ulnow)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -443,12 +443,17 @@ auto header_callback(char* b, size_t size, size_t nitems, void* userdata) -> siz
|
||||
}
|
||||
|
||||
auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
// check if stop has been requested before starting download
|
||||
if (e.GetToken().stop_requested()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
fs::FsPath tmp_buf;
|
||||
const bool has_file = !e.m_path.empty() && e.m_path != "";
|
||||
const bool has_post = !e.m_fields.m_str.empty() && e.m_fields.m_str != "";
|
||||
const bool has_file = !e.GetPath().empty() && e.GetPath() != "";
|
||||
const bool has_post = !e.GetFields().empty() && e.GetFields() != "";
|
||||
|
||||
DataStruct chunk;
|
||||
Header header_in = e.m_header;
|
||||
Header header_in = e.GetHeader();
|
||||
Header header_out;
|
||||
fs::FsNativeSd fs;
|
||||
|
||||
@@ -457,17 +462,17 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
fs.CreateDirectoryRecursivelyWithPath(tmp_buf);
|
||||
|
||||
if (auto rc = fs.CreateFile(tmp_buf, 0, 0); R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s\n", tmp_buf);
|
||||
log_write("failed to create file: %s\n", tmp_buf.s);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (R_FAILED(fs.OpenFile(tmp_buf, FsOpenMode_Write|FsOpenMode_Append, &chunk.f))) {
|
||||
log_write("failed to open file: %s\n", tmp_buf);
|
||||
log_write("failed to open file: %s\n", tmp_buf.s);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (e.m_flags.m_flags & Flag_Cache) {
|
||||
g_cache.get(e.m_path, header_in);
|
||||
if (e.GetFlags() & Flag_Cache) {
|
||||
g_cache.get(e.GetPath(), header_in);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +480,7 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
chunk.data.reserve(CHUNK_SIZE);
|
||||
|
||||
curl_easy_reset(curl);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, e.m_url.m_str.c_str());
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, e.GetUrl().c_str());
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_USERAGENT, "TotalJustice");
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
@@ -487,8 +492,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERDATA, &header_out);
|
||||
|
||||
if (has_post) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_POSTFIELDS, e.m_fields.m_str.c_str());
|
||||
log_write("setting post field: %s\n", e.m_fields.m_str.c_str());
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_POSTFIELDS, e.GetFields().c_str());
|
||||
log_write("setting post field: %s\n", e.GetFields().c_str());
|
||||
}
|
||||
|
||||
struct curl_slist* list = NULL;
|
||||
@@ -517,8 +522,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
}
|
||||
|
||||
// progress calls.
|
||||
if (e.m_on_progress) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &e.m_on_progress);
|
||||
if (e.GetOnProgress()) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &e);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc2);
|
||||
} else {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc1);
|
||||
@@ -546,16 +551,16 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
|
||||
if (res == CURLE_OK) {
|
||||
if (http_code == 304) {
|
||||
log_write("cached download: %s\n", e.m_url.m_str.c_str());
|
||||
log_write("cached download: %s\n", e.GetUrl().c_str());
|
||||
} else {
|
||||
log_write("un-cached download: %s code: %u\n", e.m_url.m_str.c_str(), http_code);
|
||||
if (e.m_flags.m_flags & Flag_Cache) {
|
||||
g_cache.set(e.m_path, header_out);
|
||||
log_write("un-cached download: %s code: %lu\n", e.GetUrl().c_str(), http_code);
|
||||
if (e.GetFlags() & Flag_Cache) {
|
||||
g_cache.set(e.GetPath(), header_out);
|
||||
}
|
||||
|
||||
fs.DeleteFile(e.m_path);
|
||||
fs.CreateDirectoryRecursivelyWithPath(e.m_path);
|
||||
if (R_FAILED(fs.RenameFile(tmp_buf, e.m_path))) {
|
||||
fs.DeleteFile(e.GetPath());
|
||||
fs.CreateDirectoryRecursivelyWithPath(e.GetPath());
|
||||
if (R_FAILED(fs.RenameFile(tmp_buf, e.GetPath()))) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
@@ -568,8 +573,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
}
|
||||
}
|
||||
|
||||
log_write("Downloaded %s %s\n", e.m_url.m_str.c_str(), curl_easy_strerror(res));
|
||||
return {success, http_code, header_out, chunk.data, e.m_path};
|
||||
log_write("Downloaded %s %s\n", e.GetUrl().c_str(), curl_easy_strerror(res));
|
||||
return {success, http_code, header_out, chunk.data, e.GetPath()};
|
||||
}
|
||||
|
||||
auto DownloadInternal(const Api& e) -> ApiResult {
|
||||
@@ -596,23 +601,18 @@ void ThreadEntry::ThreadFunc(void* p) {
|
||||
auto rc = waitSingle(waiterForUEvent(&data->m_uevent), UINT64_MAX);
|
||||
// log_write("woke up\n");
|
||||
if (!g_running) {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
if (R_FAILED(rc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#if 1
|
||||
const auto result = DownloadInternal(data->m_curl, data->m_api);
|
||||
if (g_running) {
|
||||
const DownloadEventData event_data{data->m_api.m_on_complete, result};
|
||||
if (g_running && data->m_api.GetOnComplete() && !data->m_api.GetToken().stop_requested()) {
|
||||
const DownloadEventData event_data{data->m_api.GetOnComplete(), result, data->m_api.GetToken()};
|
||||
evman::push(std::move(event_data), false);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
// mutexLock(&data->m_mutex);
|
||||
// ON_SCOPE_EXIT(mutexUnlock(&data->m_mutex));
|
||||
|
||||
data->m_in_progress = false;
|
||||
// notify the queue that there's a space free
|
||||
@@ -736,53 +736,25 @@ void Exit() {
|
||||
}
|
||||
|
||||
auto ToMemory(const Api& e) -> ApiResult {
|
||||
if (!e.m_path.empty()) {
|
||||
if (!e.GetPath().empty()) {
|
||||
return {};
|
||||
}
|
||||
return DownloadInternal(e);
|
||||
}
|
||||
|
||||
auto ToFile(const Api& e) -> ApiResult {
|
||||
if (e.m_path.empty()) {
|
||||
if (e.GetPath().empty()) {
|
||||
return {};
|
||||
}
|
||||
return DownloadInternal(e);
|
||||
}
|
||||
|
||||
auto ToMemoryAsync(const Api& api) -> bool {
|
||||
#if USE_THREAD_QUEUE
|
||||
return g_thread_queue.Add(api);
|
||||
#else
|
||||
// mutexLock(&g_thread_queue.m_mutex);
|
||||
// ON_SCOPE_EXIT(mutexUnlock(&g_thread_queue.m_mutex));
|
||||
|
||||
for (auto& entry : g_threads) {
|
||||
if (!entry.InProgress()) {
|
||||
return entry.Setup(callback, url);
|
||||
}
|
||||
}
|
||||
|
||||
log_write("failed to start download, no avaliable threads\n");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
auto ToFileAsync(const Api& e) -> bool {
|
||||
#if USE_THREAD_QUEUE
|
||||
return g_thread_queue.Add(e);
|
||||
#else
|
||||
// mutexLock(&g_thread_queue.m_mutex);
|
||||
// ON_SCOPE_EXIT(mutexUnlock(&g_thread_queue.m_mutex));
|
||||
|
||||
for (auto& entry : g_threads) {
|
||||
if (!entry.InProgress()) {
|
||||
return entry.Setup(callback, url, out);
|
||||
}
|
||||
}
|
||||
|
||||
log_write("failed to start download, no avaliable threads\n");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace sphaira::curl
|
||||
|
||||
@@ -135,7 +135,7 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig
|
||||
}
|
||||
|
||||
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create folder: %s\n", path);
|
||||
log_write("failed to create folder: %s\n", path.s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& _path,
|
||||
}
|
||||
|
||||
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create folder recursively: %s\n", path);
|
||||
log_write("failed to create folder recursively: %s\n", path.s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ bool init(long index) {
|
||||
case SetLanguage_PT: lang_name = "pt"; break;
|
||||
case SetLanguage_RU: lang_name = "ru"; break;
|
||||
case SetLanguage_ZHTW: lang_name = "zh"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
const fs::FsPath sdmc_path = "/config/sphaira/i18n/" + lang_name + ".json";
|
||||
|
||||
@@ -4,14 +4,19 @@
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-function"
|
||||
#pragma GCC diagnostic ignored "-Warray-bounds="
|
||||
#include "nanovg/stb_image.h"
|
||||
#pragma GCC diagnostic ignored "-Wcast-qual"
|
||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_STATIC
|
||||
#define STBI_WRITE_NO_STDIO
|
||||
#include "stb_image_write.h"
|
||||
#include <stb_image_write.h>
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#define STB_IMAGE_RESIZE_STATIC
|
||||
#include "stb_image_resize2.h"
|
||||
#include <stb_image_resize2.h>
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ auto nro_scan_internal(const fs::FsPath& path, std::vector<NroEntry>& nros, bool
|
||||
if (e.type == FsDirEntryType_Dir) {
|
||||
// assert(!root && "dir should only be scanned on non-root!");
|
||||
fs::FsPath fullpath;
|
||||
std::snprintf(fullpath, sizeof(fullpath), "%s/%s/%s.nro", path, e.name, e.name);
|
||||
std::snprintf(fullpath, sizeof(fullpath), "%s/%s/%s.nro", path.s, e.name, e.name);
|
||||
|
||||
// fast path for detecting an nro in a folder
|
||||
NroEntry entry;
|
||||
@@ -133,12 +133,12 @@ auto nro_scan_internal(const fs::FsPath& path, std::vector<NroEntry>& nros, bool
|
||||
nros.emplace_back(entry);
|
||||
} else {
|
||||
// slow path...
|
||||
std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path, e.name);
|
||||
std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path.s, e.name);
|
||||
nro_scan_internal(fullpath, nros, hide_sphaira, nested, scan_all_dir, false);
|
||||
}
|
||||
} else if (e.type == FsDirEntryType_File && std::string_view{e.name}.ends_with(".nro")) {
|
||||
fs::FsPath fullpath;
|
||||
std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path, e.name);
|
||||
std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path.s, e.name);
|
||||
|
||||
NroEntry entry;
|
||||
if (R_SUCCEEDED(nro_parse_internal(fs, fullpath, entry))) {
|
||||
@@ -148,7 +148,7 @@ auto nro_scan_internal(const fs::FsPath& path, std::vector<NroEntry>& nros, bool
|
||||
R_SUCCEED();
|
||||
}
|
||||
} else {
|
||||
log_write("error when trying to parse %s\n", fullpath);
|
||||
log_write("error when trying to parse %s\n", fullpath.s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ void loop(void* args) {
|
||||
continue;
|
||||
}
|
||||
|
||||
log_write("got name: %s\n", name);
|
||||
log_write("got name: %s\n", name.s);
|
||||
|
||||
u32 filesize{};
|
||||
if (!recvall(connfd, &filesize, sizeof(filesize))) {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <yyjson.h>
|
||||
#include <nanovg/stb_image.h>
|
||||
#include <stb_image.h>
|
||||
#include <minizip/unzip.h>
|
||||
#include <mbedtls/md5.h>
|
||||
#include <ranges>
|
||||
@@ -65,60 +65,27 @@ auto BuildIconUrl(const Entry& e) -> std::string {
|
||||
return out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
auto BuildInfoUrl(const Entry& e) -> std::string {
|
||||
char out[0x100];
|
||||
std::snprintf(out, sizeof(out), "%s/packages/%s/info.json", URL_BASE, e.name.c_str());
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto BuildBannerUrl(const Entry& e) -> std::string {
|
||||
char out[0x100];
|
||||
std::snprintf(out, sizeof(out), "%s/packages/%s/screen.png", URL_BASE, e.name.c_str());
|
||||
return out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
auto BuildScreensUrl(const Entry& e, u8 num) -> std::string {
|
||||
char out[0x100];
|
||||
std::snprintf(out, sizeof(out), "%s/packages/%s/screen%u.png", URL_BASE, e.name.c_str(), num+1);
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto BuildMainifestUrl(const Entry& e) -> std::string {
|
||||
char out[0x100];
|
||||
std::snprintf(out, sizeof(out), "%s/packages/%s/manifest.install", URL_BASE, e.name.c_str());
|
||||
return out;
|
||||
}
|
||||
|
||||
auto BuildZipUrl(const Entry& e) -> std::string {
|
||||
char out[0x100];
|
||||
std::snprintf(out, sizeof(out), "%s/zips/%s.zip", URL_BASE, e.name.c_str());
|
||||
return out;
|
||||
}
|
||||
|
||||
auto BuildFeedbackUrl(std::span<u32> ids) -> std::string {
|
||||
std::string out{"https://wiiubru.com/feedback/messages?ids="};
|
||||
for (u32 i = 0; i < ids.size(); i++) {
|
||||
if (i != 0) {
|
||||
out.push_back(',');
|
||||
}
|
||||
out += std::to_string(ids[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
auto BuildIconCachePath(const Entry& e) -> fs::FsPath {
|
||||
fs::FsPath out;
|
||||
std::snprintf(out, sizeof(out), "%s/icons/%s.png", CACHE_PATH, e.name.c_str());
|
||||
std::snprintf(out, sizeof(out), "%s/icons/%s.png", CACHE_PATH.s, e.name.c_str());
|
||||
return out;
|
||||
}
|
||||
|
||||
auto BuildBannerCachePath(const Entry& e) -> fs::FsPath {
|
||||
fs::FsPath out;
|
||||
std::snprintf(out, sizeof(out), "%s/banners/%s.png", CACHE_PATH, e.name.c_str());
|
||||
std::snprintf(out, sizeof(out), "%s/banners/%s.png", CACHE_PATH.s, e.name.c_str());
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -302,7 +269,6 @@ auto AppDlToStr(u32 value) -> std::string {
|
||||
|
||||
void ReadFromInfoJson(Entry& e) {
|
||||
const auto info_path = BuildInfoCachePath(e);
|
||||
const auto manifest_path = BuildManifestCachePath(e);
|
||||
|
||||
yyjson_read_err err;
|
||||
auto doc = yyjson_read_file(info_path, YYJSON_READ_NOFLAG, nullptr, &err);
|
||||
@@ -340,9 +306,9 @@ auto UninstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
|
||||
const auto safe_buf = fs::AppendPath("/", e.path);
|
||||
// this will handle read only files, ie, hbmenu.nro
|
||||
if (R_FAILED(fs.DeleteFile(safe_buf))) {
|
||||
log_write("failed to delete file: %s\n", safe_buf);
|
||||
log_write("failed to delete file: %s\n", safe_buf.s);
|
||||
} else {
|
||||
log_write("deleted file: %s\n", safe_buf);
|
||||
log_write("deleted file: %s\n", safe_buf.s);
|
||||
// todo: delete empty directories!
|
||||
// fs::delete_directory(safe_buf);
|
||||
}
|
||||
@@ -353,9 +319,9 @@ auto UninstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
|
||||
const auto dir = BuildPackageCachePath(entry);
|
||||
pbox->NewTransfer("Removing "_i18n + dir);
|
||||
if (R_FAILED(fs.DeleteDirectoryRecursively(dir))) {
|
||||
log_write("failed to delete folder: %s\n", dir);
|
||||
log_write("failed to delete folder: %s\n", dir.s);
|
||||
} else {
|
||||
log_write("deleted: %s\n", dir);
|
||||
log_write("deleted: %s\n", dir.s);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -458,7 +424,7 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
|
||||
if (!pbox->ShouldExit()) {
|
||||
auto zfile = unzOpen64(zip_out);
|
||||
if (!zfile) {
|
||||
log_write("failed to open zip: %s\n", zip_out);
|
||||
log_write("failed to open zip: %s\n", zip_out.s);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(unzClose(zfile));
|
||||
@@ -501,7 +467,7 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
|
||||
pbox->NewTransfer(inzip);
|
||||
|
||||
if (UNZ_END_OF_LIST_OF_FILE == unzLocateFile(zfile, inzip, 0)) {
|
||||
log_write("failed to find %s\n", inzip);
|
||||
log_write("failed to find %s\n", inzip.s);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -526,19 +492,19 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.CreateFile(output, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s 0x%04X\n", output, rc);
|
||||
log_write("failed to create file: %s 0x%04X\n", output.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
if (R_FAILED(rc = fs.OpenFile(output, FsOpenMode_Write, &f))) {
|
||||
log_write("failed to open file: %s 0x%04X\n", output, rc);
|
||||
log_write("failed to open file: %s 0x%04X\n", output.s, rc);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(fsFileClose(&f));
|
||||
|
||||
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
|
||||
log_write("failed to set file size: %s 0x%04X\n", output, rc);
|
||||
log_write("failed to set file size: %s 0x%04X\n", output.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -551,12 +517,12 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
|
||||
|
||||
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
|
||||
if (bytes_read <= 0) {
|
||||
log_write("failed to read zip file: %s\n", inzip);
|
||||
log_write("failed to read zip file: %s\n", inzip.s);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) {
|
||||
log_write("failed to write file: %s 0x%04X\n", output, rc);
|
||||
log_write("failed to write file: %s 0x%04X\n", output.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -615,9 +581,9 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
|
||||
const auto safe_buf = fs::AppendPath("/", old_entry.path);
|
||||
// std::strcat(safe_buf, old_entry.path);
|
||||
if (R_FAILED(fs.DeleteFile(safe_buf))) {
|
||||
log_write("failed to delete: %s\n", safe_buf);
|
||||
log_write("failed to delete: %s\n", safe_buf.s);
|
||||
} else {
|
||||
log_write("deleted file: %s\n", safe_buf);
|
||||
log_write("deleted file: %s\n", safe_buf.s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,6 +629,7 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
|
||||
curl::Url{URL_POST_FEEDBACK},
|
||||
curl::Path{file},
|
||||
curl::Fields{post},
|
||||
curl::StopToken{this->GetToken()},
|
||||
curl::OnComplete{[](auto& result){
|
||||
if (result.success) {
|
||||
log_write("got feedback!\n");
|
||||
@@ -697,6 +664,7 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
|
||||
curl::Url{url},
|
||||
curl::Path{path},
|
||||
curl::Flags{curl::Flag_Cache},
|
||||
curl::StopToken{this->GetToken()},
|
||||
curl::OnComplete{[this, path](auto& result){
|
||||
if (result.success) {
|
||||
if (result.code == 304) {
|
||||
@@ -759,13 +727,9 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "app_dls: %s"_i18n.c_str(), AppDlToStr(m_entry.app_dls).c_str());
|
||||
text_start_y += text_inc_y;
|
||||
|
||||
// for (const auto& option : m_options) {
|
||||
const auto& text_col = theme->GetColour(ThemeEntryID_TEXT);
|
||||
|
||||
// todo: rewrite this mess and use list
|
||||
constexpr float mm = 0;//20;
|
||||
constexpr Vec4 block{968.f + mm, 110.f, 256.f - mm*2, 60.f};
|
||||
constexpr float text_xoffset{15.f};
|
||||
const float x = block.x;
|
||||
float y = 1.f + text_start_y + (text_inc_y * 3) ;
|
||||
const float h = block.h;
|
||||
@@ -779,7 +743,7 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
gfx::drawRectOutline(vg, theme, 4.f, Vec4{x, y, w, h});
|
||||
}
|
||||
|
||||
gfx::drawTextArgs(vg, x + w / 2, y + h / 2, 22, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER, theme->GetColour(ThemeEntryID_TEXT), option.display_text.c_str());
|
||||
gfx::drawTextArgs(vg, x + w / 2, y + h / 2, 22, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER, theme->GetColour(text_id), option.display_text.c_str());
|
||||
y -= block.h + 18;
|
||||
}
|
||||
|
||||
@@ -960,7 +924,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
|
||||
sort_items.push_back("Alphabetical"_i18n);
|
||||
|
||||
SidebarEntryArray::Items order_items;
|
||||
order_items.push_back("Decending"_i18n);
|
||||
order_items.push_back("Descending"_i18n);
|
||||
order_items.push_back("Ascending"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this, filter_items](s64& index_out){
|
||||
@@ -990,6 +954,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
|
||||
curl::Url{URL_JSON},
|
||||
curl::Path{REPO_PATH},
|
||||
curl::Flags{curl::Flag_Cache},
|
||||
curl::StopToken{this->GetToken()},
|
||||
curl::OnComplete{[this](auto& result){
|
||||
if (result.success) {
|
||||
m_repo_download_state = ImageDownloadState::Done;
|
||||
@@ -1071,6 +1036,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
curl::Url{url},
|
||||
curl::Path{path},
|
||||
curl::Flags{curl::Flag_Cache},
|
||||
curl::StopToken{this->GetToken()},
|
||||
curl::OnComplete{[this, &image](auto& result) {
|
||||
if (result.success) {
|
||||
image.state = ImageDownloadState::Done;
|
||||
@@ -1294,7 +1260,7 @@ void Menu::Sort() {
|
||||
case SortType_Updated: {
|
||||
if (lhs.updated_num == rhs.updated_num) {
|
||||
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
|
||||
} else if (m_order == OrderType_Decending) {
|
||||
} else if (m_order == OrderType_Descending) {
|
||||
return lhs.updated_num > rhs.updated_num;
|
||||
} else {
|
||||
return lhs.updated_num < rhs.updated_num;
|
||||
@@ -1303,7 +1269,7 @@ void Menu::Sort() {
|
||||
case SortType_Downloads: {
|
||||
if (lhs.app_dls == rhs.app_dls) {
|
||||
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
|
||||
} else if (m_order == OrderType_Decending) {
|
||||
} else if (m_order == OrderType_Descending) {
|
||||
return lhs.app_dls > rhs.app_dls;
|
||||
} else {
|
||||
return lhs.app_dls < rhs.app_dls;
|
||||
@@ -1312,14 +1278,14 @@ void Menu::Sort() {
|
||||
case SortType_Size: {
|
||||
if (lhs.extracted == rhs.extracted) {
|
||||
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
|
||||
} else if (m_order == OrderType_Decending) {
|
||||
} else if (m_order == OrderType_Descending) {
|
||||
return lhs.extracted > rhs.extracted;
|
||||
} else {
|
||||
return lhs.extracted < rhs.extracted;
|
||||
}
|
||||
} break;
|
||||
case SortType_Alphabetical: {
|
||||
if (m_order == OrderType_Decending) {
|
||||
if (m_order == OrderType_Descending) {
|
||||
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
|
||||
} else {
|
||||
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) > 0;
|
||||
|
||||
@@ -128,7 +128,7 @@ auto GetRomDatabaseFromPath(std::string_view path) -> int {
|
||||
if ((
|
||||
p.folder.length() == db_name.length() && !strncasecmp(p.folder.data(), db_name.data(), p.folder.length())) ||
|
||||
(p.database.length() == db_name.length() && !strncasecmp(p.database.data(), db_name.data(), p.database.length()))) {
|
||||
log_write("found it :) %.*s\n", p.database.length(), p.database.data());
|
||||
log_write("found it :) %.*s\n", (int)p.database.length(), p.database.data());
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ auto GetRomDatabaseFromPath(std::string_view path) -> int {
|
||||
if ((
|
||||
p.folder.length() == db_name2.length() && !strcasecmp(p.folder.data(), db_name2.data())) ||
|
||||
(p.database.length() == db_name2.length() && !strcasecmp(p.database.data(), db_name2.data()))) {
|
||||
log_write("found it :) %.*s\n", p.database.length(), p.database.data());
|
||||
log_write("found it :) %.*s\n", (int)p.database.length(), p.database.data());
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -352,7 +352,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
sort_items.push_back("Alphabetical"_i18n);
|
||||
|
||||
SidebarEntryArray::Items order_items;
|
||||
order_items.push_back("Decending"_i18n);
|
||||
order_items.push_back("Descending"_i18n);
|
||||
order_items.push_back("Ascending"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](s64& index_out){
|
||||
@@ -452,9 +452,8 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
Scan(m_path);
|
||||
} else {
|
||||
char msg[FS_MAX_PATH];
|
||||
std::snprintf(msg, sizeof(msg), "Failed to rename file: %s", entry.name);
|
||||
App::Push(std::make_shared<ErrorBox>(rc, msg));
|
||||
const auto msg = std::string("Failed to rename file: ") + entry.name;
|
||||
App::Push(std::make_shared<ErrorBox>(rc, msg.c_str()));
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -478,10 +477,10 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
|
||||
m_fs->CreateDirectoryRecursivelyWithPath(full_path);
|
||||
if (R_SUCCEEDED(m_fs->CreateFile(full_path, 0, 0))) {
|
||||
log_write("created file: %s\n", full_path);
|
||||
log_write("created file: %s\n", full_path.s);
|
||||
Scan(m_path);
|
||||
} else {
|
||||
log_write("failed to create file: %s\n", full_path);
|
||||
log_write("failed to create file: %s\n", full_path.s);
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -499,10 +498,10 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(m_fs->CreateDirectoryRecursively(full_path))) {
|
||||
log_write("created dir: %s\n", full_path);
|
||||
log_write("created dir: %s\n", full_path.s);
|
||||
Scan(m_path);
|
||||
} else {
|
||||
log_write("failed to create dir: %s\n", full_path);
|
||||
log_write("failed to create dir: %s\n", full_path.s);
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -777,7 +776,7 @@ void Menu::InstallForwarder() {
|
||||
}
|
||||
|
||||
auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result {
|
||||
log_write("new scan path: %s\n", new_path);
|
||||
log_write("new scan path: %s\n", new_path.s);
|
||||
if (!is_walk_up && !m_path.empty() && !m_entries_current.empty()) {
|
||||
const LastFile f(GetEntry().name, m_index, m_list->GetYoff(), m_entries_current.size());
|
||||
m_previous_highlighted_file.emplace_back(f);
|
||||
@@ -868,7 +867,7 @@ auto Menu::FindFileAssocFor() -> std::vector<FileAssocEntry> {
|
||||
if (assoc_db == PATHS[db_idx].folder || assoc_db == PATHS[db_idx].database) {
|
||||
for (const auto& assoc_ext : assoc.ext) {
|
||||
if (assoc_ext == extension) {
|
||||
log_write("found ext: %s assoc_ext: %s assoc.ext: %s\n", assoc.path, assoc_ext.c_str(), extension.c_str());
|
||||
log_write("found ext: %s assoc_ext: %s assoc.ext: %s\n", assoc.path.s, assoc_ext.c_str(), extension.c_str());
|
||||
out_entries.emplace_back(assoc);
|
||||
}
|
||||
}
|
||||
@@ -886,7 +885,7 @@ auto Menu::FindFileAssocFor() -> std::vector<FileAssocEntry> {
|
||||
if (assoc.database.empty()) {
|
||||
for (const auto& assoc_ext : assoc.ext) {
|
||||
if (assoc_ext == extension) {
|
||||
log_write("found ext: %s\n", assoc.path);
|
||||
log_write("found ext: %s\n", assoc.path.s);
|
||||
out_entries.emplace_back(assoc);
|
||||
}
|
||||
}
|
||||
@@ -1034,14 +1033,14 @@ void Menu::Sort() {
|
||||
case SortType_Size: {
|
||||
if (lhs.file_size == rhs.file_size) {
|
||||
return strncasecmp(lhs.name, rhs.name, sizeof(lhs.name)) < 0;
|
||||
} else if (order == OrderType_Decending) {
|
||||
} else if (order == OrderType_Descending) {
|
||||
return lhs.file_size > rhs.file_size;
|
||||
} else {
|
||||
return lhs.file_size < rhs.file_size;
|
||||
}
|
||||
} break;
|
||||
case SortType_Alphabetical: {
|
||||
if (order == OrderType_Decending) {
|
||||
if (order == OrderType_Descending) {
|
||||
return strncasecmp(lhs.name, rhs.name, sizeof(lhs.name)) < 0;
|
||||
} else {
|
||||
return strncasecmp(lhs.name, rhs.name, sizeof(lhs.name)) > 0;
|
||||
@@ -1099,7 +1098,6 @@ void Menu::SetIndexFromLastFile(const LastFile& last_file) {
|
||||
}
|
||||
}
|
||||
SetIndex(index);
|
||||
log_write("\nnew index: %zu %zu mod: %zu\n", m_index, index % 8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,7 +1146,7 @@ void Menu::OnDeleteCallback() {
|
||||
if (p.IsDir()) {
|
||||
pbox->NewTransfer("Scanning "_i18n + full_path);
|
||||
if (R_FAILED(get_collections(full_path, p.name, collections))) {
|
||||
log_write("failed to get dir collection: %s\n", full_path);
|
||||
log_write("failed to get dir collection: %s\n", full_path.s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1166,10 +1164,10 @@ void Menu::OnDeleteCallback() {
|
||||
const auto full_path = GetNewPath(c.path, p.name);
|
||||
pbox->NewTransfer("Deleting "_i18n + full_path);
|
||||
if (p.type == FsDirEntryType_Dir) {
|
||||
log_write("deleting dir: %s\n", full_path);
|
||||
log_write("deleting dir: %s\n", full_path.s);
|
||||
m_fs->DeleteDirectory(full_path);
|
||||
} else {
|
||||
log_write("deleting file: %s\n", full_path);
|
||||
log_write("deleting file: %s\n", full_path.s);
|
||||
m_fs->DeleteFile(full_path);
|
||||
}
|
||||
}
|
||||
@@ -1194,10 +1192,10 @@ void Menu::OnDeleteCallback() {
|
||||
pbox->NewTransfer("Deleting "_i18n + full_path);
|
||||
|
||||
if (p.IsDir()) {
|
||||
log_write("deleting dir: %s\n", full_path);
|
||||
log_write("deleting dir: %s\n", full_path.s);
|
||||
m_fs->DeleteDirectory(full_path);
|
||||
} else {
|
||||
log_write("deleting file: %s\n", full_path);
|
||||
log_write("deleting file: %s\n", full_path.s);
|
||||
m_fs->DeleteFile(full_path);
|
||||
}
|
||||
}
|
||||
@@ -1212,8 +1210,6 @@ void Menu::OnDeleteCallback() {
|
||||
}
|
||||
|
||||
void Menu::OnPasteCallback() {
|
||||
bool use_progress_box{true};
|
||||
|
||||
// check if we only have 1 file / folder and is cut (rename)
|
||||
if (m_selected_files.size() == 1 && m_selected_type == SelectedType::Cut) {
|
||||
const auto& entry = m_selected_files[0];
|
||||
@@ -1263,7 +1259,7 @@ void Menu::OnPasteCallback() {
|
||||
if (p.IsDir()) {
|
||||
pbox->NewTransfer("Scanning "_i18n + full_path);
|
||||
if (R_FAILED(get_collections(full_path, p.name, collections))) {
|
||||
log_write("failed to get dir collection: %s\n", full_path);
|
||||
log_write("failed to get dir collection: %s\n", full_path.s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1300,7 +1296,7 @@ void Menu::OnPasteCallback() {
|
||||
const auto src_path = GetNewPath(c.path, p.name);
|
||||
const auto dst_path = GetNewPath(base_dst_path, p.name);
|
||||
|
||||
log_write("creating: %s to %s\n", src_path, dst_path);
|
||||
log_write("creating: %s to %s\n", src_path.s, dst_path.s);
|
||||
pbox->NewTransfer("Creating "_i18n + dst_path);
|
||||
m_fs->CreateDirectory(dst_path);
|
||||
}
|
||||
@@ -1315,7 +1311,7 @@ void Menu::OnPasteCallback() {
|
||||
const auto dst_path = GetNewPath(base_dst_path, p.name);
|
||||
|
||||
pbox->NewTransfer("Copying "_i18n + src_path);
|
||||
log_write("copying: %s to %s\n", src_path, dst_path);
|
||||
log_write("copying: %s to %s\n", src_path.s, dst_path.s);
|
||||
R_TRY_RESULT(pbox->CopyFile(src_path, dst_path), false);
|
||||
}
|
||||
}
|
||||
@@ -1415,7 +1411,7 @@ auto Menu::get_collections(const fs::FsPath& path, const fs::FsPath& parent_name
|
||||
// get a list of all the files / dirs
|
||||
FsDirCollection collection;
|
||||
R_TRY(get_collection(path, parent_name, collection, true, true, false));
|
||||
log_write("got collection: %s parent_name: %s files: %zu dirs: %zu\n", path, parent_name, collection.files.size(), collection.dirs.size());
|
||||
log_write("got collection: %s parent_name: %s files: %zu dirs: %zu\n", path.s, parent_name.s, collection.files.size(), collection.dirs.size());
|
||||
out.emplace_back(collection);
|
||||
|
||||
// for (size_t i = 0; i < collection.dirs.size(); i++) {
|
||||
@@ -1423,7 +1419,7 @@ auto Menu::get_collections(const fs::FsPath& path, const fs::FsPath& parent_name
|
||||
// use heap as to not explode the stack
|
||||
const auto new_path = std::make_unique<fs::FsPath>(Menu::GetNewPath(path, p.name));
|
||||
const auto new_parent_name = std::make_unique<fs::FsPath>(Menu::GetNewPath(parent_name, p.name));
|
||||
log_write("trying to get nested collection: %s parent_name: %s\n", *new_path, *new_parent_name);
|
||||
log_write("trying to get nested collection: %s parent_name: %s\n", new_path->s, new_parent_name->s);
|
||||
R_TRY(get_collections(*new_path, *new_parent_name, out));
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry
|
||||
log_write("found zip\n");
|
||||
auto zfile = unzOpen64(temp_file);
|
||||
if (!zfile) {
|
||||
log_write("failed to open zip: %s\n", temp_file);
|
||||
log_write("failed to open zip: %s\n", temp_file.s);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(unzClose(zfile));
|
||||
@@ -155,29 +155,29 @@ auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry
|
||||
Result rc;
|
||||
if (file_path[strlen(file_path) -1] == '/') {
|
||||
if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create folder: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to create folder: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (R_FAILED(rc = fs.CreateDirectoryRecursivelyWithPath(file_path)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create folder: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to create folder: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) {
|
||||
log_write("failed to open file: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to open file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(fsFileClose(&f));
|
||||
|
||||
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
|
||||
log_write("failed to set file size: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to set file size: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace {
|
||||
auto GenerateStarPath(const fs::FsPath& nro_path) -> fs::FsPath {
|
||||
fs::FsPath out{};
|
||||
const auto dilem = std::strrchr(nro_path.s, '/');
|
||||
std::snprintf(out, sizeof(out), "%.*s.%s.star", dilem - nro_path.s + 1, nro_path.s, dilem + 1);
|
||||
std::snprintf(out, sizeof(out), "%.*s.%s.star", int(dilem - nro_path.s + 1), nro_path.s, dilem + 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
||||
sort_items.push_back("Size (Star)"_i18n);
|
||||
|
||||
SidebarEntryArray::Items order_items;
|
||||
order_items.push_back("Decending"_i18n);
|
||||
order_items.push_back("Descending"_i18n);
|
||||
order_items.push_back("Ascending"_i18n);
|
||||
|
||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
|
||||
@@ -237,8 +237,6 @@ void Menu::SetIndex(s64 index) {
|
||||
m_list->SetYoff(0);
|
||||
}
|
||||
|
||||
const auto& e = m_entries[m_index];
|
||||
|
||||
if (IsStarEnabled()) {
|
||||
const auto star_path = GenerateStarPath(m_entries[m_index].path);
|
||||
if (fs::FsNativeSd().FileExists(star_path)) {
|
||||
@@ -279,8 +277,8 @@ void Menu::ScanHomebrew() {
|
||||
|
||||
struct IniUser {
|
||||
std::vector<NroEntry>& entires;
|
||||
Hbini* ini;
|
||||
std::string last_section;
|
||||
Hbini* ini{};
|
||||
std::string last_section{};
|
||||
} ini_user{ m_entries };
|
||||
|
||||
ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||
@@ -331,13 +329,13 @@ void Menu::Sort() {
|
||||
const auto name_cmp = [order](const NroEntry& lhs, const NroEntry& rhs) -> bool {
|
||||
auto r = strcasecmp(lhs.GetName(), rhs.GetName());
|
||||
if (!r) {
|
||||
auto r = strcasecmp(lhs.GetAuthor(), rhs.GetAuthor());
|
||||
r = strcasecmp(lhs.GetAuthor(), rhs.GetAuthor());
|
||||
if (!r) {
|
||||
auto r = strcasecmp(lhs.path, rhs.path);
|
||||
r = strcasecmp(lhs.path, rhs.path);
|
||||
}
|
||||
}
|
||||
|
||||
if (order == OrderType_Decending) {
|
||||
if (order == OrderType_Descending) {
|
||||
return r < 0;
|
||||
} else {
|
||||
return r > 0;
|
||||
@@ -351,6 +349,7 @@ void Menu::Sort() {
|
||||
} else if (!lhs.has_star.value() && rhs.has_star.value()) {
|
||||
return false;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case SortType_Updated: {
|
||||
auto lhs_timestamp = lhs.hbini.timestamp;
|
||||
auto rhs_timestamp = rhs.hbini.timestamp;
|
||||
@@ -363,7 +362,7 @@ void Menu::Sort() {
|
||||
|
||||
if (lhs_timestamp == rhs_timestamp) {
|
||||
return name_cmp(lhs, rhs);
|
||||
} else if (order == OrderType_Decending) {
|
||||
} else if (order == OrderType_Descending) {
|
||||
return lhs_timestamp > rhs_timestamp;
|
||||
} else {
|
||||
return lhs_timestamp < rhs_timestamp;
|
||||
@@ -376,10 +375,11 @@ void Menu::Sort() {
|
||||
} else if (!lhs.has_star.value() && rhs.has_star.value()) {
|
||||
return false;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case SortType_Size: {
|
||||
if (lhs.size == rhs.size) {
|
||||
return name_cmp(lhs, rhs);
|
||||
} else if (order == OrderType_Decending) {
|
||||
} else if (order == OrderType_Descending) {
|
||||
return lhs.size > rhs.size;
|
||||
} else {
|
||||
return lhs.size < rhs.size;
|
||||
@@ -392,6 +392,7 @@ void Menu::Sort() {
|
||||
} else if (!lhs.has_star.value() && rhs.has_star.value()) {
|
||||
return false;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case SortType_Alphabetical: {
|
||||
return name_cmp(lhs, rhs);
|
||||
} break;
|
||||
|
||||
@@ -258,8 +258,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
const auto scale = std::min(scale_x, scale_y);
|
||||
w = m_irs_width * scale;
|
||||
h = m_irs_height * scale;
|
||||
cx = (m_pos.x + m_pos.w / 2.0) - w / 2.0;
|
||||
cy = (m_pos.y + m_pos.h / 2.0) - h / 2.0;
|
||||
cx = (m_pos.x + m_pos.w / 2.F) - w / 2.F;
|
||||
cy = (m_pos.y + m_pos.h / 2.F) - h / 2.F;
|
||||
angle = 0;
|
||||
} break;
|
||||
case Rotation_90: {
|
||||
@@ -268,8 +268,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
const auto scale = std::min(scale_x, scale_y);
|
||||
w = m_irs_width * scale;
|
||||
h = m_irs_height * scale;
|
||||
cx = (m_pos.x + m_pos.w / 2.0) + h / 2.0;
|
||||
cy = (m_pos.y + m_pos.h / 2.0) - w / 2.0;
|
||||
cx = (m_pos.x + m_pos.w / 2.F) + h / 2.F;
|
||||
cy = (m_pos.y + m_pos.h / 2.F) - w / 2.F;
|
||||
angle = 90;
|
||||
} break;
|
||||
case Rotation_180: {
|
||||
@@ -278,8 +278,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
const auto scale = std::min(scale_x, scale_y);
|
||||
w = m_irs_width * scale;
|
||||
h = m_irs_height * scale;
|
||||
cx = (m_pos.x + m_pos.w / 2.0) + w / 2.0;
|
||||
cy = (m_pos.y + m_pos.h / 2.0) + h / 2.0;
|
||||
cx = (m_pos.x + m_pos.w / 2.F) + w / 2.F;
|
||||
cy = (m_pos.y + m_pos.h / 2.F) + h / 2.F;
|
||||
angle = 180;
|
||||
} break;
|
||||
case Rotation_270: {
|
||||
@@ -288,8 +288,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
const auto scale = std::min(scale_x, scale_y);
|
||||
w = m_irs_width * scale;
|
||||
h = m_irs_height * scale;
|
||||
cx = (m_pos.x + m_pos.w / 2.0) - h / 2.0;
|
||||
cy = (m_pos.y + m_pos.h / 2.0) + w / 2.0;
|
||||
cx = (m_pos.x + m_pos.w / 2.F) - h / 2.F;
|
||||
cy = (m_pos.y + m_pos.h / 2.F) + w / 2.F;
|
||||
angle = 270;
|
||||
} break;
|
||||
}
|
||||
@@ -382,7 +382,7 @@ void Menu::LoadDefaultConfig() {
|
||||
irsGetClusteringProcessorDefaultConfig(&m_clustering_config);
|
||||
irsGetIrLedProcessorDefaultConfig(&m_led_config);
|
||||
|
||||
m_tera_config;
|
||||
m_tera_config = {};
|
||||
m_adaptive_config = {};
|
||||
m_hand_config = {};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
|
||||
if (!pbox->ShouldExit()) {
|
||||
auto zfile = unzOpen64(zip_out);
|
||||
if (!zfile) {
|
||||
log_write("failed to open zip: %s\n", zip_out);
|
||||
log_write("failed to open zip: %s\n", zip_out.s);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(unzClose(zfile));
|
||||
@@ -96,25 +96,25 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
|
||||
Result rc;
|
||||
if (file_path[strlen(file_path) -1] == '/') {
|
||||
if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create folder: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to create folder: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) {
|
||||
log_write("failed to open file: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to open file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(fsFileClose(&f));
|
||||
|
||||
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
|
||||
log_write("failed to set file size: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to set file size: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) {
|
||||
log_write("failed to write file: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to write file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ MainMenu::MainMenu() {
|
||||
curl::Url{GITHUB_URL},
|
||||
curl::Path{CACHE_PATH},
|
||||
curl::Flags{curl::Flag_Cache},
|
||||
curl::StopToken{this->GetToken()},
|
||||
curl::Header{
|
||||
{ "Accept", "application/vnd.github+json" },
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "i18n.hpp"
|
||||
|
||||
#include <minIni.h>
|
||||
#include <nanovg/stb_image.h>
|
||||
#include <stb_image.h>
|
||||
#include <cstring>
|
||||
#include <minizip/unzip.h>
|
||||
#include <yyjson.h>
|
||||
@@ -102,32 +102,10 @@ auto apiBuildUrlDownloadInternal(const std::string& id, bool is_pack) -> std::st
|
||||
// https://api.themezer.net/?query=query{downloadPack(id:"11"){filename,url,mimetype}}
|
||||
}
|
||||
|
||||
auto apiBuildUrlDownloadTheme(const ThemeEntry& e) -> std::string {
|
||||
return apiBuildUrlDownloadInternal(e.id, false);
|
||||
}
|
||||
|
||||
auto apiBuildUrlDownloadPack(const PackListEntry& e) -> std::string {
|
||||
return apiBuildUrlDownloadInternal(e.id, true);
|
||||
}
|
||||
|
||||
auto apiBuildFilePack(const PackListEntry& e) -> fs::FsPath {
|
||||
fs::FsPath path;
|
||||
std::snprintf(path, sizeof(path), "%s/%s By %s/", THEME_FOLDER, e.details.name.c_str(), e.creator.display_name.c_str());
|
||||
return path;
|
||||
}
|
||||
|
||||
#if 0
|
||||
auto apiBuildUrlPack(const PackListEntry& e) -> std::string {
|
||||
char url[2048];
|
||||
std::snprintf(url, sizeof(url), "https://api.themezer.net/?query=query($id:String!){pack(id:$id){id,creator{display_name},details{name,description},last_updated,categories,dl_count,like_count,themes{id,details{name},layout{id,details{name}},categories,target,preview{original,thumb},last_updated,dl_count,like_count}}}&variables={\"id\":\"%s\"}", e.id.c_str());
|
||||
return url;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto apiBuildUrlThemeList(const Config& e) -> std::string {
|
||||
return apiBuildUrlListInternal(e, false);
|
||||
}
|
||||
|
||||
auto apiBuildUrlListPacks(const Config& e) -> std::string {
|
||||
return apiBuildUrlListInternal(e, true);
|
||||
}
|
||||
@@ -171,7 +149,6 @@ auto loadThemeImage(ThemeEntry& e) -> bool {
|
||||
|
||||
if (!image.image) {
|
||||
log_write("failed to load image from file: %s\n", path.s);
|
||||
log_write("failed to load image from file: %s\n", path);
|
||||
return false;
|
||||
} else {
|
||||
// log_write("loaded image from file: %s\n", path);
|
||||
@@ -189,35 +166,18 @@ void from_json(yyjson_val* json, Creator& e) {
|
||||
void from_json(yyjson_val* json, Details& e) {
|
||||
JSON_OBJ_ITR(
|
||||
JSON_SET_STR(name);
|
||||
// JSON_SET_STR(description);
|
||||
);
|
||||
}
|
||||
|
||||
void from_json(yyjson_val* json, Preview& e) {
|
||||
JSON_OBJ_ITR(
|
||||
// JSON_SET_STR(original);
|
||||
JSON_SET_STR(thumb);
|
||||
);
|
||||
}
|
||||
|
||||
void from_json(yyjson_val* json, DownloadPack& e) {
|
||||
JSON_OBJ_ITR(
|
||||
JSON_SET_STR(filename);
|
||||
JSON_SET_STR(url);
|
||||
JSON_SET_STR(mimetype);
|
||||
);
|
||||
}
|
||||
|
||||
void from_json(yyjson_val* json, ThemeEntry& e) {
|
||||
JSON_OBJ_ITR(
|
||||
JSON_SET_STR(id);
|
||||
// JSON_SET_OBJ(creator);
|
||||
// JSON_SET_OBJ(details);
|
||||
// JSON_SET_STR(last_updated);
|
||||
// JSON_SET_UINT(dl_count);
|
||||
// JSON_SET_UINT(like_count);
|
||||
// JSON_SET_ARR_STR(categories);
|
||||
// JSON_SET_STR(target);
|
||||
JSON_SET_OBJ(preview);
|
||||
);
|
||||
}
|
||||
@@ -227,10 +187,6 @@ void from_json(yyjson_val* json, PackListEntry& e) {
|
||||
JSON_SET_STR(id);
|
||||
JSON_SET_OBJ(creator);
|
||||
JSON_SET_OBJ(details);
|
||||
// JSON_SET_STR(last_updated);
|
||||
// JSON_SET_ARR_STR(categories);
|
||||
// JSON_SET_UINT(dl_count);
|
||||
// JSON_SET_UINT(like_count);
|
||||
JSON_SET_ARR_OBJ(themes);
|
||||
);
|
||||
}
|
||||
@@ -246,7 +202,6 @@ void from_json(yyjson_val* json, Pagination& e) {
|
||||
|
||||
void from_json(const std::vector<u8>& data, DownloadPack& e) {
|
||||
JSON_INIT_VEC(data, "data");
|
||||
// JSON_GET_OBJ("downloadTheme");
|
||||
JSON_GET_OBJ("downloadPack");
|
||||
JSON_OBJ_ITR(
|
||||
JSON_SET_STR(filename);
|
||||
@@ -310,14 +265,14 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
|
||||
|
||||
// create directories
|
||||
fs::FsPath dir_path;
|
||||
std::snprintf(dir_path, sizeof(dir_path), "%s/%s - By %s", THEME_FOLDER, entry.details.name.c_str(), entry.creator.display_name.c_str());
|
||||
std::snprintf(dir_path, sizeof(dir_path), "%s/%s - By %s", THEME_FOLDER.s, entry.details.name.c_str(), entry.creator.display_name.c_str());
|
||||
fs.CreateDirectoryRecursively(dir_path);
|
||||
|
||||
// 3. extract the zip
|
||||
if (!pbox->ShouldExit()) {
|
||||
auto zfile = unzOpen64(zip_out);
|
||||
if (!zfile) {
|
||||
log_write("failed to open zip: %s\n", zip_out);
|
||||
log_write("failed to open zip: %s\n", zip_out.s);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(unzClose(zfile));
|
||||
@@ -352,19 +307,19 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to create file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) {
|
||||
log_write("failed to open file: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to open file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(fsFileClose(&f));
|
||||
|
||||
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
|
||||
log_write("failed to set file size: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to set file size: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -382,7 +337,7 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) {
|
||||
log_write("failed to write file: %s 0x%04X\n", file_path, rc);
|
||||
log_write("failed to write file: %s 0x%04X\n", file_path.s, rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -618,7 +573,6 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
}
|
||||
|
||||
const float xoff = (350 - 320) / 2;
|
||||
const float yoff = (350 - 320) / 2;
|
||||
|
||||
// lazy load image
|
||||
if (e.themes.size()) {
|
||||
@@ -638,7 +592,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
switch (image.state) {
|
||||
case ImageDownloadState::None: {
|
||||
const auto path = apiBuildIconCache(theme);
|
||||
log_write("downloading theme!: %s\n", path);
|
||||
log_write("downloading theme!: %s\n", path.s);
|
||||
|
||||
const auto url = theme.preview.thumb;
|
||||
log_write("downloading url: %s\n", url.c_str());
|
||||
@@ -647,6 +601,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
curl::Url{url},
|
||||
curl::Path{path},
|
||||
curl::Flags{curl::Flag_Cache},
|
||||
curl::StopToken{this->GetToken()},
|
||||
curl::OnComplete{[this, &image](auto& result) {
|
||||
if (result.success) {
|
||||
image.state = ImageDownloadState::Done;
|
||||
@@ -733,6 +688,7 @@ void Menu::PackListDownload() {
|
||||
curl::Url{packList_url},
|
||||
curl::Path{packlist_path},
|
||||
curl::Flags{curl::Flag_Cache},
|
||||
curl::StopToken{this->GetToken()},
|
||||
curl::OnComplete{[this, page_index](auto& result){
|
||||
log_write("got themezer data\n");
|
||||
if (!result.success) {
|
||||
@@ -757,8 +713,8 @@ void Menu::PackListDownload() {
|
||||
std::snprintf(subheading, sizeof(subheading), "Page %zu / %zu"_i18n.c_str(), m_page_index+1, m_page_index_max);
|
||||
SetSubHeading(subheading);
|
||||
|
||||
log_write("a.pagination.page: %u\n", a.pagination.page);
|
||||
log_write("a.pagination.page_count: %u\n", a.pagination.page_count);
|
||||
log_write("a.pagination.page: %zu\n", a.pagination.page);
|
||||
log_write("a.pagination.page_count: %zu\n", a.pagination.page_count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace sphaira::ui::gfx {
|
||||
namespace {
|
||||
|
||||
static constexpr std::array buttons = {
|
||||
constexpr std::array buttons = {
|
||||
std::pair{Button::A, "\uE0E0"},
|
||||
std::pair{Button::B, "\uE0E1"},
|
||||
std::pair{Button::X, "\uE0E2"},
|
||||
@@ -60,18 +60,18 @@ void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, con
|
||||
float gradientX, gradientY, color;
|
||||
getHighlightAnimation(&gradientX, &gradientY, &color);
|
||||
|
||||
const auto strokeWidth = 5.0;
|
||||
const auto strokeWidth = 5.F;
|
||||
auto v2 = v;
|
||||
v2.x -= strokeWidth / 2.0;
|
||||
v2.y -= strokeWidth / 2.0;
|
||||
v2.x -= strokeWidth / 2.F;
|
||||
v2.y -= strokeWidth / 2.F;
|
||||
v2.w += strokeWidth;
|
||||
v2.h += strokeWidth;
|
||||
const auto corner_radius = 0.5;
|
||||
const auto corner_radius = 0.5F;
|
||||
|
||||
const auto shadow_width = 2.0f;
|
||||
const auto shadow_offset = 10.0f;
|
||||
const auto shadow_feather = 10.0f;
|
||||
const auto shadow_opacity = 128.0f;
|
||||
const auto shadow_width = 2.F;
|
||||
const auto shadow_offset = 10.F;
|
||||
const auto shadow_feather = 10.F;
|
||||
const auto shadow_opacity = 128.F;
|
||||
|
||||
// Shadow
|
||||
NVGpaint shadowPaint = nvgBoxGradient(vg,
|
||||
@@ -136,15 +136,6 @@ void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, con
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, const Vec4& v, const NVGpaint& p) {
|
||||
const auto corner_radius = 0.5;
|
||||
drawRectOutlineInternal(vg, theme, size, v);
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, corner_radius);
|
||||
nvgFillPaint(vg, p);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void drawTextIntenal(NVGcontext* vg, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c) {
|
||||
nvgBeginPath(vg);
|
||||
nvgFontSize(vg, size);
|
||||
@@ -173,20 +164,6 @@ void drawTextArgs(NVGcontext* vg, float x, float y, float size, int align, const
|
||||
drawText(vg, x, y, size, buffer, nullptr, align, c);
|
||||
}
|
||||
|
||||
static void drawImageInternal(NVGcontext* vg, const Vec4& v, int texture, int rounded = 0) {
|
||||
const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f);
|
||||
// drawRect(vg, x, y, w, h, paint);
|
||||
nvgBeginPath(vg);
|
||||
// nvgRect(vg, x, y, w, h);
|
||||
if (rounded == 0) {
|
||||
nvgRect(vg, v.x, v.y, v.w, v.h);
|
||||
} else {
|
||||
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded);
|
||||
}
|
||||
nvgFillPaint(vg, paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
|
||||
void drawImage(NVGcontext* vg, const Vec4& v, int texture) {
|
||||
const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f);
|
||||
drawRect(vg, v, paint, false);
|
||||
|
||||
@@ -31,7 +31,6 @@ ProgressBox::ProgressBox(const std::string& title, ProgressBoxCallback callback,
|
||||
m_pos.h = 430.f;
|
||||
m_pos.x = 255;
|
||||
m_pos.y = 145;
|
||||
145 + 430; // 575, 200, 420
|
||||
|
||||
m_pos.w = 770.f;
|
||||
m_pos.h = 295.f;
|
||||
@@ -52,9 +51,7 @@ ProgressBox::ProgressBox(const std::string& title, ProgressBoxCallback callback,
|
||||
}
|
||||
|
||||
ProgressBox::~ProgressBox() {
|
||||
mutexLock(&m_mutex);
|
||||
m_exit_requested = true;
|
||||
mutexUnlock(&m_mutex);
|
||||
m_stop_source.request_stop();
|
||||
|
||||
if (R_FAILED(threadWaitForExit(&m_thread))) {
|
||||
log_write("failed to join thread\n");
|
||||
@@ -101,7 +98,7 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
|
||||
gfx::drawTextArgs(vg, center_x, m_pos.y + 60, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), title.c_str());
|
||||
if (!transfer.empty()) {
|
||||
gfx::drawTextArgs(vg, center_x, prog_bar.y - 15 - 20 * 1.5, 20, NVG_ALIGN_CENTER, theme->GetColour(ThemeEntryID_TEXT), "%s", transfer.c_str());
|
||||
gfx::drawTextArgs(vg, center_x, prog_bar.y - 15 - 20 * 1.5F, 20, NVG_ALIGN_CENTER, theme->GetColour(ThemeEntryID_TEXT), "%s", transfer.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,16 +122,11 @@ auto ProgressBox::UpdateTransfer(s64 offset, s64 size) -> ProgressBox& {
|
||||
}
|
||||
|
||||
void ProgressBox::RequestExit() {
|
||||
mutexLock(&m_mutex);
|
||||
m_exit_requested = true;
|
||||
mutexUnlock(&m_mutex);
|
||||
m_stop_source.request_stop();
|
||||
}
|
||||
|
||||
auto ProgressBox::ShouldExit() -> bool {
|
||||
mutexLock(&m_mutex);
|
||||
const auto exit_requested = m_exit_requested;
|
||||
mutexUnlock(&m_mutex);
|
||||
return exit_requested;
|
||||
return m_stop_source.stop_requested();
|
||||
}
|
||||
|
||||
auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {
|
||||
|
||||
@@ -9,9 +9,9 @@ namespace sphaira::ui {
|
||||
ScrollableText::ScrollableText(const std::string& text, float x, float y, float y_clip, float w, float font_size)
|
||||
: m_font_size{font_size}
|
||||
, m_y_off_base{y}
|
||||
, m_y_off{y}
|
||||
, m_clip_y{y_clip}
|
||||
, m_end_w{w}
|
||||
, m_y_off{y}
|
||||
{
|
||||
SetActions(
|
||||
std::make_pair(Button::LS_DOWN, Action{[this](){
|
||||
|
||||
@@ -274,7 +274,7 @@ auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_SIDEBAR));
|
||||
gfx::drawText(vg, m_title_pos, m_title_size, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
|
||||
if (!m_sub.empty()) {
|
||||
gfx::drawTextArgs(vg, m_pos.x + m_pos.w - 30.f, m_title_pos.y + 10.f, 18, NVG_ALIGN_TOP | NVG_ALIGN_RIGHT, theme->GetColour(ThemeEntryID_TEXT), m_sub.c_str());
|
||||
gfx::drawTextArgs(vg, m_pos.x + m_pos.w - 30.f, m_title_pos.y + 10.f, 16, NVG_ALIGN_TOP | NVG_ALIGN_RIGHT, theme->GetColour(ThemeEntryID_TEXT_INFO), m_sub.c_str());
|
||||
}
|
||||
gfx::drawRect(vg, m_top_bar, theme->GetColour(ThemeEntryID_LINE));
|
||||
gfx::drawRect(vg, m_bottom_bar, theme->GetColour(ThemeEntryID_LINE));
|
||||
@@ -328,7 +328,7 @@ void Sidebar::SetupButtons() {
|
||||
RemoveActions();
|
||||
|
||||
// add entry actions
|
||||
for (const auto& [button, action] : m_items[m_index]->GetActions()) {
|
||||
for (const auto& [button, action] : m_items[m_index]->GetActions()) {
|
||||
SetAction(button, action);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user