17 Commits
0.6.0 ... 0.6.2

Author SHA1 Message Date
ITotalJustice
e002aa9ec2 only disable audio in applet mode if an app is suspended, bump version. 2025-01-17 03:52:32 +00:00
ITotalJustice
0aaf460dbf bump version for release (i forgot to do this before making a new release...) 2025-01-16 21:48:40 +00:00
ITotalJustice
76c8b806d0 bump ftpsrv version to 1.2.2 which fixes mdtm. 2025-01-16 21:40:24 +00:00
glitched_nx
61783bc530 update de.json with missing translations and corrections (#95) 2025-01-16 21:38:47 +00:00
ITotalJustice
a3a2a04991 fix hbmenu restore prompt not triggering if /hbmenu.nro does not exist
fixes #99
2025-01-16 21:28:13 +00:00
ITotalJustice
b6304fca75 fix deko3d mem leak when using docked mode
fixes #97
2025-01-16 21:24:21 +00:00
ITotalJustice
5612ae5691 disable audio in applet mode due to audren fatal.
fixes #92
2025-01-16 21:03:26 +00:00
ITotalJustice
657c160599 enable warning flags, fix all warning, default init all vars, bump stb libs used in nanovg
fixes #98
2025-01-16 21:01:17 +00:00
HenryBaby
f66494aeb5 Fixed the "decending" typo. (#91)
Co-authored-by: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com>
2025-01-16 05:17:59 +00:00
shadow2560
650e7812e5 Update french language. (#94)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2025-01-16 05:13:31 +00:00
Ny'hrarr
cca54340a2 Update pt.json (#93) 2025-01-16 05:13:08 +00:00
cucholix
8161b52e7b Update es.json (#90)
Updated es.json localization, missing strings translated
2025-01-16 05:12:41 +00:00
Yorunokyujitsu
9390bd3865 add new strings and update ko.json, ja.json (#88) 2025-01-14 16:06:28 +00:00
ITotalJustice
483be133a5 mention discord server in readme
fixes #70
2025-01-14 16:01:39 +00:00
ITotalJustice
e2022eac4c progress box should use stop source for requesting exit 2025-01-14 15:54:34 +00:00
ITotalJustice
977331c3b2 remove download non-thread_queue code, fix thread queue exit bug
due to the previous commit, requesting the stop token to exit during a download
would cause the thread queue itself to exit.
2025-01-14 15:45:52 +00:00
ITotalJustice
64a40ae672 use stop token to manage object lifetime across async callbacks, such as download async 2025-01-14 15:35:09 +00:00
52 changed files with 746 additions and 734 deletions

View File

@@ -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
| | |

View File

@@ -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 ",

View File

@@ -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?",

View File

@@ -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'."
}
}

View File

@@ -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?",

View File

@@ -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.": ""
}
}

View File

@@ -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に戻しますか?",

View File

@@ -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로 교체할까요?",

View File

@@ -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?": "",

View File

@@ -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": "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."
}

View File

@@ -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?": "",

View File

@@ -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?",

View File

@@ -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?",

View File

@@ -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",

View File

@@ -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>
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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{};

View File

@@ -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{};

View File

@@ -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

View File

@@ -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};

View File

@@ -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

View File

@@ -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};
};

View File

@@ -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{};

View File

@@ -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};

View File

@@ -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{};
};

View File

@@ -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};
};

View File

@@ -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

View File

@@ -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{};

View File

@@ -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.

View File

@@ -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

View File

@@ -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 {

View File

@@ -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};

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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))) {

View File

@@ -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;

View File

@@ -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));
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 = {};

View File

@@ -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" },
},

View File

@@ -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);
}
});
}

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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](){

View File

@@ -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);
}