23 Commits
0.3.0 ... 0.4.1

Author SHA1 Message Date
ITotalJustice
0570c14343 bump version for release 2024-12-20 11:17:15 +00:00
ITotalJustice
66f2171995 use localtime instead of gmtime
fixes #23
2024-12-18 17:51:08 +00:00
J0hnTR
3178f11596 Update ru.json (#29)
Native RU-speaker here, just verified autotranslation a bit.
2024-12-18 14:36:50 +00:00
ITotalJustice
e2d9db8928 fix appstore sort strings not being rendered 2024-12-18 00:28:59 +00:00
ITotalJustice
945d1f3ae6 add updater, remove white theme (it was unfished), remove more dead code, bump version for release 2024-12-18 00:04:27 +00:00
Battosai94
0585bec6e5 Update fr.json (#17)
Minor changes to FR translation
2024-12-17 23:15:53 +00:00
Yorunokyujitsu
11f4f3000a Korean translation (#19) 2024-12-17 23:15:30 +00:00
Sanras
474843915c Add OLED Black Theme (#20) 2024-12-17 23:12:37 +00:00
Ny'hrarr
3146b951f2 New icon (#18)
* New icon
2024-12-17 22:54:45 +00:00
ITotalJustice
2db9b72416 improve fr translation (credit to @Battosai94)
see: https://github.com/ITotalJustice/sphaira/issues/2#issuecomment-2549628348
2024-12-17 21:12:26 +00:00
ITotalJustice
9b4710d386 show time in main menu, change battery % symbol to use a small version 2024-12-17 21:04:36 +00:00
Aurelia
ddf5b94f4d i18n: improve de locale (#16)
revised some wording, false friends and neologisms
2024-12-17 20:50:15 +00:00
ITotalJustice
ecb2567757 add workflow 2024-12-17 20:05:33 +00:00
ITotalJustice
b59a162473 fix not exiting to home menu whilst replacing hbmenu 2024-12-17 19:54:49 +00:00
ITotalJustice
ef5ff520d1 Add files via upload (#11)
Corrected Japanese translation

Co-authored-by: HoRy205 <101063179+HoRy205@users.noreply.github.com>
2024-12-17 16:56:00 +00:00
ITotalJustice
433c2e220c fix overlapping text in fs
fixes #13
2024-12-17 16:21:14 +00:00
do-kiss
98ad2f485b Chinese translation (#12)
Chinese translation
2024-12-17 16:05:07 +00:00
ITotalJustice
9966e57e12 option to install nro from fs, swap LR display, load translations locally, fix scrolling sound, add file name to rename swkdb
- reduce nxlink svcsleep to reduce latency between polling.
- translations can now be loaded from /config/sphaira/i18n/name.json, this is to help aid those creating translations.
- swap LR position in display. the fix is a hack, but it'll do for now.
- sound effects are now consistent throught the app.
- renaming a file will now show the current file name in swkbd, makes it easier to rename from config.ini.template -> config.ini
- removed some dead code that was unused.
- add credits to the readme.
- speed up playlog ini parsing by browsing the ini rather that doing a query for each entry.
2024-12-17 16:03:05 +00:00
shadow2560
c11990e1bd Improve french language (#10)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2024-12-17 13:29:18 +00:00
LNLenost
9b1c0226e1 Added Italian translation (#8)
* Delete assets/romfs/i18n/it.json (old Italian translation)

* Uploaded new, correct Italian translations.
2024-12-17 13:13:36 +00:00
WE1ZARD
f38a671a7f use translatation from native Chinese (#3) 2024-12-17 12:58:17 +00:00
Ny'hrarr
fe952dc9f2 Improve Portuguese translation (#1)
* Improved Portuguese translation

* Update pt.json
2024-12-17 01:49:21 +00:00
ITotalJustice
d063ffcb20 remove old theme entries, make "Back" be the default hovered option when installing forwarder 2024-12-16 22:51:58 +00:00
37 changed files with 733 additions and 663 deletions

35
.github/workflows/build_presets.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: build
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
preset: [Release, RelWithDebInfo, MinSizeRel, Debug]
runs-on: ${{ matrix.os }}
container: devkitpro/devkita64:latest
steps:
- uses: actions/checkout@v3
# fetch latest cmake
- uses: lukka/get-cmake@latest
- name: Configure CMake
run: |
cmake --preset ${{ matrix.preset }}
- name: Build
run: cmake --build --preset ${{ matrix.preset }} --parallel 4
- uses: actions/upload-artifact@master
with:
name: sphaira-${{ matrix.preset }}
path: build/${{ matrix.preset }}/sphaira.nro

View File

@@ -51,3 +51,5 @@ see `assets/romfs/assoc/` for more examples of file assoc entries
- libpulsar
- minIni
- gbatemp
- hb-appstore
- everyone who has contributed to this project!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -14,57 +14,57 @@
"Info": "Info",
"Delete": "Löschen",
"Hide Sphaira": "Sphaira verstecken",
"Are you sure you want to delete ": "Sind Sie sicher, dass Sie löschen möchten? ",
"Install Forwarder": "Weiterleitung installieren",
"WARNING: Installing forwarders will lead to a ban!": "ACHTUNG: Der Einbau von Forwardern führt zu einem Verbot!",
"Are you sure you want to delete ": "Mit dem Löschvorgang fortfahren?",
"Install Forwarder": "Forwarder installieren",
"WARNING: Installing forwarders will lead to a ban!": "ACHTUNG: Die Installation von Forwardern führt zu einem Ban!",
"Back": "Zurück",
"Install": "Installieren",
"Fs": "Fs",
"App": "App",
"Menu": "Speisekarte",
"Menu": "Menu",
"Homebrew": "Homebrew",
"FileBrowser": "DateiBrowser",
"Open": "Offen",
"Open": "Öffnen",
"Theme Options": "Themenoptionen",
"Select Theme": "Wählen Sie Thema aus",
"Select Theme": "Wählen Sie Theme aus",
"Shuffle": "Shuffle",
"Music": "Musik",
"Show Hidden": "Versteckt anzeigen",
"Show Hidden": "Versteckte anzeigen",
"Folders First": "Ordner zuerst",
"Hidden Last": "Zuletzt versteckt",
"Yes": "Ja",
"No": "NEIN",
"No": "Nein",
"Network Options": "Netzwerkoptionen",
"Nxlink": "Nxlink",
"Check for update": "Suchen Sie nach Updates",
"Check for update": "Nach Updates suchen",
"File Options": "Dateioptionen",
"Cut": "Schneiden",
"Copy": "Kopie",
"Cut": "Ausschneiden",
"Copy": "Kopieren",
"Rename": "Umbenennen",
"Advanced Options": "Datei erstellen",
"Advanced Options": "Erweiterte Optionen",
"Create File": "Datei erstellen",
"Create Folder": "Ordner erstellen",
"View as text": "Als Text anzeigen",
"View as text (unfinished)": "Als Text anzeigen (unvollendet)",
"View as text (unfinished)": "Als Text anzeigen (unfertig)",
"Set Archive Bit": "Archivbit setzen",
"AppStore Options": "AppStore-Optionen",
"All": "Alle",
"Games": "Spiele",
"Emulators": "Emulatoren",
"Tools": "Werkzeuge",
"Advanced": "Fortschrittlich",
"Themes": "Themen",
"Legacy": "Vermächtnis",
"Advanced": "Erweitert",
"Themes": "Themes",
"Legacy": "Legacy",
"Misc": "Sonstiges",
"Downloads": "Downloads",
"Filter": "Filter",
"Search": "Suchen",
"Menu Options": "Menüoptionen",
"Header": "Kopfzeile",
"Theme": "Thema",
"Header": "Header",
"Theme": "Theme",
"Network": "Netzwerk",
"Logging": "Protokollierung",
"Enabled": "Ermöglicht",
"Logging": "Logging",
"Enabled": "Aktiviert",
"Disabled": "Deaktiviert",
"Replace hbmenu on exit": "Ersetzen Sie hbmenu beim Beenden",
"Misc Options": "Verschiedene Optionen",
@@ -98,16 +98,16 @@
"80x60": "80x60",
"40x30": "40x30",
"20x15": "20x15",
"Controller": "Regler",
"Controller": "Controller",
"Rotation": "Drehung",
"Colour": "Farbe",
"Light Target": "Leichtes Ziel",
"Gain": "Gewinnen",
"Gain": "Gain",
"Negative Image": "Negatives Bild",
"Format": "Format",
"Trimming Format": "Zuschneideformat",
"External Light Filter": "Externer Lichtfilter",
"Load Default": "Standard laden",
"Load Default": "Standardoptionen laden",
"No Internet": "Kein Internet",
"[Applet Mode]": "[Applet-Modus]",
"Language": "Sprache"

View File

@@ -1,52 +1,52 @@
{
"Launch": "Lancement",
"Options": "Possibilités",
"Homebrew Options": "Options de brassage maison",
"Sort By": "Trier par",
"Sort Options": "Options de tri",
"Launch": "Exécuter",
"Options": "Options",
"Homebrew Options": "Options Homebrew",
"Sort By": "Tri Par",
"Sort Options": "Options de Tri",
"Updated": "Mis à jour",
"Size": "Taille",
"Alphabetical": "Alphabétique",
"Decending": "Décroissant",
"Ascending": "Ascendant",
"Sort": "Trier",
"Order": "Commande",
"Info": "Informations",
"Ascending": "Croissant",
"Sort": "Tri",
"Order": "Ordre",
"Info": "Info.",
"Delete": "Supprimer",
"Hide Sphaira": "Masquer Sphaira",
"Are you sure you want to delete ": "Etes-vous sûr de vouloir supprimer ",
"Install Forwarder": "Installer le redirecteur",
"WARNING: Installing forwarders will lead to a ban!": "ATTENTION : L'installation de transitaires entraînera une interdiction !",
"Back": "Dos",
"Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ",
"Install Forwarder": "Installer le Forwarder",
"WARNING: Installing forwarders will lead to a ban!": "ATTENTION: L'installation de forwarders entraînera un ban!",
"Back": "Retour",
"Install": "Installer",
"Fs": "Fs",
"App": "Application",
"App": "App.",
"Menu": "Menu",
"Homebrew": "Homebrew",
"FileBrowser": "Navigateur de fichiers",
"FileBrowser": "Navigateur de Fichiers",
"Open": "Ouvrir",
"Theme Options": "Options de thème",
"Select Theme": "Sélectionnez un thème",
"Shuffle": "Mélanger",
"Theme Options": "Options de Thème",
"Select Theme": "Choisir un Thème",
"Shuffle": "Aléatoire",
"Music": "Musique",
"Show Hidden": "Afficher masqué",
"Folders First": "Les dossiers d'abord",
"Hidden Last": "Dernier caché",
"Show Hidden": "Afficher Masqués",
"Folders First": "Dossiers en Premier",
"Hidden Last": "Masqués en Dernier",
"Yes": "Oui",
"No": "Non",
"Network Options": "Options réseau",
"Nxlink": "Nxlien",
"Check for update": "Vérifier la mise à jour",
"File Options": "Options de fichier",
"Network Options": "Options Réseau",
"Nxlink": "Nxlink",
"Check for update": "Vérification d'une mise à jour",
"File Options": "Options de Fichier",
"Cut": "Couper",
"Copy": "Copie",
"Rename": "Rebaptiser",
"Advanced Options": "Créer un fichier",
"Create File": "Créer un fichier",
"Create Folder": "Créer un dossier",
"Copy": "Copier",
"Rename": "Renommer",
"Advanced Options": "Options Avancées",
"Create File": "Créer un Fichier",
"Create Folder": "Créer un Dossier",
"View as text": "Afficher sous forme de texte",
"View as text (unfinished)": "Afficher sous forme de texte (inachevé)",
"Set Archive Bit": "Définir le bit d'archive",
"Set Archive Bit": "Définir le Bit d'Archive",
"AppStore Options": "Options de l'AppStore",
"All": "Tous",
"Games": "Jeux",
@@ -54,45 +54,45 @@
"Tools": "Outils",
"Advanced": "Avancé",
"Themes": "Thèmes",
"Legacy": "Héritage",
"Legacy": "Legacy",
"Misc": "Divers",
"Downloads": "Téléchargements",
"Filter": "Filtre",
"Search": "Recherche",
"Menu Options": "Options des menus",
"Menu Options": "Options des Menus",
"Header": "En-tête",
"Theme": "Thème",
"Network": "Réseau",
"Logging": "Enregistrement",
"Enabled": "Activé",
"Disabled": "Désactivé",
"Replace hbmenu on exit": "Remplacer hbmenu à la sortie",
"Misc Options": "Diverses options",
"Themezer": "Thème",
"Logging": "Journalisation",
"Enabled": "Activé(e)",
"Disabled": "Désactivé(e)",
"Replace hbmenu on exit": "Remplacer hbmenu en sortie",
"Misc Options": "Options Diverses",
"Themezer": "Themezer",
"Irs": "Irs",
"Web": "Web",
"Download": "Télécharger",
"Next Page": "Page suivante",
"Prev Page": "Page précédente",
"Pad ": "Tampon ",
" (Unconnected)": " (Sans rapport)",
"Next Page": "Page Suiv.",
"Prev Page": "Page Préc.",
"Pad ": "Manette ",
" (Unconnected)": " (Non connectée)",
"HandHeld": "Portable",
" (Available)": " (Disponible)",
"0 (Sideways)": "0 (latéralement)",
"90 (Flat)": "90 (plat)",
"180 (-Sideways)": "180 (-Côté)",
"270 (Upside down)": "270 (à l'envers)",
"0 (Sideways)": "0 (Paysage)",
"90 (Flat)": "90 (Portrait)",
"180 (-Sideways)": "180 (-Paysage)",
"270 (Upside down)": "270 (Inversé)",
"Grey": "Gris",
"Ironbow": "Arc de fer",
"Ironbow": "Ironbow",
"Green": "Vert",
"Red": "Rouge",
"Blue": "Bleu",
"All leds": "Toutes les LED",
"Bright group": "Groupe lumineux",
"Dim group": "Groupe de gradation",
"Dim group": "Groupe sombre",
"None": "Aucun",
"Normal image": "Image normale",
"Negative image": "Image négative",
"Negative image": "Négatif",
"320x240": "320x240",
"160x120": "160x120",
"80x60": "80x60",
@@ -101,13 +101,13 @@
"Controller": "Contrôleur",
"Rotation": "Rotation",
"Colour": "Couleur",
"Light Target": "Cible légère",
"Gain": "Gagner",
"Negative Image": "Image négative",
"Light Target": "Luminosité",
"Gain": "Gain",
"Negative Image": "Négatif",
"Format": "Format",
"Trimming Format": "Format de découpage",
"External Light Filter": "Filtre de lumière externe",
"Load Default": "Charger par défaut",
"Trimming Format": "Format de Découpe",
"External Light Filter": "Filtre de Lumière Externe",
"Load Default": "Charger par Défaut",
"No Internet": "Pas d'Internet",
"[Applet Mode]": "[Mode Applet]",
"Language": "Langue"

View File

@@ -1,69 +1,69 @@
{
"Launch": "Lancio",
"Launch": "Lancia",
"Options": "Opzioni",
"Homebrew Options": "Opzioni per l'homebrew",
"Homebrew Options": "Opzioni Homebrew",
"Sort By": "Ordina per",
"Sort Options": "Opzioni di ordinamento",
"Sort Options": "Opzioni filtro",
"Updated": "Aggiornato",
"Size": "Misurare",
"Alphabetical": "Alfabetico",
"Decending": "Decrescente",
"Ascending": "Ascendente",
"Sort": "Ordinare",
"Order": "Ordine",
"Ascending": "Crescente",
"Sort": "Riordina",
"Order": "Ordina",
"Info": "Informazioni",
"Delete": "Eliminare",
"Delete": "Elimina",
"Hide Sphaira": "Nascondi Sphaira",
"Are you sure you want to delete ": "Sei sicuro di voler eliminare? ",
"Install Forwarder": "Installa lo spedizioniere",
"Install Forwarder": "Installa forwarder",
"WARNING: Installing forwarders will lead to a ban!": "ATTENZIONE: l'installazione di forwarder porterà al ban!",
"Back": "Indietro",
"Install": "Installare",
"Install": "Installa",
"Fs": "Fs",
"App": "App",
"Menu": "Menu",
"Homebrew": "Birra fatta in casa",
"Homebrew": "Homebrew",
"FileBrowser": "FileBrowser",
"Open": "Aprire",
"Theme Options": "Opzioni del tema",
"Select Theme": "Seleziona Tema",
"Open": "Apri",
"Theme Options": "Opzioni tema",
"Select Theme": "Seleziona tema",
"Shuffle": "Mescola",
"Music": "Musica",
"Show Hidden": "Mostra nascosto",
"Folders First": "Prima le cartelle",
"Hidden Last": "Ultimo nascosto",
"Yes": "SÌ",
"No": "NO",
"Yes": "Sì",
"No": "No",
"Network Options": "Opzioni di rete",
"Nxlink": "Nxlink",
"Check for update": "Controlla l'aggiornamento",
"Check for update": "Controlla aggiornamenti",
"File Options": "Opzioni file",
"Cut": "Taglio",
"Cut": "Taglia",
"Copy": "Copia",
"Rename": "Rinominare",
"Advanced Options": "Crea file",
"Rename": "Rinomina",
"Advanced Options": "Opzioni avanzate",
"Create File": "Crea file",
"Create Folder": "Crea cartella",
"View as text": "Visualizza come testo",
"View as text (unfinished)": "Visualizza come testo (non finito)",
"Set Archive Bit": "Imposta bit di archivio",
"Set Archive Bit": "Imposta Archive Bit",
"AppStore Options": "Opzioni dell'App Store",
"All": "Tutto",
"Games": "Giochi",
"Emulators": "Emulatori",
"Tools": "Utensili",
"Tools": "Strumenti",
"Advanced": "Avanzato",
"Themes": "Temi",
"Legacy": "Eredità",
"Legacy": "Legacy",
"Misc": "Varie",
"Downloads": "Download",
"Filter": "Filtro",
"Search": "Ricerca",
"Menu Options": "Opzioni del menu",
"Menu Options": "Opzioni menu",
"Header": "Intestazione",
"Theme": "Tema",
"Network": "Rete",
"Logging": "Registrazione",
"Logging": "Logging",
"Enabled": "Abilitato",
"Disabled": "Disabilitato",
"Replace hbmenu on exit": "Sostituisci hbmenu all'uscita",
@@ -71,19 +71,19 @@
"Themezer": "Themezer",
"Irs": "Irs",
"Web": "Rete",
"Download": "Scaricamento",
"Download": "Download",
"Next Page": "Pagina successiva",
"Prev Page": "Pagina precedente",
"Pad ": "Pad ",
" (Unconnected)": " (Non connesso)",
"HandHeld": "Tenuto in mano",
"HandHeld": "HandHeld",
" (Available)": " (Disponibile)",
"0 (Sideways)": "0 (lateralmente)",
"0 (Sideways)": "0 (Di lato)",
"90 (Flat)": "90 (Piatto)",
"180 (-Sideways)": "180 (-lateralmente)",
"180 (-Sideways)": "180 (-Di lato)",
"270 (Upside down)": "270 (Capovolto)",
"Grey": "Grigio",
"Ironbow": "Arco di ferro",
"Ironbow": "Ironbow",
"Green": "Verde",
"Red": "Rosso",
"Blue": "Blu",
@@ -98,7 +98,7 @@
"80x60": "80x60",
"40x30": "40x30",
"20x15": "20×15",
"Controller": "Controllore",
"Controller": "Controller",
"Rotation": "Rotazione",
"Colour": "Colore",
"Light Target": "Bersaglio leggero",

View File

@@ -1,44 +1,44 @@
{
"Launch": "打ち上げ",
"Options": "オプション",
"Homebrew Options": "自作オプション",
"Launch": "起動",
"Options": "設定",
"Homebrew Options": "Homebrew設定",
"Sort By": "並べ替え",
"Sort Options": "並べ替えオプション",
"Updated": "更新されました",
"Size": "サイズ",
"Sort Options": "並べ替え設定",
"Updated": "最近使った順",
"Size": "ファイルサイズ",
"Alphabetical": "アルファベット順",
"Decending": "降順",
"Ascending": "上昇",
"Sort": "選別",
"Order": "注文",
"Sort": "並べ替え",
"Order": "順番",
"Info": "情報",
"Delete": "消去",
"Hide Sphaira": "ハイド・スファイラ",
"Are you sure you want to delete ": "削除してもよろしいですか ",
"Install Forwarder": "フォワーダーのインストール",
"WARNING: Installing forwarders will lead to a ban!": "警告: フォワーダーをインストールすると禁止されます。",
"Hide Sphaira": "Sphairaを非表示",
"Are you sure you want to delete ": "消去してもよろしいですか ",
"Install Forwarder": "Forwarderのインストール",
"WARNING: Installing forwarders will lead to a ban!": "警告: ForwarderをインストールするとBANされます。",
"Back": "戻る",
"Install": "インストール",
"Fs": "Fs",
"Fs": "ファイル",
"App": "アプリ",
"Menu": "メニュー",
"Homebrew": "自作",
"Homebrew": "Homebrew",
"FileBrowser": "ファイルブラウザ",
"Open": "開ける",
"Theme Options": "テーマのオプション",
"Select Theme": "テーマの選択",
"Open": "開",
"Theme Options": "テーマ設定",
"Select Theme": "テーマを選ぶ",
"Shuffle": "シャッフル",
"Music": "音楽",
"Show Hidden": "非表示を表示",
"Folders First": "フォルダーを最初に",
"Hidden Last": "隠された最後",
"Music": "BGM",
"Show Hidden": "非表示ファイルを表示",
"Folders First": "フォルダーを優先",
"Hidden Last": "非表示ファイルを劣後",
"Yes": "はい",
"No": "いいえ",
"Network Options": "ネットワークオプション",
"Network Options": "ネットワーク設定",
"Nxlink": "Nxlink",
"Check for update": "アップデート確認する",
"File Options": "ファイルオプション",
"Cut": "カット",
"Check for update": "アップデート確認",
"File Options": "ファイル設定",
"Cut": "切り取り",
"Copy": "コピー",
"Rename": "名前の変更",
"Advanced Options": "ファイルの作成",
@@ -47,30 +47,30 @@
"View as text": "テキストとして表示",
"View as text (unfinished)": "テキストとして表示 (未完成)",
"Set Archive Bit": "アーカイブビットの設定",
"AppStore Options": "AppStore オプション",
"AppStore Options": "AppStoreの設定",
"All": "全て",
"Games": "ゲーム",
"Emulators": "エミュレータ",
"Tools": "ツール",
"Advanced": "高度な",
"Themes": "テーマ",
"Legacy": "遺産",
"Legacy": "レガシー",
"Misc": "その他",
"Downloads": "ダウンロード",
"Filter": "フィルター",
"Search": "検索",
"Menu Options": "メニューオプション",
"Header": "ヘッダ",
"Menu Options": "メニュー設定",
"Header": "ヘッダ",
"Theme": "テーマ",
"Network": "ネットワーク",
"Logging": "ロギング",
"Logging": "ログの取得",
"Enabled": "有効",
"Disabled": "無効",
"Replace hbmenu on exit": "終了時に hbmenu を置き換える",
"Misc Options": "その他のオプション",
"Themezer": "テーマ設定者",
"Irs": "イルス",
"Web": "ウェブ",
"Misc Options": "その他",
"Themezer": "Themezer",
"Irs": "Joy-Con IRカメラ",
"Web": "ウェブブラウザ",
"Download": "ダウンロード",
"Next Page": "次のページ",
"Prev Page": "前のページ",
@@ -98,7 +98,7 @@
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20x15",
"Controller": "コントローラ",
"Controller": "コントローラ",
"Rotation": "回転",
"Colour": "色",
"Light Target": "ライトターゲット",
@@ -109,6 +109,6 @@
"External Light Filter": "外光フィルター",
"Load Default": "デフォルトをロード",
"No Internet": "インターネットなし",
"[Applet Mode]": "アプレットモード]",
"[Applet Mode]": "Appletモード]",
"Language": "言語"
}

View File

@@ -1,114 +1,114 @@
{
"Launch": "시작하다",
"Options": "옵션",
"Homebrew Options": "홈브류 옵션",
"Sort By": "정렬 기준",
"Sort Options": "정렬 옵션",
"Updated": "업데이트",
"Size": "크기",
"Launch": "실행",
"Options": "설정",
"Homebrew Options": "홈브류 설정",
"Sort By": "정렬",
"Sort Options": "정렬 설정",
"Updated": "업데이트",
"Size": "크기",
"Alphabetical": "알파벳순",
"Decending": "내림차순",
"Ascending": "오름차순",
"Sort": "류",
"Order": "주문하다",
"Sort": "류",
"Order": "정렬",
"Info": "정보",
"Delete": "삭제",
"Hide Sphaira": "스파이라 숨기기",
"Are you sure you want to delete ": "삭제하시겠습니까? ",
"Install Forwarder": "포워더 설치",
"WARNING: Installing forwarders will lead to a ban!": "경고: 전달자를 설치하면 금지됩니다!",
"Back": "뒤쪽에",
"Install": "설치하다",
"Fs": "Fs",
"Hide Sphaira": "Sphaira 숨기기",
"Are you sure you want to delete ": "정말 삭제하시겠습니까? ",
"Install Forwarder": "바로가기 설치",
"WARNING: Installing forwarders will lead to a ban!": "주의: 바로가기 설치시 BAN 위험이 있습니다!",
"Back": "뒤",
"Install": "설치",
"Fs": "파일 탐색기",
"App": "앱",
"Menu": "메뉴",
"Homebrew": "홈브류",
"FileBrowser": "파일브라우저",
"Open": "열려 있는",
"Theme Options": "테마 옵션",
"FileBrowser": "파일 탐색기",
"Open": "열",
"Theme Options": "테마 설정",
"Select Theme": "테마 선택",
"Shuffle": "혼합",
"Music": "음악",
"Show Hidden": "숨겨진 표시",
"Folders First": "폴더 먼저",
"Hidden Last": "숨겨진 마지막",
"Shuffle": "셔플",
"Music": "BGM",
"Show Hidden": "숨겨진 항목 표시",
"Folders First": "폴더 우선 정렬",
"Hidden Last": "숨겨진 항목 후순 정렬",
"Yes": "예",
"No": "아니요",
"Network Options": "네트워크 옵션",
"Network Options": "네트워크 설정",
"Nxlink": "Nxlink",
"Check for update": "업데이트 확인",
"File Options": "파일 옵션",
"Cut": "자르다",
"File Options": "파일 설정",
"Cut": "잘라내기",
"Copy": "복사",
"Rename": "이름 바꾸기",
"Advanced Options": "파일 생성",
"Advanced Options": "고급 설정",
"Create File": "파일 생성",
"Create Folder": "폴더 생성",
"Create Folder": "폴더",
"View as text": "텍스트로 보기",
"View as text (unfinished)": "텍스트로 보기(미완성)",
"Set Archive Bit": "보관 비트 설정",
"AppStore Options": "앱스토어 옵션",
"All": "모두",
"Games": "계략",
"View as text (unfinished)": "텍스트로 보기 (미완성)",
"Set Archive Bit": "아카이브 비트 설정",
"AppStore Options": "앱스토어 설정",
"All": "전체",
"Games": "게임",
"Emulators": "에뮬레이터",
"Tools": "도구",
"Advanced": "고급",
"Advanced": "고급",
"Themes": "테마",
"Legacy": "유산",
"Legacy": "레거시",
"Misc": "기타",
"Downloads": "다운로드",
"Downloads": "다운로드",
"Filter": "필터",
"Search": "찾다",
"Menu Options": "메뉴 옵션",
"Search": "검색",
"Menu Options": "메뉴",
"Header": "헤더",
"Theme": "주제",
"Network": "회로망",
"Logging": "벌채 반출",
"Enabled": "활성화됨",
"Disabled": "장애가 있는",
"Theme": "테마",
"Network": "네트워크",
"Logging": "로깅",
"Enabled": "",
"Disabled": "",
"Replace hbmenu on exit": "종료 시 hbmenu 교체",
"Misc Options": "기타 옵션",
"Themezer": "테마저",
"Irs": "국세청",
"Web": "편물",
"Misc Options": "기타",
"Themezer": "Themezer",
"Irs": "Joy-Con IR 카메라",
"Web": "웹 브라우저",
"Download": "다운로드",
"Next Page": "다음 페이지",
"Prev Page": "이전 페이지",
"Pad ": "인주 ",
" (Unconnected)": " (연결되지 않음)",
"HandHeld": "휴대용",
"Pad ": "Joy-Con ",
" (Unconnected)": " (연결음)",
"HandHeld": "본체 연결",
" (Available)": " (사용 가능)",
"0 (Sideways)": "0(가로)",
"90 (Flat)": "90(플랫)",
"180 (-Sideways)": "180 (-옆으로)",
"270 (Upside down)": "270 (거꾸로)",
"Grey": "회색",
"0 (Sideways)": "0 (좌회전)",
"90 (Flat)": "90 (정방향)",
"180 (-Sideways)": "180 (우회전)",
"270 (Upside down)": "270 (역전)",
"Grey": "그레이",
"Ironbow": "아이언보우",
"Green": "녹색",
"Red": "빨간색",
"Blue": "파란색",
"All leds": "모든 LED",
"Bright group": "밝은 그룹",
"Dim group": "희미한 그룹",
"None": "없음",
"Normal image": "일반 이미지",
"Negative image": "부정적인 이미지",
"320x240": "320x240",
"160x120": "160x120",
"80x60": "80x60",
"40x30": "40x30",
"20x15": "20x15",
"Controller": "제어 장치",
"Rotation": "회전",
"Colour": "색상",
"Light Target": "라이트 타겟",
"Gain": "얻다",
"Negative Image": "네거티브 이미지",
"Format": "체재",
"Trimming Format": "트리밍 형식",
"Green": "그린",
"Red": "레드",
"Blue": "블루",
"All leds": "모든 LED 켜기",
"Bright group": "Bright LED 켜기",
"Dim group": "Dim LED 켜기",
"None": "LED 끄기",
"Normal image": "일반",
"Negative image": "반전",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"Controller": "컨트롤러",
"Rotation": "화면 회전",
"Colour": "팔레트",
"Light Target": "반사 표적",
"Gain": "대비",
"Negative Image": "화상 이미지",
"Format": "해상도",
"Trimming Format": "트리밍 해상도",
"External Light Filter": "외부 조명 필터",
"Load Default": "기본값 로드",
"No Internet": "인터넷 없음",
"Load Default": "기본값으로 설정",
"No Internet": "네트워크 연결 없음",
"[Applet Mode]": "[애플릿 모드]",
"Language": "언어"
}

View File

@@ -1,7 +1,7 @@
{
"Launch": "Lançar",
"Launch": "Iniciar",
"Options": "Opções",
"Homebrew Options": "Opções de fermentação caseira",
"Homebrew Options": "Opções do Homebrew",
"Sort By": "Ordenar por",
"Sort Options": "Opções de classificação",
"Updated": "Atualizado",
@@ -14,19 +14,19 @@
"Info": "Informações",
"Delete": "Excluir",
"Hide Sphaira": "Esconder Sphaira",
"Are you sure you want to delete ": "Tem certeza de que deseja excluir ",
"Install Forwarder": "Instalar encaminhador",
"WARNING: Installing forwarders will lead to a ban!": "AVISO: A instalação de encaminhadores levará ao banimento!",
"Are you sure you want to delete ": "Excluir ",
"Install Forwarder": "Instalar forwarder",
"WARNING: Installing forwarders will lead to a ban!": "AVISO: Isso pode resultar em um banimento!",
"Back": "Voltar",
"Install": "Instalar",
"Fs": "Fs",
"App": "Aplicativo",
"Menu": "Menu",
"Homebrew": "Cerveja caseira",
"Homebrew": "Homebrew",
"FileBrowser": "Navegador de arquivos",
"Open": "Abrir",
"Theme Options": "Opções de tema",
"Select Theme": "Selecione o tema",
"Select Theme": "Selecionar tema",
"Shuffle": "Embaralhar",
"Music": "Música",
"Show Hidden": "Mostrar oculto",
@@ -36,10 +36,10 @@
"No": "Não",
"Network Options": "Opções de rede",
"Nxlink": "Nxlink",
"Check for update": "Verifique se há atualização",
"Check for update": "Verificar se há atualização",
"File Options": "Opções de arquivo",
"Cut": "Corte",
"Copy": "Cópia",
"Cut": "Cortar",
"Copy": "Copiar",
"Rename": "Renomear",
"Advanced Options": "Criar arquivo",
"Create File": "Criar arquivo",
@@ -56,39 +56,39 @@
"Themes": "Temas",
"Legacy": "Legado",
"Misc": "Diversos",
"Downloads": "Transferências",
"Downloads": "Downloads",
"Filter": "Filtro",
"Search": "Procurar",
"Menu Options": "Opções de cardápio",
"Menu Options": "Opções do menu",
"Header": "Cabeçalho",
"Theme": "Tema",
"Network": "Rede",
"Logging": "Registro",
"Logging": "Logging",
"Enabled": "Habilitado",
"Disabled": "Desabilitado",
"Replace hbmenu on exit": "Substitua hbmenu ao sair",
"Misc Options": "Opções diversas",
"Themezer": "Temazer",
"Irs": "Receita Federal",
"Themezer": "Themezer",
"Irs": "Irs",
"Web": "Rede",
"Download": "Download",
"Next Page": "Próxima página",
"Prev Page": "Página anterior",
"Pad ": "Almofada ",
"Pad ": "Pad ",
" (Unconnected)": " (Desconectado)",
"HandHeld": "Portátil",
" (Available)": " (Disponível)",
"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)",
"Grey": "Cinza",
"Ironbow": "Arco de Ferro",
"Ironbow": "Arco de ferro",
"Green": "Verde",
"Red": "Vermelho",
"Blue": "Azul",
"All leds": "Todos os LEDs",
"Bright group": "Grupo brilhante",
"Bright group": "Grupo claro",
"Dim group": "Grupo escuro",
"None": "Nenhum",
"Normal image": "Imagem normal",
@@ -98,7 +98,7 @@
"80x60": "80x60",
"40x30": "40x30",
"20x15": "20x15",
"Controller": "Controlador",
"Controller": "Controle",
"Rotation": "Rotação",
"Colour": "Cor",
"Light Target": "Alvo leve",
@@ -109,6 +109,6 @@
"External Light Filter": "Filtro de luz externo",
"Load Default": "Carregar padrão",
"No Internet": "Sem Internet",
"[Applet Mode]": "[Modo miniaplicativo]",
"[Applet Mode]": "[Modo Applet]",
"Language": "Idioma"
}

View File

@@ -1,89 +1,89 @@
{
"Launch": "Запуск",
"Options": "Параметры",
"Homebrew Options": "Варианты домашнего пивоварения",
"Homebrew Options": "Параметры Homebrew",
"Sort By": "Сортировать по",
"Sort Options": "Параметры сортировки",
"Updated": "Обновлено",
"Size": "Размер",
"Alphabetical": "Алфавитный",
"Decending": "по убыванию",
"Ascending": "восходящий",
"Alphabetical": "По наименованию",
"Decending": "По убыванию",
"Ascending": "По возрастанию",
"Sort": "Сортировать",
"Order": "Заказ",
"Order": "Порядок",
"Info": "Информация",
"Delete": "Удалить",
"Hide Sphaira": "Скрыть Сфаиру",
"Hide Sphaira": "Скрыть Sphaira",
"Are you sure you want to delete ": "Вы уверены, что хотите удалить ",
"Install Forwarder": "Установить переадресатор",
"Install Forwarder": "Установить форвардер",
"WARNING: Installing forwarders will lead to a ban!": "ВНИМАНИЕ: Установка форвардеров приведет к бану!",
"Back": "Назад",
"Install": "Установить",
"Fs": "Фс",
"App": "Приложение",
"Menu": "Меню",
"Homebrew": "Домашнее пиво",
"FileBrowser": "ФайлБраузер",
"Homebrew": "Homebrew",
"FileBrowser": "Файловый менеджер",
"Open": "Открыть",
"Theme Options": "Параметры темы",
"Select Theme": "Выберите тему",
"Shuffle": "Перетасовать",
"Music": "Музыка",
"Show Hidden": "Показать скрытое",
"Show Hidden": "Показать скрытые",
"Folders First": "Папки в первую очередь",
"Hidden Last": "Скрытый последний",
"Hidden Last": "Скрытые в последнюю очередь",
"Yes": "Да",
"No": "Нет",
"Network Options": "Параметры сети",
"Nxlink": "Нкслинк",
"Nxlink": "Nxlink",
"Check for update": "Проверить наличие обновлений",
"File Options": "Параметры файла",
"Cut": "Резать",
"Cut": "Вырезать",
"Copy": "Копировать",
"Rename": "Переименовать",
"Advanced Options": "Создать файл",
"Advanced Options": "Расширенные параметры",
"Create File": "Создать файл",
"Create Folder": "Создать папку",
"View as text": "Посмотреть как текст",
"View as text (unfinished)": "Посмотреть как текст (незакончено)",
"Set Archive Bit": "Установить бит архива",
"Set Archive Bit": "Установить Archive Bit",
"AppStore Options": "Параметры магазина приложений",
"All": "Все",
"Games": "Игры",
"Emulators": "Эмуляторы",
"Tools": "Инструменты",
"Advanced": ередовой",
"Advanced": родвинутые",
"Themes": "Темы",
"Legacy": "Наследие",
"Misc": "Разное",
"Legacy": "Легаси",
"Misc": "Прочее",
"Downloads": "Загрузки",
"Filter": "Фильтр",
"Search": "Поиск",
"Menu Options": "Опции меню",
"Menu Options": "Параметры меню",
"Header": "Заголовок",
"Theme": "Тема",
"Network": "Сеть",
"Logging": "Ведение журнала",
"Logging": "Журналирование",
"Enabled": "Включено",
"Disabled": "Неполноценный",
"Disabled": "Отключено",
"Replace hbmenu on exit": "Заменить hbmenu при выходе",
"Misc Options": "Разные параметры",
"Themezer": "Темезер",
"Irs": "IRS",
"Misc Options": "Прочие параметры",
"Themezer": "Themezer",
"Irs": "Irs",
"Web": "Интернет",
"Download": "Скачать",
"Next Page": "Следующая страница",
"Prev Page": "Предыдущая страница",
"Pad ": "Подушка ",
"Pad ": "Pad ",
" (Unconnected)": " (Не подключено)",
"HandHeld": "Ручной",
" (Available)": " (Доступный)",
"0 (Sideways)": "0 (вбок)",
"90 (Flat)": "90 (квартира)",
"HandHeld": "Портативный",
" (Available)": " (Доступно)",
"0 (Sideways)": "0 (набок)",
"90 (Flat)": "90 (ровно)",
"180 (-Sideways)": "180 (-вбок)",
"270 (Upside down)": "270 (перевернутый)",
"270 (Upside down)": "270 (перевернуто)",
"Grey": "Серый",
"Ironbow": "Железный лук",
"Ironbow": "Стальной",
"Green": "Зеленый",
"Red": "Красный",
"Blue": "Синий",
@@ -92,7 +92,7 @@
"Dim group": "Тусклая группа",
"None": "Никто",
"Normal image": "Обычное изображение",
"Negative image": "Негативный имидж",
"Negative image": "Негативное изображение",
"320x240": "320x240",
"160x120": "160x120",
"80x60": "80х60",
@@ -101,13 +101,13 @@
"Controller": "Контроллер",
"Rotation": "Вращение",
"Colour": "Цвет",
"Light Target": "Легкая мишень",
"Light Target": "Световая мишень",
"Gain": "Прирост",
"Negative Image": "Негативное изображение",
"Format": "Формат",
"Trimming Format": "Формат обрезки",
"External Light Filter": "Внешний светофильтр",
"Load Default": "Загрузить по умолчанию",
"Load Default": "Загрузить умолчания",
"No Internet": "Нет Интернета",
"[Applet Mode]": "[Режим апплета]",
"Language": "Язык"

View File

@@ -1,98 +1,99 @@
{
"Launch": "发射",
"Launch": "启动",
"Options": "选项",
"Homebrew Options": "自制选项",
"Homebrew Options": "插件选项",
"Sort By": "排序方式",
"Sort Options": "排序选项",
"Updated": "已更新",
"Size": "尺寸",
"Updated": "最近使用",
"Size": "大小",
"Alphabetical": "按字母顺序",
"Decending": "降序",
"Ascending": "升序",
"Sort": "种类",
"Order": "命令",
"Sort": "排序",
"Order": "顺序",
"Info": "信息",
"Delete": "删除",
"Hide Sphaira": "隐藏斯菲拉",
"Hide Sphaira": "在插件列表中隐藏Sphaira",
"Are you sure you want to delete ": "您确定要删除吗 ",
"Install Forwarder": "安装转发器",
"WARNING: Installing forwarders will lead to a ban!": "警告:安装转发器将导致禁止",
"Back": "后退",
"Install Forwarder": "安装前端应用",
"WARNING: Installing forwarders will lead to a ban!": "警告:安装前端应用可能导致ban机",
"Back": "返回",
"Install": "安装",
"Fs": "FS",
"App": "应用程序",
"Fs": "文件系统",
"App": "插件",
"Menu": "菜单",
"Homebrew": "自制",
"FileBrowser": "文件浏览器",
"Homebrew": "插件列表",
"AppStore": "插件商店",
"FileBrowser": "文件浏览",
"Open": "打开",
"Theme Options": "主题选项",
"Select Theme": "选择主题",
"Shuffle": "随机播放",
"Music": "音乐",
"Show Hidden": "显示隐藏",
"Folders First": "文件夹优先",
"Hidden Last": "隐藏后",
"Yes": "是",
"No": "",
"Show Hidden": "显示隐藏项目",
"Folders First": "文件夹靠前",
"Hidden Last": "隐藏项目置后",
"Yes": "是",
"No": "",
"Network Options": "网络选项",
"Nxlink": "恩克斯联",
"Nxlink": "Nxlink开发工具",
"Check for update": "检查更新",
"File Options": "文件选项",
"Cut": "切",
"Cut": "切",
"Copy": "复制",
"Rename": "重命名",
"Advanced Options": "创建文件",
"Create File": "建文件",
"Create Folder": "建文件夹",
"Advanced Options": "高级选项",
"Create File": "建文件",
"Create Folder": "建文件夹",
"View as text": "以文本形式查看",
"View as text (unfinished)": "以文本形式查看(未完",
"Set Archive Bit": "设置存档",
"AppStore Options": "应用商店选项",
"View as text (unfinished)": "以文本形式查看(未完",
"Set Archive Bit": "设置存档标志",
"AppStore Options": "插件商店选项",
"All": "全部",
"Games": "游戏",
"Emulators": "模拟器",
"Tools": "工具",
"Advanced": "先进的",
"Advanced": "高级",
"Themes": "主题",
"Legacy": "遗产",
"Legacy": "可更新",
"Misc": "杂项",
"Downloads": "下载",
"Filter": "筛选",
"Search": "搜索",
"Menu Options": "菜单选项",
"Header": "标",
"Header": "标",
"Theme": "主题",
"Network": "网络",
"Logging": "记录",
"Logging": "日志",
"Enabled": "启用",
"Disabled": "残疾人",
"Replace hbmenu on exit": "退出替换 hbmenu",
"Misc Options": "其他选项",
"Themezer": "主题",
"Irs": "国税局",
"Web": "网",
"Disabled": "禁用",
"Replace hbmenu on exit": "退出后用Sphaira替换hbmenu",
"Misc Options": "杂项设置",
"Themezer": "在线主题",
"Irs": "红外成像",
"Web": "网页浏览器",
"Download": "下载",
"Next Page": "下一页",
"Prev Page": "上一页",
"Pad ": "软垫 ",
"Pad ": "手柄 ",
" (Unconnected)": " (未连接)",
"HandHeld": "手持式",
" (Available)": " (可用的)",
"0 (Sideways)": "0(横向)",
"90 (Flat)": "90(平)",
"180 (-Sideways)": "180-横向)",
"270 (Upside down)": "270(颠倒)",
"Grey": "灰色",
"Ironbow": "铁弓",
"Green": "绿色",
"Red": "红色",
"Blue": "蓝色",
"All leds": "所有 LED",
"Bright group": "光明集团",
"Dim group": "暗组",
"None": "没有任何",
"0 (Sideways)": "0",
"90 (Flat)": "90",
"180 (-Sideways)": "180",
"270 (Upside down)": "270",
"Grey": "灰色",
"Ironbow": "紫黄",
"Green": "绿色",
"Red": "红色",
"Blue": "蓝色",
"All leds": "全部",
"Bright group": "亮色组",
"Dim group": "暗组",
"None": "",
"Normal image": "正常图像",
"Negative image": "负像",
"Negative image": "负片图像",
"320x240": "320x240",
"160x120": "160x120",
"80x60": "80x60",
@@ -101,14 +102,14 @@
"Controller": "控制器",
"Rotation": "旋转",
"Colour": "颜色",
"Light Target": "光目标",
"Gain": "获得",
"Negative Image": "负面形象",
"Light Target": "光目标",
"Gain": "增益",
"Negative Image": "负片图像",
"Format": "格式",
"Trimming Format": "剪格式",
"External Light Filter": "外部滤光片",
"Trimming Format": "剪格式",
"External Light Filter": "外部光滤镜",
"Load Default": "加载默认值",
"No Internet": "没有互联网",
"No Internet": "网络未连接",
"[Applet Mode]": "[小程序模式]",
"Language": "语言"
}

View File

@@ -1,18 +1,18 @@
[meta]
name="White not finished"
author=TotalJustice
name=OLED Black
author=iTotalJustice/Sanras
version=1.0.0
preview=romfs:/theme/preview.jpg
[theme]
background=0xEBEBEBff
background=0x000000ff
cursor=romfs:/theme/cursor.png
cursor_drag=romfs:/theme/cursor_drag.png
grid=0x46464630
selected=0x464646ff
grid=0x46464640
selected=0x323232ff
selected_overlay=0x00ffc8ff
text=0x2D2D2Dff
text_selected=0x3A50F0ff
text=0xfbfbfbff
text_selected=0x00ffc8ff
icon_audio=romfs:/theme/icon_audio.png
icon_video=romfs:/theme/icon_video.png

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.13)
set(sphaira_VERSION 0.3.0)
set(sphaira_VERSION 0.4.1)
project(sphaira
VERSION ${sphaira_VERSION}

View File

@@ -7,6 +7,8 @@ namespace sphaira::i18n {
bool init(long index);
void exit();
std::string get(const char* str);
} // namespace sphaira::i18n
inline namespace literals {

View File

@@ -14,20 +14,20 @@ struct Hbini {
};
struct NroEntry {
fs::FsPath path;
s64 size;
NacpStruct nacp;
fs::FsPath path{};
s64 size{};
NacpStruct nacp{};
std::vector<u8> icon;
u64 icon_size;
u64 icon_offset;
std::vector<u8> icon{};
u64 icon_size{};
u64 icon_offset{};
FsTimeStampRaw timestamp;
Hbini hbini;
FsTimeStampRaw timestamp{};
Hbini hbini{};
int image; // nvg image
int x,y,w,h; // image
bool is_nacp_valid;
int image{}; // nvg image
int x,y,w,h{}; // image
bool is_nacp_valid{};
auto GetName() const -> const char* {
return nacp.lang[0].name;

View File

@@ -5,7 +5,7 @@
namespace sphaira::swkbd {
Result ShowText(std::string& out, const char* guide = nullptr, s64 len_min = -1, s64 len_max = -1);
Result ShowNumPad(s64& out, const char* guide = nullptr, s64 len_min = -1, s64 len_max = -1);
Result ShowText(std::string& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = FS_MAX_PATH);
Result ShowNumPad(s64& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = FS_MAX_PATH);
} // namespace sphaira::swkbd

View File

@@ -36,6 +36,9 @@ struct Menu final : MenuBase {
return m_entries;
}
static Result InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon);
static Result InstallHomebrewFromPath(const fs::FsPath& path);
private:
static constexpr inline const char* INI_SECTION = "homebrew";

View File

@@ -7,6 +7,15 @@
namespace sphaira::ui::menu::main {
enum class UpdateState {
// still downloading json from github
Pending,
// no update available.
None,
// update available!
Update,
};
// this holds 2 menus and allows for switching between them
struct MainMenu final : Widget {
MainMenu();
@@ -31,7 +40,7 @@ private:
std::string m_update_url{};
std::string m_update_version{};
std::string m_update_description{};
bool m_update_avaliable{};
UpdateState m_update_state{UpdateState::Pending};
};
} // namespace sphaira::ui::menu::main

View File

@@ -144,10 +144,8 @@ struct ElementEntry {
enum ThemeEntryID {
ThemeEntryID_BACKGROUND,
ThemeEntryID_LOGO,
ThemeEntryID_GRID,
ThemeEntryID_GRID_HOVER,
ThemeEntryID_SELECTED,
ThemeEntryID_SELECTED_OVERLAY,
ThemeEntryID_TEXT,

View File

@@ -26,16 +26,6 @@ struct Widget : public Object {
return m_focus;
}
// void PushWidget(std::shared_ptr<Widget> widget);
// void PopWidget();
void SetParent(Widget* parent) {
m_parent = parent;
}
auto GetParent() -> Widget* {
return m_parent;
}
auto HasAction(Button button) const -> bool;
void SetAction(Button button, Action action);
void SetActions(std::same_as<std::pair<Button, Action>> auto ...args) {
@@ -66,8 +56,6 @@ struct Widget : public Object {
using Actions = std::map<Button, Action>;
// using Actions = std::unordered_map<Button, Action>;
Actions m_actions;
Widget* m_parent{};
// std::vector<std::shared_ptr<Widget>> widgets;
bool m_focus{false};
bool m_pop{false};
};

View File

@@ -524,7 +524,7 @@ void App::Poll() {
m_touch_info.finger_id = touch_state.touches[0].finger_id;
m_touch_info.is_touching = true;
m_touch_info.is_tap = true;
PlaySoundEffect(SoundEffect_Limit);
// PlaySoundEffect(SoundEffect_Limit);
} else if (touch_state.count >= 1 && m_touch_info.is_touching && m_touch_info.finger_id == touch_state.touches[0].finger_id) {
m_touch_info.prev_x = m_touch_info.cur_x;
m_touch_info.prev_y = m_touch_info.cur_y;
@@ -597,39 +597,6 @@ auto App::GetVg() -> NVGcontext* {
return g_app->vg;
}
#if 0
void App::UpdateList() {
const auto index_copy = this->index;
const auto start_copy = this->start;
else if (controller.down) {
// todo: replace with actual focus
PlaySoundEffect(SoundEffect_Limit);
}
if (controller.any_direction()) {
if (index_copy != this->index) {
PlaySoundEffect(SoundEffect_Focus);
if (start_copy != this->start) {
// float r = randomGet64() % 100;
// float pitch = r / 100.0;
// plsrPlayerSetPitch(m_sound_ids[SoundEffect_Scroll], pitch);
PlaySoundEffect(SoundEffect_Scroll);
}
if (this->index == 0 || this->index == this->nro_entries.size() || ((controller.down & HidNpadButton_AnyLeft) && this->index && this->index % 3 == 0) || ((controller.down & HidNpadButton_AnyRight) && this->index && (this->index + 1) % 3 == 0)) {
PlaySoundEffect(SoundEffect_Limit);
}
} else {
const auto mask = HidNpadButton_AnyDown | HidNpadButton_AnyUp | HidNpadButton_AnyLeft | HidNpadButton_AnyRight;
if (controller.down & mask) {
PlaySoundEffect(SoundEffect_Limit);
}
}
}
}
#endif
void DrawElement(float x, float y, float w, float h, ThemeEntryID id) {
const auto& e = g_app->m_theme.elements[id];
@@ -738,12 +705,8 @@ void App::LoadTheme(const fs::FsPath& path) {
app->PlaySoundEffect(SoundEffect_Music);
}
}
} else if (key == "logo") {
theme.elements[ThemeEntryID_LOGO] = app->LoadElement(value);
} else if (key == "grid") {
theme.elements[ThemeEntryID_GRID] = app->LoadElement(value);
} else if (key == "grid_hover") {
theme.elements[ThemeEntryID_GRID_HOVER] = app->LoadElement(value);
} else if (key == "selected") {
theme.elements[ThemeEntryID_SELECTED] = app->LoadElement(value);
} else if (key == "selected_overlay") {
@@ -862,8 +825,8 @@ App::App(const char* argv0) {
m_app_path = argv0;
}
// set pop-back if applet and we are hbmenu
if (!IsApplication() && IsHbmenu()) {
// set if we are hbmenu
if (IsHbmenu()) {
__nx_applet_exit_mode = 1;
}
@@ -1083,6 +1046,29 @@ App::~App() {
} else {
log_write("success with copying over root file!\n");
}
} else if (IsHbmenu()) {
// check we have a version that's newer than current.
fs::FsNativeSd fs;
NacpStruct sphaira_nacp;
fs::FsPath sphaira_path = "/switch/sphaira/sphaira.nro";
Result rc;
rc = nro_get_nacp(sphaira_path, sphaira_nacp);
if (R_FAILED(rc) || std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
sphaira_path = "/switch/sphaira.nro";
rc = nro_get_nacp(sphaira_path, sphaira_nacp);
}
// found sphaira, now lets get compare version
if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
if (std::strcmp(APP_VERSION, sphaira_nacp.display_version) < 0) {
if (R_FAILED(rc = fs.copy_entire_file(GetExePath(), sphaira_path, true))) {
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path, rc, R_MODULE(rc), R_DESCRIPTION(rc));
} else {
log_write("success with updating hbmenu!\n");
}
}
}
}
if (App::GetNxlinkEnable()) {

View File

@@ -11,7 +11,7 @@ std::vector<u8> g_i18n_data;
yyjson_doc* json;
yyjson_val* root;
std::string get(const char* str, size_t len) {
std::string get_internal(const char* str, size_t len) {
if (!json || !root) {
log_write("no json or root\n");
return str;
@@ -78,8 +78,18 @@ bool init(long index) {
default: lang_name = "en"; break;
}
const fs::FsPath path = "romfs:/i18n/" + lang_name + ".json";
if (R_SUCCEEDED(fs::FsStdio().read_entire_file(path, g_i18n_data))) {
const fs::FsPath sdmc_path = "/config/sphaira/i18n/" + lang_name + ".json";
const fs::FsPath romfs_path = "romfs:/i18n/" + lang_name + ".json";
fs::FsPath path = sdmc_path;
// try and load override translation first
Result rc = fs::FsNativeSd().read_entire_file(path, g_i18n_data);
if (R_FAILED(rc)) {
path = romfs_path;
rc = fs::FsStdio().read_entire_file(path, g_i18n_data);
}
if (R_SUCCEEDED(rc)) {
json = yyjson_read((const char*)g_i18n_data.data(), g_i18n_data.size(), YYJSON_READ_ALLOW_TRAILING_COMMAS|YYJSON_READ_ALLOW_COMMENTS|YYJSON_READ_ALLOW_INVALID_UNICODE);
if (json) {
root = yyjson_doc_get_root(json);
@@ -107,12 +117,16 @@ void exit() {
g_i18n_data.clear();
}
std::string get(const char* str) {
return get_internal(str, std::strlen(str));
}
} // namespace sphaira::i18n
namespace literals {
std::string operator"" _i18n(const char* str, size_t len) {
return sphaira::i18n::get(str, len);
return sphaira::i18n::get_internal(str, len);
}
} // namespace literals

View File

@@ -71,7 +71,6 @@ auto nro_parse_internal(fs::FsNative& fs, const fs::FsPath& path, NroEntry& entr
} else {
R_TRY(fsFileRead(&f, data.header.size + asset.nacp.offset, &entry.nacp, sizeof(entry.nacp), FsReadOption_None, &bytes_read));
entry.is_nacp_valid = true;
log_write("got nacp\n");
}
// lazy load the icons
@@ -241,7 +240,7 @@ auto nro_get_icon(const fs::FsPath& path) -> std::vector<u8> {
R_TRY_RESULT(fsFileRead(&f, data.header.size, &asset, sizeof(asset), FsReadOption_None, &bytes_read), {});
R_UNLESS(asset.magic == NROASSETHEADER_MAGIC, {});
return nro_get_icon_internal(&f, asset.icon.size, asset.icon.offset);
return nro_get_icon_internal(&f, asset.icon.size, data.header.size + asset.icon.offset);
}
auto nro_get_nacp(const fs::FsPath& path, NacpStruct& nacp) -> Result {

View File

@@ -224,7 +224,7 @@ void loop(void* args) {
};
while (!g_quit) {
svcSleepThread(33'333'333);
svcSleepThread(1000000);
if (poll_network_change()) {
continue;
@@ -267,7 +267,7 @@ void loop(void* args) {
sockaddr_in sa_remote{};
while (!g_quit) {
svcSleepThread(33'333'333);
svcSleepThread(10000);
if (poll_network_change()) {
break;
@@ -297,7 +297,7 @@ void loop(void* args) {
}
fs::FsPath name{};
if (namelen > sizeof(name)) {
if (namelen >= sizeof(name)) {
log_write("namelen is bigger than name: 0x%X\n", socketGetLastResult());
continue;
}

View File

@@ -10,7 +10,7 @@ struct Config {
bool numpad{};
};
Result ShowInternal(Config& cfg, const char* guide, s64 len_min, s64 len_max) {
Result ShowInternal(Config& cfg, const char* guide, const char* initial, s64 len_min, s64 len_max) {
SwkbdConfig c;
R_TRY(swkbdCreate(&c, 0));
swkbdConfigMakePresetDefault(&c);
@@ -24,6 +24,10 @@ Result ShowInternal(Config& cfg, const char* guide, s64 len_min, s64 len_max) {
swkbdConfigSetGuideText(&c, guide);
}
if (initial) {
swkbdConfigSetInitialText(&c, initial);
}
if (len_min >= 0) {
swkbdConfigSetStringLenMin(&c, len_min);
}
@@ -37,16 +41,16 @@ Result ShowInternal(Config& cfg, const char* guide, s64 len_min, s64 len_max) {
} // namespace
Result ShowText(std::string& out, const char* guide, s64 len_min, s64 len_max) {
Result ShowText(std::string& out, const char* guide, const char* initial, s64 len_min, s64 len_max) {
Config cfg;
R_TRY(ShowInternal(cfg, guide, len_min, len_max));
R_TRY(ShowInternal(cfg, guide, initial, len_min, len_max));
out = cfg.out_text;
R_SUCCEED();
}
Result ShowNumPad(s64& out, const char* guide, s64 len_min, s64 len_max) {
Result ShowNumPad(s64& out, const char* guide, const char* initial, s64 len_min, s64 len_max) {
Config cfg;
R_TRY(ShowInternal(cfg, guide, len_min, len_max));
R_TRY(ShowInternal(cfg, guide, initial, len_min, len_max));
out = std::atoll(cfg.out_text);
R_SUCCEED();
}

View File

@@ -661,11 +661,13 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
std::make_pair(Button::DPAD_DOWN | Button::RS_DOWN, Action{[this](){
if (m_index < (m_options.size() - 1)) {
SetIndex(m_index + 1);
App::PlaySoundEffect(SoundEffect_Focus);
}
}}),
std::make_pair(Button::DPAD_UP | Button::RS_UP, Action{[this](){
if (m_index != 0) {
SetIndex(m_index - 1);
App::PlaySoundEffect(SoundEffect_Focus);
}
}}),
std::make_pair(Button::X, Action{"Options"_i18n, [this](){
@@ -1085,21 +1087,14 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"}, m_n
// check the date, if older than 1day, then fetch new file
// this relaxes the spam to their server, don't want to fetch repo
// every time the user opens the app!
const auto time_file = (time_t)time_stamp.created;
const auto time_cur = (time_t)current_time;
const auto tm_file = *gmtime(&time_file);
const auto tm_cur = *gmtime(&time_cur);
if (tm_cur.tm_yday > tm_file.tm_yday || tm_cur.tm_year > tm_file.tm_year) {
log_write("repo.json expired, downloading new! cur_yday: %d file_yday: %d | cur_year: %d file_year: %d\n", tm_cur.tm_yday, tm_file.tm_yday, tm_cur.tm_year, tm_file.tm_year);
const auto time_file = time_stamp.created;
const auto time_cur = current_time;
const auto day = 60 * 60 * 24;
if (time_file > time_cur || time_cur - time_file >= day) {
log_write("repo.json expired, downloading new! time_file: %zu time_cur: %zu\n", time_file, time_cur);
download_file = true;
} else {
log_write("repo.json not expired! cur_yday: %d file_yday: %d | cur_year: %d file_year: %d\n", tm_cur.tm_yday, tm_file.tm_yday, tm_cur.tm_year, tm_file.tm_year);
// time_file = (time_t)time_stamp.modified;
// tm_file = *gmtime(&time_file);
// log_write("repo.json not expired! cur_yday: %d file_yday: %d | cur_year: %d file_year: %d\n", tm_cur.tm_yday, tm_file.tm_yday, tm_cur.tm_year, tm_file.tm_year);
// time_file = (time_t)time_stamp.accessed;
// tm_file = *gmtime(&time_file);
// log_write("repo.json not expired! cur_yday: %d file_yday: %d | cur_year: %d file_year: %d\n", tm_cur.tm_yday, tm_file.tm_yday, tm_cur.tm_year, tm_file.tm_year);
log_write("repo.json not expired! time_file: %zu time_cur: %zu\n", time_file, time_cur);
}
}
@@ -1436,7 +1431,7 @@ void Menu::Sort() {
char subheader[128]{};
std::snprintf(subheader, sizeof(subheader), "Sort: %s | Filter: %s | Order: %s", SORT_STR[m_sort], FILTER_STR[m_filter], ORDER_STR[m_order]);
std::snprintf(subheader, sizeof(subheader), "Sort: %s | Filter: %s | Order: %s", i18n::get(SORT_STR[m_sort]).c_str(), i18n::get(FILTER_STR[m_filter]).c_str(), i18n::get(ORDER_STR[m_order]).c_str());
SetTitleSubHeading(subheader);
std::sort(m_entries_current.begin(), m_entries_current.end(), sorter);

View File

@@ -1,4 +1,5 @@
#include "ui/menus/filebrowser.hpp"
#include "ui/menus/homebrew.hpp"
#include "ui/sidebar.hpp"
#include "ui/option_box.hpp"
#include "ui/popup_list.hpp"
@@ -303,8 +304,6 @@ auto get_collection(fs::FsNative& fs, const fs::FsPath& path, const fs::FsPath&
R_SUCCEED();
}
#if 1
// recursion
auto get_collections(fs::FsNative& fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out) -> Result {
// get a list of all the files / dirs
FsDirCollection collection;
@@ -323,48 +322,6 @@ auto get_collections(fs::FsNative& fs, const fs::FsPath& path, const fs::FsPath&
R_SUCCEED();
}
#else
// normal
auto get_collections(fs::FsNative& fs, fs::FsPath path, fs::FsPath parent_name, FsDirCollections& out) -> Result {
// get a list of all the files / dirs
struct StoredStack {
fs::FsPath path;
fs::FsPath parent_name;
s64 index;
};
std::stack<StoredStack> stack;
s64 index{};
// std::vector<StoredStack> indexes;
while (true) {
FsDirCollection collection;
R_TRY(get_collection(fs, path, parent_name, collection, true, true, false));
out.emplace_back(collection);
if (collection.dirs.size()) {
stack.emplace(path, parent_name, index);
index = 0;
}
}
FsDirCollection collection;
R_TRY(get_collection(fs, 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());
out.emplace_back(collection);
// for (size_t i = 0; i < collection.dirs.size(); i++) {
for (const auto&p : collection.dirs) {
// 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);
R_TRY(get_collections(fs, *new_path, *new_parent_name, out));
}
R_SUCCEED();
}
#endif
auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out) -> Result {
fs::FsNativeSd fs;
@@ -392,6 +349,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
std::make_pair(Button::DOWN, Action{[this](){
if (m_index < (m_entries_current.size() - 1)) {
SetIndex(m_index + 1);
App::PlaySoundEffect(SoundEffect_Scroll);
if (m_index - m_index_offset >= 8) {
log_write("moved down\n");
m_index_offset++;
@@ -401,6 +359,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
std::make_pair(Button::UP, Action{[this](){
if (m_index != 0) {
SetIndex(m_index - 1);
App::PlaySoundEffect(SoundEffect_Scroll);
if (m_index < m_index_offset ) {
log_write("moved up\n");
m_index_offset--;
@@ -571,12 +530,13 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
}
// can't rename more than 1 file
if (m_entries_current.size() && m_selected_count < 2) {
if (m_entries_current.size() && !m_selected_count) {
options->Add(std::make_shared<SidebarEntryCallback>("Rename"_i18n, [this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, "Set New File Name", -1, FS_MAX_PATH)) && !out.empty()) {
const auto& entry = GetEntry();
const auto src_path = GetNewPathCurrent();
const auto& entry = GetEntry();
const auto name = entry.GetName();
if (R_SUCCEEDED(swkbd::ShowText(out, "Set New File Name", name.c_str())) && !out.empty() && out != name) {
const auto src_path = GetNewPath(entry);
const auto dst_path = GetNewPath(m_path, out);
Result rc;
@@ -603,7 +563,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
options->Add(std::make_shared<SidebarEntryCallback>("Create File"_i18n, [this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, "Set File Name", -1, FS_MAX_PATH)) && !out.empty()) {
if (R_SUCCEEDED(swkbd::ShowText(out, "Set File Name")) && !out.empty()) {
fs::FsPath full_path;
if (out[0] == '/') {
full_path = out;
@@ -624,7 +584,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
options->Add(std::make_shared<SidebarEntryCallback>("Create Folder"_i18n, [this](){
std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, "Set Folder Name", -1, FS_MAX_PATH)) && !out.empty()) {
if (R_SUCCEEDED(swkbd::ShowText(out, "Set Folder Name")) && !out.empty()) {
fs::FsPath full_path;
if (out[0] == '/') {
full_path = out;
@@ -648,18 +608,22 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
}
if (m_entries_current.size()) {
if (HasTypeInSelectedEntries(FsDirEntryType_File) && m_selected_count < 2 && !FindFileAssocFor().empty()) {
if (HasTypeInSelectedEntries(FsDirEntryType_File) && !m_selected_count && (GetEntry().GetExtension() == "nro" || !FindFileAssocFor().empty())) {
options->Add(std::make_shared<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){;
#if 1
App::Push(std::make_shared<OptionBox>(
"WARNING: Installing forwarders will lead to a ban!"_i18n,
"Back"_i18n, "Install"_i18n, 1, [this](auto op_index){
"Back"_i18n, "Install"_i18n, 0, [this](auto op_index){
if (op_index && *op_index) {
InstallForwarder();
if (GetEntry().GetExtension() == "nro") {
if (R_FAILED(homebrew::Menu::InstallHomebrewFromPath(GetNewPathCurrent()))) {
log_write("failed to create forwarder\n");
}
} else {
InstallForwarder();
}
}
}
));
#endif
}));
}
@@ -809,7 +773,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
nvgSave(vg);
nvgScissor(vg, x + text_xoffset+65, y, w-(x+text_xoffset+65+50), h);
const auto txt_clip = std::min(GetY() + GetH(), y + h) - y;
nvgScissor(vg, x + text_xoffset+65, y, w-(x+text_xoffset+65+50), txt_clip);
gfx::drawText(vg, x + text_xoffset+65, y + (h / 2.f), 20.f, e.name, NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->elements[text_id].colour);
nvgRestore(vg);
@@ -826,8 +791,9 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
}
const auto t = (time_t)(e.time_stamp.modified);
const auto tm = gmtime(&t);
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->elements[text_id].colour, "%02u/%02u/%u", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900);
struct tm tm{};
localtime_r(&t, &tm);
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->elements[text_id].colour, "%02u/%02u/%u", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900);
if ((double)e.file_size / 1024.0 / 1024.0 <= 0.009) {
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->elements[text_id].colour, "%.2f KiB", (double)e.file_size / 1024.0);
} else {
@@ -910,25 +876,6 @@ void Menu::InstallForwarder() {
title, items, [this, assoc_list](auto op_index){
if (op_index) {
const auto assoc = assoc_list[*op_index];
#if 1
#if 0
NroEntry nro{};
log_write("parsing nro\n");
if (R_FAILED(nro_parse(assoc.path, nro))) {
log_write("failed nro parse\n");
return;
}
OwoConfig config{};
config.nro_path = nro.path.toString();
// config.args = nro_add_arg_file(GetNewPathCurrent());
config.name = nro.nacp.lang[0].name;// + std::string{" | "} + file_name;
// config.name = file_name;
config.nacp = nro.nacp;
// config.icon = GetRomIcon(pbox, file_name, extension, database, nro);
config.icon = nro.icon;// GetRomIcon(pbox, file_name, extension, database, nro);
App::Install(config);
#else
log_write("pushing it\n");
App::Push(std::make_shared<ProgressBox>("Installing Forwarder", [assoc, this](auto pbox) -> bool {
log_write("inside callback\n");
@@ -961,36 +908,6 @@ void Menu::InstallForwarder() {
return R_SUCCEEDED(App::Install(pbox, config));
}));
#endif
#else
const auto& assoc = assoc_list[*op_index];
log_write("doing nro parse\n");
NroEntry nro{};
if (R_SUCCEEDED(nro_parse(assoc.path.c_str(), nro))) {
log_write("got nro data\n");
std::string file_name = GetEntry().GetInternalName();
std::string extension = GetEntry().GetInternalExtension();
if (auto pos = file_name.find_last_of('.'); pos != std::string::npos) {
log_write("got filename\n");
file_name = file_name.substr(0, pos);
log_write("got filename2: %s\n\n", file_name.c_str());
}
const auto database = GetRomDatabaseFromPath(m_path);
OwoConfig config{};
config.nro_path = assoc.path;
config.args = nro_add_arg_file(GetNewPathCurrent());
config.name = nro.nacp.lang[0].name + std::string{" | "} + file_name;
// config.name = file_name;
config.nacp = nro.nacp;
config.icon = GetRomIcon(file_name, extension, database, nro);
App::Install(config);
}
#endif
} else {
log_write("pressed B to skip launch...\n");
}

View File

@@ -17,38 +17,6 @@
namespace sphaira::ui::menu::homebrew {
namespace {
constexpr const char* SORT_STR[] = {
"Updated",
"Size",
"Alphabetical",
};
constexpr const char* ORDER_STR[] = {
"Desc",
"Asc",
};
// returns seconds as: hh:mm:ss
auto TimeFormat(u64 sec) -> std::string {
char buf[9];
const auto s = sec % 60;
const auto h = sec / 60 % 60;
const auto d = sec / 60 / 60 % 24;
if (sec < 60) {
if (!sec) {
return "00:00:00";
}
std::snprintf(buf, sizeof(buf), "00:00:%02lu", s);
} else if (sec < 3600) {
std::snprintf(buf, sizeof(buf), "00:%02lu:%02lu", h, s);
} else {
std::snprintf(buf, sizeof(buf), "%02lu:%02lu:%02lu", d, h, s);
}
return std::string{buf};
}
} // namespace
@@ -72,11 +40,10 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
if (m_index < (m_entries.size() - 1)) {
if (m_index < (m_entries.size() - 3)) {
SetIndex(m_index + 3);
App::PlaySoundEffect(SoundEffect_Scroll);
} else {
SetIndex(m_entries.size() - 1);
App::PlaySoundEffect(SoundEffect_Scroll);
}
App::PlaySoundEffect(SoundEffect_Scroll);
if (m_index - m_start >= 9) {
log_write("moved down\n");
m_start += 3;
@@ -153,7 +120,7 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
options->Add(std::make_shared<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){
App::Push(std::make_shared<OptionBox>(
"WARNING: Installing forwarders will lead to a ban!"_i18n,
"Back"_i18n, "Install"_i18n, 1, [this](auto op_index){
"Back"_i18n, "Install"_i18n, 0, [this](auto op_index){
if (op_index && *op_index) {
InstallHomebrew();
}
@@ -263,11 +230,7 @@ void Menu::SetIndex(std::size_t index) {
void Menu::InstallHomebrew() {
const auto& nro = m_entries[m_index];
OwoConfig config{};
config.nro_path = nro.path.toString();
config.nacp = nro.nacp;
config.icon = nro.icon;
App::Install(config);
InstallHomebrew(nro.path, nro.nacp, nro.icon);
}
void Menu::ScanHomebrew() {
@@ -275,22 +238,11 @@ void Menu::ScanHomebrew() {
nro_scan("/switch", m_entries, m_hide_sphaira.Get());
log_write("nros found: %zu time_taken: %.2f\n", m_entries.size(), ts.GetSeconds());
// todo: optimise this. maybe create a file per entry
// which would speed up parsing
for (auto& e : m_entries) {
if (ini_hassection(e.path, App::PLAYLOG_PATH)) {
// log_write("has section for: %s\n", e.path);
e.hbini.timestamp = ini_getl(e.path, "timestamp", 0, App::PLAYLOG_PATH);
}
e.image = 0; // images are lazy loaded
}
#if 0
struct IniUser {
std::vector<NroEntry>& entires;
Hbini* ini;
std::string last_section;
} ini_user { m_entries };
} ini_user{ m_entries };
ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
auto user = static_cast<IniUser*>(UserData);
@@ -308,14 +260,16 @@ void Menu::ScanHomebrew() {
}
if (user->ini) {
if (!strcmp(Key, "timestamp")) {
user->ini->timestamp = atoi(Value);
} else if (!strcmp(Key, "launch_count")) {
user->ini->launch_count = atoi(Value);
}
}
// app->
log_write("found: %s %s %s\n", Section, Key, Value);
// log_write("found: %s %s %s\n", Section, Key, Value);
return 1;
}, &ini_user, App::PLAYLOG_PATH);
#endif
this->Sort();
SetIndex(0);
@@ -374,4 +328,19 @@ void Menu::SortAndFindLastFile() {
Sort();
}
Result Menu::InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon) {
OwoConfig config{};
config.nro_path = path.toString();
config.nacp = nacp;
config.icon = icon;
return App::Install(config);
}
Result Menu::InstallHomebrewFromPath(const fs::FsPath& path) {
NacpStruct nacp;
R_TRY(nro_get_nacp(path, nacp))
const auto icon = nro_get_icon(path);
return InstallHomebrew(path, nacp, icon);
}
} // namespace sphaira::ui::menu::homebrew

View File

@@ -1,76 +1,187 @@
#include "ui/menus/main_menu.hpp"
#include "ui/menus/irs_menu.hpp"
#include "ui/menus/themezer.hpp"
#include "ui/sidebar.hpp"
#include "ui/popup_list.hpp"
#include "ui/option_box.hpp"
#include "ui/progress_box.hpp"
#include "ui/error_box.hpp"
#include "app.hpp"
#include "log.hpp"
#include "download.hpp"
#include "defines.hpp"
#include "ui/menus/irs_menu.hpp"
#include "ui/menus/themezer.hpp"
#include "web.hpp"
#include "i18n.hpp"
#include <cstring>
#include <minizip/unzip.h>
#include <yyjson.h>
namespace sphaira::ui::menu::main {
namespace {
#if 0
bool parseSearch(const char *parse_string, const char *filter, char* new_string) {
char c;
u32 offset = 0;
const u32 filter_len = std::strlen(filter) - 1;
auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string version) -> bool {
static fs::FsPath zip_out{"/switch/sphaira/cache/update.zip"};
constexpr auto chunk_size = 1024 * 512; // 512KiB
while ((c = parse_string[offset++]) != '\0') {
if (c == *filter) {
for (u32 i = 0; c == filter[i]; i++) {
c = parse_string[offset++];
if (i == filter_len) {
for (u32 j = 0; c != '\"'; j++) {
new_string[j] = c;
new_string[j+1] = '\0';
c = parse_string[offset++];
fs::FsNativeSd fs;
R_TRY_RESULT(fs.GetFsOpenResult(), false);
// 1. download the zip
if (!pbox->ShouldExit()) {
pbox->NewTransfer("Downloading "_i18n + version);
log_write("starting download: %s\n", url.c_str());
DownloadClearCache(url);
if (!DownloadFile(url, zip_out, "", [pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){
if (pbox->ShouldExit()) {
return false;
}
pbox->UpdateTransfer(dlnow, dltotal);
return true;
})) {
log_write("error with download\n");
// push popup error box
return false;
}
}
ON_SCOPE_EXIT(fs.DeleteFile(zip_out));
// 2. extract the zip
if (!pbox->ShouldExit()) {
auto zfile = unzOpen64(zip_out);
if (!zfile) {
log_write("failed to open zip: %s\n", zip_out);
return false;
}
ON_SCOPE_EXIT(unzClose(zfile));
unz_global_info64 pglobal_info;
if (UNZ_OK != unzGetGlobalInfo64(zfile, &pglobal_info)) {
return false;
}
for (int i = 0; i < pglobal_info.number_entry; i++) {
if (i > 0) {
if (UNZ_OK != unzGoToNextFile(zfile)) {
log_write("failed to unzGoToNextFile\n");
return false;
}
}
if (UNZ_OK != unzOpenCurrentFile(zfile)) {
log_write("failed to open current file\n");
return false;
}
ON_SCOPE_EXIT(unzCloseCurrentFile(zfile));
unz_file_info64 info;
fs::FsPath file_path;
if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, file_path, sizeof(file_path), 0, 0, 0, 0)) {
log_write("failed to get current info\n");
return false;
}
if (file_path[0] != '/') {
file_path = fs::AppendPath("/", file_path);
}
Result rc;
if (file_path[strlen(file_path) -1] == '/') {
if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_ResultPathAlreadyExists) {
log_write("failed to create folder: %s 0x%04X\n", file_path, rc);
return false;
}
} else {
Result rc;
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_ResultPathAlreadyExists) {
log_write("failed to create file: %s 0x%04X\n", file_path, 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);
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);
return false;
}
std::vector<char> buf(chunk_size);
u64 offset{};
while (offset < info.uncompressed_size) {
if (pbox->ShouldExit()) {
return false;
}
return true;
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
if (bytes_read <= 0) {
// log_write("failed to read zip file: %s\n", inzip.c_str());
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", file_path, rc);
return false;
}
pbox->UpdateTransfer(offset, info.uncompressed_size);
offset += bytes_read;
}
}
}
}
return false;
log_write("finished update :)\n");
return true;
}
#endif
} // namespace
MainMenu::MainMenu() {
#if 0
DownloadMemoryAsync("https://api.github.com/repos/ITotalJustice/sys-patch/releases/latest", [this](std::vector<u8>& data, bool success){
data.push_back('\0');
auto raw_str = (const char*)data.data();
char out_str[0x301];
DownloadMemoryAsync("https://api.github.com/repos/ITotalJustice/sphaira/releases/latest", "", [this](std::vector<u8>& data, bool success){
m_update_state = UpdateState::None;
auto json = yyjson_read((const char*)data.data(), data.size(), 0);
R_UNLESS(json, false);
ON_SCOPE_EXIT(yyjson_doc_free(json));
if (parseSearch(raw_str, "tag_name\":\"", out_str)) {
m_update_version = out_str;
if (strcasecmp("v1.5.0", m_update_version.c_str())) {
m_update_avaliable = true;
}
log_write("FOUND IT : %s\n", out_str);
}
auto root = yyjson_doc_get_root(json);
R_UNLESS(root, false);
if (parseSearch(raw_str, "browser_download_url\":\"", out_str)) {
m_update_url = out_str;
log_write("FOUND IT : %s\n", out_str);
}
auto tag_key = yyjson_obj_get(root, "tag_name");
R_UNLESS(tag_key, false);
if (parseSearch(raw_str, "body\":\"", out_str)) {
m_update_description = out_str;
// m_update_description.replace("\r\n\r\n", "\n");
log_write("FOUND IT : %s\n", out_str);
}
const auto version = yyjson_get_str(tag_key);
R_UNLESS(version, false);
R_UNLESS(std::strcmp(APP_VERSION, version) < 0, false);
auto assets = yyjson_obj_get(root, "assets");
R_UNLESS(assets, false);
auto idx0 = yyjson_arr_get(assets, 0);
R_UNLESS(idx0, false);
auto url_key = yyjson_obj_get(idx0, "browser_download_url");
R_UNLESS(url_key, false);
const auto url = yyjson_get_str(url_key);
R_UNLESS(url, false);
m_update_version = version;
m_update_url = url;
m_update_state = UpdateState::Update;
log_write("found url: %s\n", url);
App::Notify("Update avaliable: "_i18n + m_update_version);
return true;
});
#endif
AddOnLPress();
AddOnRPress();
@@ -128,22 +239,26 @@ MainMenu::MainMenu() {
options->Add(std::make_shared<SidebarEntryBool>("Nxlink"_i18n, App::GetNxlinkEnable(), [this](bool& enable){
App::SetNxlinkEnable(enable);
}, "Enabled"_i18n, "Disabled"_i18n));
options->Add(std::make_shared<SidebarEntryCallback>("Check for update"_i18n, [this](){
App::Notify("Not Implemented"_i18n);
}));
if (m_update_state == UpdateState::Update) {
options->Add(std::make_shared<SidebarEntryCallback>("Download update: "_i18n + m_update_version, [this](){
App::Push(std::make_shared<ProgressBox>("Downloading "_i18n + m_update_version, [this](auto pbox){
return InstallUpdate(pbox, m_update_url, m_update_version);
}, [this](bool success){
if (success) {
m_update_state = UpdateState::None;
} else {
App::Push(std::make_shared<ui::ErrorBox>(MAKERESULT(351, 1), "Failed to download update"));
}
}, 2));
}));
}
}));
options->Add(std::make_shared<SidebarEntryArray>("Language"_i18n, language_items, [this, language_items](std::size_t& index_out){
App::SetLanguage(index_out);
}, (std::size_t)App::GetLanguage()));
if (m_update_avaliable) {
std::string str = "Update avaliable: "_i18n + m_update_version;
options->Add(std::make_shared<SidebarEntryCallback>(str, [this](){
App::Notify("Not Implemented"_i18n);
}));
}
options->Add(std::make_shared<SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [this](bool& enable){
App::SetLogEnable(enable);
}, "Enabled"_i18n, "Disabled"_i18n));

View File

@@ -32,6 +32,10 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) {
u32 strength{};
u32 ip{};
const auto t = time(NULL);
struct tm tm{};
localtime_r(&t, &tm);
// todo: app thread poll every 1s and this query the result
psmGetBatteryChargePercentage(&battery_percetange);
psmGetChargerType(&charger_type);
@@ -54,7 +58,8 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) {
start_x -= spacing;
// draw("version %s", APP_VERSION);
draw("%u%%", battery_percetange);
draw("%u\uFE6A", battery_percetange);
draw("%02u:%02u:%02u", tm.tm_hour, tm.tm_min, tm.tm_sec);
if (ip) {
draw("%u.%u.%u.%u", ip&0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF);
} else {

View File

@@ -508,7 +508,7 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
options->Add(std::make_shared<SidebarEntryCallback>("Page"_i18n, [this](){
s64 out;
if (R_SUCCEEDED(swkbd::ShowNumPad(out, "Enter Page Number", -1, 3))) {
if (R_SUCCEEDED(swkbd::ShowNumPad(out, "Enter Page Number", nullptr, -1, 3))) {
if (out < m_page_index_max) {
m_page_index = out;
PackListDownload();

View File

@@ -457,7 +457,7 @@ void drawButton(NVGcontext* vg, float x, float y, float size, Button button) {
drawText(vg, x, y, size, getButton(button), nullptr, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, getColour(Colour::WHITE));
}
void drawButtons(NVGcontext* vg, const Widget::Actions& actions, const NVGcolor& c, float start_x) {
void drawButtons(NVGcontext* vg, const Widget::Actions& _actions, const NVGcolor& c, float start_x) {
nvgBeginPath(vg);
nvgFontSize(vg, 24.f);
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
@@ -467,6 +467,22 @@ void drawButtons(NVGcontext* vg, const Widget::Actions& actions, const NVGcolor&
const float y = 675.f;
float bounds[4]{};
// swaps L/R position, idc how shit this is, it's called once per frame.
std::vector<std::pair<Button, Action>> actions;
actions.reserve(_actions.size());
for (const auto a: _actions) {
// swap
if (a.first == Button::R && actions.size() && actions.back().first == Button::L) {
const auto s = actions.back();
actions.back() = a;
actions.emplace_back(s);
} else {
actions.emplace_back(a);
}
}
for (const auto& [button, action] : actions) {
if (action.IsHidden() || action.m_hint.empty()) {
continue;

View File

@@ -123,6 +123,7 @@ auto OptionBox::Setup(std::size_t index) -> void {
m_entries[m_index].Selected(false);
m_index--;
m_entries[m_index].Selected(true);
App::PlaySoundEffect(SoundEffect_Focus);
}
}}),
std::make_pair(Button::RIGHT, Action{[this](){
@@ -130,6 +131,7 @@ auto OptionBox::Setup(std::size_t index) -> void {
m_entries[m_index].Selected(false);
m_index++;
m_entries[m_index].Selected(true);
App::PlaySoundEffect(SoundEffect_Focus);
}
}}),
std::make_pair(Button::A, Action{[this](){

View File

@@ -90,6 +90,8 @@ auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void {
return;
}
const auto old_index = m_index;
if (controller->GotDown(Button::DOWN) && m_index < (m_items.size() - 1)) {
m_index++;
m_selected_y += m_block.h;
@@ -98,7 +100,10 @@ auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void {
m_selected_y -= m_block.h;
}
OnLayoutChange();
if (old_index != m_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
OnLayoutChange();
}
}
auto PopupList::OnLayoutChange() -> void {

View File

@@ -27,6 +27,7 @@ ScrollableText::ScrollableText(const std::string& text, float x, float y, float
}
m_y_off -= m_step;
m_index++;
App::PlaySoundEffect(SoundEffect_Scroll);
}}),
std::make_pair(Button::LS_UP, Action{[this](){
if (m_y_off == m_y_off_base) {
@@ -34,6 +35,7 @@ ScrollableText::ScrollableText(const std::string& text, float x, float y, float
}
m_y_off += m_step;
m_index--;
App::PlaySoundEffect(SoundEffect_Scroll);
}})
);

View File

@@ -234,6 +234,7 @@ auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void {
// if we moved
if (m_index != old_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
m_items[old_index]->OnFocusLost();
m_items[m_index]->OnFocusGained();

View File

@@ -7,8 +7,10 @@ namespace sphaira::ui {
void Widget::Update(Controller* controller, TouchInfo* touch) {
for (const auto& [button, action] : m_actions) {
if ((action.m_type & ActionType::DOWN) && controller->GotDown(button)) {
if (static_cast<u64>(button) & static_cast<u64>(Button::ANY_BUTTON)) {
App::PlaySoundEffect(SoundEffect_Focus);
}
action.Invoke(true);
App::PlaySoundEffect(SoundEffect_Focus);
}
else if ((action.m_type & ActionType::UP) && controller->GotUp(button)) {
action.Invoke(false);