27 Commits

Author SHA1 Message Date
ITotalJustice
43ebab52d4 bump version for new release 0.11.3 -> 0.12.0 2025-06-05 01:00:08 +01:00
ITotalJustice
a5f9eaa392 compress save backup by default 2025-06-05 00:42:29 +01:00
ITotalJustice
cc2064f296 remove save support from ftpsrv as it may conflict with the new save menu. 2025-06-05 00:32:40 +01:00
ITotalJustice
f2462cff81 add save backup/restore, fix filebrowser touch screen, optimise all zip/unzip file code by using bigger stdio buffers. 2025-06-05 00:17:55 +01:00
xHR
b2e25abf08 fixed long strings, translate untranslated (#164) 2025-06-04 14:13:57 +01:00
ITotalJustice
cd0817bd11 add explicit sleep in between batch delete calls in order to not pin core3.
otherwise, core3 is pinned and button inputs (including the power button) become unresponsive.
2025-06-03 02:36:53 +01:00
ITotalJustice
e88ca8ede1 fix nvjpg icon loading if w*bpp != pitch. add build option to enable/disable nvjpg (for testing). 2025-06-03 02:16:23 +01:00
ITotalJustice
7a83269d98 disable nvjpg for homebrew menu (for now).
see https://github.com/averne/oss-nvjpg/issues/1.
2025-06-02 23:09:31 +01:00
ITotalJustice
4be1d48215 use oss-nvjpg for loading jpeg images (homebrew, games and themezer).
slightly faster loading on avg compared to stbi.
2025-06-02 22:18:38 +01:00
ITotalJustice
8485ff1e99 use nxtc (nx title cache) for caching titles in the game menu. 2025-06-02 20:52:08 +01:00
Chronoss
be66b10f49 Update fr.json (#162) 2025-06-02 18:02:01 +01:00
游家小少
1f22971493 中文语言文件更新 (#163)
* Update zh.json

Revise some Chinese translations
2025-06-02 18:01:43 +01:00
ITotalJustice
ea943088e5 usbds only set zlt on write 2025-06-02 18:00:40 +01:00
ITotalJustice
298be4a344 appstore add option to show installed files for app, add option to launch app website. slightly round scrollbar. 2025-06-02 17:51:48 +01:00
ITotalJustice
f37fc13b7c bump yyjson version 0.10.0 -> 0.11.1 (silences cmake warning) 2025-06-01 23:51:41 +01:00
ITotalJustice
506b74868e remove old screenshots. 2025-05-31 22:15:16 +01:00
ITotalJustice
4a59d1cfda add support for loading custom forwarder gif/logo from file. 2025-05-31 22:14:02 +01:00
ITotalJustice
7201c8347f handle left/right side menu swapping.
see #153
2025-05-31 18:45:57 +01:00
ITotalJustice
c8a3df3cfc merge Chinese translation from #159
fixes #159
2025-05-31 18:20:10 +01:00
ITotalJustice
2ef7742903 Merge branch 'multi_thread_everything' 2025-05-31 18:08:17 +01:00
ITotalJustice
f98135325a fix icon/list layout where the highlighted border would be clipped. 2025-05-31 18:03:12 +01:00
Yorunokyujitsu
fd765aa8c8 Updated theme, new translated strings, adjust left side split-screen x position. (#156)
* Add a new ThemeEntryID for split-screen and selected items and modify the theme.

* Adjust the position of the left side split-screen in the filebrowser menu.

* Add new strings and update Korean and Japanese translations.

* fix ja.json.

---------

Co-authored-by: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com>
2025-05-31 18:02:53 +01:00
Ny'hrarr
ec93dd5a7d Add default theme and update screenshots (#160)
* Create sphaira_theme.ini

* Tweak colors and add default

* Add updated screenshots
2025-05-31 17:36:54 +01:00
Ny'hrarr
0e885ff2d5 Pt patches (#157)
* Update pt.json

* Update pt.json

* Added plural and one missing entry.
2025-05-31 17:35:55 +01:00
ITotalJustice
5893cb575e fix ncz block installs, fix error module value being out of range, display error on install from filebrowser.
the issue with block installs was that i was not tracking the ncz block offset in between transfers.
this resulted in the block size being used for each transfer, rather then size-offset.

for blocks that were always compressed, this silently worked as zstd stream can handle multiple frames.
however, if there existed compressed and uncompressed blocks, then this bug would be exposed.

thanks to Marulv for reporting the bug.
2025-05-31 17:30:28 +01:00
ITotalJustice
b46136b959 optimise fs CreateDirectoryRecursively() by checking if the path already exists prior to the loop. 2025-05-30 13:16:39 +01:00
ITotalJustice
390c1e870d multi-thread zip and unzip code. option to download appstore zip to mem. hasher mem support. 2025-05-30 12:34:29 +01:00
80 changed files with 3735 additions and 1346 deletions

View File

@@ -4,16 +4,16 @@ A homebrew menu for the Nintendo Switch.
[See the GBATemp thread for more details / discussion](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/). [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. [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 ## Showcase
| | | | | |
:-------------------------:|:-------------------------: :-------------------------:|:-------------------------:
![Img](assets/screenshots/2024121522512100-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121522514300-879193CD6A8B96CD00931A628B1187CB.jpg) ![Img](assets/screenshots/homebrew.jpg) | ![Img](assets/screenshots/games.jpg)
![Img](assets/screenshots/2024121522513300-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121523084100-879193CD6A8B96CD00931A628B1187CB.jpg) ![Img](assets/screenshots/appstore.jpg) | ![Img](assets/screenshots/appstore_page.jpg)
![Img](assets/screenshots/2024121522505300-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121522502300-879193CD6A8B96CD00931A628B1187CB.jpg) ![Img](assets/screenshots/file_browser.jpg) | ![Img](assets/screenshots/launch_options.jpg)
![Img](assets/screenshots/2024121523033200-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121523070300-879193CD6A8B96CD00931A628B1187CB.jpg) ![Img](assets/screenshots/themezer.jpg) | ![Img](assets/screenshots/web.jpg)
## Bug reports ## Bug reports
@@ -86,16 +86,21 @@ The output will be found in `build/MinSizeRel/sphaira.nro`
## Credits ## Credits
- borealis - [borealis](https://github.com/natinusala/borealis)
- stb - [stb](https://github.com/nothings/stb)
- yyjson - [yyjson](https://github.com/ibireme/yyjson)
- nx-hbmenu - [nx-hbmenu](https://github.com/switchbrew/nx-hbmenu)
- nx-hbloader - [nx-hbloader](https://github.com/switchbrew/nx-hbloader)
- deko3d-nanovg - [deko3d-nanovg](https://github.com/Adubbz/nanovg-deko3d)
- libpulsar - [libpulsar](https://github.com/p-sam/switch-libpulsar)
- minIni - [minIni](https://github.com/compuphase/minIni)
- GBATemp - [GBATemp](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/)
- hb-appstore - [hb-appstore](https://github.com/fortheusers/hb-appstore)
- haze - [haze](https://github.com/Atmosphere-NX/Atmosphere/tree/master/troposphere/haze)
- nxdumptool (for gamecard bin dumping and rsa verify code) - [nxdumptool](https://github.com/DarkMatterCore/nxdumptool) (for gamecard bin dumping and rsa verify code)
- [libusbhsfs](https://github.com/DarkMatterCore/libusbhsfs)
- [libnxtc](https://github.com/DarkMatterCore/libnxtc)
- [oss-nvjpg](https://github.com/averne/oss-nvjpg)
- [nsz](https://github.com/nicoboss/nsz)
- [themezer](https://themezer.net/)
- Everyone who has contributed to this project! - Everyone who has contributed to this project!

View File

@@ -3,6 +3,7 @@
"No Internet": "Kein Internet", "No Internet": "Kein Internet",
"Switch-Handheld!": "Handheld!", "Switch-Handheld!": "Handheld!",
"Switch-Docked!": "Angedockt!", "Switch-Docked!": "Angedockt!",
"Warning! Logs are enabled, Sphaira will run slowly!": "",
"Audio disabled due to suspended game": "Audio deaktivert wegen Spielabbruch", "Audio disabled due to suspended game": "Audio deaktivert wegen Spielabbruch",
"Are you sure you wish to cancel?": "Bist du sicher dass du abbrechen willst?", "Are you sure you wish to cancel?": "Bist du sicher dass du abbrechen willst?",
"An error occurred": "", "An error occurred": "",
@@ -27,6 +28,8 @@
"Nxlink Connected": "NXLink | Verbunden", "Nxlink Connected": "NXLink | Verbunden",
"Nxlink Upload": "NXLink | wird hochgeladen...", "Nxlink Upload": "NXLink | wird hochgeladen...",
"Nxlink Finished": "NXLink | Hochladen beendet", "Nxlink Finished": "NXLink | Hochladen beendet",
"Hdd": "",
"Hdd write protect": "",
"Language": "Sprache", "Language": "Sprache",
"Auto": "Systemsprache", "Auto": "Systemsprache",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "", "No meta entries found...\n": "",
"Updating application record list": "", "Updating application record list": "",
"Dump": "", "Dump": "",
"Dump options": "",
"Dump Options": "",
"Select content to dump": "", "Select content to dump": "",
"Dump All": "", "Dump All": "",
"Dump Application": "", "Dump Application": "",
"Dump Patch": "", "Dump Patch": "",
"Dump AddOnContent": "", "Dump AddOnContent": "",
"Dump DataPatch": "", "Dump DataPatch": "",
"Created nested folder": "",
"Append folder with .xci": "",
"Trim XCI": "",
"Label trimmed XCI": "",
"Multi-threaded USB transfer": "",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "", "Select dump location": "",
"microSD card (/dumps/NSP/)": "", "microSD card (/dumps/)": "",
"USB transfer (Switch 2 Switch)": "", "USB transfer (Switch 2 Switch)": "",
"/dev/null (Speed Test)": "", "/dev/null (Speed Test)": "",
"Dumping": "", "Dumping": "",
"Dump successfull!": "", "Dump successfull!": "",
"Dump failed!": "", "Dump failed!": "",
"Success": "",
"Delete successfull!": "", "Delete successfull!": "",
"Delete failed!": "", "Delete failed!": "",
"Success": "",
"Themezer": "Themezer | NX Themes", "Themezer": "Themezer | NX Themes",
"Themezer Options": " Themezer | Optionen", "Themezer Options": " Themezer | Optionen",
@@ -86,6 +102,8 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "Lade JSON-File", "Downloading json": "Lade JSON-File",
"Select asset to download for ": "Wähle Asset für den Download von ", "Select asset to download for ": "Wähle Asset für den Download von ",
"Failed to download json": "",
"Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"FTP Install (EXPERIMENTAL)": "", "FTP Install (EXPERIMENTAL)": "",
@@ -122,9 +140,12 @@
"GC": "", "GC": "",
"System memory %.1f GB": "", "System memory %.1f GB": "",
"microSD card %.1f GB": "", "microSD card %.1f GB": "",
"Nand Install": "",
"SD Card Install": "",
"Exit": "", "Exit": "",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "", "Gc install success!": "",
"Gc install failed!": "", "Gc install failed!": "",
@@ -160,14 +181,14 @@
"Negative image": "Negativ-Bild", "Negative image": "Negativ-Bild",
"Format": "Format", "Format": "Format",
"Trimming Format": "Beschnitt-Format", "Trimming Format": "Beschnitt-Format",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Externes Lichtfilter", "External Light Filter": "Externes Lichtfilter",
"Load Default": "Standard laden", "Load Default": "Standard laden",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Erweitert...", "Advanced": "Erweitert...",
"Advanced Options": " Erweitert | Optionen", "Advanced Options": " Erweitert | Optionen",
"Logging": "Protokollieren", "Logging": "Protokollieren",
@@ -181,10 +202,12 @@
"Restored hbmenu": "hbmenu wurde wiederhergestellt", "Restored hbmenu": "hbmenu wurde wiederhergestellt",
"Restart Sphaira?": "sphaira erneut starten?", "Restart Sphaira?": "sphaira erneut starten?",
"Press OK to restart Sphaira": "Drücke OK um sphaira erneut zustarten", "Press OK to restart Sphaira": "Drücke OK um sphaira erneut zustarten",
"Boost CPU during transfer": "",
"Text scroll speed": "Laufschrift Tempo", "Text scroll speed": "Laufschrift Tempo",
"Slow": "Niedrig", "Slow": "Niedrig",
"Normal": "Mittel", "Normal": "Mittel",
"Fast": "Hoch", "Fast": "Hoch",
"Set left-side menu": "",
"Set right-side menu": "", "Set right-side menu": "",
"Install options": "", "Install options": "",
"Install Options": "", "Install Options": "",
@@ -194,7 +217,6 @@
"Install location": "Einhängepunkt", "Install location": "Einhängepunkt",
"System memory": "NAND Systemspeicher", "System memory": "NAND Systemspeicher",
"microSD card": "SD-Karte", "microSD card": "SD-Karte",
"Boost CPU clock": "",
"Allow downgrade": "", "Allow downgrade": "",
"Skip if already installed": "", "Skip if already installed": "",
"Ticket only": "", "Ticket only": "",
@@ -225,6 +247,8 @@
"Updating ncm databse": "Aktualisiere NCM-Datenbank", "Updating ncm databse": "Aktualisiere NCM-Datenbank",
"Pushing application record": "Übertrage Anwendungsdaten", "Pushing application record": "Übertrage Anwendungsdaten",
"Failed to install forwarder": "Fehler beim installieren des Forwarders", "Failed to install forwarder": "Fehler beim installieren des Forwarders",
"Unstar": "Kein Favorit",
"Star": "Favorit",
"Unstarred ": "Favorit entfernt ", "Unstarred ": "Favorit entfernt ",
"Starred ": "Favorit ", "Starred ": "Favorit ",
"Failed to remove old forwarder, please manually remove it!": "", "Failed to remove old forwarder, please manually remove it!": "",
@@ -257,12 +281,13 @@
"Copy": "Kopieren", "Copy": "Kopieren",
"Copying ": "Kopiert wird: ", "Copying ": "Kopiert wird: ",
"Paste": "Einfügen", "Paste": "Einfügen",
"Paste ": "Einfügen von: ", "Paste file(s)?": "",
" file(s)?": " Datei/en?",
"Pasting ": "Eingefügt wird: ", "Pasting ": "Eingefügt wird: ",
"Pasting": "Eingefügt wurde:", "Pasting": "Eingefügt wurde:",
"Rename": "Umbenennen", "Rename": "Umbenennen",
"Set New File Name": "Neuen Dateinamen festlegen", "Set New File Name": "Neuen Dateinamen festlegen",
"Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "", "Extract zip": "",
"Extract Options": "", "Extract Options": "",
"Extract here": "", "Extract here": "",
@@ -285,13 +310,17 @@
"Create Folder": "Neuer Ordner", "Create Folder": "Neuer Ordner",
"Set Folder Name": "Ordner umbenennen", "Set Folder Name": "Ordner umbenennen",
"Creating ": "Erstellt wird: ", "Creating ": "Erstellt wird: ",
"View as text (unfinished)": "Als Text anzeigen",
"Upload": "", "Upload": "",
"Select upload location": "", "Select upload location": "",
"No upload locations set!": "", "No upload locations set!": "",
"Uploading": "", "Uploading": "",
"Upload successfull!": "", "Upload successfull!": "",
"Upload failed!": "", "Upload failed!": "",
"View as text (unfinished)": "Als Text anzeigen", "Hash": "",
"Hash Options": "",
"Hashing": "",
"Failed to hash file...": "",
"Ignore read only": "Schreibschutz umgehen?", "Ignore read only": "Schreibschutz umgehen?",
"Mount": "Einhängen", "Mount": "Einhängen",
"Sd": "SD-Karte | Root-Verzeichnis", "Sd": "SD-Karte | Root-Verzeichnis",
@@ -302,6 +331,7 @@
"Launch ": "Starte ", "Launch ": "Starte ",
"Launch option for: ": "Start Option für: ", "Launch option for: ": "Start Option für: ",
"Select launcher for: ": "Wähle Launcher für: ", "Select launcher for: ": "Wähle Launcher für: ",
"Close FileBrowser?": "",
"Sort By": "Sortierung", "Sort By": "Sortierung",
"Sort Options": " Sortierung | Optionen", "Sort Options": " Sortierung | Optionen",
@@ -335,16 +365,16 @@
"Search": "Suchen", "Search": "Suchen",
"Options": "Optionen", "Options": "Optionen",
"Split": "",
"OK": "OK", "OK": "OK",
"Back": "Zurück", "Back": "Zurück",
"Select": "Auswählen", "Select": "Auswählen",
"Open": "Öffne", "Open": "Öffne",
"Close": "",
"Launch": "Starte", "Launch": "Starte",
"Restart": "Neustart", "Restart": "Neustart",
"Next": "", "Next": "",
"Prev": "", "Prev": "",
"Unstar": "Kein Favorit",
"Star": "Favorit",
"Yes": "Ja", "Yes": "Ja",
"No": "Nein", "No": "Nein",
"On": "", "On": "",
@@ -367,12 +397,13 @@
"Remove": "Entfernen", "Remove": "Entfernen",
"Completely remove ": "Komplett gelöscht wird: ", "Completely remove ": "Komplett gelöscht wird: ",
"Removing ": "Entfernt wird: ", "Removing ": "Entfernt wird: ",
"Removed ": "Entfernt wurde: ",
"Uninstalling ": "Deinstalliert wird: ", "Uninstalling ": "Deinstalliert wird: ",
"Removed ": "Entfernt wurde: ",
"Download": "Download", "Download": "Download",
"Downloading ": "Heruntergeladen wird: ", "Downloading ": "Heruntergeladen wird: ",
"Downloaded ": "Heruntergeladen wurde: ", "Downloaded ": "Heruntergeladen wurde: ",
"Download via the Network options!": "",
"Update": "Update", "Update": "Update",
"Update avaliable: ": "Update verfügbar: ", "Update avaliable: ": "Update verfügbar: ",

View File

@@ -3,6 +3,7 @@
"No Internet": "No Internet", "No Internet": "No Internet",
"Switch-Handheld!": "Switch-Handheld!", "Switch-Handheld!": "Switch-Handheld!",
"Switch-Docked!": "Switch-Docked!", "Switch-Docked!": "Switch-Docked!",
"Warning! Logs are enabled, Sphaira will run slowly!": "Warning! Logs are enabled, Sphaira will run slowly!",
"Audio disabled due to suspended game": "Audio disabled due to suspended game", "Audio disabled due to suspended game": "Audio disabled due to suspended game",
"Are you sure you wish to cancel?": "Are you sure you wish to cancel?", "Are you sure you wish to cancel?": "Are you sure you wish to cancel?",
"An error occurred": "An error occurred", "An error occurred": "An error occurred",
@@ -27,6 +28,8 @@
"Nxlink Connected": "Nxlink Connected", "Nxlink Connected": "Nxlink Connected",
"Nxlink Upload": "Nxlink Upload", "Nxlink Upload": "Nxlink Upload",
"Nxlink Finished": "Nxlink Finished", "Nxlink Finished": "Nxlink Finished",
"Hdd": "Hdd",
"Hdd write protect": "Hdd write protect",
"Language": "Language", "Language": "Language",
"Auto": "Auto", "Auto": "Auto",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "No meta entries found...\n", "No meta entries found...\n": "No meta entries found...\n",
"Updating application record list": "Updating application record list", "Updating application record list": "Updating application record list",
"Dump": "Dump", "Dump": "Dump",
"Dump options": "Dump options",
"Dump Options": "Dump Options",
"Select content to dump": "Select content to dump", "Select content to dump": "Select content to dump",
"Dump All": "Dump All", "Dump All": "Dump All",
"Dump Application": "Dump Application", "Dump Application": "Dump Application",
"Dump Patch": "Dump Patch", "Dump Patch": "Dump Patch",
"Dump AddOnContent": "Dump AddOnContent", "Dump AddOnContent": "Dump AddOnContent",
"Dump DataPatch": "Dump DataPatch", "Dump DataPatch": "Dump DataPatch",
"Created nested folder": "Created nested folder",
"Append folder with .xci": "Append folder with .xci",
"Trim XCI": "Trim XCI",
"Label trimmed XCI": "Label trimmed XCI",
"Multi-threaded USB transfer": "Multi-threaded USB transfer",
"Dump All Bins": "Dump All Bins",
"Dump XCI": "Dump XCI",
"Dump Card ID Set": "Dump Card ID Set",
"Dump Card UID": "Dump Card UID",
"Dump Certificate": "Dump Certificate",
"Dump Initial Data": "Dump Initial Data",
"Select dump location": "Select dump location", "Select dump location": "Select dump location",
"microSD card (/dumps/NSP/)": "microSD card (/dumps/NSP/)", "microSD card (/dumps/)": "microSD card (/dumps/)",
"USB transfer (Switch 2 Switch)": "USB transfer (Switch 2 Switch)", "USB transfer (Switch 2 Switch)": "USB transfer (Switch 2 Switch)",
"/dev/null (Speed Test)": "/dev/null (Speed Test)", "/dev/null (Speed Test)": "/dev/null (Speed Test)",
"Dumping": "Dumping", "Dumping": "Dumping",
"Dump successfull!": "Dump successfull!", "Dump successfull!": "Dump successfull!",
"Dump failed!": "Dump failed!", "Dump failed!": "Dump failed!",
"Success": "Success",
"Delete successfull!": "Delete successfull!", "Delete successfull!": "Delete successfull!",
"Delete failed!": "Delete failed!", "Delete failed!": "Delete failed!",
"Success": "Success",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Themezer Options", "Themezer Options": "Themezer Options",
@@ -86,6 +102,8 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "Downloading json", "Downloading json": "Downloading json",
"Select asset to download for ": "Select asset to download for ", "Select asset to download for ": "Select asset to download for ",
"Failed to download json": "Failed to download json",
"Failed to download app!": "Failed to download app!",
"FTP Install": "FTP Install", "FTP Install": "FTP Install",
"FTP Install (EXPERIMENTAL)": "FTP Install (EXPERIMENTAL)", "FTP Install (EXPERIMENTAL)": "FTP Install (EXPERIMENTAL)",
@@ -122,9 +140,12 @@
"GC": "GC", "GC": "GC",
"System memory %.1f GB": "System memory %.1f GB", "System memory %.1f GB": "System memory %.1f GB",
"microSD card %.1f GB": "microSD card %.1f GB", "microSD card %.1f GB": "microSD card %.1f GB",
"Nand Install": "Nand Install",
"SD Card Install": "SD Card Install",
"Exit": "Exit", "Exit": "Exit",
"Install disabled...\nPlease enable installing via the install options.": "Install disabled...\nPlease enable installing via the install options.",
"No GameCard inserted": "No GameCard inserted",
"GameCard is already trimmed!": "GameCard is already trimmed!",
"WARNING: GameCard is already trimmed!": "WARNING: GameCard is already trimmed!",
"Continue": "Continue",
"Gc install success!": "Gc install success!", "Gc install success!": "Gc install success!",
"Gc install failed!": "Gc install failed!", "Gc install failed!": "Gc install failed!",
@@ -160,14 +181,14 @@
"Negative image": "Negative image", "Negative image": "Negative image",
"Format": "Format", "Format": "Format",
"Trimming Format": "Trimming Format", "Trimming Format": "Trimming Format",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "External Light Filter", "External Light Filter": "External Light Filter",
"Load Default": "Load Default", "Load Default": "Load Default",
"Web": "Web",
"Select URL": "Select URL",
"Enter custom URL": "Enter custom URL",
"Enter URL": "Enter URL",
"Advanced": "Advanced", "Advanced": "Advanced",
"Advanced Options": "Advanced Options", "Advanced Options": "Advanced Options",
"Logging": "Logging", "Logging": "Logging",
@@ -181,10 +202,12 @@
"Restored hbmenu": "Restored hbmenu", "Restored hbmenu": "Restored hbmenu",
"Restart Sphaira?": "Restart Sphaira?", "Restart Sphaira?": "Restart Sphaira?",
"Press OK to restart Sphaira": "Press OK to restart Sphaira", "Press OK to restart Sphaira": "Press OK to restart Sphaira",
"Boost CPU during transfer": "Boost CPU during transfer",
"Text scroll speed": "Text scroll speed", "Text scroll speed": "Text scroll speed",
"Slow": "Slow", "Slow": "Slow",
"Normal": "Normal", "Normal": "Normal",
"Fast": "Fast", "Fast": "Fast",
"Set left-side menu": "Set left-side menu",
"Set right-side menu": "Set right-side menu", "Set right-side menu": "Set right-side menu",
"Install options": "Install options", "Install options": "Install options",
"Install Options": "Install Options", "Install Options": "Install Options",
@@ -194,7 +217,6 @@
"Install location": "Install location", "Install location": "Install location",
"System memory": "System memory", "System memory": "System memory",
"microSD card": "microSD card", "microSD card": "microSD card",
"Boost CPU clock": "Boost CPU clock",
"Allow downgrade": "Allow downgrade", "Allow downgrade": "Allow downgrade",
"Skip if already installed": "Skip if already installed", "Skip if already installed": "Skip if already installed",
"Ticket only": "Ticket only", "Ticket only": "Ticket only",
@@ -225,6 +247,8 @@
"Updating ncm databse": "Updating ncm databse", "Updating ncm databse": "Updating ncm databse",
"Pushing application record": "Pushing application record", "Pushing application record": "Pushing application record",
"Failed to install forwarder": "Failed to install forwarder", "Failed to install forwarder": "Failed to install forwarder",
"Unstar": "Unstar",
"Star": "Star",
"Unstarred ": "Unstarred ", "Unstarred ": "Unstarred ",
"Starred ": "Starred ", "Starred ": "Starred ",
"Failed to remove old forwarder, please manually remove it!": "Failed to remove old forwarder, please manually remove it!", "Failed to remove old forwarder, please manually remove it!": "Failed to remove old forwarder, please manually remove it!",
@@ -257,12 +281,13 @@
"Copy": "Copy", "Copy": "Copy",
"Copying ": "Copying ", "Copying ": "Copying ",
"Paste": "Paste", "Paste": "Paste",
"Paste ": "Paste ", "Paste file(s)?": "Paste file(s)?",
" file(s)?": " file(s)?",
"Pasting ": "Pasting ", "Pasting ": "Pasting ",
"Pasting": "Pasting", "Pasting": "Pasting",
"Rename": "Rename", "Rename": "Rename",
"Set New File Name": "Set New File Name", "Set New File Name": "Set New File Name",
"Failed to delete directory": "Failed to delete directory",
"Failed to delete file": "Failed to delete file",
"Extract zip": "Extract zip", "Extract zip": "Extract zip",
"Extract Options": "Extract Options", "Extract Options": "Extract Options",
"Extract here": "Extract here", "Extract here": "Extract here",
@@ -285,13 +310,17 @@
"Create Folder": "Create Folder", "Create Folder": "Create Folder",
"Set Folder Name": "Set Folder Name", "Set Folder Name": "Set Folder Name",
"Creating ": "Creating ", "Creating ": "Creating ",
"View as text (unfinished)": "View as text (unfinished)",
"Upload": "Upload", "Upload": "Upload",
"Select upload location": "Select upload location", "Select upload location": "Select upload location",
"No upload locations set!": "No upload locations set!", "No upload locations set!": "No upload locations set!",
"Uploading": "Uploading", "Uploading": "Uploading",
"Upload successfull!": "Upload successfull!", "Upload successfull!": "Upload successfull!",
"Upload failed!": "Upload failed!", "Upload failed!": "Upload failed!",
"View as text (unfinished)": "View as text (unfinished)", "Hash": "Hash",
"Hash Options": "Hash Options",
"Hashing": "Hashing",
"Failed to hash file...": "Failed to hash file...",
"Ignore read only": "Ignore read only", "Ignore read only": "Ignore read only",
"Mount": "Mount", "Mount": "Mount",
"Sd": "Sd", "Sd": "Sd",
@@ -302,6 +331,7 @@
"Launch ": "Launch ", "Launch ": "Launch ",
"Launch option for: ": "Launch option for: ", "Launch option for: ": "Launch option for: ",
"Select launcher for: ": "Select launcher for: ", "Select launcher for: ": "Select launcher for: ",
"Close FileBrowser?": "Close FileBrowser?",
"Sort By": "Sort By", "Sort By": "Sort By",
"Sort Options": "Sort Options", "Sort Options": "Sort Options",
@@ -335,16 +365,16 @@
"Search": "Search", "Search": "Search",
"Options": "Options", "Options": "Options",
"Split": "Split",
"OK": "OK", "OK": "OK",
"Back": "Back", "Back": "Back",
"Select": "Select", "Select": "Select",
"Open": "Open", "Open": "Open",
"Close": "Close",
"Launch": "Launch", "Launch": "Launch",
"Restart": "Restart", "Restart": "Restart",
"Next": "Next", "Next": "Next",
"Prev": "Prev", "Prev": "Prev",
"Unstar": "Unstar",
"Star": "Star",
"Yes": "Yes", "Yes": "Yes",
"No": "No", "No": "No",
"On": "On", "On": "On",
@@ -367,12 +397,13 @@
"Remove": "Remove", "Remove": "Remove",
"Completely remove ": "Completely remove ", "Completely remove ": "Completely remove ",
"Removing ": "Removing ", "Removing ": "Removing ",
"Removed ": "Removed ",
"Uninstalling ": "Uninstalling ", "Uninstalling ": "Uninstalling ",
"Removed ": "Removed ",
"Download": "Download", "Download": "Download",
"Downloading ": "Downloading ", "Downloading ": "Downloading ",
"Downloaded ": "Downloaded ", "Downloaded ": "Downloaded ",
"Download via the Network options!": "Download via the Network options!",
"Update": "Update", "Update": "Update",
"Update avaliable: ": "Update avaliable: ", "Update avaliable: ": "Update avaliable: ",

View File

@@ -3,6 +3,7 @@
"No Internet": "Sin Internet", "No Internet": "Sin Internet",
"Switch-Handheld!": "¡Switch-Modo-Portátil!", "Switch-Handheld!": "¡Switch-Modo-Portátil!",
"Switch-Docked!": "¡Switch-Modo-TV!", "Switch-Docked!": "¡Switch-Modo-TV!",
"Warning! Logs are enabled, Sphaira will run slowly!": "",
"Audio disabled due to suspended game": "", "Audio disabled due to suspended game": "",
"Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?", "Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?",
"An error occurred": "", "An error occurred": "",
@@ -27,6 +28,8 @@
"Nxlink Connected": "NXlink conectado", "Nxlink Connected": "NXlink conectado",
"Nxlink Upload": "NXlink subida", "Nxlink Upload": "NXlink subida",
"Nxlink Finished": "NXlink finalizado", "Nxlink Finished": "NXlink finalizado",
"Hdd": "",
"Hdd write protect": "",
"Language": "Idioma", "Language": "Idioma",
"Auto": "Automático", "Auto": "Automático",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "", "No meta entries found...\n": "",
"Updating application record list": "", "Updating application record list": "",
"Dump": "", "Dump": "",
"Dump options": "",
"Dump Options": "",
"Select content to dump": "", "Select content to dump": "",
"Dump All": "", "Dump All": "",
"Dump Application": "", "Dump Application": "",
"Dump Patch": "", "Dump Patch": "",
"Dump AddOnContent": "", "Dump AddOnContent": "",
"Dump DataPatch": "", "Dump DataPatch": "",
"Created nested folder": "",
"Append folder with .xci": "",
"Trim XCI": "",
"Label trimmed XCI": "",
"Multi-threaded USB transfer": "",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "", "Select dump location": "",
"microSD card (/dumps/NSP/)": "", "microSD card (/dumps/)": "",
"USB transfer (Switch 2 Switch)": "", "USB transfer (Switch 2 Switch)": "",
"/dev/null (Speed Test)": "", "/dev/null (Speed Test)": "",
"Dumping": "", "Dumping": "",
"Dump successfull!": "", "Dump successfull!": "",
"Dump failed!": "", "Dump failed!": "",
"Success": "",
"Delete successfull!": "", "Delete successfull!": "",
"Delete failed!": "", "Delete failed!": "",
"Success": "",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Opciones de Themezer", "Themezer Options": "Opciones de Themezer",
@@ -86,6 +102,8 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "Descargando json", "Downloading json": "Descargando json",
"Select asset to download for ": "Seleccionar recurso a descargar para ", "Select asset to download for ": "Seleccionar recurso a descargar para ",
"Failed to download json": "",
"Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"FTP Install (EXPERIMENTAL)": "", "FTP Install (EXPERIMENTAL)": "",
@@ -122,9 +140,12 @@
"GC": "", "GC": "",
"System memory %.1f GB": "", "System memory %.1f GB": "",
"microSD card %.1f GB": "", "microSD card %.1f GB": "",
"Nand Install": "",
"SD Card Install": "",
"Exit": "", "Exit": "",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "", "Gc install success!": "",
"Gc install failed!": "", "Gc install failed!": "",
@@ -160,14 +181,14 @@
"Negative image": "Imagen negativa", "Negative image": "Imagen negativa",
"Format": "Formato", "Format": "Formato",
"Trimming Format": "Formato de recorte", "Trimming Format": "Formato de recorte",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Filtro de luz externa", "External Light Filter": "Filtro de luz externa",
"Load Default": "Cargar predeterminado", "Load Default": "Cargar predeterminado",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Avanzado", "Advanced": "Avanzado",
"Advanced Options": "Opciones avanzadas", "Advanced Options": "Opciones avanzadas",
"Logging": "Registro", "Logging": "Registro",
@@ -181,10 +202,12 @@
"Restored hbmenu": "hbmenu restaurado", "Restored hbmenu": "hbmenu restaurado",
"Restart Sphaira?": "¿Reiniciar sphaira?", "Restart Sphaira?": "¿Reiniciar sphaira?",
"Press OK to restart Sphaira": "Presiona OK para reiniciar sphaira", "Press OK to restart Sphaira": "Presiona OK para reiniciar sphaira",
"Boost CPU during transfer": "",
"Text scroll speed": "Velocidad de scroll", "Text scroll speed": "Velocidad de scroll",
"Slow": "Lento", "Slow": "Lento",
"Normal": "Normal", "Normal": "Normal",
"Fast": "Rápido", "Fast": "Rápido",
"Set left-side menu": "",
"Set right-side menu": "", "Set right-side menu": "",
"Install options": "", "Install options": "",
"Install Options": "", "Install Options": "",
@@ -194,7 +217,6 @@
"Install location": "Dispositivo de instalación", "Install location": "Dispositivo de instalación",
"System memory": "Memoria de sistema", "System memory": "Memoria de sistema",
"microSD card": "microSD", "microSD card": "microSD",
"Boost CPU clock": "",
"Allow downgrade": "", "Allow downgrade": "",
"Skip if already installed": "", "Skip if already installed": "",
"Ticket only": "", "Ticket only": "",
@@ -225,6 +247,8 @@
"Updating ncm databse": "Actualizando base de datos ncm", "Updating ncm databse": "Actualizando base de datos ncm",
"Pushing application record": "Registro de aplicación", "Pushing application record": "Registro de aplicación",
"Failed to install forwarder": "Fallo al instalar forwarder", "Failed to install forwarder": "Fallo al instalar forwarder",
"Unstar": "Quitar favorito",
"Star": "Favorito",
"Unstarred ": "Quitar Favorito", "Unstarred ": "Quitar Favorito",
"Starred ": "Favorito", "Starred ": "Favorito",
"Failed to remove old forwarder, please manually remove it!": "", "Failed to remove old forwarder, please manually remove it!": "",
@@ -257,12 +281,13 @@
"Copy": "Copiar", "Copy": "Copiar",
"Copying ": "Copiando ", "Copying ": "Copiando ",
"Paste": "Pegar", "Paste": "Pegar",
"Paste ": "Pegar ", "Paste file(s)?": "",
" file(s)?": " ¿archivo(s)?",
"Pasting ": "Pegando ", "Pasting ": "Pegando ",
"Pasting": "Pegando", "Pasting": "Pegando",
"Rename": "Renombrar", "Rename": "Renombrar",
"Set New File Name": "Establecer nuevo nombre de archivo", "Set New File Name": "Establecer nuevo nombre de archivo",
"Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "", "Extract zip": "",
"Extract Options": "", "Extract Options": "",
"Extract here": "", "Extract here": "",
@@ -285,13 +310,17 @@
"Create Folder": "Crear carpeta", "Create Folder": "Crear carpeta",
"Set Folder Name": "Establecer nombre de carpeta", "Set Folder Name": "Establecer nombre de carpeta",
"Creating ": "Creando ", "Creating ": "Creando ",
"View as text (unfinished)": "Ver como texto (sin terminar)",
"Upload": "", "Upload": "",
"Select upload location": "", "Select upload location": "",
"No upload locations set!": "", "No upload locations set!": "",
"Uploading": "", "Uploading": "",
"Upload successfull!": "", "Upload successfull!": "",
"Upload failed!": "", "Upload failed!": "",
"View as text (unfinished)": "Ver como texto (sin terminar)", "Hash": "",
"Hash Options": "",
"Hashing": "",
"Failed to hash file...": "",
"Ignore read only": "Ignorar sólo lectura", "Ignore read only": "Ignorar sólo lectura",
"Mount": "Montar", "Mount": "Montar",
"Sd": "SD", "Sd": "SD",
@@ -302,6 +331,7 @@
"Launch ": "Abrir ", "Launch ": "Abrir ",
"Launch option for: ": "Opción de abrir con: ", "Launch option for: ": "Opción de abrir con: ",
"Select launcher for: ": "Seleccionar abrir con: ", "Select launcher for: ": "Seleccionar abrir con: ",
"Close FileBrowser?": "",
"Sort By": "Ordenar por", "Sort By": "Ordenar por",
"Sort Options": "Opciones de clasificación", "Sort Options": "Opciones de clasificación",
@@ -335,16 +365,16 @@
"Search": "Buscar", "Search": "Buscar",
"Options": "Opciones", "Options": "Opciones",
"Split": "",
"OK": "OK", "OK": "OK",
"Back": "Atrás", "Back": "Atrás",
"Select": "Seleccionar", "Select": "Seleccionar",
"Open": "Abrir", "Open": "Abrir",
"Close": "",
"Launch": "Ejecutar", "Launch": "Ejecutar",
"Restart": "Reiniciar", "Restart": "Reiniciar",
"Next": "", "Next": "",
"Prev": "", "Prev": "",
"Unstar": "Quitar favorito",
"Star": "Favorito",
"Yes": "Sí", "Yes": "Sí",
"No": "No", "No": "No",
"On": "", "On": "",
@@ -367,12 +397,13 @@
"Remove": "Borrar", "Remove": "Borrar",
"Completely remove ": "Eliminar completamente", "Completely remove ": "Eliminar completamente",
"Removing ": "Removiendo ", "Removing ": "Removiendo ",
"Removed ": "Removido ",
"Uninstalling ": "Desinstalando ", "Uninstalling ": "Desinstalando ",
"Removed ": "Removido ",
"Download": "Descargar", "Download": "Descargar",
"Downloading ": "Descargando ", "Downloading ": "Descargando ",
"Downloaded ": "Descargado ", "Downloaded ": "Descargado ",
"Download via the Network options!": "",
"Update": "Actualizar", "Update": "Actualizar",
"Update avaliable: ": "Actualización disponible: ", "Update avaliable: ": "Actualización disponible: ",

View File

@@ -1,32 +1,35 @@
{ {
"[Applet Mode]": "[Mode Applet]", "[Applet Mode]": "[Mode Applet]",
"No Internet": "Pas d'Internet", "No Internet": "Pas d'Internet",
"Switch-Handheld!": "Switch-Portable", "Switch-Handheld!": "Mode Portable",
"Switch-Docked!": "Switch-Dockée", "Switch-Docked!": "Mode Dockée",
"Audio disabled due to suspended game": "Audio désactivé à cause d'un jeu suspendu", "Warning! Logs are enabled, Sphaira will run slowly!": "Attention ! Les journaux sont activés, Sphaira fonctionnera lentement !",
"Audio disabled due to suspended game": "Audio désactivé à cause d'un jeu en pause",
"Are you sure you wish to cancel?": "Souhaitez-vous vraiment annuler ?", "Are you sure you wish to cancel?": "Souhaitez-vous vraiment annuler ?",
"An error occurred": "Une erreur s'est produite", "An error occurred": "Une erreur s'est produite",
"If this message appears repeatedly, please open an issue.": "Si ce message apparait en boucle veuillez ouvrir une issue.", "If this message appears repeatedly, please open an issue.": "Si ce message apparaît en boucle, veuillez ouvrir une issue sur :",
"Menu Options": "Options des Menus", "Menu Options": "Paramètres",
"Menu": "Menu", "Menu": "Paramètres",
"Theme": "Thème", "Theme": "Thèmes",
"Theme Options": "Options de Thème", "Theme Options": "Paramètres des Thèmes",
"Select Theme": "Choisir un Thème", "Select Theme": "Choisir un thème",
"Music": "Musique", "Music": "Musique",
"12 Hour Time": "Temps sur 12 heures", "12 Hour Time": "Format horloge 12 heures",
"Download Default Music": "Télécharger la musique par défaut", "Download Default Music": "Télécharger la musique par défaut",
"Failed to download default_music.bfstm, please try again": "Echec du téléchargement de default_music.bfstm, veuillez réessayer", "Failed to download default_music.bfstm, please try again": "Échec du téléchargement de default_music.bfstm, veuillez réessayer",
"Overwrite current default music?": "Remplacer la musique actuelle par défaut ?", "Overwrite current default music?": "Remplacer la musique actuelle par défaut ?",
"Network": "Réseau", "Network": "Réseau",
"Network Options": "Options Réseau", "Network Options": "Paramètres Réseau",
"Ftp": "FTP", "Ftp": "FTP",
"Mtp": "MTP", "Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink Connecté", "Nxlink Connected": "Nxlink Connecté",
"Nxlink Upload": "Nxlink téléversement", "Nxlink Upload": "Téléchargement Nxlink",
"Nxlink Finished": "Nxlink terminé", "Nxlink Finished": "Nxlink terminé",
"Hdd": "Disque Dur",
"Hdd write protect": "Protection en écriture du disque dur",
"Language": "Langue", "Language": "Langue",
"Auto": "Auto", "Auto": "Auto",
@@ -47,49 +50,64 @@
"Misc": "Divers", "Misc": "Divers",
"Misc Options": "Options Diverses", "Misc Options": "Options Diverses",
"Games": "Jeux", "Games": "Jeux installés",
"Game Options": "Options de jeu", "Game Options": "Options des jeux",
"Hide forwarders": "Masquer les forwarders", "Hide forwarders": "Masquer les forwarders",
"Launch random game": "Lancer un jeu au hasard", "Launch random game": "Lancer un jeu au hasard",
"List meta records": "Lister les enregistrements meta", "List meta records": "Lister les méta-enregistrements",
"Entries": "Entrées", "Entries": "Meta disponible",
"Failed to list application meta entries": "Le listage des entrées meta de l'application a échoué", "Failed to list application meta entries": "Le listage des entrées meta de l'application a échoué",
"No meta entries found...\n": "Aucune entrée meta trouvée...\n", "No meta entries found...\n": "Aucune entrée meta trouvée...\n",
"Updating application record list": "Mise à jour de la liste d'enregistrement de l'application", "Updating application record list": "Mise à jour de la liste d'enregistrement de l'application",
"Dump": "Dumper", "Dump": "Dumper",
"Select content to dump": "Sélectionner un contenu à dumper", "Dump options": "Option du Dumper",
"Dump options": "Paramètres",
"Select content to dump": "Contenu à dumper",
"Dump All": "Tout dumper", "Dump All": "Tout dumper",
"Dump Application": "Dumper Application", "Dump Application": "Dumper uniquement l'application (jeu)",
"Dump Patch": "Dumper mise à jour", "Dump Patch": "Dumper uniquement la mise à jour",
"Dump AddOnContent": "Dumper DLCs", "Dump AddOnContent": "Dumper uniquement le(s) DLCs",
"Dump DataPatch": "Dumper patch de données", "Dump DataPatch": "Dumper le patch de données",
"Select dump location": "Sélectionner l'emplacement du dump", "Created nested folder": "Créer un dossier imbriqué",
"microSD card (/dumps/NSP/)": "carte microSD (/dumps/NSP/)", "Append folder with .xci": "Ajouter un dossier avec .xci",
"Trim XCI": "Trimmer le XCI",
"Label trimmed XCI": "Étiquette sur le XCI trimmer",
"Multi-threaded USB transfer": "Transfert USB multithread",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "Sélectionner l'emplacement du dump :",
"microSD card (/dumps/)": "carte microSD (/dumps/)",
"USB transfer (Switch 2 Switch)": "Transfert USB (Switch à Switch", "USB transfer (Switch 2 Switch)": "Transfert USB (Switch à Switch",
"/dev/null (Speed Test)": "/dev/null (test de vitesse)", "/dev/null (Speed Test)": "/dev/null (test de vitesse)",
"Dumping": "Dump en cours", "Dumping": "Dump en cours...",
"Dump successfull!": "Dump réussi !", "Dump successfull!": "Dump réussi !",
"Dump failed!": "Dump échoué!", "Dump failed!": "Échec du dump !",
"Delete successful!": "Fichier supprimé !",
"Delete failed!": "Échec de la suppression !",
"Success": "Succès", "Success": "Succès",
"Delete successfull!": "Suprression réussie!",
"Delete failed!": "Suprression échouée!",
"Themezer": "Themezer", "Themezer": "Themezer (thèmes Switch)",
"Themezer Options": "Options Themezer", "Themezer Options": "Paramètres",
"Nsfw": "Nsfw", "Nsfw": "Contenu NSFW",
"Page": "Page", "Page": "Page",
"Page %zu / %zu": "Page %zu / %zu", "Page %zu / %zu": "Page %zu / %zu",
"Enter Page Number": "Entrez Un Numéro De Page", "Enter Page Number": "Numéro de la page :",
"Bad Page": "Page inexistante", "Bad Page": "Page inexistante",
"Download theme?": "Télécharger le thème?", "Download theme?": "Voulez-vous télécharger ce thème ?",
"GitHub": "GitHub", "GitHub": "Github",
"Downloading json": "Téléchargement du json", "Downloading json": "Téléchargement du .json",
"Select asset to download for ": "Sélectionner l'asset à télécharger pour ", "Select asset to download for ": "Sélectionnez l'actif à télécharger pour :",
"Failed to download json": "Échec du téléchargement de .json",
"Failed to download app!": "Échec du téléchargement de l'application",
"FTP Install": "Installation via FTP", "FTP Install": "Installer contenu via FTP",
"FTP Install (EXPERIMENTAL)": "Installation via FTP (EXPERIMENTAL)", "FTP Install (EXPERIMENTAL)": "Installation via FTP (EXPERIMENTAL)",
"Connection Type: WiFi | Strength: ": "Type de connexion: WiFi, force du signal: ", "Connection Type: WiFi | Strength: ": "Type de connexion : WiFi / Force du signal :",
"Connection Type: Ethernet": "Type de connexion : Ethernet", "Connection Type: Ethernet": "Type de connexion : Ethernet",
"Connection Type: None": "Type de connexion : Aucune", "Connection Type: None": "Type de connexion : Aucune",
"Host:": "Hôte :", "Host:": "Hôte :",
@@ -101,11 +119,11 @@
"Failed to install via FTP, press B to exit...": "Installation via FTP échouée, appuyer sur B pour quitter...", "Failed to install via FTP, press B to exit...": "Installation via FTP échouée, appuyer sur B pour quitter...",
"Ftp install success!": "Installation via FTP réussie !", "Ftp install success!": "Installation via FTP réussie !",
"Ftp install failed!": "Installation via FTP échouée !", "Ftp install failed!": "Installation via FTP échouée !",
"USB Install": "Installation via USB", "USB Install": "Installer contenu via USB",
"USB": "USB", "USB": "Installation via USB",
"Connected, waiting for file list...": "Connecté, en attente de la liste des fichiers...", "Connected, waiting for file list...": "Connecté, en attente de la liste des fichiers...",
"Connected, starting transfer...": "Connecté, début du transfère...", "Connected, starting transfer...": "Connecté, début du transfère...",
"Failed to init usb, press B to exit...": "Echec de l'initialisation de l'USB, appuyer sur B pour quitter...", "Failed to init usb, press B to exit...": "Échec de l'initialisation de l'USB, appuyer sur B pour quitter...",
"Waiting for connection...": "En attente de la connexion...", "Waiting for connection...": "En attente de la connexion...",
"Transferring data...": "Transfère de données...", "Transferring data...": "Transfère de données...",
"USB connected, sending file list": "USB connecté, envoi de la liste des fichiers", "USB connected, sending file list": "USB connecté, envoi de la liste des fichiers",
@@ -117,26 +135,29 @@
"Usb install success!": "Installation via USB réussie !", "Usb install success!": "Installation via USB réussie !",
"Usb install failed!": "Installation via USB échouée !", "Usb install failed!": "Installation via USB échouée !",
"Press B to exit...": "Appuyer sur B pour quitter...", "Press B to exit...": "Appuyer sur B pour quitter...",
"GameCard Install": "Installation de la cartouche", "GameCard Install": "Installation de la cartouche du jeu",
"GameCard": "Cartouche", "GameCard": "Carte du jeu",
"GC": "GC", "GC": "GC",
"System memory %.1f GB": "Mémoire système %.1f GB", "System memory %.1f GB": "Mémoire système %.1f GB",
"microSD card %.1f GB": "Carte microSD %.1f GB", "microSD card %.1f GB": "Carte microSD %.1f GB",
"Nand Install": "Installer sur la Nand",
"SD Card Install": "Installer sur la carte SD",
"Exit": "Quitter", "Exit": "Quitter",
"Install disabled...\nPlease enable installing via the install options.": "Installation désactivée...\nVeuillez activer l'installation via les options d'installation.",
"No GameCard inserted": "Aucune cartouche de jeu insérée !",
"GameCard is already trimmed!": "La carte du jeu est déjà trimmer !",
"WARNING: GameCard is already trimmed!": "AVERTISSEMENT : la carte du jeu est déjà trimmer ! ",
"Continue": "Continuer",
"Gc install success!": "Installation de la cartouche réussie !", "Gc install success!": "Installation de la cartouche réussie !",
"Gc install failed!": "Installation de la cartouche échouée !", "Gc install failed!": "Installation de la cartouche échouée !",
"IRS (Infrared Joycon Camera)": "IRS (infrarouge de la caméra du Joycon", "IRS (Infrared Joycon Camera)": "IRS (infrarouge de la cam du Joycon",
"IRS": "IRS", "IRS": "Infrarouge Camera",
"Irs": "Irs", "Irs": "Irs",
"Ambient Noise Level: ": "Niveau De Bruit Ambiant: ", "Ambient Noise Level: ": "Niveau de bruit ambiant: ",
"Controller": "Contrôleur", "Controller": "Contrôleur",
"Pad ": "Manette", "Pad ": "Manette",
"HandHeld": "Portable", "HandHeld": "Joycon",
" (Available)": " (Disponible)", " (Available)": " (Connectée)",
" (Unsupported)": "Non supporté", " (Unsupported)": "Non supportée",
" (Unconnected)": " (Non connectée)", " (Unconnected)": " (Non connectée)",
"Rotation": "Rotation", "Rotation": "Rotation",
"0 (Sideways)": "0 (Paysage)", "0 (Sideways)": "0 (Paysage)",
@@ -155,59 +176,60 @@
"Dim group": "Groupe sombre", "Dim group": "Groupe sombre",
"None": "Aucun", "None": "Aucun",
"Gain": "Gain", "Gain": "Gain",
"Negative Image": "Image Négative", "Negative Image": "Format d'image",
"Normal image": "Image normale", "Normal image": "Normale",
"Negative image": "Image négative", "Negative image": "Négative",
"Format": "Format", "Format": "Format d'image",
"Trimming Format": "Format de Découpe", "Trimming Format": "Format du découpage",
"320x240": "320×240", "External Light Filter": "Filtre de lumière externe",
"160x120": "160×120", "Load Default": "Rétablir par Défaut",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Filtre de Lumière Externe",
"Load Default": "Charger par Défaut",
"Advanced": "Avancé", "Web": "Web",
"Advanced Options": "Options Avancées", "Select URL": "Sélectionnez une adresse",
"Logging": "Journalisation", "Enter custom URL": "Entrez une adresse personnalisée",
"Replace hbmenu on exit": "Remplacer hbmenu quand quitté", "Enter URL": "Enter l'adresse :",
"Restore hbmenu?": "Restaurer hbmenu?",
"Restore": "Restaurer", "Advanced": "Menu avancé",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmenu.nro n'a pas été trouvé\nUtiliser l'Appstore pour réinstaller le hbmenu", "Advanced Options": "Menu avancé",
"Failed to restore hbmenu, please re-download hbmenu": "Echec de la restauration de hbmenu, veuillez le réinstaller", "Logging": "Fichier journal",
"Failed to restore hbmenu, using sphaira instead": "Echec de la restauration de hbmenu, sphaira sera utilisé à la place", "Replace hbmenu on exit": "Remplacer HBmenu par Sphaira",
"Restored hbmenu, closing sphaira": "Hbmenu restauré, fermeture de sphaira", "Restore hbmenu?": "Restaurer HBmenu ?",
"Restored hbmenu": "Hbmenu restauré", "Restore": "Restaurer !",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmenu.nro non trouvé\nRéinstaller le HBmenu via Appstore",
"Failed to restore hbmenu, please re-download hbmenu": "Échec de la restauration du HBmenu, veuillez le réinstaller",
"Failed to restore hbmenu, using sphaira instead": "Échec de la restauration de HBmenu, Sphaira sera utilisé à la place",
"Restored hbmenu, closing sphaira": "Hbmenu restauré, fermeture de Sphaira",
"Restored hbmenu": "HBmenu restauré !",
"Restart Sphaira?": "Redémarrer Sphaira ?", "Restart Sphaira?": "Redémarrer Sphaira ?",
"Press OK to restart Sphaira": "Appuyez sur OK pour redémarrer Sphaira", "Press OK to restart Sphaira": "Appuyez sur OK pour redémarrer Sphaira",
"Boost CPU during transfer": "Augmenter le CPU pendant le transfert",
"Text scroll speed": "Vitesse de défilement du texte", "Text scroll speed": "Vitesse de défilement du texte",
"Slow": "Lent", "Slow": "Lent",
"Normal": "Normal", "Normal": "Normal",
"Fast": "Rapide", "Fast": "Rapide",
"Set right-side menu": "Configurer le menu de droite", "Set left-side menu": "Menu de gauche par défaut",
"Install options": "Options d'installation", "Set right-side menu": "Menu de droite par défaut",
"Install options": "Options d'installation des jeux",
"Install Options": "Options d'Installation", "Install Options": "Options d'Installation",
"Enable sysmmc": "Activer sur la sysmmc", "Enable sysmmc": "Afficher la SYSmmc",
"Enable emummc": "Activer sur l'emummc", "Enable emummc": "Afficher sur l'EMUmmc",
"Show install warning": "Afficher l'avertissement d'installation", "Show install warning": "Avertissement lors d'installation",
"Install location": "Emplacement d'installation", "Install location": "Emplacement d'installation",
"System memory": "Mémoire système", "System memory": "Mémoire système",
"microSD card": "Carte microSD", "microSD card": "Carte microSD",
"Boost CPU clock": "Augmenter la vitesse de l'horloge CPU",
"Allow downgrade": "Autoriser le downgrade", "Allow downgrade": "Autoriser le downgrade",
"Skip if already installed": "Ignorer si déjà installé", "Skip if already installed": "Ignorer si déjà installé",
"Ticket only": "Seulement le ticket", "Ticket only": "Seulement le ticket",
"Skip base": "Ignorer base", "Skip base": "Ignorer la base du jeu",
"Skip patch": "Ignorer mise à jour", "Skip patch": "Ignorer les mises à jour",
"Skip dlc": "Ignorer DLC", "Skip dlc": "Ignorer le(s) DLC",
"Skip data patch": "Ignorer patch de données", "Skip data patch": "Ignorer le patch de données",
"Skip ticket": "Ignorer ticket", "Skip ticket": "Ignorer ticket",
"Skip NCA hash verify": "Ignorer la vérification du hash NCA", "Skip NCA hash verify": "Vérification du hash NCA",
"Skip RSA header verify": "Ignorer la vérification de l'entête RSA", "Skip RSA header verify": "Vérification de l'entête RSA",
"Skip RSA NPDM verify": "Ignorer la vérification RSA NPDM", "Skip RSA NPDM verify": "Vérification RSA NPDM",
"Ignore distribution bit": "Ignorer le bit de distribution", "Ignore distribution bit": "Ignorer le bit de distribution",
"Convert to standard crypto": "Convertir vers la crypto standard", "Convert to standard crypto": "Convertir en crypto standard",
"Lower master key": "Abaisser la master key", "Lower master key": "Abaisser la master key",
"Lower system version": "Abaisser la version du système", "Lower system version": "Abaisser la version du système",
@@ -217,23 +239,25 @@
"Hide Sphaira": "Masquer Sphaira", "Hide Sphaira": "Masquer Sphaira",
"Install Forwarder": "Installer le Forwarder", "Install Forwarder": "Installer le Forwarder",
"WARNING: Installing forwarders will lead to a ban!": "ATTENTION : L'installation de forwarders entraînera un ban !", "WARNING: Installing forwarders will lead to a ban!": "ATTENTION : L'installation de forwarders entraînera un ban !",
"Installing Forwarder": "Installation Du Forwarder", "Installing Forwarder": "Installation du Forwarder...",
"Creating Program": "Création de Program", "Creating Program": "Création de Program",
"Creating Control": "Création de Control", "Creating Control": "Création de Control",
"Creating Meta": "Création de Meta", "Creating Meta": "Création de Meta",
"Writing Nca": "Ecriture NCA", "Writing Nca": "Écriture NCA",
"Updating ncm databse": "Mise à jour de ncm databse", "Updating ncm databse": "Mise à jour de la base de données ncm",
"Pushing application record": "Ajout de l'enregistrement de l'application", "Pushing application record": "Ajout de l'enregistrement de l'application",
"Failed to install forwarder": "Echec de l'installation du forwarder", "Failed to install forwarder": "Échec de l'installation du forwarder",
"Unstarred ": "Retiré des favories ", "Unstar": "Retirer des favories",
"Starred ": "Ajouté aux favories ", "Star": "Ajouter aux favories",
"Failed to remove old forwarder, please manually remove it!": "Supression de l'ancien forwarder échouée, supprimez-le manuellement!", "Unstarred ": "Retiré des favories : ",
"Starred ": "Ajouté aux favories : ",
"Failed to remove old forwarder, please manually remove it!": "Suppression de l'ancien forwarder échouée, supprimez-le manuellement !",
"AppStore": "AppStore", "AppStore": "AppStore",
"Appstore": "Magasin d'applications", "Appstore": "Boutique d'applications",
"Store": "Magasin", "Store": "AppStore",
"Filter: %s | Sort : %s | Order: %s" : "Filtre : %s | Tri : %s | Ordre : %s", "Filter: %s | Sort : %s | Order: %s" : "Filtre : %s | Tri : %s | Ordre : %s",
"AppStore Options": "Options de l'AppStore", "AppStore Options": "Options du AppStore",
"Info": "Info.", "Info": "Info.",
"Changelog": "Journal des modifications", "Changelog": "Journal des modifications",
"Details": "Détails", "Details": "Détails",
@@ -242,7 +266,7 @@
"category: %s": "catégorie : %s", "category: %s": "catégorie : %s",
"extracted: %.2f MiB": "Extrait: %.2f MiB", "extracted: %.2f MiB": "Extrait: %.2f MiB",
"app_dls: %s": "app_dls: %s", "app_dls: %s": "app_dls: %s",
"More by Author": "Plus de cet Auteur", "More by Author": "Plus de cet auteur",
"Leave Feedback": "Laisser un avis", "Leave Feedback": "Laisser un avis",
"FileBrowser": "Explorateur de Fichiers", "FileBrowser": "Explorateur de Fichiers",
@@ -250,19 +274,20 @@
"%zd files": "%zd fichiers", "%zd files": "%zd fichiers",
"%zd dirs": "%zd dossiers", "%zd dirs": "%zd dossiers",
"File Options": "Options de Fichier", "File Options": "Options de Fichier",
"Show Hidden": "Afficher Masqués", "Show Hidden": "Afficher fichier masqué",
"Folders First": "Dossiers en Premier", "Folders First": "Dossiers en Premier",
"Hidden Last": "Masqués en Dernier", "Hidden Last": "Masqués en Dernier",
"Cut": "Couper", "Cut": "Couper",
"Copy": "Copier", "Copy": "Copier",
"Copying ": "Copie en cours ", "Copying ": "Copie en cours...",
"Paste": "Coller", "Paste": "Coller",
"Paste ": "Coller ", "Paste file(s)?": "Copier le colis ?",
" file(s)?": " fichier(s)?", "Pasting": "Collage en cours...",
"Pasting ": "Collage en cours ", "Pasting": "Collage en cours...",
"Pasting": "Collage en cours",
"Rename": "Renommer", "Rename": "Renommer",
"Set New File Name": "Nouveau Nom Du Fichier", "Set New File Name": "Nouveau nom du fichier",
"Failed to delete directory": "Échec de la suppression du répertoire",
"Failed to delete file": "Échec de la suppression du répertoire",
"Extract zip": "Extraire le zip", "Extract zip": "Extraire le zip",
"Extract Options": "Options d'extraction", "Extract Options": "Options d'extraction",
"Extract here": "Extraire ici", "Extract here": "Extraire ici",
@@ -270,38 +295,43 @@
"Are you sure you want to extract to root?": "Souhaitez-vous vraiment extraire à la racine ?", "Are you sure you want to extract to root?": "Souhaitez-vous vraiment extraire à la racine ?",
"Extract to...": "Extraire vers...", "Extract to...": "Extraire vers...",
"Enter the path to the folder to extract into": "Entrer le chemin du répertoire vers lequel extraire", "Enter the path to the folder to extract into": "Entrer le chemin du répertoire vers lequel extraire",
"Extracting ": "Extraction en cours ", "Extracting ": "Extraction en cours...",
"Extract success!": "Extraction réussie !", "Extract success!": "Extraction réussie !",
"Extract failed!": "Extraction échouée !", "Extract failed!": "Extraction échouée !",
"Compress to zip": "Compresser en zip", "Compress to zip": "Compresser en zip",
"Compress Options": "Options de compression", "Compress Options": "Options de compression",
"Compress": "Compresser", "Compress": "Compresser",
"Compress to...": "Compresser vers...", "Compress to...": "Compresser vers...",
"Compressing ": "Compression en cours ", "Compressing ": "Compression en cours...",
"Compress success!": "Compression réussie !", "Compress success!": "Compression réussie !",
"Compress failed!": "Compression échouée !", "Compress failed!": "Compression échouée !",
"Create File": "Créer un Fichier", "Create File": "Créer un fichier",
"Set File Name": "Nommer Le Fichier", "Set File Name": "Nommer Le fichier",
"Create Folder": "Créer un Dossier", "Create Folder": "Créer un dossier",
"Set Folder Name": "Nommer Le Dossier", "Set Folder Name": "Nommer la dossier",
"Creating ": "Création ", "Creating ": "Création ",
"Upload": "Upload",
"Select upload location": "Sélectionner l'emplacement d'upload",
"No upload locations set!": "Aucun emplacement d'upload configuré!",
"Uploading": "Upload en cours",
"Upload successfull!": "Upload réussi!",
"Upload failed!": "Upload échoué!",
"View as text (unfinished)": "Afficher sous forme de texte (inachevé)", "View as text (unfinished)": "Afficher sous forme de texte (inachevé)",
"Upload": "Télécharger",
"Select upload location": "Emplacement du téléchargement :",
"No upload locations set!": "Aucun emplacement configuré !",
"Uploading": "Téléchargement en cours...",
"Upload successfull!": "Téléchargement réussi !",
"Upload failed!": "Téléchargement échoué !",
"Hash": "Hash",
"Hash Options": "Options du Hash",
"Hashing": "Hachage",
"Failed to hash file...": "Échec du hachage du fichier...",
"Ignore read only": "Ignorer lecture seule", "Ignore read only": "Ignorer lecture seule",
"Mount": "Monter", "Mount": "Monter",
"Sd": "Sd", "Sd": "SD",
"Image System memory": "Image de la mémoire System", "Image System memory": "Image de la mémoire Système",
"Image microSD card": "Image de la Carte microSD", "Image microSD card": "Image de la Carte microSD",
"Empty...": "Vide...", "Empty...": "Vide...",
"Open with DayBreak?": "Ouvrir avec DayBreak ?", "Open with DayBreak?": "Ouvrir avec DayBreak ?",
"Launch ": "Lancer ", "Launch ": "Lancer ",
"Launch option for: ": "Option de lancement pour : ", "Launch option for: ": "Option de lancement pour : ",
"Select launcher for: ": "Sélectionner le lanceur pour : ", "Select launcher for: ": "Sélectionner le lanceur pour : ",
"Close FileBrowser?": "Fermer le navigateur de fichiers ?",
"Sort By": "Tri Par", "Sort By": "Tri Par",
"Sort Options": "Options de Tri", "Sort Options": "Options de Tri",
@@ -311,15 +341,15 @@
"Tools": "Outils", "Tools": "Outils",
"Themes": "Thèmes", "Themes": "Thèmes",
"Legacy": "Legacy", "Legacy": "Legacy",
"Sort": "Tri", "Sort": "Trier",
"Size": "Taille", "Size": "Taille",
"Size (Star)": "Taille (Favories)", "Size (Star)": "Taille (Favoris)",
"Alphabetical": "Alphabétique", "Alphabetical": "Alphabétique",
"Alphabetical (Star)": "Alphabétique (Favories)", "Alphabetical (Star)": "Alphabétique (Favoris)",
"Updated": "Mis à jour", "Updated": "Mis à jour",
"Updated (Star)": "Mis à jour (Favories)", "Updated (Star)": "Mis à jour (Favoris)",
"Downloads": "Téléchargements", "Downloads": "Téléchargements",
"Likes": "Likes", "Likes": "Aimer",
"ID": "ID", "ID": "ID",
"Order": "Ordre", "Order": "Ordre",
"Descending": "Décroissant", "Descending": "Décroissant",
@@ -332,19 +362,19 @@
"List": "Liste", "List": "Liste",
"Icon": "Icône", "Icon": "Icône",
"Grid": "Grille", "Grid": "Grille",
"Search": "Recherche", "Search": "Lancer la recherche",
"Options": "Options", "Options": "Options",
"Split": "Découper",
"OK": "OK", "OK": "OK",
"Back": "Retour", "Back": "Retour",
"Select": "Sélectionner", "Select": "Sélectionner",
"Open": "Ouvrir", "Open": "Ouvrir",
"Close": "Fermer",
"Launch": "Exécuter", "Launch": "Exécuter",
"Restart": "Redémarrer", "Restart": "Redémarrer",
"Next": "Suivant", "Next": "Suivant",
"Prev": "Précédent", "Prev": "Précédent",
"Unstar": "Retirer des favories",
"Star": "Ajouter aux favories",
"Yes": "Oui", "Yes": "Oui",
"No": "Non", "No": "Non",
"On": "On", "On": "On",
@@ -352,41 +382,42 @@
"Install": "Installer", "Install": "Installer",
"Install Selected files?": "Installer les fichiers sélectionnés ?", "Install Selected files?": "Installer les fichiers sélectionnés ?",
"Installing ": "Installation en cours ", "Installing ": "Installation en cours...",
"Installed ": "Installé ", "Installed ": "Installé ",
"Installed!": "Installé !", "Installed!": "Installé !",
"Trying to load ": "Tente de charger ", "Trying to load ": "Essayer de charger ",
"Checking MD5": "Vérification MD5", "Checking MD5": "Vérification MD5",
"Delete": "Supprimer", "Delete": "Supprimer",
"Delete Selected files?": "Supprimer les fichiers sélectionnés ?", "Delete Selected files?": "Supprimer les fichiers sélectionnés ?",
"Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ", "Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ",
"Scanning ": "Scan en cours ", "Scanning ": "Scan en cours...",
"Deleting ": "Suppression en cours ", "Deleting ": "Suppression en cours...",
"Deleting": "Suppression en cours", "Deleting": "Suppression en cours...",
"Remove": "Supprimer", "Remove": "Supprimer",
"Completely remove ": "Supprimer totalement ", "Completely remove ": "Supprimer totalement ",
"Removing ": "Suppression en cours ", "Removing ": "Suppression en cours...",
"Uninstalling ": "Désinstallation en cours...",
"Removed ": "Supprimé ", "Removed ": "Supprimé ",
"Uninstalling ": "Désinstallation en cours ",
"Download": "Télécharger", "Download": "Télécharger",
"Downloading ": "Téléchargement en cours ", "Downloading ": "Téléchargement en cours...",
"Downloaded ": "Téléchargé ", "Downloaded ": "Téléchargé ",
"Download via the Network options!": "Téléchargez via les options réseau !",
"Update": "Mise à jour", "Update": "Mise à jour",
"Update avaliable: ": "Mise à jour disponible : ", "Update avaliable: ": "Mise à jour disponible : ",
"Download update: ": "Télécharger la mise à jour : ", "Download update: ": "Télécharger la mise à jour : ",
"Updated to ": "Mis à jour vers ", "Updated to ": "Mis à jour vers ",
"Failed to download update": "Echec du téléchargement de la mise à jour", "Failed to download update": "Échec du téléchargement de la mise à jour !",
"%zu hours %zu minutes remaining": "%zu heures %zu minutes restantes", "%zu hours %zu minutes remaining": "%zu heures %zu minutes restantes",
"%zu minutes %zu seconds remaining": "%zu minutes %zu secondes restantes", "%zu minutes %zu seconds remaining": "%zu minutes %zu secondes restantes",
"%zu seconds remaining": "%zu secondes restantes", "%zu seconds remaining": "%zu secondes restantes",
"Loading...": "Chargement...", "Loading...": "Chargement...",
"Loading": "Chargement en cours", "Loading": "Chargement en cours...",
"Empty!": "Vide !", "Empty!": "Vide !",
"Not Ready...": "Pas prêt", "Not Ready...": "Pas prêt !",
"Error loading page!": "Erreur du chargement de la page!" "Error loading page!": "Erreur du chargement de la page!"
} }

View File

@@ -3,6 +3,7 @@
"No Internet": "Niente Internet", "No Internet": "Niente Internet",
"Switch-Handheld!": "Switch Portatile", "Switch-Handheld!": "Switch Portatile",
"Switch-Docked!": "Switch Dock", "Switch-Docked!": "Switch Dock",
"Warning! Logs are enabled, Sphaira will run slowly!": "",
"Audio disabled due to suspended game": "Audio disabilitato poichè un app è in pausa", "Audio disabled due to suspended game": "Audio disabilitato poichè un app è in pausa",
"Are you sure you wish to cancel?": "Sei sicuro di voler annullare?", "Are you sure you wish to cancel?": "Sei sicuro di voler annullare?",
"An error occurred": "", "An error occurred": "",
@@ -27,6 +28,8 @@
"Nxlink Connected": "Nxlink connesso", "Nxlink Connected": "Nxlink connesso",
"Nxlink Upload": "Nxlink upload", "Nxlink Upload": "Nxlink upload",
"Nxlink Finished": "Nxlink finito", "Nxlink Finished": "Nxlink finito",
"Hdd": "",
"Hdd write protect": "",
"Language": "Lingua", "Language": "Lingua",
"Auto": "Auto", "Auto": "Auto",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "", "No meta entries found...\n": "",
"Updating application record list": "", "Updating application record list": "",
"Dump": "", "Dump": "",
"Dump options": "",
"Dump Options": "",
"Select content to dump": "", "Select content to dump": "",
"Dump All": "", "Dump All": "",
"Dump Application": "", "Dump Application": "",
"Dump Patch": "", "Dump Patch": "",
"Dump AddOnContent": "", "Dump AddOnContent": "",
"Dump DataPatch": "", "Dump DataPatch": "",
"Created nested folder": "",
"Append folder with .xci": "",
"Trim XCI": "",
"Label trimmed XCI": "",
"Multi-threaded USB transfer": "",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "", "Select dump location": "",
"microSD card (/dumps/NSP/)": "", "microSD card (/dumps/)": "",
"USB transfer (Switch 2 Switch)": "", "USB transfer (Switch 2 Switch)": "",
"/dev/null (Speed Test)": "", "/dev/null (Speed Test)": "",
"Dumping": "", "Dumping": "",
"Dump successfull!": "", "Dump successfull!": "",
"Dump failed!": "", "Dump failed!": "",
"Success": "",
"Delete successfull!": "", "Delete successfull!": "",
"Delete failed!": "", "Delete failed!": "",
"Success": "",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Impostazioni Themezer", "Themezer Options": "Impostazioni Themezer",
@@ -86,6 +102,8 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "Scaricamento json", "Downloading json": "Scaricamento json",
"Select asset to download for ": "", "Select asset to download for ": "",
"Failed to download json": "",
"Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"FTP Install (EXPERIMENTAL)": "", "FTP Install (EXPERIMENTAL)": "",
@@ -122,9 +140,12 @@
"GC": "", "GC": "",
"System memory %.1f GB": "", "System memory %.1f GB": "",
"microSD card %.1f GB": "", "microSD card %.1f GB": "",
"Nand Install": "",
"SD Card Install": "",
"Exit": "", "Exit": "",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "", "Gc install success!": "",
"Gc install failed!": "", "Gc install failed!": "",
@@ -160,14 +181,14 @@
"Negative image": "Immagine negativa", "Negative image": "Immagine negativa",
"Format": "Formato", "Format": "Formato",
"Trimming Format": "Formato di ritaglio", "Trimming Format": "Formato di ritaglio",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Filtro luce esterno", "External Light Filter": "Filtro luce esterno",
"Load Default": "Carica predefinito", "Load Default": "Carica predefinito",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Avanzato", "Advanced": "Avanzato",
"Advanced Options": "Opzioni avanzate", "Advanced Options": "Opzioni avanzate",
"Logging": "Logging", "Logging": "Logging",
@@ -181,10 +202,12 @@
"Restored hbmenu": "hbmenu ripristinato", "Restored hbmenu": "hbmenu ripristinato",
"Restart Sphaira?": "Vuoi riavviare Sphaira?", "Restart Sphaira?": "Vuoi riavviare Sphaira?",
"Press OK to restart Sphaira": "Premi OK per riavviare Sphaira", "Press OK to restart Sphaira": "Premi OK per riavviare Sphaira",
"Boost CPU during transfer": "",
"Text scroll speed": "", "Text scroll speed": "",
"Slow": "", "Slow": "",
"Normal": "", "Normal": "",
"Fast": "", "Fast": "",
"Set left-side menu": "",
"Set right-side menu": "", "Set right-side menu": "",
"Install options": "", "Install options": "",
"Install Options": "", "Install Options": "",
@@ -194,7 +217,6 @@
"Install location": "Installa posizione", "Install location": "Installa posizione",
"System memory": "Memoria di sistema", "System memory": "Memoria di sistema",
"microSD card": "Scheda microSD", "microSD card": "Scheda microSD",
"Boost CPU clock": "",
"Allow downgrade": "", "Allow downgrade": "",
"Skip if already installed": "", "Skip if already installed": "",
"Ticket only": "", "Ticket only": "",
@@ -225,6 +247,8 @@
"Updating ncm databse": "", "Updating ncm databse": "",
"Pushing application record": "", "Pushing application record": "",
"Failed to install forwarder": "", "Failed to install forwarder": "",
"Unstar": "Rimuovi dai preferiti",
"Star": "Aggiungi ai preferiti",
"Unstarred ": "", "Unstarred ": "",
"Starred ": "", "Starred ": "",
"Failed to remove old forwarder, please manually remove it!": "", "Failed to remove old forwarder, please manually remove it!": "",
@@ -257,12 +281,13 @@
"Copy": "Copia", "Copy": "Copia",
"Copying ": "Copio", "Copying ": "Copio",
"Paste": "Incolla", "Paste": "Incolla",
"Paste ": "Incolla ", "Paste file(s)?": "",
" file(s)?": "(i)file?",
"Pasting ": "Incollo", "Pasting ": "Incollo",
"Pasting": "Incollo", "Pasting": "Incollo",
"Rename": "Rinomina", "Rename": "Rinomina",
"Set New File Name": "Imposta nuovo nome", "Set New File Name": "Imposta nuovo nome",
"Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "", "Extract zip": "",
"Extract Options": "", "Extract Options": "",
"Extract here": "", "Extract here": "",
@@ -285,13 +310,17 @@
"Create Folder": "Crea cartella", "Create Folder": "Crea cartella",
"Set Folder Name": "Imposta nome", "Set Folder Name": "Imposta nome",
"Creating ": "Creazione", "Creating ": "Creazione",
"View as text (unfinished)": "Visualizza come testo (non finito)",
"Upload": "", "Upload": "",
"Select upload location": "", "Select upload location": "",
"No upload locations set!": "", "No upload locations set!": "",
"Uploading": "", "Uploading": "",
"Upload successfull!": "", "Upload successfull!": "",
"Upload failed!": "", "Upload failed!": "",
"View as text (unfinished)": "Visualizza come testo (non finito)", "Hash": "",
"Hash Options": "",
"Hashing": "",
"Failed to hash file...": "",
"Ignore read only": "Ignora read only", "Ignore read only": "Ignora read only",
"Mount": "Monta", "Mount": "Monta",
"Sd": "SD", "Sd": "SD",
@@ -302,6 +331,7 @@
"Launch ": "Lancia", "Launch ": "Lancia",
"Launch option for: ": "Lancia opzione per", "Launch option for: ": "Lancia opzione per",
"Select launcher for: ": "Scegli launcher per", "Select launcher for: ": "Scegli launcher per",
"Close FileBrowser?": "",
"Sort By": "Ordina per", "Sort By": "Ordina per",
"Sort Options": "Opzioni filtro", "Sort Options": "Opzioni filtro",
@@ -335,16 +365,16 @@
"Search": "Ricerca", "Search": "Ricerca",
"Options": "Opzioni", "Options": "Opzioni",
"Split": "",
"OK": "OK", "OK": "OK",
"Back": "Indietro", "Back": "Indietro",
"Select": "Seleziona", "Select": "Seleziona",
"Open": "Apri", "Open": "Apri",
"Close": "",
"Launch": "Lancia", "Launch": "Lancia",
"Restart": "Riavvia", "Restart": "Riavvia",
"Next": "", "Next": "",
"Prev": "", "Prev": "",
"Unstar": "Rimuovi dai preferiti",
"Star": "Aggiungi ai preferiti",
"Yes": "Sì", "Yes": "Sì",
"No": "No", "No": "No",
"On": "", "On": "",
@@ -367,12 +397,13 @@
"Remove": "Rimuovi", "Remove": "Rimuovi",
"Completely remove ": "Elimina definitivamente", "Completely remove ": "Elimina definitivamente",
"Removing ": "Rimozione", "Removing ": "Rimozione",
"Removed ": "Rimosso",
"Uninstalling ": "Disinstallazione", "Uninstalling ": "Disinstallazione",
"Removed ": "Rimosso",
"Download": "Download", "Download": "Download",
"Downloading ": "Scaricando", "Downloading ": "Scaricando",
"Downloaded ": "Scaricato", "Downloaded ": "Scaricato",
"Download via the Network options!": "",
"Update": "Aggiorna", "Update": "Aggiorna",
"Update avaliable: ": "Aggiornamento disponibile", "Update avaliable: ": "Aggiornamento disponibile",

View File

@@ -1,12 +1,13 @@
{ {
"[Applet Mode]": "Appletモード", "[Applet Mode]": "Appletモード",
"No Internet": "インターネットなし", "No Internet": "インターネットなし",
"Switch-Handheld!": "ハンドヘルド!", "Switch-Handheld!": "ハンドヘルドになりました!",
"Switch-Docked!": "ドック接続!", "Switch-Docked!": "ドック接続しました!",
"Audio disabled due to suspended game": "ゲームが一時停止状態の場合、オーディオは無効になります", "Warning! Logs are enabled, Sphaira will run slowly!": "警告: ログが有効になったため、Sphairaの速度が遅くなります",
"Audio disabled due to suspended game": "ゲームが一時停止状態の場合、BGMは無効になります",
"Are you sure you wish to cancel?": "本当に取り消しますか?", "Are you sure you wish to cancel?": "本当に取り消しますか?",
"An error occurred": "不具合のお知らせ", "An error occurred": "不具合のお知らせ",
"If this message appears repeatedly, please open an issue.": "このメッセージが繰り返し表示される場合、問題を開いてください", "If this message appears repeatedly, please open an issue.": "このメッセージが繰り返し表示される場合、問題を開いてください",
"Menu Options": "メニュー設定", "Menu Options": "メニュー設定",
"Menu": "メニュー", "Menu": "メニュー",
@@ -24,9 +25,11 @@
"Ftp": "FTP", "Ftp": "FTP",
"Mtp": "MTP", "Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink 接続", "Nxlink Connected": "Nxlink接続しました",
"Nxlink Upload": "Nxlink アップロード", "Nxlink Upload": "Nxlinkアップロードされました",
"Nxlink Finished": "Nxlink 終了", "Nxlink Finished": "Nxlink終了します",
"Hdd": "HDD",
"Hdd write protect": "HDD書き込み保護",
"Language": "言語", "Language": "言語",
"Auto": "自動", "Auto": "自動",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "メタエントリが見つかりませんでした\n", "No meta entries found...\n": "メタエントリが見つかりませんでした\n",
"Updating application record list": "ゲームのレコードを更新しています", "Updating application record list": "ゲームのレコードを更新しています",
"Dump": "吸出し", "Dump": "吸出し",
"Dump options": "吸出し設定",
"Dump Options": "吸出し設定",
"Select content to dump": "吸出すコンテンツを選択", "Select content to dump": "吸出すコンテンツを選択",
"Dump All": "全て", "Dump All": "全て",
"Dump Application": "ゲームのみ", "Dump Application": "ゲームのみ",
"Dump Patch": "ゲームパッチのみ", "Dump Patch": "ゲームパッチのみ",
"Dump AddOnContent": "DLCのみ", "Dump AddOnContent": "DLCのみ",
"Dump DataPatch": "DLCパッチのみ", "Dump DataPatch": "DLCパッチのみ",
"Created nested folder": "ネストされたフォルダを作成",
"Append folder with .xci": ".xci付きフォルダを追加",
"Trim XCI": "XCIをトリム",
"Label trimmed XCI": "トリム済みXCIのラベルを指定",
"Multi-threaded USB transfer": "マルチスレッドのUSB転送",
"Dump All Bins": "すべてのBINを吸出す",
"Dump XCI": "XCIを吸出す",
"Dump Card ID Set": "ゲームカードIDを吸出す",
"Dump Card UID": "ゲームカードUIDを吸出す",
"Dump Certificate": "証明書を吸出す",
"Dump Initial Data": "初期データを吸出す",
"Select dump location": "吸出し位置を選択", "Select dump location": "吸出し位置を選択",
"microSD card (/dumps/NSP/)": "SDカード (/dumps/NSP/)", "microSD card (/dumps/)": "SDカード (/dumps/)",
"USB transfer (Switch 2 Switch)": "USB転送 (Switch 2 Switch)", "USB transfer (Switch 2 Switch)": "USB転送 (Switch 2 Switch)",
"/dev/null (Speed Test)": "/dev/null (Speed Test)", "/dev/null (Speed Test)": "/dev/null (Speed Test)",
"Dumping": "吸出し中", "Dumping": "吸出し中",
"Dump successfull!": "吸出し完了!", "Dump successfull!": "吸出し完了!",
"Dump failed!": "吸出し失敗!", "Dump failed!": "吸出し失敗!",
"Success": "完了",
"Delete successfull!": "削除完了!", "Delete successfull!": "削除完了!",
"Delete failed!": "削除失敗!", "Delete failed!": "削除失敗!",
"Success": "完了",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Themezer設定", "Themezer Options": "Themezer設定",
@@ -86,14 +102,16 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "JSONからダウンロード", "Downloading json": "JSONからダウンロード",
"Select asset to download for ": "ダウンロードアイテムを選択 ", "Select asset to download for ": "ダウンロードアイテムを選択 ",
"Failed to download json": "JSONからのダウンロードに失敗しました!",
"Failed to download app!": "アプリのダウンロードに失敗しました!",
"FTP Install": "FTPでインストール", "FTP Install": "FTPでインストール",
"FTP Install (EXPERIMENTAL)": "FTPでインストール(実験機能)", "FTP Install (EXPERIMENTAL)": "FTPでインストール(実験機能)",
"Connection Type: WiFi | Strength: ": "接続: WiFi | 強度: ", "Connection Type: WiFi | Strength: ": "接続: WiFi | 強度: ",
"Connection Type: Ethernet": "接続: イーサネット", "Connection Type: Ethernet": "接続: イーサネット",
"Connection Type: None": "接続: なし", "Connection Type: None": "接続: なし",
"Host:": "ホースと:", "Host:": "ホスト:",
"Port:": "Port:", "Port:": "ポート:",
"Username:": "ユーザー名:", "Username:": "ユーザー名:",
"Password:": "暗証番号:", "Password:": "暗証番号:",
"SSID:": "SSID:", "SSID:": "SSID:",
@@ -103,14 +121,14 @@
"Ftp install failed!": "FTPインストール失敗!", "Ftp install failed!": "FTPインストール失敗!",
"USB Install": "USBインストール", "USB Install": "USBインストール",
"USB": "USBインストール", "USB": "USBインストール",
"Connected, waiting for file list...": "接続されました、ファイル リスト待機中", "Connected, waiting for file list...": "接続されました、ファイルリストを待っています",
"Connected, starting transfer...": "接続されました、転送開始", "Connected, starting transfer...": "接続されました、転送開始します",
"Failed to init usb, press B to exit...": "USB接続できませんでした、を押して終了します", "Failed to init usb, press B to exit...": "USB接続できませんでした、を押して終了します",
"Waiting for connection...": "接続待機中", "Waiting for connection...": "接続待機中",
"Transferring data...": "データ転送", "Transferring data...": "データ転送しています",
"USB connected, sending file list": "接続されました、ファイルリスト送信", "USB connected, sending file list": "接続されました、ファイルリスト送信します",
"Sent file list, waiting for command...": "ファイルリストを送信しました、入力待機中", "Sent file list, waiting for command...": "ファイルリストを送信しました、入力を待っています",
"waiting for usb connection...": "USB接続待機中", "waiting for usb connection...": "USB接続を待っています",
"Disable MTP for usb install": "USBインストールのため、MTPを無効にします", "Disable MTP for usb install": "USBインストールのため、MTPを無効にします",
"Re-enabled MTP": "MTPに再接続します", "Re-enabled MTP": "MTPに再接続します",
"Installed via usb": "USBインストールに成功しました", "Installed via usb": "USBインストールに成功しました",
@@ -122,9 +140,12 @@
"GC": "ゲームカード", "GC": "ゲームカード",
"System memory %.1f GB": "本体保存メモリー %.1f GB", "System memory %.1f GB": "本体保存メモリー %.1f GB",
"microSD card %.1f GB": "SDカード %.1f GB", "microSD card %.1f GB": "SDカード %.1f GB",
"Nand Install": "本体保存メモリーにインストール",
"SD Card Install": "SDカードにインストール",
"Exit": "もどる", "Exit": "もどる",
"Install disabled...\nPlease enable installing via the install options.": "インストールが無効化されています\nインストール設定で有効にしてください",
"No GameCard inserted": "ゲームカードが挿入されていません",
"GameCard is already trimmed!": "ゲームカードがすでにトリムされています!",
"WARNING: GameCard is already trimmed!": "警告: ゲームカードがすでにトリムされています!",
"Continue": "つづく",
"Gc install success!": "ゲームカードインストール完了!", "Gc install success!": "ゲームカードインストール完了!",
"Gc install failed!": "ゲームカードインストール失敗!", "Gc install failed!": "ゲームカードインストール失敗!",
@@ -160,14 +181,14 @@
"Negative image": "ネガティブなイメージ", "Negative image": "ネガティブなイメージ",
"Format": "解像度", "Format": "解像度",
"Trimming Format": "トリミングされた解像度", "Trimming Format": "トリミングされた解像度",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "外光フィルター", "External Light Filter": "外光フィルター",
"Load Default": "基本設定に戻す", "Load Default": "基本設定に戻す",
"Web": "ウェブサイト",
"Select URL": "URLを選択してください",
"Enter custom URL": "URLを直接入力",
"Enter URL": "URLを記入してください",
"Advanced": "高度な", "Advanced": "高度な",
"Advanced Options": "高度設定", "Advanced Options": "高度設定",
"Logging": "ログの取得", "Logging": "ログの取得",
@@ -181,10 +202,12 @@
"Restored hbmenu": "hbmenuに復元されました", "Restored hbmenu": "hbmenuに復元されました",
"Restart Sphaira?": "Sphairaを再起動しますか?", "Restart Sphaira?": "Sphairaを再起動しますか?",
"Press OK to restart Sphaira": "確認ボタンを押してSphairaを再起動", "Press OK to restart Sphaira": "確認ボタンを押してSphairaを再起動",
"Boost CPU during transfer": "転送する間CPUを加速",
"Text scroll speed": "流れる文字の速さ", "Text scroll speed": "流れる文字の速さ",
"Slow": "遅", "Slow": "遅",
"Normal": "普通", "Normal": "普通",
"Fast": "速", "Fast": "速",
"Set left-side menu": "左側メニュー設定",
"Set right-side menu": "右側メニュー設定", "Set right-side menu": "右側メニュー設定",
"Install options": "インストール設定", "Install options": "インストール設定",
"Install Options": "インストール設定", "Install Options": "インストール設定",
@@ -194,7 +217,6 @@
"Install location": "インストール経路", "Install location": "インストール経路",
"System memory": "本体保存メモリー", "System memory": "本体保存メモリー",
"microSD card": "SDカード", "microSD card": "SDカード",
"Boost CPU clock": "CPUクロックをブースト",
"Allow downgrade": "ダウングレード許可", "Allow downgrade": "ダウングレード許可",
"Skip if already installed": "既にインストールされている場合はスキップします", "Skip if already installed": "既にインストールされている場合はスキップします",
"Ticket only": "チケットのみ設置", "Ticket only": "チケットのみ設置",
@@ -225,6 +247,8 @@
"Updating ncm databse": "ncmのDBをアップデート中", "Updating ncm databse": "ncmのDBをアップデート中",
"Pushing application record": "アプリの記録をプッシュ中", "Pushing application record": "アプリの記録をプッシュ中",
"Failed to install forwarder": "Forwarderのインストール失敗", "Failed to install forwarder": "Forwarderのインストール失敗",
"Unstar": "お気に入り解除",
"Star": "お気に入り",
"Unstarred ": "お気に入り解除: ", "Unstarred ": "お気に入り解除: ",
"Starred ": "お気に入りに登録: ", "Starred ": "お気に入りに登録: ",
"Failed to remove old forwarder, please manually remove it!": "古いForwarderを削除できませんでした、手動で削除してください!", "Failed to remove old forwarder, please manually remove it!": "古いForwarderを削除できませんでした、手動で削除してください!",
@@ -247,8 +271,8 @@
"FileBrowser": "ファイルブラウザ", "FileBrowser": "ファイルブラウザ",
"Files": "ファイル", "Files": "ファイル",
"%zd files": "%zd個のファイル", "%zd files": "%zd ファイル",
"%zd dirs": "%zd個のフォルダー", "%zd dirs": "%zd フォルダー",
"File Options": "ファイル設定", "File Options": "ファイル設定",
"Show Hidden": "非表示ファイルを表示", "Show Hidden": "非表示ファイルを表示",
"Folders First": "フォルダーを優先", "Folders First": "フォルダーを優先",
@@ -257,12 +281,13 @@
"Copy": "コピー", "Copy": "コピー",
"Copying ": "コピー中 ", "Copying ": "コピー中 ",
"Paste": "ペースト", "Paste": "ペースト",
"Paste ": " ", "Paste file(s)?": "",
" file(s)?": "個のファイルをペーストしますか?",
"Pasting ": "ペースト中 ", "Pasting ": "ペースト中 ",
"Pasting": "ペースト中", "Pasting": "ペースト中",
"Rename": "名前の変更", "Rename": "名前の変更",
"Set New File Name": "新しい名前を入力", "Set New File Name": "新しい名前を入力",
"Failed to delete directory": "フォルダーを削除できませんでした",
"Failed to delete file": "ファイルを削除できませんでした",
"Extract zip": "ZIPファイルを解凍", "Extract zip": "ZIPファイルを解凍",
"Extract Options": "解凍設定", "Extract Options": "解凍設定",
"Extract here": "ここに解凍", "Extract here": "ここに解凍",
@@ -285,13 +310,17 @@
"Create Folder": "フォルダーの作成", "Create Folder": "フォルダーの作成",
"Set Folder Name": "名前を入力", "Set Folder Name": "名前を入力",
"Creating ": "作成中 ", "Creating ": "作成中 ",
"View as text (unfinished)": "テキストとして表示 (未完成)",
"Upload": "アップロード", "Upload": "アップロード",
"Select upload location": "アップロードの位置を設定", "Select upload location": "アップロードの位置を設定",
"No upload locations set!": "アップロードの位置が設定されていません", "No upload locations set!": "アップロードの位置が設定されていません",
"Uploading": "アップロード中", "Uploading": "アップロード中",
"Upload successfull!": "アップロード完了!", "Upload successfull!": "アップロード完了!",
"Upload failed!": "アップロード失敗!", "Upload failed!": "アップロード失敗!",
"View as text (unfinished)": "テキストとして表示 (未完成)", "Hash": "ハッシュ",
"Hash Options": "ハッシュ設定",
"Hashing": "ハッシュ化中",
"Failed to hash file...": "ハッシュ化できませんでした",
"Ignore read only": "読み取り専用を無視する", "Ignore read only": "読み取り専用を無視する",
"Mount": "マウント", "Mount": "マウント",
"Sd": "SDメモリーカード", "Sd": "SDメモリーカード",
@@ -302,6 +331,7 @@
"Launch ": "起動しますか", "Launch ": "起動しますか",
"Launch option for: ": "起動設定: ", "Launch option for: ": "起動設定: ",
"Select launcher for: ": "起動ランチャーを選ぶ: ", "Select launcher for: ": "起動ランチャーを選ぶ: ",
"Close FileBrowser?": "ファイルブラウザを閉じますか?",
"Sort By": "並べ替え", "Sort By": "並べ替え",
"Sort Options": "並べ替え設定", "Sort Options": "並べ替え設定",
@@ -335,16 +365,16 @@
"Search": "検索", "Search": "検索",
"Options": "設定", "Options": "設定",
"Split": "スクリーン分割",
"OK": "確認", "OK": "確認",
"Back": "戻る", "Back": "戻る",
"Select": "選択", "Select": "選択",
"Open": "開く", "Open": "開く",
"Close": "閉じる",
"Launch": "起動", "Launch": "起動",
"Restart": "再起動", "Restart": "再起動",
"Next": "次へ", "Next": "次へ",
"Prev": "前へ", "Prev": "前へ",
"Unstar": "お気に入り解除",
"Star": "お気に入り",
"Yes": "はい", "Yes": "はい",
"No": "いいえ", "No": "いいえ",
"On": "オン", "On": "オン",
@@ -367,18 +397,19 @@
"Remove": "除去", "Remove": "除去",
"Completely remove ": "除去しますか ", "Completely remove ": "除去しますか ",
"Removing ": "除去中 ", "Removing ": "除去中 ",
"Removed ": "除去完了 ",
"Uninstalling ": "アンインストール中 ", "Uninstalling ": "アンインストール中 ",
"Removed ": "除去完了 ",
"Download": "ダウンロード", "Download": "ダウンロード",
"Downloading ": "ダウンロード中 ", "Downloading ": "ダウンロード中 ",
"Downloaded ": "ダウンロード完了 ", "Downloaded ": "ダウンロード完了 ",
"Download via the Network options!": "ネットワーク設定からダウンロードしました!",
"Update": "アップデート", "Update": "アップデート",
"Update avaliable: ": "アップデート可能: ", "Update avaliable: ": "アップデート可能: ",
"Download update: ": "アップデートをダウンロード: ", "Download update: ": "アップデートをダウンロード: ",
"Updated to ": "アップデート: ", "Updated to ": "アップデート: ",
"Failed to download update": "アップデートのダウンロード失敗", "Failed to download update": "アップデートのダウンロード失敗しました",
"%zu hours %zu minutes remaining": "残り %zu 時間 %zu 分", "%zu hours %zu minutes remaining": "残り %zu 時間 %zu 分",
"%zu minutes %zu seconds remaining": "残り %zu 分", "%zu minutes %zu seconds remaining": "残り %zu 分",

View File

@@ -3,6 +3,7 @@
"No Internet": "인터넷 연결 없음", "No Internet": "인터넷 연결 없음",
"Switch-Handheld!": "휴대모드로 전환됨!", "Switch-Handheld!": "휴대모드로 전환됨!",
"Switch-Docked!": "독 모드로 전환됨!", "Switch-Docked!": "독 모드로 전환됨!",
"Warning! Logs are enabled, Sphaira will run slowly!": "경고: 로깅 활성화, 앱이 느려집니다!",
"Audio disabled due to suspended game": "게임 실행 중에는 BGM이 비활성화 됩니다", "Audio disabled due to suspended game": "게임 실행 중에는 BGM이 비활성화 됩니다",
"Are you sure you wish to cancel?": "정말 취소할까요?", "Are you sure you wish to cancel?": "정말 취소할까요?",
"An error occurred": "오류가 발생했습니다!", "An error occurred": "오류가 발생했습니다!",
@@ -27,6 +28,8 @@
"Nxlink Connected": "Nxlink 연결됨", "Nxlink Connected": "Nxlink 연결됨",
"Nxlink Upload": "Nxlink 업로드", "Nxlink Upload": "Nxlink 업로드",
"Nxlink Finished": "Nxlink 종료됨", "Nxlink Finished": "Nxlink 종료됨",
"Hdd": "HDD",
"Hdd write protect": "HDD 쓰기 방지",
"Language": "언어", "Language": "언어",
"Auto": "자동", "Auto": "자동",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "메타 항목을 찾을 수 없습니다...\n", "No meta entries found...\n": "메타 항목을 찾을 수 없습니다...\n",
"Updating application record list": "앱 기록 업데이트 중", "Updating application record list": "앱 기록 업데이트 중",
"Dump": "덤프", "Dump": "덤프",
"Select content to dump": "덤프 옵션", "Dump options": "덤프 옵션",
"Dump Options": "덤프 옵션",
"Select content to dump": "덤프 콘텐츠 선택",
"Dump All": "모든 콘텐츠", "Dump All": "모든 콘텐츠",
"Dump Application": "게임", "Dump Application": "게임",
"Dump Patch": "게임 패치", "Dump Patch": "게임 패치",
"Dump AddOnContent": "DLC", "Dump AddOnContent": "DLC",
"Dump DataPatch": "DLC 패치", "Dump DataPatch": "DLC 패치",
"Created nested folder": "중첩 폴더 생성",
"Append folder with .xci": ".xci 파일이 포함된 폴더 추가",
"Trim XCI": "XCI 트림",
"Label trimmed XCI": "트림된 XCI의 라벨 지정",
"Multi-threaded USB transfer": "멀티스레드 USB 전송",
"Dump All Bins": "모든 바이너리 덤프",
"Dump XCI": "XCI 덤프",
"Dump Card ID Set": "카트리지 ID 덤프",
"Dump Card UID": "카트리지 UID 덤프",
"Dump Certificate": "서명 덤프",
"Dump Initial Data": "초기화 데이터 덤프",
"Select dump location": "덤프 위치", "Select dump location": "덤프 위치",
"microSD card (/dumps/NSP/)": "SD 카드 (sdmc:/dumps/NSP/)", "microSD card (/dumps/)": "SD 카드 (sdmc:/dumps/)",
"USB transfer (Switch 2 Switch)": "USB 전송 (Switch 2 Switch)", "USB transfer (Switch 2 Switch)": "USB 전송 (Switch 2 Switch)",
"/dev/null (Speed Test)": "/dev/null (Speed Test)", "/dev/null (Speed Test)": "/dev/null (벤치마크)",
"Dumping": "덤프 중", "Dumping": "덤프 중",
"Dump successfull!": "덤프 완료!", "Dump successfull!": "덤프 완료!",
"Dump failed!": "덤프 실패!", "Dump failed!": "덤프 실패!",
"Success": "완료!",
"Delete successfull!": "삭제 완료!", "Delete successfull!": "삭제 완료!",
"Delete failed!": "삭제 실패!", "Delete failed!": "삭제 실패!",
"Success": "완료!",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Themezer 옵션", "Themezer Options": "Themezer 옵션",
@@ -86,6 +102,8 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "JSON에서 다운로드", "Downloading json": "JSON에서 다운로드",
"Select asset to download for ": "다운로드 아이템: ", "Select asset to download for ": "다운로드 아이템: ",
"Failed to download json": "JSON에서 다운로드 실패!",
"Failed to download app!": "앱 다운로드 실패!",
"FTP Install": "FTP 설치", "FTP Install": "FTP 설치",
"FTP Install (EXPERIMENTAL)": "FTP 설치 (실험실 기능)", "FTP Install (EXPERIMENTAL)": "FTP 설치 (실험실 기능)",
@@ -122,9 +140,12 @@
"GC": "카트리지", "GC": "카트리지",
"System memory %.1f GB": "본체 저장 메모리 %.1f GB", "System memory %.1f GB": "본체 저장 메모리 %.1f GB",
"microSD card %.1f GB": "SD 카드 %.1f GB", "microSD card %.1f GB": "SD 카드 %.1f GB",
"Nand Install": "본체 저장 메모리에 설치",
"SD Card Install": "SD 카드에 설치",
"Exit": "나가기", "Exit": "나가기",
"Install disabled...\nPlease enable installing via the install options.": "설치가 비활성화 되었습니다\n설치 옵션에서 활성화해주세요",
"No GameCard inserted": "카트리지가 없습니다",
"GameCard is already trimmed!": "카트리지를 이미 트림했습니다",
"WARNING: GameCard is already trimmed!": "경고: 카트리지를 이미 트림했습니다",
"Continue": "계속",
"Gc install success!": "카트리지 설치 완료!", "Gc install success!": "카트리지 설치 완료!",
"Gc install failed!": "카트리지 설치 실패!", "Gc install failed!": "카트리지 설치 실패!",
@@ -160,14 +181,14 @@
"Negative image": "반전", "Negative image": "반전",
"Format": "해상도", "Format": "해상도",
"Trimming Format": "트리밍 해상도", "Trimming Format": "트리밍 해상도",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "외부 조명 필터", "External Light Filter": "외부 조명 필터",
"Load Default": "기본값으로 설정", "Load Default": "기본값으로 설정",
"Web": "웹 브라우저",
"Select URL": "URL 주소 선택",
"Enter custom URL": "직접 지정",
"Enter URL": "URL을 기입하세요",
"Advanced": "고급", "Advanced": "고급",
"Advanced Options": "고급 옵션", "Advanced Options": "고급 옵션",
"Logging": "로깅", "Logging": "로깅",
@@ -181,10 +202,12 @@
"Restored hbmenu": "hbmenu 복원됨", "Restored hbmenu": "hbmenu 복원됨",
"Restart Sphaira?": "Sphaira를 재시작할까요?", "Restart Sphaira?": "Sphaira를 재시작할까요?",
"Press OK to restart Sphaira": "확인 버튼 입력하여 Sphaira 재시작", "Press OK to restart Sphaira": "확인 버튼 입력하여 Sphaira 재시작",
"Boost CPU during transfer": "전송시 CPU 부스트",
"Text scroll speed": "긴 텍스트 표시 속도", "Text scroll speed": "긴 텍스트 표시 속도",
"Slow": "천천히", "Slow": "천천히",
"Normal": "보통", "Normal": "보통",
"Fast": "빠르게", "Fast": "빠르게",
"Set left-side menu": "좌측 메뉴 설정",
"Set right-side menu": "우측 메뉴 설정", "Set right-side menu": "우측 메뉴 설정",
"Install options": "설치 옵션", "Install options": "설치 옵션",
"Install Options": "설치 옵션", "Install Options": "설치 옵션",
@@ -194,7 +217,6 @@
"Install location": "설치 위치", "Install location": "설치 위치",
"System memory": "본체 저장 메모리", "System memory": "본체 저장 메모리",
"microSD card": "SD 카드", "microSD card": "SD 카드",
"Boost CPU clock": "CPU 클럭 향상",
"Allow downgrade": "다운그레이드 허용", "Allow downgrade": "다운그레이드 허용",
"Skip if already installed": "설치된 항목 건너뛰기", "Skip if already installed": "설치된 항목 건너뛰기",
"Ticket only": "티켓만 설치", "Ticket only": "티켓만 설치",
@@ -217,7 +239,7 @@
"Hide Sphaira": "Sphaira 숨기기", "Hide Sphaira": "Sphaira 숨기기",
"Install Forwarder": "바로가기 설치", "Install Forwarder": "바로가기 설치",
"WARNING: Installing forwarders will lead to a ban!": "경고: 시스낸드에 설치 시, 밴 위험이 있습니다!", "WARNING: Installing forwarders will lead to a ban!": "경고: 시스낸드에 설치 시, 밴 위험이 있습니다!",
"Installing Forwarder": "바로가기 설치", "Installing Forwarder": "바로가기 설치",
"Creating Program": "프로그램 생성", "Creating Program": "프로그램 생성",
"Creating Control": "컨트롤 생성", "Creating Control": "컨트롤 생성",
"Creating Meta": "메타 생성", "Creating Meta": "메타 생성",
@@ -225,6 +247,8 @@
"Updating ncm databse": "Ncm 데이터베이스 업데이트", "Updating ncm databse": "Ncm 데이터베이스 업데이트",
"Pushing application record": "응용 프로그램 기록 푸싱", "Pushing application record": "응용 프로그램 기록 푸싱",
"Failed to install forwarder": "바로가기 설치 실패함", "Failed to install forwarder": "바로가기 설치 실패함",
"Unstar": "즐겨찾기 해제",
"Star": "즐겨찾기",
"Unstarred ": "즐겨찾기 해제: ", "Unstarred ": "즐겨찾기 해제: ",
"Starred ": "즐겨찾기 등록: ", "Starred ": "즐겨찾기 등록: ",
"Failed to remove old forwarder, please manually remove it!": "바로가기 제거 실패함, 직접 제거해주세요!", "Failed to remove old forwarder, please manually remove it!": "바로가기 제거 실패함, 직접 제거해주세요!",
@@ -257,12 +281,13 @@
"Copy": "복사", "Copy": "복사",
"Copying ": "복사 중 ", "Copying ": "복사 중 ",
"Paste": "붙여넣기", "Paste": "붙여넣기",
"Paste ": " ", "Paste file(s)?": "붙여넣을까요?",
" file(s)?": "개 항목을 붙여넣을까요?", "Pasting ": "붙여넣는 중 ",
"Pasting": "붙여넣는 중", "Pasting": "붙여넣는 중",
"Pasting": "붙여넣기",
"Rename": "이름 바꾸기", "Rename": "이름 바꾸기",
"Set New File Name": "새 파일명 입력", "Set New File Name": "새 파일명 입력",
"Failed to delete directory": "폴더 삭제 실패",
"Failed to delete file": "파일 삭제 실패",
"Extract zip": "압축 해제", "Extract zip": "압축 해제",
"Extract Options": "압축 해제 옵션", "Extract Options": "압축 해제 옵션",
"Extract here": "여기에 풀기", "Extract here": "여기에 풀기",
@@ -285,13 +310,17 @@
"Create Folder": "새 폴더", "Create Folder": "새 폴더",
"Set Folder Name": "폴더명 입력", "Set Folder Name": "폴더명 입력",
"Creating ": "생성 중 ", "Creating ": "생성 중 ",
"View as text (unfinished)": "텍스트로 보기 (미완성)",
"Upload": "업로드", "Upload": "업로드",
"Select upload location": "업로드 위치 선택", "Select upload location": "업로드 위치 선택",
"No upload locations set!": "업로드 위치가 설정되지 않았습니다!", "No upload locations set!": "업로드 위치가 설정되지 않았습니다!",
"Uploading": "업로드 중 ", "Uploading": "업로드 중 ",
"Upload successfull!": "업로드 완료!", "Upload successfull!": "업로드 완료!",
"Upload failed!": "업로드 실패!", "Upload failed!": "업로드 실패!",
"View as text (unfinished)": "텍스트로 보기 (미완성)", "Hash": "해시",
"Hash Options": "해시 옵션",
"Hashing": "해시화중",
"Failed to hash file...": "해시화에 실패했습니다...",
"Ignore read only": "읽기 전용 설정 무시", "Ignore read only": "읽기 전용 설정 무시",
"Mount": "마운트", "Mount": "마운트",
"Sd": "SD 카드", "Sd": "SD 카드",
@@ -302,6 +331,7 @@
"Launch ": "실행할까요 ", "Launch ": "실행할까요 ",
"Launch option for: ": "실행 옵션: ", "Launch option for: ": "실행 옵션: ",
"Select launcher for: ": "실행 런처: ", "Select launcher for: ": "실행 런처: ",
"Close FileBrowser?": "파일 탐색창을 닫을까요?",
"Sort By": "정렬", "Sort By": "정렬",
"Sort Options": "정렬 옵션", "Sort Options": "정렬 옵션",
@@ -335,16 +365,16 @@
"Search": "검색", "Search": "검색",
"Options": "설정", "Options": "설정",
"Split": "화면 분할",
"OK": "확인", "OK": "확인",
"Back": "뒤로", "Back": "뒤로",
"Select": "선택", "Select": "선택",
"Open": "열기", "Open": "열기",
"Close": "닫기",
"Launch": "실행", "Launch": "실행",
"Restart": "재시작", "Restart": "재시작",
"Next": "다음", "Next": "다음",
"Prev": "이전", "Prev": "이전",
"Unstar": "즐겨찾기 해제",
"Star": "즐겨찾기",
"Yes": "예", "Yes": "예",
"No": "아니요", "No": "아니요",
"On": "켬", "On": "켬",
@@ -367,12 +397,13 @@
"Remove": "제거", "Remove": "제거",
"Completely remove ": "정말 삭제할까요 ", "Completely remove ": "정말 삭제할까요 ",
"Removing ": "제거 중 ", "Removing ": "제거 중 ",
"Uninstalling ": "설치 제거중 ",
"Removed ": "제거됨: ", "Removed ": "제거됨: ",
"Uninstalling ": "설치 제거됨: ",
"Download": "다운로드", "Download": "다운로드",
"Downloading ": "다운로드 중 ", "Downloading ": "다운로드 중 ",
"Downloaded ": "다운로드 완료: ", "Downloaded ": "다운로드 완료: ",
"Download via the Network options!": "네트워크 옵션에서 다운로드했습니다!",
"Update": "업데이트", "Update": "업데이트",
"Update avaliable: ": "업데이트 가능: ", "Update avaliable: ": "업데이트 가능: ",

View File

@@ -3,6 +3,7 @@
"No Internet": "Geen internet", "No Internet": "Geen internet",
"Switch-Handheld!": "", "Switch-Handheld!": "",
"Switch-Docked!": "", "Switch-Docked!": "",
"Warning! Logs are enabled, Sphaira will run slowly!": "",
"Audio disabled due to suspended game": "", "Audio disabled due to suspended game": "",
"Are you sure you wish to cancel?": "", "Are you sure you wish to cancel?": "",
"An error occurred": "", "An error occurred": "",
@@ -27,6 +28,8 @@
"Nxlink Connected": "", "Nxlink Connected": "",
"Nxlink Upload": "", "Nxlink Upload": "",
"Nxlink Finished": "", "Nxlink Finished": "",
"Hdd": "",
"Hdd write protect": "",
"Language": "Taal", "Language": "Taal",
"Auto": "", "Auto": "",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "", "No meta entries found...\n": "",
"Updating application record list": "", "Updating application record list": "",
"Dump": "", "Dump": "",
"Dump options": "",
"Dump Options": "",
"Select content to dump": "", "Select content to dump": "",
"Dump All": "", "Dump All": "",
"Dump Application": "", "Dump Application": "",
"Dump Patch": "", "Dump Patch": "",
"Dump AddOnContent": "", "Dump AddOnContent": "",
"Dump DataPatch": "", "Dump DataPatch": "",
"Created nested folder": "",
"Append folder with .xci": "",
"Trim XCI": "",
"Label trimmed XCI": "",
"Multi-threaded USB transfer": "",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "", "Select dump location": "",
"microSD card (/dumps/NSP/)": "", "microSD card (/dumps/)": "",
"USB transfer (Switch 2 Switch)": "", "USB transfer (Switch 2 Switch)": "",
"/dev/null (Speed Test)": "", "/dev/null (Speed Test)": "",
"Dumping": "", "Dumping": "",
"Dump successfull!": "", "Dump successfull!": "",
"Dump failed!": "", "Dump failed!": "",
"Success": "",
"Delete successfull!": "", "Delete successfull!": "",
"Delete failed!": "", "Delete failed!": "",
"Success": "",
"Themezer": "Themamaker", "Themezer": "Themamaker",
"Themezer Options": "", "Themezer Options": "",
@@ -86,6 +102,8 @@
"GitHub": "", "GitHub": "",
"Downloading json": "", "Downloading json": "",
"Select asset to download for ": "", "Select asset to download for ": "",
"Failed to download json": "",
"Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"FTP Install (EXPERIMENTAL)": "", "FTP Install (EXPERIMENTAL)": "",
@@ -122,9 +140,12 @@
"GC": "", "GC": "",
"System memory %.1f GB": "", "System memory %.1f GB": "",
"microSD card %.1f GB": "", "microSD card %.1f GB": "",
"Nand Install": "",
"SD Card Install": "",
"Exit": "", "Exit": "",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "", "Gc install success!": "",
"Gc install failed!": "", "Gc install failed!": "",
@@ -160,14 +181,14 @@
"Negative image": "Negatief beeld", "Negative image": "Negatief beeld",
"Format": "Formaat", "Format": "Formaat",
"Trimming Format": "Trimformaat", "Trimming Format": "Trimformaat",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Extern lichtfilter", "External Light Filter": "Extern lichtfilter",
"Load Default": "Standaard laden", "Load Default": "Standaard laden",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Geavanceerd", "Advanced": "Geavanceerd",
"Advanced Options": "Bestand maken", "Advanced Options": "Bestand maken",
"Logging": "Loggen", "Logging": "Loggen",
@@ -181,10 +202,12 @@
"Restored hbmenu": "", "Restored hbmenu": "",
"Restart Sphaira?": "", "Restart Sphaira?": "",
"Press OK to restart Sphaira": "", "Press OK to restart Sphaira": "",
"Boost CPU during transfer": "",
"Text scroll speed": "", "Text scroll speed": "",
"Slow": "", "Slow": "",
"Normal": "", "Normal": "",
"Fast": "", "Fast": "",
"Set left-side menu": "",
"Set right-side menu": "", "Set right-side menu": "",
"Install options": "", "Install options": "",
"Install Options": "", "Install Options": "",
@@ -194,7 +217,6 @@
"Install location": "", "Install location": "",
"System memory": "", "System memory": "",
"microSD card": "", "microSD card": "",
"Boost CPU clock": "",
"Allow downgrade": "", "Allow downgrade": "",
"Skip if already installed": "", "Skip if already installed": "",
"Ticket only": "", "Ticket only": "",
@@ -225,6 +247,8 @@
"Updating ncm databse": "", "Updating ncm databse": "",
"Pushing application record": "", "Pushing application record": "",
"Failed to install forwarder": "", "Failed to install forwarder": "",
"Unstar": "",
"Star": "",
"Unstarred ": "", "Unstarred ": "",
"Starred ": "", "Starred ": "",
"Failed to remove old forwarder, please manually remove it!": "", "Failed to remove old forwarder, please manually remove it!": "",
@@ -257,12 +281,13 @@
"Copy": "Kopiëren", "Copy": "Kopiëren",
"Copying ": "", "Copying ": "",
"Paste": "", "Paste": "",
"Paste ": "", "Paste file(s)?": "",
" file(s)?": "",
"Pasting ": "", "Pasting ": "",
"Pasting": "", "Pasting": "",
"Rename": "Hernoemen", "Rename": "Hernoemen",
"Set New File Name": "", "Set New File Name": "",
"Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "", "Extract zip": "",
"Extract Options": "", "Extract Options": "",
"Extract here": "", "Extract here": "",
@@ -285,13 +310,17 @@
"Create Folder": "Map maken", "Create Folder": "Map maken",
"Set Folder Name": "", "Set Folder Name": "",
"Creating ": "", "Creating ": "",
"View as text (unfinished)": "Bekijk als tekst (onvoltooid)",
"Upload": "", "Upload": "",
"Select upload location": "", "Select upload location": "",
"No upload locations set!": "", "No upload locations set!": "",
"Uploading": "", "Uploading": "",
"Upload successfull!": "", "Upload successfull!": "",
"Upload failed!": "", "Upload failed!": "",
"View as text (unfinished)": "Bekijk als tekst (onvoltooid)", "Hash": "",
"Hash Options": "",
"Hashing": "",
"Failed to hash file...": "",
"Ignore read only": "", "Ignore read only": "",
"Mount": "", "Mount": "",
"Sd": "", "Sd": "",
@@ -302,6 +331,7 @@
"Launch ": "", "Launch ": "",
"Launch option for: ": "", "Launch option for: ": "",
"Select launcher for: ": "", "Select launcher for: ": "",
"Close FileBrowser?": "",
"Sort By": "Sorteer op", "Sort By": "Sorteer op",
"Sort Options": "Sorteeropties", "Sort Options": "Sorteeropties",
@@ -335,16 +365,16 @@
"Search": "Zoekopdracht", "Search": "Zoekopdracht",
"Options": "Opties", "Options": "Opties",
"Split": "",
"OK": "", "OK": "",
"Back": "Terug", "Back": "Terug",
"Select": "", "Select": "",
"Open": "Open", "Open": "Open",
"Close": "",
"Launch": "Launch", "Launch": "Launch",
"Restart": "", "Restart": "",
"Next": "", "Next": "",
"Prev": "", "Prev": "",
"Unstar": "",
"Star": "",
"Yes": "Ja", "Yes": "Ja",
"No": "Nee", "No": "Nee",
"On": "", "On": "",
@@ -367,12 +397,13 @@
"Remove": "", "Remove": "",
"Completely remove ": "", "Completely remove ": "",
"Removing ": "", "Removing ": "",
"Removed ": "",
"Uninstalling ": "", "Uninstalling ": "",
"Removed ": "",
"Download": "Downloaden", "Download": "Downloaden",
"Downloading ": "", "Downloading ": "",
"Downloaded ": "", "Downloaded ": "",
"Download via the Network options!": "",
"Update": "", "Update": "",
"Update avaliable: ": "", "Update avaliable: ": "",

View File

@@ -3,15 +3,16 @@
"No Internet": "Sem internet", "No Internet": "Sem internet",
"Switch-Handheld!": "Switch-Portátil", "Switch-Handheld!": "Switch-Portátil",
"Switch-Docked!": "Switch-Docado", "Switch-Docked!": "Switch-Docado",
"Warning! Logs are enabled, Sphaira will run slowly!": "",
"Audio disabled due to suspended game": "Áudio desativado devido ao software suspenso.", "Audio disabled due to suspended game": "Áudio desativado devido ao software suspenso.",
"Are you sure you wish to cancel?": "Tem certeza de que quer cancelar?", "Are you sure you wish to cancel?": "Tem certeza de que quer cancelar?",
"An error occurred": "Ocorreu um erro.", "An error occurred": "Ocorreu um erro.",
"If this message appears repeatedly, please open an issue.": "Se esta mensagem aparecer repetidamente, abra um issue.", "If this message appears repeatedly, please open an issue.": "Se esta mensagem aparecer repetidamente, abra um issue.",
"Menu Options": "Opções do menu", "Menu Options": "Menu",
"Menu": "Menu", "Menu": "Menu",
"Theme": "Tema", "Theme": "Tema",
"Theme Options": "Opções de tema", "Theme Options": "Tema",
"Select Theme": "Estilo", "Select Theme": "Estilo",
"Music": "Música", "Music": "Música",
"12 Hour Time": "Relógio de 12 horas", "12 Hour Time": "Relógio de 12 horas",
@@ -20,7 +21,7 @@
"Overwrite current default music?": "Substituir a música padrão atual?", "Overwrite current default music?": "Substituir a música padrão atual?",
"Network": "Rede", "Network": "Rede",
"Network Options": "Opções de rede", "Network Options": "Rede",
"Ftp": "Servidor FTP", "Ftp": "Servidor FTP",
"Mtp": "Escuta MTP", "Mtp": "Escuta MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
@@ -48,23 +49,36 @@
"Ukrainian": "Українська", "Ukrainian": "Українська",
"Misc": "Diversos", "Misc": "Diversos",
"Misc Options": "Opções diversas", "Misc Options": "Diversos",
"Games": "Softwares", "Games": "Softwares",
"Game Options": "Opções de software", "Game Options": "Opções do software",
"Hide forwarders": "Ocultar atalhos forwarder", "Hide forwarders": "Ocultar atalhos forwarder",
"Launch random game": "Iniciar um software aleatório", "Launch random game": "Iniciar um software aleatório",
"List meta records": "Registro de dados", "List meta records": "Registro de dados",
"Entries": "Entradas", "Entries": "Entradas",
"Failed to list application meta entries": "Falha ao listar as meta entradas do aplicativo.", "Failed to list application meta entries": "Falha ao listar as meta entradas do software.",
"No meta entries found...\n": "Nenhuma entrada de registros encontrada...\n", "No meta entries found...\n": "Nenhuma entrada de registros encontrada...\n",
"Updating application record list": "Atualizando a lista de registros do software...", "Updating application record list": "Atualizando a lista de registros do software...",
"Dump": "Exportar dados", "Dump": "Exportar dados",
"Dump options": "Opções de exportação",
"Dump Options": "Opções de exportação",
"Select content to dump": "Exportação de dados", "Select content to dump": "Exportação de dados",
"Dump All": "Exportar tudo", "Dump All": "Exportar tudo",
"Dump Application": "Exportar software base", "Dump Application": "Exportar software base",
"Dump Patch": "Exportar atualização", "Dump Patch": "Exportar atualização",
"Dump AddOnContent": "Exportar DLCs", "Dump AddOnContent": "Exportar DLCs",
"Dump DataPatch": "Exportar atualização de DLCs", "Dump DataPatch": "Exportar atualização de DLCs",
"Created nested folder": "Criar pastas aninhadas",
"Append folder with .xci": "Acrescentar .xci à pasta",
"Trim XCI": "Aparar arquivos .xci",
"Label trimmed XCI": "Rotular .xci aparados",
"Multi-threaded USB transfer": "Trasfêrencia em multithread",
"Dump All Bins": "Exportar todos os binários",
"Dump XCI": "Exportar .xci",
"Dump Card ID Set": "Exportar conjunto de IDs",
"Dump Card UID": "Exportar UID",
"Dump Certificate": "Exportar certificado",
"Dump Initial Data": "Exportar dados iniciais",
"Select dump location": "Selecione o local de exportação", "Select dump location": "Selecione o local de exportação",
"microSD card (/dumps/)": "Cartão microSD (/dumps/)", "microSD card (/dumps/)": "Cartão microSD (/dumps/)",
"USB transfer (Switch 2 Switch)": "Transferência via USB (Switch 2 Switch)", "USB transfer (Switch 2 Switch)": "Transferência via USB (Switch 2 Switch)",
@@ -72,9 +86,9 @@
"Dumping": "Exportando...", "Dumping": "Exportando...",
"Dump successfull!": "Exportação concluída.", "Dump successfull!": "Exportação concluída.",
"Dump failed!": "Exportação falhou.", "Dump failed!": "Exportação falhou.",
"Success": "Concluído.",
"Delete successfull!": "Remoção concluída.", "Delete successfull!": "Remoção concluída.",
"Delete failed!": "Remoção falhou.", "Delete failed!": "Remoção falhou.",
"Success": "Concluído.",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Opções do Themezer", "Themezer Options": "Opções do Themezer",
@@ -88,11 +102,13 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "Baixando JSON", "Downloading json": "Baixando JSON",
"Select asset to download for ": "Selecione o recurso para baixar de ", "Select asset to download for ": "Selecione o recurso para baixar de ",
"Failed to download json": "",
"Failed to download app!": "",
"FTP Install": "Instalação via FTP", "FTP Install": "Instalação via FTP",
"FTP Install (EXPERIMENTAL)": "Instalação via FTP (EXPERIMENTAL)", "FTP Install (EXPERIMENTAL)": "Instalação via FTP (experimental)",
"Connection Type: WiFi | Strength: ": "Conexão por rede Wi-Fi | Intensidade do sinal: ", "Connection Type: WiFi | Strength: ": "Conexão por rede Wi-Fi | Intensidade do sinal: ",
"Connection Type: Ethernet": "Conexão por cabo (ethernet)", "Connection Type: Ethernet": "Conexão por cabo Ethernet",
"Connection Type: None": "Sem conexão", "Connection Type: None": "Sem conexão",
"Host:": "Host:", "Host:": "Host:",
"Port:": "Porta:", "Port:": "Porta:",
@@ -124,11 +140,16 @@
"GC": "Cartão de jogo", "GC": "Cartão de jogo",
"System memory %.1f GB": "Memória do console %.1f GB", "System memory %.1f GB": "Memória do console %.1f GB",
"microSD card %.1f GB": "Cartão microSD %.1f GB", "microSD card %.1f GB": "Cartão microSD %.1f GB",
"Nand Install": "Instalar na memória do console",
"SD Card Install": "Instalar no cartão microSD",
"Exit": "Sair", "Exit": "Sair",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "Instalação de cartão de jogo concluída.", "Gc install success!": "Instalação de cartão de jogo concluída.",
"Gc install failed!": "Instalação de cartão de jogo falhou.", "Gc install failed!": "Instalação de cartão de jogo falhou.",
"Gc install success!": "Instalação do cartão de jogo concluída.",
"Gc install failed!": "Instalação do cartão de jogo falhou.",
"IRS (Infrared Joycon Camera)": "Câmera de movimento IR", "IRS (Infrared Joycon Camera)": "Câmera de movimento IR",
"IRS": "Câmera de movimento IR", "IRS": "Câmera de movimento IR",
@@ -162,22 +183,26 @@
"Negative image": "Imagem negativa", "Negative image": "Imagem negativa",
"Format": "Formato", "Format": "Formato",
"Trimming Format": "Formato do recorte", "Trimming Format": "Formato do recorte",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Filtro de luz externa", "External Light Filter": "Filtro de luz externa",
"Load Default": "Restaurar padrão", "Load Default": "Restaurar padrão",
"Advanced": "Avançados", "Web": "Navegador de internet",
"Select URL": "Selecione uma URL",
"Enter custom URL": "Digitar URL",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Opções avançadas",
"Advanced Options": "Opções avançadas", "Advanced Options": "Opções avançadas",
"Logging": "Registro de depuração", "Logging": "Registro de depuração",
"Replace hbmenu on exit": "Substituir hbmenu ao sair", "Replace hbmenu on exit": "Substituir hbmenu ao sair",
"Restore hbmenu?": "Restaurar hbmenu?", "Restore hbmenu?": "Restaurar hbmenu?",
"Restore": "Restaurar", "Restore": "Restaurar",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Falha ao buscar /switch/hbmenu.nro\nUse a loja (AppStore) para reinstalar o hbmenu.", "Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Falha ao buscar /switch/hbmenu.nro\nUse a loja (AppStore) para reinstalar o hbmenu.",
"Failed to restore hbmenu, please re-download hbmenu": "Falha ao restaurar o hbmenu, tente baixa-lo novamente.", "Failed to restore hbmenu, please re-download hbmenu": "Falha ao restaurar hbmenu, tente baixa-lo novamente.",
"Failed to restore hbmenu, using sphaira instead": "Falha ao restaurar hbmenu, usando sphaira no seu lugar.", "Failed to restore hbmenu, using sphaira instead": "Falha ao restaurar hbmenu, usando sphaira no seu lugar.",
"Restored hbmenu, closing sphaira": "hbmenu restaurado, fechando sphaira.", "Restored hbmenu, closing sphaira": "hbmenu restaurado, fechando sphaira.",
"Restored hbmenu": "hbmenu restaurado.", "Restored hbmenu": "hbmenu restaurado.",
@@ -198,7 +223,6 @@
"Install location": "Local de instalação", "Install location": "Local de instalação",
"System memory": "Memória do console", "System memory": "Memória do console",
"microSD card": "Cartão microSD", "microSD card": "Cartão microSD",
"Boost CPU clock": "Impulsionar CPU",
"Allow downgrade": "Permitir downgrade", "Allow downgrade": "Permitir downgrade",
"Skip if already installed": "Pular se já instalado", "Skip if already installed": "Pular se já instalado",
"Ticket only": "Instalar apenas tickets", "Ticket only": "Instalar apenas tickets",
@@ -220,12 +244,12 @@
"Append folder with .xci": "Acrescentar .xci à pasta", "Append folder with .xci": "Acrescentar .xci à pasta",
"Trim XCI": "Aparar arquivos .xci", "Trim XCI": "Aparar arquivos .xci",
"Label trimmed XCI": "Rotular .xci aparados", "Label trimmed XCI": "Rotular .xci aparados",
"Multi-threaded USB transfer": "Trasfêrencia em multithread", "Multi-threaded USB transfer": "Transferências em multithread",
"Homebrew": "Aplicativos", "Homebrew": "Homebrews",
"Apps": "Aplicativos", "Apps": "Homebrews",
"Homebrew Options": "Opções de aplicativo", "Homebrew Options": "Opções do homebrew",
"Hide Sphaira": "Esconder sphaira", "Hide Sphaira": "Esconder sphaira da lista",
"Install Forwarder": "Instalar atalho forwarder", "Install Forwarder": "Instalar atalho forwarder",
"WARNING: Installing forwarders will lead to a ban!": "AVISO: Instalar atalhos forwarder pode resultar em um banimento!", "WARNING: Installing forwarders will lead to a ban!": "AVISO: Instalar atalhos forwarder pode resultar em um banimento!",
"Installing Forwarder": "Instalando atalho forwarder...", "Installing Forwarder": "Instalando atalho forwarder...",
@@ -236,6 +260,8 @@
"Updating ncm databse": "Atualizando base de dados NCM", "Updating ncm databse": "Atualizando base de dados NCM",
"Pushing application record": "Aplicando registro do software", "Pushing application record": "Aplicando registro do software",
"Failed to install forwarder": "Falha ao instalar atalho forwarder.", "Failed to install forwarder": "Falha ao instalar atalho forwarder.",
"Unstar": "Desfavoritar",
"Star": "Favoritar",
"Unstarred ": "Desfavoritado ", "Unstarred ": "Desfavoritado ",
"Starred ": "Favoritado ", "Starred ": "Favoritado ",
"Failed to remove old forwarder, please manually remove it!": "Falha ao desinstalar atalho forwarder, tente remove-lo manualmente.", "Failed to remove old forwarder, please manually remove it!": "Falha ao desinstalar atalho forwarder, tente remove-lo manualmente.",
@@ -260,23 +286,24 @@
"Files": "Arquivos", "Files": "Arquivos",
"%zd files": "%zd arquivo(s)", "%zd files": "%zd arquivo(s)",
"%zd dirs": "%zd diretório(s)", "%zd dirs": "%zd diretório(s)",
"File Options": "Opções de arquivo", "File Options": "Opções do arquivo",
"Show Hidden": "Mostrar ocultos", "Show Hidden": "Mostrar arquivos ocultos",
"Folders First": "Pastas primeiro", "Folders First": "Ordenar pastas primeiro",
"Hidden Last": "Ocultos por último", "Hidden Last": "Ordenar ocultos por último",
"Split": "Dividir", "Split": "Dividir",
"Close": "Fechar", "Close": "Fechar",
"Cut": "Recortar", "Cut": "Recortar",
"Copy": "Copiar", "Copy": "Copiar",
"Copying ": "Copiando ", "Copying ": "Copiando ",
"Paste": "Colar", "Paste": "Colar",
"Paste ": "Colar ", "Paste file(s)?": "",
" file(s)?": " arquivo(s)?",
"Pasting ": "Colando ", "Pasting ": "Colando ",
"Pasting": "Colando ", "Pasting": "Colando ",
"Rename": "Renomear", "Rename": "Renomear",
"Set New File Name": "Defina o nome do novo arquivo", "Set New File Name": "Defina o nome do novo arquivo",
"Extract zip": "Extrair zip", "Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "Extrair .zip",
"Extract Options": "Opções de extração", "Extract Options": "Opções de extração",
"Extract here": "Extrair aqui", "Extract here": "Extrair aqui",
"Extract to root": "Extrair para a raiz", "Extract to root": "Extrair para a raiz",
@@ -286,8 +313,8 @@
"Extracting ": "Extraindo ", "Extracting ": "Extraindo ",
"Extract success!": "Extração concluída.", "Extract success!": "Extração concluída.",
"Extract failed!": "Extração falhou.", "Extract failed!": "Extração falhou.",
"Compress to zip": "Comprimir em zip", "Compress to zip": "Comprimir em .zip",
"Compress Options": "Opções de compressão", "Compress Options": "Comprimir em .zip",
"Compress": "Comprimir", "Compress": "Comprimir",
"Compress to...": "Comprimir para...", "Compress to...": "Comprimir para...",
"Compressing ": "Comprimindo ", "Compressing ": "Comprimindo ",
@@ -298,29 +325,32 @@
"Create Folder": "Criar pasta", "Create Folder": "Criar pasta",
"Set Folder Name": "Defina o nome da pasta", "Set Folder Name": "Defina o nome da pasta",
"Creating ": "Criando ", "Creating ": "Criando ",
"View as text (unfinished)": "Ver como texto (inacabado)",
"Upload": "Enviar", "Upload": "Enviar",
"Select upload location": "Selecione o local de envio", "Select upload location": "Selecione o local de envio",
"No upload locations set!": "Nenhum local de envio definido.", "No upload locations set!": "Nenhum local de envio definido.",
"Uploading": "Enviando", "Uploading": "Enviando",
"Upload successfull!": "Envio concluído.", "Upload successfull!": "Envio concluído.",
"Upload failed!": "Envio falhou.", "Upload failed!": "Envio falhou.",
"Hash": "Hash", "Hash": "Calcular hash",
"Hash Options": "Opções de hash", "Hash Options": "Calcular hash",
"Hashing": "",
"Failed to hash file...": "",
"View as text (unfinished)": "Ver como texto (inacabado)", "View as text (unfinished)": "Ver como texto (inacabado)",
"Ignore read only": "Ignorar modo somente leitura", "Ignore read only": "Ignorar modo somente leitura",
"Mount": "Montar", "Mount": "Montar",
"Sd": "SD", "Sd": "SD",
"Image System memory": "Imagem (memória do console)", "Image System memory": "Imagem (memória do console)",
"Image microSD card": "Imagem (cartão microSD)", "Image microSD card": "Imagem (cartão microSD)",
"Empty...": "Vazio", "Empty...": "Vazio.",
"Open with DayBreak?": "Abrir com DayBreak?", "Open with DayBreak?": "Abrir com DayBreak?",
"Launch ": "Iniciar ", "Launch ": "Iniciar ",
"Launch option for: ": "Opções de inicialização para: ", "Launch option for: ": "Opções de inicialização para: ",
"Select launcher for: ": "Selecionar inicializador para: ", "Select launcher for: ": "Selecionar inicializador para: ",
"Close FileBrowser?": "Fechar o gerenciador de arquivos?", "Close FileBrowser?": "Fechar gerenciador de arquivos?",
"Sort By": "Ordernar", "Sort By": "Ordenar",
"Sort Options": "Opções de ordenação", "Sort Options": "Ordenar",
"Filter": "Filtro", "Filter": "Filtro",
"All": "Todos", "All": "Todos",
"Emulators": "Emuladores", "Emulators": "Emuladores",
@@ -351,16 +381,16 @@
"Search": "Buscar", "Search": "Buscar",
"Options": "Opções", "Options": "Opções",
"Split": "Dividir",
"OK": "OK", "OK": "OK",
"Back": "Voltar", "Back": "Voltar",
"Select": "Selecionar", "Select": "Selecionar",
"Open": "Abrir", "Open": "Abrir",
"Close": "Fechar",
"Launch": "Iniciar", "Launch": "Iniciar",
"Restart": "Reiniciar", "Restart": "Reiniciar",
"Next": "Prómixo", "Next": "Prómixo",
"Prev": "Anterior", "Prev": "Anterior",
"Unstar": "Desfavoritar",
"Star": "Favoritar",
"Yes": "Sim", "Yes": "Sim",
"No": "Não", "No": "Não",
"On": "Sim", "On": "Sim",
@@ -383,12 +413,13 @@
"Remove": "Remover", "Remove": "Remover",
"Completely remove ": "Remover completamente ", "Completely remove ": "Remover completamente ",
"Removing ": "Removendo ", "Removing ": "Removendo ",
"Removed ": "Removido ",
"Uninstalling ": "Desinstalando ", "Uninstalling ": "Desinstalando ",
"Removed ": "Removido ",
"Download": "Baixar", "Download": "Baixar",
"Downloading ": "Baixando ", "Downloading ": "Baixando ",
"Downloaded ": "Baixado ", "Downloaded ": "Baixado ",
"Download via the Network options!": "",
"Update": "Atualizar", "Update": "Atualizar",
"Update avaliable: ": "Atualização disponível: ", "Update avaliable: ": "Atualização disponível: ",

View File

@@ -3,6 +3,7 @@
"No Internet": "Нет интернета", "No Internet": "Нет интернета",
"Switch-Handheld!": "Режим Портатива", "Switch-Handheld!": "Режим Портатива",
"Switch-Docked!": "Режим Дока", "Switch-Docked!": "Режим Дока",
"Warning! Logs are enabled, Sphaira will run slowly!": "",
"Audio disabled due to suspended game": "Звук отключён из-за приостановки игры", "Audio disabled due to suspended game": "Звук отключён из-за приостановки игры",
"Are you sure you wish to cancel?": "Вы уверены, что хотите отменить?", "Are you sure you wish to cancel?": "Вы уверены, что хотите отменить?",
"An error occurred": "Произошла ошибка", "An error occurred": "Произошла ошибка",
@@ -59,22 +60,35 @@
"No meta entries found...\n": "Мета-записей не найдено...\n", "No meta entries found...\n": "Мета-записей не найдено...\n",
"Updating application record list": "Обновление списка записей приложений", "Updating application record list": "Обновление списка записей приложений",
"Dump": "Дамп", "Dump": "Дамп",
"Dump options": "Опции дампа",
"Dump Options": "Опции дампа",
"Select content to dump": "Выберите содержимое для дампа", "Select content to dump": "Выберите содержимое для дампа",
"Dump All": "Дамп всего", "Dump All": "Дамп всего",
"Dump Application": "Дамп приложения", "Dump Application": "Дамп приложения",
"Dump Patch": "Дамп патча", "Dump Patch": "Дамп патча",
"Dump AddOnContent": "Дамп дополнительного контента", "Dump AddOnContent": "Дамп дополнительного контента",
"Dump DataPatch": "Дамп патча данных", "Dump DataPatch": "Дамп патча данных",
"Created nested folder": "Создать вложенную папку",
"Append folder with .xci": "Добавить папку с .xci",
"Trim XCI": "Обрезать .xci",
"Label trimmed XCI": "Пометить обрезанный .xci",
"Multi-threaded USB transfer": "Многоядерная USB передача",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "Выберите место для дампа", "Select dump location": "Выберите место для дампа",
"microSD card (/dumps/NSP/)": "microSD карта (/dumps/NSP/)", "microSD card (/dumps/)": "microSD карта (/dumps/)",
"USB transfer (Switch 2 Switch)": "Передача по USB (Switch 2 Switch)", "USB transfer (Switch 2 Switch)": "Передача по USB (Switch 2 Switch)",
"/dev/null (Speed Test)": "/dev/null (тест скорости)", "/dev/null (Speed Test)": "/dev/null (тест скорости)",
"Dumping": "Снятие дампа", "Dumping": "Снятие дампа",
"Dump successfull!": "Дамп выполнен успешно!", "Dump successfull!": "Дамп выполнен успешно!",
"Dump failed!": "Сбой дампа!", "Dump failed!": "Сбой дампа!",
"Success": "Успех",
"Delete successfull!": "Удаление успешно!", "Delete successfull!": "Удаление успешно!",
"Delete failed!": "Ошибка удаления!", "Delete failed!": "Ошибка удаления!",
"Success": "Успех",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Опции Themezer", "Themezer Options": "Опции Themezer",
@@ -88,6 +102,8 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "Загрузка json", "Downloading json": "Загрузка json",
"Select asset to download for ": "Выберите ресурс для загрузки: ", "Select asset to download for ": "Выберите ресурс для загрузки: ",
"Failed to download json": "",
"Failed to download app!": "",
"FTP Install": "Установка по FTP", "FTP Install": "Установка по FTP",
"FTP Install (EXPERIMENTAL)": "Установка по FTP (ЭКСПЕРИМЕНТАЛЬНО)", "FTP Install (EXPERIMENTAL)": "Установка по FTP (ЭКСПЕРИМЕНТАЛЬНО)",
@@ -124,9 +140,12 @@
"GC": "GC", "GC": "GC",
"System memory %.1f GB": "Память системы %.1f ГБ", "System memory %.1f GB": "Память системы %.1f ГБ",
"microSD card %.1f GB": "Карта microSD %.1f ГБ", "microSD card %.1f GB": "Карта microSD %.1f ГБ",
"Nand Install": "Установка в NAND",
"SD Card Install": "Установка на SD карту",
"Exit": "Выход", "Exit": "Выход",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "Установка с картриджа успешна!", "Gc install success!": "Установка с картриджа успешна!",
"Gc install failed!": "Сбой установки с картриджа!", "Gc install failed!": "Сбой установки с картриджа!",
@@ -162,14 +181,14 @@
"Negative image": "Включен", "Negative image": "Включен",
"Format": "Разрешение", "Format": "Разрешение",
"Trimming Format": "Обрезка", "Trimming Format": "Обрезка",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Внешний светофильтр", "External Light Filter": "Внешний светофильтр",
"Load Default": "По умолчанию", "Load Default": "По умолчанию",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Продвинутые", "Advanced": "Продвинутые",
"Advanced Options": "Расширенные опции", "Advanced Options": "Расширенные опции",
"Logging": "Журналирование", "Logging": "Журналирование",
@@ -198,7 +217,6 @@
"Install location": "Место установки", "Install location": "Место установки",
"System memory": "NAND", "System memory": "NAND",
"microSD card": "microSD", "microSD card": "microSD",
"Boost CPU clock": "Разгон CPU",
"Allow downgrade": "Разрешить даунгрейд", "Allow downgrade": "Разрешить даунгрейд",
"Skip if already installed": "Пропуск установленного", "Skip if already installed": "Пропуск установленного",
"Ticket only": "Только тикет", "Ticket only": "Только тикет",
@@ -214,13 +232,6 @@
"Convert to standard crypto": "Конверт. в стандарт. крипт.", "Convert to standard crypto": "Конверт. в стандарт. крипт.",
"Lower master key": "Снизить мастер-ключ", "Lower master key": "Снизить мастер-ключ",
"Lower system version": "Снизить версию системы", "Lower system version": "Снизить версию системы",
"Dump options": "Опции дампа",
"Dump Options": "Опции дампа",
"Created nested folder": "Создать вложенную папку",
"Append folder with .xci": "Добавить папку с .xci",
"Trim XCI": "Обрезать .xci",
"Label trimmed XCI": "Пометить обрезанный .xci",
"Multi-threaded USB transfer": "Многоядерная USB передача",
"Homebrew": "Homebrew", "Homebrew": "Homebrew",
"Apps": "Приложения", "Apps": "Приложения",
@@ -236,6 +247,8 @@
"Updating ncm databse": "Обновление базы данных NCM", "Updating ncm databse": "Обновление базы данных NCM",
"Pushing application record": "Добавление записи приложения", "Pushing application record": "Добавление записи приложения",
"Failed to install forwarder": "Не удалось установить форвардер", "Failed to install forwarder": "Не удалось установить форвардер",
"Unstar": "Убрать из избранного",
"Star": "Добавить в избранное",
"Unstarred ": "Удалено из избранного: ", "Unstarred ": "Удалено из избранного: ",
"Starred ": "Добавлено в избранное: ", "Starred ": "Добавлено в избранное: ",
"Failed to remove old forwarder, please manually remove it!": "Не удалось удалить старый форвардер, удалите его вручную!", "Failed to remove old forwarder, please manually remove it!": "Не удалось удалить старый форвардер, удалите его вручную!",
@@ -264,18 +277,17 @@
"Show Hidden": "Показать скрытые", "Show Hidden": "Показать скрытые",
"Folders First": "Папки в начале", "Folders First": "Папки в начале",
"Hidden Last": "Скрытые в конце", "Hidden Last": "Скрытые в конце",
"Split": "Разделить",
"Close": "Закрыть",
"Cut": "Вырезать", "Cut": "Вырезать",
"Copy": "Копировать", "Copy": "Копировать",
"Copying ": "Копирование ", "Copying ": "Копирование ",
"Paste": "Вставить", "Paste": "Вставить",
"Paste ": "Вставить ", "Paste file(s)?": "",
" file(s)?": " файл(ов)?",
"Pasting ": "Вставка ", "Pasting ": "Вставка ",
"Pasting": "Вставка", "Pasting": "Вставка",
"Rename": "Переименовать", "Rename": "Переименовать",
"Set New File Name": "Задайте новое имя файла", "Set New File Name": "Задайте новое имя файла",
"Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "Распаковать .zip", "Extract zip": "Распаковать .zip",
"Extract Options": "Опции распаковки", "Extract Options": "Опции распаковки",
"Extract here": "Распаковать сюда", "Extract here": "Распаковать сюда",
@@ -298,6 +310,7 @@
"Create Folder": "Создать папку", "Create Folder": "Создать папку",
"Set Folder Name": "Укажите имя папки", "Set Folder Name": "Укажите имя папки",
"Creating ": "Создание ", "Creating ": "Создание ",
"View as text (unfinished)": "Открыть как текст (не доделано)",
"Upload": "Отправить", "Upload": "Отправить",
"Select upload location": "Выберите место загрузки", "Select upload location": "Выберите место загрузки",
"No upload locations set!": "Места загрузки не заданы!", "No upload locations set!": "Места загрузки не заданы!",
@@ -306,7 +319,8 @@
"Upload failed!": "Сбой Отправки!", "Upload failed!": "Сбой Отправки!",
"Hash": "Хэш", "Hash": "Хэш",
"Hash Options": "Опции хэша", "Hash Options": "Опции хэша",
"View as text (unfinished)": "Открыть как текст (не доделано)", "Hashing": "",
"Failed to hash file...": "",
"Ignore read only": "Игнор. только для чтения", "Ignore read only": "Игнор. только для чтения",
"Mount": "Монтирован", "Mount": "Монтирован",
"Sd": "Micro SD", "Sd": "Micro SD",
@@ -351,16 +365,16 @@
"Search": "Поиск", "Search": "Поиск",
"Options": "Опции", "Options": "Опции",
"Split": "Разделить",
"OK": "ОК", "OK": "ОК",
"Back": "Назад", "Back": "Назад",
"Select": "Выбрать", "Select": "Выбрать",
"Open": "Открыть", "Open": "Открыть",
"Close": "Закрыть",
"Launch": "Запуск", "Launch": "Запуск",
"Restart": "Перезапустить", "Restart": "Перезапустить",
"Next": "Далее", "Next": "Далее",
"Prev": "Назад", "Prev": "Назад",
"Unstar": "Убрать из избранного",
"Star": "Добавить в избранное",
"Yes": "Да", "Yes": "Да",
"No": "Нет", "No": "Нет",
"On": "Вкл", "On": "Вкл",
@@ -383,12 +397,13 @@
"Remove": "Удалить", "Remove": "Удалить",
"Completely remove ": "Полностью удалить ", "Completely remove ": "Полностью удалить ",
"Removing ": "Удаляется ", "Removing ": "Удаляется ",
"Removed ": "Удалено ",
"Uninstalling ": "Деинсталляция ", "Uninstalling ": "Деинсталляция ",
"Removed ": "Удалено ",
"Download": "Скачать", "Download": "Скачать",
"Downloading ": "Загрузка ", "Downloading ": "Загрузка ",
"Downloaded ": "Загружено ", "Downloaded ": "Загружено ",
"Download via the Network options!": "",
"Update": "Обновить", "Update": "Обновить",
"Update avaliable: ": "Доступно обновление: ", "Update avaliable: ": "Доступно обновление: ",

View File

@@ -3,6 +3,7 @@
"No Internet": "Ingen Internetanslutning", "No Internet": "Ingen Internetanslutning",
"Switch-Handheld!": "Switch Handhållen!", "Switch-Handheld!": "Switch Handhållen!",
"Switch-Docked!": "Switch Dockad!", "Switch-Docked!": "Switch Dockad!",
"Warning! Logs are enabled, Sphaira will run slowly!": "",
"Audio disabled due to suspended game": "Ljud är avstängt på grund av bakgrundsprogram", "Audio disabled due to suspended game": "Ljud är avstängt på grund av bakgrundsprogram",
"Are you sure you wish to cancel?": "Är du säker på att du vill avbryta?", "Are you sure you wish to cancel?": "Är du säker på att du vill avbryta?",
"An error occurred": "", "An error occurred": "",
@@ -27,6 +28,8 @@
"Nxlink Connected": "Nxlink ansluten", "Nxlink Connected": "Nxlink ansluten",
"Nxlink Upload": "Nxlink överför", "Nxlink Upload": "Nxlink överför",
"Nxlink Finished": "Nxlink klar", "Nxlink Finished": "Nxlink klar",
"Hdd": "",
"Hdd write protect": "",
"Language": "Språk", "Language": "Språk",
"Auto": "Auto", "Auto": "Auto",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "", "No meta entries found...\n": "",
"Updating application record list": "", "Updating application record list": "",
"Dump": "", "Dump": "",
"Dump options": "",
"Dump Options": "",
"Select content to dump": "", "Select content to dump": "",
"Dump All": "", "Dump All": "",
"Dump Application": "", "Dump Application": "",
"Dump Patch": "", "Dump Patch": "",
"Dump AddOnContent": "", "Dump AddOnContent": "",
"Dump DataPatch": "", "Dump DataPatch": "",
"Created nested folder": "",
"Append folder with .xci": "",
"Trim XCI": "",
"Label trimmed XCI": "",
"Multi-threaded USB transfer": "",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "", "Select dump location": "",
"microSD card (/dumps/NSP/)": "", "microSD card (/dumps/)": "",
"USB transfer (Switch 2 Switch)": "", "USB transfer (Switch 2 Switch)": "",
"/dev/null (Speed Test)": "", "/dev/null (Speed Test)": "",
"Dumping": "", "Dumping": "",
"Dump successfull!": "", "Dump successfull!": "",
"Dump failed!": "", "Dump failed!": "",
"Success": "",
"Delete successfull!": "", "Delete successfull!": "",
"Delete failed!": "", "Delete failed!": "",
"Success": "",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Themezer-alternativ", "Themezer Options": "Themezer-alternativ",
@@ -86,6 +102,8 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "Laddar ner JSON", "Downloading json": "Laddar ner JSON",
"Select asset to download for ": "Välj tillgång att ladda ner för ", "Select asset to download for ": "Välj tillgång att ladda ner för ",
"Failed to download json": "",
"Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"FTP Install (EXPERIMENTAL)": "", "FTP Install (EXPERIMENTAL)": "",
@@ -122,9 +140,12 @@
"GC": "", "GC": "",
"System memory %.1f GB": "", "System memory %.1f GB": "",
"microSD card %.1f GB": "", "microSD card %.1f GB": "",
"Nand Install": "",
"SD Card Install": "",
"Exit": "", "Exit": "",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "", "Gc install success!": "",
"Gc install failed!": "", "Gc install failed!": "",
@@ -160,14 +181,14 @@
"Negative image": "Negativ bild", "Negative image": "Negativ bild",
"Format": "Format", "Format": "Format",
"Trimming Format": "Trimningsformat", "Trimming Format": "Trimningsformat",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Externt ljusfilter", "External Light Filter": "Externt ljusfilter",
"Load Default": "Ladda standard", "Load Default": "Ladda standard",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Avancerat", "Advanced": "Avancerat",
"Advanced Options": "Avancerade alternativ", "Advanced Options": "Avancerade alternativ",
"Logging": "Loggning", "Logging": "Loggning",
@@ -181,10 +202,12 @@
"Restored hbmenu": "Återställde hbmenu.", "Restored hbmenu": "Återställde hbmenu.",
"Restart Sphaira?": "Starta om Sphaira?", "Restart Sphaira?": "Starta om Sphaira?",
"Press OK to restart Sphaira": "", "Press OK to restart Sphaira": "",
"Boost CPU during transfer": "",
"Text scroll speed": "", "Text scroll speed": "",
"Slow": "", "Slow": "",
"Normal": "", "Normal": "",
"Fast": "", "Fast": "",
"Set left-side menu": "",
"Set right-side menu": "", "Set right-side menu": "",
"Install options": "", "Install options": "",
"Install Options": "", "Install Options": "",
@@ -194,7 +217,6 @@
"Install location": "Installationsplats", "Install location": "Installationsplats",
"System memory": "Systemminne", "System memory": "Systemminne",
"microSD card": "microSD-kort", "microSD card": "microSD-kort",
"Boost CPU clock": "",
"Allow downgrade": "", "Allow downgrade": "",
"Skip if already installed": "", "Skip if already installed": "",
"Ticket only": "", "Ticket only": "",
@@ -225,6 +247,8 @@
"Updating ncm databse": "Uppdaterar ncm-databas", "Updating ncm databse": "Uppdaterar ncm-databas",
"Pushing application record": "Skickar programpost", "Pushing application record": "Skickar programpost",
"Failed to install forwarder": "Misslyckades att installera genväg", "Failed to install forwarder": "Misslyckades att installera genväg",
"Unstar": "Avmarkera stjärna",
"Star": "Markera stjärna",
"Unstarred ": "Avmarkerad ", "Unstarred ": "Avmarkerad ",
"Starred ": "Markerad ", "Starred ": "Markerad ",
"Failed to remove old forwarder, please manually remove it!": "", "Failed to remove old forwarder, please manually remove it!": "",
@@ -257,12 +281,13 @@
"Copy": "Kopiera", "Copy": "Kopiera",
"Copying ": "Kopierar ", "Copying ": "Kopierar ",
"Paste": "Klistra in", "Paste": "Klistra in",
"Paste ": "Klistra in ", "Paste file(s)?": "",
" file(s)?": " fil(er)?",
"Pasting ": "Klistrar in ", "Pasting ": "Klistrar in ",
"Pasting": "Klistrar in", "Pasting": "Klistrar in",
"Rename": "Byt namn", "Rename": "Byt namn",
"Set New File Name": "Ange nytt filnamn", "Set New File Name": "Ange nytt filnamn",
"Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "", "Extract zip": "",
"Extract Options": "", "Extract Options": "",
"Extract here": "", "Extract here": "",
@@ -285,13 +310,17 @@
"Create Folder": "Skapa mapp", "Create Folder": "Skapa mapp",
"Set Folder Name": "Ange mappnamn", "Set Folder Name": "Ange mappnamn",
"Creating ": "Skapar ", "Creating ": "Skapar ",
"View as text (unfinished)": "Visa som text (ofärdig)",
"Upload": "", "Upload": "",
"Select upload location": "", "Select upload location": "",
"No upload locations set!": "", "No upload locations set!": "",
"Uploading": "", "Uploading": "",
"Upload successfull!": "", "Upload successfull!": "",
"Upload failed!": "", "Upload failed!": "",
"View as text (unfinished)": "Visa som text (ofärdig)", "Hash": "",
"Hash Options": "",
"Hashing": "",
"Failed to hash file...": "",
"Ignore read only": "Ignorera skrivskydd", "Ignore read only": "Ignorera skrivskydd",
"Mount": "Montera", "Mount": "Montera",
"Sd": "Sd", "Sd": "Sd",
@@ -302,6 +331,7 @@
"Launch ": "Starta ", "Launch ": "Starta ",
"Launch option for: ": "Startalternativ för: ", "Launch option for: ": "Startalternativ för: ",
"Select launcher for: ": "Välj startprogram för: ", "Select launcher for: ": "Välj startprogram för: ",
"Close FileBrowser?": "",
"Sort By": "Sortera efter", "Sort By": "Sortera efter",
"Sort Options": "Sorteringsalternativ", "Sort Options": "Sorteringsalternativ",
@@ -335,16 +365,16 @@
"Search": "Sök", "Search": "Sök",
"Options": "Alternativ", "Options": "Alternativ",
"Split": "",
"OK": "OK", "OK": "OK",
"Back": "Tillbaka", "Back": "Tillbaka",
"Select": "Välj", "Select": "Välj",
"Open": "Öppna", "Open": "Öppna",
"Close": "",
"Launch": "Starta", "Launch": "Starta",
"Restart": "Starta om", "Restart": "Starta om",
"Next": "", "Next": "",
"Prev": "", "Prev": "",
"Unstar": "Avmarkera stjärna",
"Star": "Markera stjärna",
"Yes": "Ja", "Yes": "Ja",
"No": "Nej", "No": "Nej",
"On": "", "On": "",
@@ -367,12 +397,13 @@
"Remove": "Ta bort", "Remove": "Ta bort",
"Completely remove ": "Ta bort helt ", "Completely remove ": "Ta bort helt ",
"Removing ": "Tar bort ", "Removing ": "Tar bort ",
"Removed ": "Borttagen ",
"Uninstalling ": "Avinstallerar ", "Uninstalling ": "Avinstallerar ",
"Removed ": "Borttagen ",
"Download": "Ladda ner", "Download": "Ladda ner",
"Downloading ": "Laddar ner ", "Downloading ": "Laddar ner ",
"Downloaded ": "Nedladdad ", "Downloaded ": "Nedladdad ",
"Download via the Network options!": "",
"Update": "Uppdatera", "Update": "Uppdatera",
"Update avaliable: ": "Uppdatering tillgänglig: ", "Update avaliable: ": "Uppdatering tillgänglig: ",

View File

@@ -1,11 +1,12 @@
{ {
"[Applet Mode]": "[Режим Аплету]", "[Applet Mode]": "[Аплет]",
"No Internet": "Без інтернету", "No Internet": "Без інтернету",
"Switch-Handheld!": "Switch - Портатив!", "Switch-Handheld!": "Switch - Портатив!",
"Switch-Docked!": "Switch - Докований!", "Switch-Docked!": "Switch - Док!",
"Warning! Logs are enabled, Sphaira will run slowly!": "Увага! Логи увімкнені, Sphaira працюватиме повільно!",
"Audio disabled due to suspended game": "Аудіо вимкнено через призупинену програму", "Audio disabled due to suspended game": "Аудіо вимкнено через призупинену програму",
"Are you sure you wish to cancel?": "Ви впевнені, що хочете скасувати?", "Are you sure you wish to cancel?": "Ви впевнені, що хочете скасувати?",
"An error occurred": "", "An error occurred": "Сталася помилка",
"If this message appears repeatedly, please open an issue.": "Якщо це повідомлення з'являється повторно, будь ласка, повідомте про проблему.", "If this message appears repeatedly, please open an issue.": "Якщо це повідомлення з'являється повторно, будь ласка, повідомте про проблему.",
"Menu Options": "Опції меню", "Menu Options": "Опції меню",
@@ -16,8 +17,8 @@
"Music": "Музика", "Music": "Музика",
"12 Hour Time": "12-годинний формат часу", "12 Hour Time": "12-годинний формат часу",
"Download Default Music": "Завантажити музику за замовчуванням", "Download Default Music": "Завантажити музику за замовчуванням",
"Failed to download default_music.bfstm, please try again": "", "Failed to download default_music.bfstm, please try again": "Не вдалося завантажити default_music.bfstm, спробуйте ще раз",
"Overwrite current default music?": "", "Overwrite current default music?": "Перезаписати поточну музику за замовчуванням?",
"Network": "Мережа", "Network": "Мережа",
"Network Options": "Опції мережі", "Network Options": "Опції мережі",
@@ -27,6 +28,8 @@
"Nxlink Connected": "Nxlink підключено", "Nxlink Connected": "Nxlink підключено",
"Nxlink Upload": "Nxlink | Завантаження", "Nxlink Upload": "Nxlink | Завантаження",
"Nxlink Finished": "Nxlink | Завершено", "Nxlink Finished": "Nxlink | Завершено",
"Hdd": "HDD",
"Hdd write protect": "Захист HDD від запису",
"Language": "Мова", "Language": "Мова",
"Auto": "Автоматично", "Auto": "Автоматично",
@@ -53,26 +56,39 @@
"Launch random game": "Запустити випадкову гру", "Launch random game": "Запустити випадкову гру",
"List meta records": "Список метаданих записів", "List meta records": "Список метаданих записів",
"Entries": "Записи", "Entries": "Записи",
"Failed to list application meta entries": "", "Failed to list application meta entries": "Не вдалося вивести список метаданих програм",
"No meta entries found...\n": "", "No meta entries found...\n": "Не знайдено метаданих...\n",
"Updating application record list": "", "Updating application record list": "Оновлення списку записів програм",
"Dump": "", "Dump": "Дамп",
"Select content to dump": "", "Dump options": "Опції дампу",
"Dump All": "", "Dump Options": "Опції дампу",
"Dump Application": "", "Select content to dump": "Виберіть вміст для дампу",
"Dump Patch": "", "Dump All": "Дамп всього",
"Dump AddOnContent": "", "Dump Application": "Дамп програми",
"Dump DataPatch": "", "Dump Patch": "Дамп патчу",
"Select dump location": "", "Dump AddOnContent": "Дамп DLC",
"microSD card (/dumps/NSP/)": "", "Dump DataPatch": "Дамп патчу даних",
"USB transfer (Switch 2 Switch)": "", "Created nested folder": "Створено вкладену теку",
"/dev/null (Speed Test)": "", "Append folder with .xci": "Додати до теки .xci",
"Dumping": "", "Trim XCI": "Обрізати XCI",
"Dump successfull!": "", "Label trimmed XCI": "Позначити обрізаний XCI",
"Dump failed!": "", "Multi-threaded USB transfer": "Многопоточ. передача USB",
"Success": "", "Dump All Bins": "Дамп всіх BIN-файлів",
"Delete successfull!": "", "Dump XCI": "Дамп XCI",
"Delete failed!": "", "Dump Card ID Set": "Дамп набору ID картриджа",
"Dump Card UID": "Дамп UID картриджа",
"Dump Certificate": "Дамп сертифіката",
"Dump Initial Data": "Дамп початкових даних",
"Select dump location": "Виберіть місце для дампу",
"microSD card (/dumps/)": "SD-карта (/dumps/)",
"USB transfer (Switch 2 Switch)": "Передача USB (Switch <-> Switch)",
"/dev/null (Speed Test)": "Тест швидкості (/dev/null)",
"Dumping": "Дампінг",
"Dump successfull!": "Дамп успішний!",
"Dump failed!": "Помилка дампу!",
"Delete successfull!": "Видалення успішне!",
"Delete failed!": "Помилка видалення!",
"Success": "Успіх",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Опції Themezer", "Themezer Options": "Опції Themezer",
@@ -86,6 +102,8 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "Завантаження JSON", "Downloading json": "Завантаження JSON",
"Select asset to download for ": "Виберіть ресурс для завантаження для ", "Select asset to download for ": "Виберіть ресурс для завантаження для ",
"Failed to download json": "Не вдалося завантажити JSON",
"Failed to download app!": "Не вдалося завантажити програму!",
"FTP Install": "Встановлення через FTP", "FTP Install": "Встановлення через FTP",
"FTP Install (EXPERIMENTAL)": "Встановлення через FTP (ЕКСПЕРИМЕНТАЛЬНО)", "FTP Install (EXPERIMENTAL)": "Встановлення через FTP (ЕКСПЕРИМЕНТАЛЬНО)",
@@ -103,14 +121,14 @@
"Ftp install failed!": "Встановлення через FTP не вдалося.", "Ftp install failed!": "Встановлення через FTP не вдалося.",
"USB Install": "Встановлення через USB", "USB Install": "Встановлення через USB",
"USB": "USB", "USB": "USB",
"Connected, waiting for file list...": "", "Connected, waiting for file list...": "Підключено, очікування списку файлів...",
"Connected, starting transfer...": "", "Connected, starting transfer...": "Підключено, початок передачі...",
"Failed to init usb, press B to exit...": "Не вдалося ініціалізувати USB, натисніть B для виходу...", "Failed to init usb, press B to exit...": "Не вдалося ініціалізувати USB, натисніть B для виходу...",
"Waiting for connection...": "Очікування підключення...", "Waiting for connection...": "Очікування підключення...",
"Transferring data...": "Передача даних...", "Transferring data...": "Передача даних...",
"USB connected, sending file list": "", "USB connected, sending file list": "USB підключено, надсилання списку файлів",
"Sent file list, waiting for command...": "", "Sent file list, waiting for command...": "Список файлів надіслано, очікування команди...",
"waiting for usb connection...": "", "waiting for usb connection...": "очікування USB підключення...",
"Disable MTP for usb install": "Вимкнути MTP для встановлення через USB", "Disable MTP for usb install": "Вимкнути MTP для встановлення через USB",
"Re-enabled MTP": "MTP знову увімкнено", "Re-enabled MTP": "MTP знову увімкнено",
"Installed via usb": "Встановлено через USB", "Installed via usb": "Встановлено через USB",
@@ -119,17 +137,20 @@
"Press B to exit...": "Натисніть B для виходу...", "Press B to exit...": "Натисніть B для виходу...",
"GameCard Install": "Встановлення з картриджа", "GameCard Install": "Встановлення з картриджа",
"GameCard": "Картридж", "GameCard": "Картридж",
"GC": "", "GC": "К",
"System memory %.1f GB": "", "System memory %.1f GB": "Пам'ять консолі %.1f ГБ",
"microSD card %.1f GB": "", "microSD card %.1f GB": "SD-карта %.1f ГБ",
"Nand Install": "", "Exit": "Вихід",
"SD Card Install": "", "Install disabled...\nPlease enable installing via the install options.": "Встановлення вимкнено...\nБудь ласка, увімкніть встановлення в опціях.",
"Exit": "", "No GameCard inserted": "Картридж не вставлено",
"GameCard is already trimmed!": "Картридж вже обрізано!",
"WARNING: GameCard is already trimmed!": "УВАГА: Картридж вже обрізано!",
"Continue": "Продовжити",
"Gc install success!": "Встановлення з картриджа успішно завершено.", "Gc install success!": "Встановлення з картриджа успішно завершено.",
"Gc install failed!": "Встановлення з картриджа не вдалося.", "Gc install failed!": "Встановлення з картриджа не вдалося.",
"IRS (Infrared Joycon Camera)": "ІЧ (Інфрачервона камера Joycon)", "IRS (Infrared Joycon Camera)": "ІЧ (Інфрачервона камера Joycon)",
"IRS": "", "IRS": "ІЧ",
"Irs": "ІЧ-сенсор", "Irs": "ІЧ-сенсор",
"Ambient Noise Level: ": "Рівень навколишнього шуму: ", "Ambient Noise Level: ": "Рівень навколишнього шуму: ",
"Controller": "Контролер", "Controller": "Контролер",
@@ -157,17 +178,17 @@
"Gain": "Підсилення", "Gain": "Підсилення",
"Negative Image": "Негативне зображення", "Negative Image": "Негативне зображення",
"Normal image": "Нормальне зображення", "Normal image": "Нормальне зображення",
"Negative image": "", "Negative image": "Негативне зображення",
"Format": "Формат", "Format": "Формат",
"Trimming Format": "Формат обрізки", "Trimming Format": "Формат обрізки",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Фільтр зовнішнього освітлення", "External Light Filter": "Фільтр зовнішнього освітлення",
"Load Default": "Завантажити типові", "Load Default": "Завантажити типові",
"Web": "Веб",
"Select URL": "Вибрати URL",
"Enter custom URL": "Ввести власний URL",
"Enter URL": "Ввести URL",
"Advanced": "Додатково", "Advanced": "Додатково",
"Advanced Options": "Додаткові опції", "Advanced Options": "Додаткові опції",
"Logging": "Логування", "Logging": "Логування",
@@ -181,37 +202,38 @@
"Restored hbmenu": "hbmenu відновлено", "Restored hbmenu": "hbmenu відновлено",
"Restart Sphaira?": "Перезапустити Sphaira?", "Restart Sphaira?": "Перезапустити Sphaira?",
"Press OK to restart Sphaira": "Натисніть OK для перезапуску Sphaira", "Press OK to restart Sphaira": "Натисніть OK для перезапуску Sphaira",
"Boost CPU during transfer": "Розгін при передачі",
"Text scroll speed": "Швидк. прокрутки", "Text scroll speed": "Швидк. прокрутки",
"Slow": "Повільно", "Slow": "Повільно",
"Normal": "Нормально", "Normal": "Середнє",
"Fast": "Швидко", "Fast": "Швидко",
"Set left-side menu": "Ліве меню",
"Set right-side menu": "Праве меню", "Set right-side menu": "Праве меню",
"Install options": "Опції встановлення", "Install options": "Опції встановлення",
"Install Options": "Опції встановлення", "Install Options": "Опції встановлення",
"Enable sysmmc": "", "Enable sysmmc": "Дозволити в sysMMC",
"Enable emummc": "", "Enable emummc": "Дозволити в emuMMC",
"Show install warning": "Попередж. при встанов.", "Show install warning": "Попередж. при встанов.",
"Install location": "Місце встановлення", "Install location": "Ставити в",
"System memory": "Пам'ять консолі", "System memory": "NAND",
"microSD card": "SD-карта", "microSD card": "microSD",
"Boost CPU clock": "Розігнати CPU",
"Allow downgrade": "Дозволити відкат", "Allow downgrade": "Дозволити відкат",
"Skip if already installed": "Пропуск, якщо встановл.", "Skip if already installed": "Пропуск встановленого",
"Ticket only": "Тільки тікет", "Ticket only": "Тільки тікет",
"Skip base": "Пропустити базу", "Skip base": "Пропустити базу",
"Skip patch": "Пропустити патч", "Skip patch": "Пропустити патч",
"Skip dlc": "Пропустити DLC", "Skip dlc": "Пропустити DLC",
"Skip data patch": "Пропустити патч даних", "Skip data patch": "Пропустити патч даних",
"Skip ticket": "Пропустити тікет", "Skip ticket": "Пропустити тікет",
"Skip NCA hash verify": "", "Skip NCA hash verify": "Не перевір. NCA hash",
"Skip RSA header verify": "Пропуск перевірку заголовка RSA", "Skip RSA header verify": "Не перевір. RSA header",
"Skip RSA NPDM verify": "Пропуск перевірку NPDM RSA", "Skip RSA NPDM verify": "Не перевір. NPDM RSA",
"Ignore distribution bit": "Ігнорувати біт розподілу", "Ignore distribution bit": "Ігнор. біт розподілу",
"Convert to standard crypto": "Конвертувати у стандартне шифрування", "Convert to standard crypto": "Конверт. у стандарт. шифр.",
"Lower master key": "Знизити майстер-ключ", "Lower master key": "Знизити майстер-ключ",
"Lower system version": "Знизити версію системи", "Lower system version": "Знизити версію системи",
"Homebrew": "Домашні програми", "Homebrew": "Homebrew",
"Apps": "Програми", "Apps": "Програми",
"Homebrew Options": "Опції домашніх програм", "Homebrew Options": "Опції домашніх програм",
"Hide Sphaira": "Приховати Sphaira", "Hide Sphaira": "Приховати Sphaira",
@@ -225,12 +247,14 @@
"Updating ncm databse": "Оновлення бази даних NCM", "Updating ncm databse": "Оновлення бази даних NCM",
"Pushing application record": "Запис даних програми", "Pushing application record": "Запис даних програми",
"Failed to install forwarder": "Не вдалося встановити форвардер", "Failed to install forwarder": "Не вдалося встановити форвардер",
"Unstar": "Прибрати з обраного",
"Star": "Позначити зіркою",
"Unstarred ": "Знято зірку з ", "Unstarred ": "Знято зірку з ",
"Starred ": "Позначено зіркою ", "Starred ": "Позначено зіркою ",
"Failed to remove old forwarder, please manually remove it!": "", "Failed to remove old forwarder, please manually remove it!": "Не вдалося видалити старий форвардер, видаліть його вручну!",
"AppStore": "Магазин програм", "AppStore": "AppStore",
"Appstore": "", "Appstore": "AppStore",
"Store": "Магазин", "Store": "Магазин",
"Filter: %s | Sort: %s | Order: %s": "Фільтр: %s | Сорт.: %s | Порядок: %s", "Filter: %s | Sort: %s | Order: %s": "Фільтр: %s | Сорт.: %s | Порядок: %s",
"AppStore Options": "Опції магазину програм", "AppStore Options": "Опції магазину програм",
@@ -257,41 +281,46 @@
"Copy": "Копіювати", "Copy": "Копіювати",
"Copying ": "Копіювання ", "Copying ": "Копіювання ",
"Paste": "Вставити", "Paste": "Вставити",
"Paste ": "Вставити: ", "Paste file(s)?": "Вставити файл(и)?",
" file(s)?": " файл(и)?",
"Pasting ": "Вставлення ", "Pasting ": "Вставлення ",
"Pasting": "Вставлення", "Pasting": "Вставлення",
"Rename": "Перейменувати", "Rename": "Перейменувати",
"Set New File Name": "Введіть нове ім'я файлу", "Set New File Name": "Введіть нове ім'я файлу",
"Extract zip": "", "Failed to delete directory": "Не вдалося видалити теку",
"Extract Options": "", "Failed to delete file": "Не вдалося видалити файл",
"Extract here": "", "Extract zip": "Розпакувати zip",
"Extract to root": "", "Extract Options": "Опції розпакування",
"Are you sure you want to extract to root?": "", "Extract here": "Розпакувати тут",
"Extract to...": "", "Extract to root": "Розпакувати в корінь",
"Enter the path to the folder to extract into": "", "Are you sure you want to extract to root?": "Ви впевнені, що хочете розпакувати в корінь?",
"Extracting ": "", "Extract to...": "Розпакувати в...",
"Extract success!": "", "Enter the path to the folder to extract into": "Введіть шлях до теки для розпакування",
"Extract failed!": "", "Extracting ": "Розпакування ",
"Extract success!": "Розпакування успішне!",
"Extract failed!": "Помилка розпакування!",
"Compress to zip": "Стиснути в zip", "Compress to zip": "Стиснути в zip",
"Compress Options": "", "Compress Options": "Опції стиснення",
"Compress": "", "Compress": "Стиснути",
"Compress to...": "", "Compress to...": "Стиснути в...",
"Compressing ": "", "Compressing ": "Стиснення ",
"Compress success!": "", "Compress success!": "Стиснення успішне!",
"Compress failed!": "", "Compress failed!": "Помилка стиснення!",
"Create File": "Створити файл", "Create File": "Створити файл",
"Set File Name": "Введіть ім'я файлу", "Set File Name": "Введіть ім'я файлу",
"Create Folder": "Створити теку", "Create Folder": "Створити теку",
"Set Folder Name": "Введіть ім'я теки", "Set Folder Name": "Введіть ім'я теки",
"Creating ": "Створення ", "Creating ": "Створення ",
"Upload": "",
"Select upload location": "",
"No upload locations set!": "",
"Uploading": "",
"Upload successfull!": "",
"Upload failed!": "",
"View as text (unfinished)": "Переглянути як текст (незавершено)", "View as text (unfinished)": "Переглянути як текст (незавершено)",
"Upload": "Завантажити (на сервер)",
"Select upload location": "Виберіть місце для завантаження",
"No upload locations set!": "Не встановлено місць для завантаження!",
"Uploading": "Завантаження (на сервер)",
"Upload successfull!": "Завантаження успішне!",
"Upload failed!": "Помилка завантаження!",
"Hash": "Хеш",
"Hash Options": "Опції хешування",
"Hashing": "Хешування",
"Failed to hash file...": "Не вдалося обчислити хеш файлу...",
"Ignore read only": "Ігнорувати лише читання", "Ignore read only": "Ігнорувати лише читання",
"Mount": "Монтувати", "Mount": "Монтувати",
"Sd": "SD-карта", "Sd": "SD-карта",
@@ -302,6 +331,7 @@
"Launch ": "Запустити ", "Launch ": "Запустити ",
"Launch option for: ": "Опція запуску для: ", "Launch option for: ": "Опція запуску для: ",
"Select launcher for: ": "Виберіть лаунчер для: ", "Select launcher for: ": "Виберіть лаунчер для: ",
"Close FileBrowser?": "Закрити файловий менеджер?",
"Sort By": "Сортувати за", "Sort By": "Сортувати за",
"Sort Options": "Опції сортування", "Sort Options": "Опції сортування",
@@ -328,23 +358,23 @@
"Ascending": "За зростанням", "Ascending": "За зростанням",
"Ascending (Up)": "За зростанням (вгору)", "Ascending (Up)": "За зростанням (вгору)",
"Asc": "Зрост.", "Asc": "Зрост.",
"Layout": "", "Layout": "Макет",
"List": "", "List": "Список",
"Icon": "", "Icon": "Іконки",
"Grid": "", "Grid": "Сітка",
"Search": "Пошук", "Search": "Пошук",
"Options": "Налаштування", "Options": "Налаштування",
"Split": "Розділити",
"OK": "ОК", "OK": "ОК",
"Back": "Назад", "Back": "Назад",
"Select": "Вибрати", "Select": "Вибрати",
"Open": "Відкрити", "Open": "Відкрити",
"Close": "Закрити",
"Launch": "Запустити", "Launch": "Запустити",
"Restart": "Перезапустити", "Restart": "Перезапустити",
"Next": "Наступний", "Next": "Наступний",
"Prev": "Попередній", "Prev": "Попередній",
"Unstar": "Прибрати з обраного",
"Star": "Позначити зіркою",
"Yes": "Так", "Yes": "Так",
"No": "Ні", "No": "Ні",
"On": "Увімк.", "On": "Увімк.",
@@ -367,12 +397,13 @@
"Remove": "Видалити", "Remove": "Видалити",
"Completely remove ": "Повністю видалити ", "Completely remove ": "Повністю видалити ",
"Removing ": "Видалення ", "Removing ": "Видалення ",
"Removed ": "Видалено ",
"Uninstalling ": "Видалення ", "Uninstalling ": "Видалення ",
"Removed ": "Видалено ",
"Download": "Завантажити", "Download": "Завантажити",
"Downloading ": "Завантаження ", "Downloading ": "Завантаження ",
"Downloaded ": "Завантажено ", "Downloaded ": "Завантажено ",
"Download via the Network options!": "Завантажуйте через опції мережі!",
"Update": "Оновити", "Update": "Оновити",
"Update avaliable: ": "Доступне оновлення: ", "Update avaliable: ": "Доступне оновлення: ",
@@ -380,9 +411,9 @@
"Updated to ": "Оновлено до ", "Updated to ": "Оновлено до ",
"Failed to download update": "Не вдалося завантажити оновлення", "Failed to download update": "Не вдалося завантажити оновлення",
"%zu hours %zu minutes remaining": "", "%zu hours %zu minutes remaining": "залишилось %zu год %zu хв",
"%zu minutes %zu seconds remaining": "", "%zu minutes %zu seconds remaining": "залишилось %zu хв %zu сек",
"%zu seconds remaining": "", "%zu seconds remaining": "залишилось %zu сек",
"Loading...": "Завантаження...", "Loading...": "Завантаження...",
"Loading": "Завантаження", "Loading": "Завантаження",

View File

@@ -3,6 +3,7 @@
"No Internet": "Không có Internet", "No Internet": "Không có Internet",
"Switch-Handheld!": "Switch-Handheld!", "Switch-Handheld!": "Switch-Handheld!",
"Switch-Docked!": "Switch-Docked!", "Switch-Docked!": "Switch-Docked!",
"Warning! Logs are enabled, Sphaira will run slowly!": "",
"Audio disabled due to suspended game": "", "Audio disabled due to suspended game": "",
"Are you sure you wish to cancel?": "Bạn có chắn muốn huỷ không?", "Are you sure you wish to cancel?": "Bạn có chắn muốn huỷ không?",
"An error occurred": "", "An error occurred": "",
@@ -27,6 +28,8 @@
"Nxlink Connected": "Nxlink Kết Nối", "Nxlink Connected": "Nxlink Kết Nối",
"Nxlink Upload": "Nxlink Đăng Tải", "Nxlink Upload": "Nxlink Đăng Tải",
"Nxlink Finished": "Nxlink Hoàn Thành", "Nxlink Finished": "Nxlink Hoàn Thành",
"Hdd": "",
"Hdd write protect": "",
"Language": "Ngôn ngữ", "Language": "Ngôn ngữ",
"Auto": "Tự động", "Auto": "Tự động",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "", "No meta entries found...\n": "",
"Updating application record list": "", "Updating application record list": "",
"Dump": "", "Dump": "",
"Dump options": "",
"Dump Options": "",
"Select content to dump": "", "Select content to dump": "",
"Dump All": "", "Dump All": "",
"Dump Application": "", "Dump Application": "",
"Dump Patch": "", "Dump Patch": "",
"Dump AddOnContent": "", "Dump AddOnContent": "",
"Dump DataPatch": "", "Dump DataPatch": "",
"Created nested folder": "",
"Append folder with .xci": "",
"Trim XCI": "",
"Label trimmed XCI": "",
"Multi-threaded USB transfer": "",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "", "Select dump location": "",
"microSD card (/dumps/NSP/)": "", "microSD card (/dumps/)": "",
"USB transfer (Switch 2 Switch)": "", "USB transfer (Switch 2 Switch)": "",
"/dev/null (Speed Test)": "", "/dev/null (Speed Test)": "",
"Dumping": "", "Dumping": "",
"Dump successfull!": "", "Dump successfull!": "",
"Dump failed!": "", "Dump failed!": "",
"Success": "",
"Delete successfull!": "", "Delete successfull!": "",
"Delete failed!": "", "Delete failed!": "",
"Success": "",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Tuỳ chọn Themezer", "Themezer Options": "Tuỳ chọn Themezer",
@@ -86,6 +102,8 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "Đang tải json", "Downloading json": "Đang tải json",
"Select asset to download for ": "Chọn nội dung để tải xuống cho ", "Select asset to download for ": "Chọn nội dung để tải xuống cho ",
"Failed to download json": "",
"Failed to download app!": "",
"FTP Install": "", "FTP Install": "",
"FTP Install (EXPERIMENTAL)": "", "FTP Install (EXPERIMENTAL)": "",
@@ -122,9 +140,12 @@
"GC": "", "GC": "",
"System memory %.1f GB": "", "System memory %.1f GB": "",
"microSD card %.1f GB": "", "microSD card %.1f GB": "",
"Nand Install": "",
"SD Card Install": "",
"Exit": "", "Exit": "",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "", "Gc install success!": "",
"Gc install failed!": "", "Gc install failed!": "",
@@ -160,14 +181,14 @@
"Negative image": "Ảnh âm bản", "Negative image": "Ảnh âm bản",
"Format": "Định dạng", "Format": "Định dạng",
"Trimming Format": "Định dạng cắt tỉa", "Trimming Format": "Định dạng cắt tỉa",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "Bộ lộc ánh sáng bên ngoài", "External Light Filter": "Bộ lộc ánh sáng bên ngoài",
"Load Default": "Tải mặc định", "Load Default": "Tải mặc định",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Mở rộng", "Advanced": "Mở rộng",
"Advanced Options": "Tuỳ chọn mở rộng", "Advanced Options": "Tuỳ chọn mở rộng",
"Logging": "Logging", "Logging": "Logging",
@@ -181,10 +202,12 @@
"Restored hbmenu": "Đã khôi phục hbmenu", "Restored hbmenu": "Đã khôi phục hbmenu",
"Restart Sphaira?": "Khởi động lại Sphaira?", "Restart Sphaira?": "Khởi động lại Sphaira?",
"Press OK to restart Sphaira": "", "Press OK to restart Sphaira": "",
"Boost CPU during transfer": "",
"Text scroll speed": "", "Text scroll speed": "",
"Slow": "", "Slow": "",
"Normal": "", "Normal": "",
"Fast": "", "Fast": "",
"Set left-side menu": "",
"Set right-side menu": "", "Set right-side menu": "",
"Install options": "", "Install options": "",
"Install Options": "", "Install Options": "",
@@ -194,7 +217,6 @@
"Install location": "Vị trí cài đặt", "Install location": "Vị trí cài đặt",
"System memory": "Bộ nhớ máy", "System memory": "Bộ nhớ máy",
"microSD card": "Thẻ nhớ", "microSD card": "Thẻ nhớ",
"Boost CPU clock": "",
"Allow downgrade": "", "Allow downgrade": "",
"Skip if already installed": "", "Skip if already installed": "",
"Ticket only": "", "Ticket only": "",
@@ -225,6 +247,8 @@
"Updating ncm databse": "Cập nhật ncm databse", "Updating ncm databse": "Cập nhật ncm databse",
"Pushing application record": "Đẩy ứng dụng", "Pushing application record": "Đẩy ứng dụng",
"Failed to install forwarder": "Cài đặt ra ngoài màn hình thất bại", "Failed to install forwarder": "Cài đặt ra ngoài màn hình thất bại",
"Unstar": "Xoá yêu thích",
"Star": "Yêu thích",
"Unstarred ": "Bỏ yêu thích ", "Unstarred ": "Bỏ yêu thích ",
"Starred ": "Đã yêu thích ", "Starred ": "Đã yêu thích ",
"Failed to remove old forwarder, please manually remove it!": "", "Failed to remove old forwarder, please manually remove it!": "",
@@ -257,12 +281,13 @@
"Copy": "Sao chép", "Copy": "Sao chép",
"Copying ": "Đang sao chép ", "Copying ": "Đang sao chép ",
"Paste": "Dán", "Paste": "Dán",
"Paste ": "Paste ", "Paste file(s)?": "",
" file(s)?": " tập tin(nhiều)?",
"Pasting ": "Đang dán ", "Pasting ": "Đang dán ",
"Pasting": "Đang dán", "Pasting": "Đang dán",
"Rename": "Đổi tên", "Rename": "Đổi tên",
"Set New File Name": "Đặt tên mới cho tập tin", "Set New File Name": "Đặt tên mới cho tập tin",
"Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "", "Extract zip": "",
"Extract Options": "", "Extract Options": "",
"Extract here": "", "Extract here": "",
@@ -285,13 +310,17 @@
"Create Folder": "Tạo thư mục", "Create Folder": "Tạo thư mục",
"Set Folder Name": "Đặt tên thư mục", "Set Folder Name": "Đặt tên thư mục",
"Creating ": "Đang tạo ", "Creating ": "Đang tạo ",
"View as text (unfinished)": "Xem dạng văn bản (chưa xong)",
"Upload": "", "Upload": "",
"Select upload location": "", "Select upload location": "",
"No upload locations set!": "", "No upload locations set!": "",
"Uploading": "", "Uploading": "",
"Upload successfull!": "", "Upload successfull!": "",
"Upload failed!": "", "Upload failed!": "",
"View as text (unfinished)": "Xem dạng văn bản (chưa xong)", "Hash": "",
"Hash Options": "",
"Hashing": "",
"Failed to hash file...": "",
"Ignore read only": "Bỏ qua chỉ đọc", "Ignore read only": "Bỏ qua chỉ đọc",
"Mount": "Gắn", "Mount": "Gắn",
"Sd": "Sd", "Sd": "Sd",
@@ -302,6 +331,7 @@
"Launch ": "Chạy ", "Launch ": "Chạy ",
"Launch option for: ": "Chạy với tuỳ chọn cho: ", "Launch option for: ": "Chạy với tuỳ chọn cho: ",
"Select launcher for: ": "Chọn trình chạy cho: ", "Select launcher for: ": "Chọn trình chạy cho: ",
"Close FileBrowser?": "",
"Sort By": "Sắp xếp bởi", "Sort By": "Sắp xếp bởi",
"Sort Options": "Tuỳ chọn sắp xếp", "Sort Options": "Tuỳ chọn sắp xếp",
@@ -335,16 +365,16 @@
"Search": "Tìm kiếm", "Search": "Tìm kiếm",
"Options": "Tuỳ chọn", "Options": "Tuỳ chọn",
"Split": "",
"OK": "OK", "OK": "OK",
"Back": "Trở về", "Back": "Trở về",
"Select": "Chọn", "Select": "Chọn",
"Open": "Mở", "Open": "Mở",
"Close": "",
"Launch": "Chạy", "Launch": "Chạy",
"Restart": "Khởi động lại", "Restart": "Khởi động lại",
"Next": "", "Next": "",
"Prev": "", "Prev": "",
"Unstar": "Xoá yêu thích",
"Star": "Yêu thích",
"Yes": "Có", "Yes": "Có",
"No": "Không", "No": "Không",
"On": "", "On": "",
@@ -367,12 +397,13 @@
"Remove": "Gỡ", "Remove": "Gỡ",
"Completely remove ": "Đã gỡ thành công ", "Completely remove ": "Đã gỡ thành công ",
"Removing ": "Đang gỡ ", "Removing ": "Đang gỡ ",
"Removed ": "Đã gỡ ",
"Uninstalling ": "Đang gỡ cài đặt ", "Uninstalling ": "Đang gỡ cài đặt ",
"Removed ": "Đã gỡ ",
"Download": "Tải về", "Download": "Tải về",
"Downloading ": "Đang tải xuống ", "Downloading ": "Đang tải xuống ",
"Downloaded ": "Đã tải xong ", "Downloaded ": "Đã tải xong ",
"Download via the Network options!": "",
"Update": "Cập nhật", "Update": "Cập nhật",
"Update avaliable: ": "Cập nhậc có sẵn: ", "Update avaliable: ": "Cập nhậc có sẵn: ",

View File

@@ -3,9 +3,10 @@
"No Internet": "网络未连接", "No Internet": "网络未连接",
"Switch-Handheld!": "切换至掌机模式!", "Switch-Handheld!": "切换至掌机模式!",
"Switch-Docked!": "切换至底座模式!", "Switch-Docked!": "切换至底座模式!",
"Warning! Logs are enabled, Sphaira will run slowly!": "警告日志已启用将导致Sphaira运行缓慢",
"Audio disabled due to suspended game": "由于游戏暂停,音频已禁用", "Audio disabled due to suspended game": "由于游戏暂停,音频已禁用",
"Are you sure you wish to cancel?": "您确定要取消吗?", "Are you sure you wish to cancel?": "您确定要取消吗?",
"An error occurred": "", "An error occurred": "发生错误",
"If this message appears repeatedly, please open an issue.": "若此消息反复出现,请提交问题报告。", "If this message appears repeatedly, please open an issue.": "若此消息反复出现,请提交问题报告。",
"Menu Options": "菜单选项", "Menu Options": "菜单选项",
@@ -16,8 +17,8 @@
"Music": "音乐", "Music": "音乐",
"12 Hour Time": "12小时制时间", "12 Hour Time": "12小时制时间",
"Download Default Music": "下载默认音乐", "Download Default Music": "下载默认音乐",
"Failed to download default_music.bfstm, please try again": "", "Failed to download default_music.bfstm, please try again": "下载default_music.bfstm失败请重试",
"Overwrite current default music?": "", "Overwrite current default music?": "覆盖当前默认音乐?",
"Network": "网络", "Network": "网络",
"Network Options": "网络选项", "Network Options": "网络选项",
@@ -27,6 +28,8 @@
"Nxlink Connected": "Nxlink 已连接", "Nxlink Connected": "Nxlink 已连接",
"Nxlink Upload": "Nxlink 上传中", "Nxlink Upload": "Nxlink 上传中",
"Nxlink Finished": "Nxlink 已结束", "Nxlink Finished": "Nxlink 已结束",
"Hdd": "HDD",
"Hdd write protect": "HDD写保护",
"Language": "语言", "Language": "语言",
"Auto": "自动", "Auto": "自动",
@@ -53,26 +56,26 @@
"Launch random game": "开启随机游戏", "Launch random game": "开启随机游戏",
"List meta records": "列出元数据记录", "List meta records": "列出元数据记录",
"Entries": "条目", "Entries": "条目",
"Failed to list application meta entries": "", "Failed to list application meta entries": "未能列出应用程序元条目",
"No meta entries found...\n": "", "No meta entries found...\n": "未找到元条目...\n",
"Updating application record list": "", "Updating application record list": "更新应用程序记录列表",
"Dump": "", "Dump": "转储",
"Select content to dump": "", "Select content to dump": "选择要转储的内容",
"Dump All": "", "Dump All": "转储全部内容",
"Dump Application": "", "Dump Application": "转储应用程序(本体)",
"Dump Patch": "", "Dump Patch": "转储补丁(DLC)",
"Dump AddOnContent": "", "Dump AddOnContent": "转储追加内容(UPD)",
"Dump DataPatch": "", "Dump DataPatch": "转储游戏数据",
"Select dump location": "", "Select dump location": "选择转储位置",
"microSD card (/dumps/NSP/)": "", "microSD card (/dumps/NSP/)": "microSD卡/dumps/NSP/",
"USB transfer (Switch 2 Switch)": "", "USB transfer (Switch 2 Switch)": "USB传输(Switch 2 Switch)",
"/dev/null (Speed Test)": "", "/dev/null (Speed Test)": "/dev/null速度测试",
"Dumping": "", "Dumping": "正在转储",
"Dump successfull!": "", "Dump successfull!": "转储成功!",
"Dump failed!": "", "Dump failed!": "转储失败!",
"Success": "", "Success": "成功",
"Delete successfull!": "", "Delete successfull!": "删除成功!",
"Delete failed!": "", "Delete failed!": "删除失败!",
"Themezer": "在线主题", "Themezer": "在线主题",
"Themezer Options": "在线主题选项", "Themezer Options": "在线主题选项",
@@ -86,10 +89,12 @@
"GitHub": "GitHub", "GitHub": "GitHub",
"Downloading json": "正在下载 json", "Downloading json": "正在下载 json",
"Select asset to download for ": "选择要下载的资源用于 ", "Select asset to download for ": "选择要下载的资源用于 ",
"Failed to download json": "下载json失败",
"Failed to download app!": "下载应用程序失败!",
"FTP Install": "通过 FTP 安装", "FTP Install": "通过 FTP 安装",
"FTP Install (EXPERIMENTAL)": "通过 FTP 安装(实验性)", "FTP Install (EXPERIMENTAL)": "通过 FTP 安装(实验性)",
"Connection Type: WiFi | Strength: ": "", "Connection Type: WiFi | Strength: ": "连接类型WiFi |强度:",
"Connection Type: Ethernet": "连接类型:以太网", "Connection Type: Ethernet": "连接类型:以太网",
"Connection Type: None": "连接类型:无", "Connection Type: None": "连接类型:无",
"Host:": "主机:", "Host:": "主机:",
@@ -103,14 +108,14 @@
"Ftp install failed!": "通过 FTP 安装失败。", "Ftp install failed!": "通过 FTP 安装失败。",
"USB Install": "通过 USB 安装", "USB Install": "通过 USB 安装",
"USB": "USB", "USB": "USB",
"Connected, waiting for file list...": "", "Connected, waiting for file list...": "已连接,正在等待文件列表...",
"Connected, starting transfer...": "", "Connected, starting transfer...": "已连接,开始传输...",
"Failed to init usb, press B to exit...": "USB 初始化失败,按 B 键退出...", "Failed to init usb, press B to exit...": "USB 初始化失败,按 B 键退出...",
"Waiting for connection...": "等待连接中...", "Waiting for connection...": "等待连接中...",
"Transferring data...": "正在传输数据...", "Transferring data...": "正在传输数据...",
"USB connected, sending file list": "", "USB connected, sending file list": "USB已连接正在发送文件列表",
"Sent file list, waiting for command...": "", "Sent file list, waiting for command...": "已发送文件列表,正在等待命令...",
"waiting for usb connection...": "", "waiting for usb connection...": "等待USB连接...",
"Disable MTP for usb install": "暂时禁用 USB 安装的 MTP 功能", "Disable MTP for usb install": "暂时禁用 USB 安装的 MTP 功能",
"Re-enabled MTP": "重新启用 MTP", "Re-enabled MTP": "重新启用 MTP",
"Installed via usb": "通过 USB 安装", "Installed via usb": "通过 USB 安装",
@@ -119,17 +124,20 @@
"Press B to exit...": "按 B 键退出...", "Press B to exit...": "按 B 键退出...",
"GameCard Install": "卡带安装", "GameCard Install": "卡带安装",
"GameCard": "卡带", "GameCard": "卡带",
"GC": "", "GC": "GC",
"System memory %.1f GB": "", "System memory %.1f GB": "系统内存 %.1f GB",
"microSD card %.1f GB": "", "microSD card %.1f GB": "SD卡内存 %.1f GB",
"Nand Install": "", "Exit": "退出",
"SD Card Install": "", "Install disabled...\nPlease enable installing via the install options.": "安装已禁用...\n请通过安装选项启用安装。",
"Exit": "", "No GameCard inserted": "未插入游戏卡",
"GameCard is already trimmed!": "游戏卡已被修剪!",
"WARNING: GameCard is already trimmed!": "警告:游戏卡已被修剪!",
"Continue": "继续",
"Gc install success!": "游戏安装成功。", "Gc install success!": "游戏安装成功。",
"Gc install failed!": "游戏安装失败。", "Gc install failed!": "游戏安装失败。",
"IRS (Infrared Joycon Camera)": "", "IRS (Infrared Joycon Camera)": "IRS(Joycon红外摄像头)",
"IRS": "", "IRS": "IRS",
"Irs": "红外成像", "Irs": "红外成像",
"Ambient Noise Level: ": "环境噪声等级:", "Ambient Noise Level: ": "环境噪声等级:",
"Controller": "控制器", "Controller": "控制器",
@@ -159,7 +167,7 @@
"Normal image": "正常图像", "Normal image": "正常图像",
"Negative image": "负片图像", "Negative image": "负片图像",
"Format": "格式", "Format": "格式",
"Trimming Format": "剪格式", "Trimming Format": "剪格式",
"320x240": "320×240", "320x240": "320×240",
"160x120": "160×120", "160x120": "160×120",
"80x60": "80×60", "80x60": "80×60",
@@ -168,6 +176,11 @@
"External Light Filter": "外部光滤镜", "External Light Filter": "外部光滤镜",
"Load Default": "加载默认值", "Load Default": "加载默认值",
"Web": "网络",
"Select URL": "选择链接",
"Enter custom URL": "输入自定义链接",
"Enter URL": "输入网址",
"Advanced": "高级", "Advanced": "高级",
"Advanced Options": "高级选项", "Advanced Options": "高级选项",
"Logging": "日志", "Logging": "日志",
@@ -181,15 +194,17 @@
"Restored hbmenu": "已恢复 hbmenu", "Restored hbmenu": "已恢复 hbmenu",
"Restart Sphaira?": "重启 Sphaira", "Restart Sphaira?": "重启 Sphaira",
"Press OK to restart Sphaira": "按OK键以重启shphaira菜单", "Press OK to restart Sphaira": "按OK键以重启shphaira菜单",
"Boost CPU during transfer": "在传输过程中提升CPU",
"Text scroll speed": "文本滚动速度", "Text scroll speed": "文本滚动速度",
"Slow": "慢", "Slow": "慢",
"Normal": "正常", "Normal": "正常",
"Fast": "快", "Fast": "快",
"Set right-side menu": "", "Set left-side menu": "设置左侧菜单",
"Set right-side menu": "设置右侧菜单",
"Install options": "安装选项", "Install options": "安装选项",
"Install Options": "安装选项", "Install Options": "安装选项",
"Enable sysmmc": "", "Enable sysmmc": "启用sysmmc",
"Enable emummc": "", "Enable emummc": "启用emummc",
"Show install warning": "显示安装警告", "Show install warning": "显示安装警告",
"Install location": "安装位置", "Install location": "安装位置",
"System memory": "主机内存", "System memory": "主机内存",
@@ -203,13 +218,20 @@
"Skip dlc": "跳过 DLC可下载内容", "Skip dlc": "跳过 DLC可下载内容",
"Skip data patch": "跳过数据补丁", "Skip data patch": "跳过数据补丁",
"Skip ticket": "跳过票据", "Skip ticket": "跳过票据",
"Skip NCA hash verify": "", "Skip NCA hash verify": "跳过NCA哈希验证",
"Skip RSA header verify": "跳过 RSA 头部验证", "Skip RSA header verify": "跳过 RSA 头部验证",
"Skip RSA NPDM verify": "跳过 RSA NPDM 验证", "Skip RSA NPDM verify": "跳过 RSA NPDM 验证",
"Ignore distribution bit": "忽略分布位", "Ignore distribution bit": "忽略分布位",
"Convert to standard crypto": "转换为标准加密方式", "Convert to standard crypto": "转换为标准加密方式",
"Lower master key": "降低主密钥", "Lower master key": "降低主密钥",
"Lower system version": "降低系统版本", "Lower system version": "降低系统版本",
"Dump options": "转储选项",
"Dump Options": "转储选项",
"Created nested folder": "已创建嵌套文件夹",
"Append folder with .xci": "用.xci附加文件夹",
"Trim XCI": "修剪 XCI",
"Label trimmed XCI": "标记已修剪的XCI",
"Multi-threaded USB transfer": "多线程USB传输",
"Homebrew": "应用列表", "Homebrew": "应用列表",
"Apps": "应用", "Apps": "应用",
@@ -225,12 +247,14 @@
"Updating ncm databse": "正在更新ncm数据库", "Updating ncm databse": "正在更新ncm数据库",
"Pushing application record": "正在推送应用记录", "Pushing application record": "正在推送应用记录",
"Failed to install forwarder": "前端应用安装失败", "Failed to install forwarder": "前端应用安装失败",
"Unstar": "取消星标",
"Star": "星标",
"Unstarred ": "取消星标 ", "Unstarred ": "取消星标 ",
"Starred ": "已星标 ", "Starred ": "已星标 ",
"Failed to remove old forwarder, please manually remove it!": "", "Failed to remove old forwarder, please manually remove it!": "删除旧前端应用失败,请手动删除!",
"AppStore": "应用商店", "AppStore": "应用商店",
"Appstore": "", "Appstore": "应用商店",
"Store": "商店", "Store": "商店",
"Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s", "Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s",
"AppStore Options": "应用商店选项", "AppStore Options": "应用商店选项",
@@ -257,41 +281,46 @@
"Copy": "复制", "Copy": "复制",
"Copying ": "正在复制 ", "Copying ": "正在复制 ",
"Paste": "粘贴", "Paste": "粘贴",
"Paste ": "粘贴 ", "Paste file(s)?": "粘贴 个文件(夹)",
" file(s)?": "个文件(夹)",
"Pasting ": "正在粘贴 ", "Pasting ": "正在粘贴 ",
"Pasting": "正在粘贴", "Pasting": "正在粘贴",
"Rename": "重命名", "Rename": "重命名",
"Set New File Name": "输入新命名", "Set New File Name": "输入新命名",
"Extract zip": "", "Failed to delete directory": "删除目录失败",
"Extract Options": "", "Failed to delete file": "删除文件失败",
"Extract here": "", "Extract zip": "解压 zip",
"Extract to root": "", "Extract Options": "解压选项",
"Are you sure you want to extract to root?": "", "Extract here": "解压到这里",
"Extract to...": "", "Extract to root": "解压到根目录",
"Enter the path to the folder to extract into": "", "Are you sure you want to extract to root?": "您确定要解压到根目录吗?",
"Extracting ": "", "Extract to...": "解压到...",
"Extract success!": "", "Enter the path to the folder to extract into": "输入要解压到的文件夹的路径",
"Extract failed!": "", "Extracting ": "正在解压",
"Extract success!": "解压成功!",
"Extract failed!": "解压失败!",
"Compress to zip": "压缩到zip", "Compress to zip": "压缩到zip",
"Compress Options": "", "Compress Options": "压缩选项",
"Compress": "", "Compress": "压缩",
"Compress to...": "", "Compress to...": "压缩到...",
"Compressing ": "", "Compressing ": "正在压缩 ",
"Compress success!": "", "Compress success!": "压缩成功!",
"Compress failed!": "", "Compress failed!": "压缩失败",
"Create File": "新建文件", "Create File": "新建文件",
"Set File Name": "输入文件名", "Set File Name": "输入文件名",
"Create Folder": "新建文件夹", "Create Folder": "新建文件夹",
"Set Folder Name": "输入文件夹名", "Set Folder Name": "输入文件夹名",
"Creating ": "正在创建 ", "Creating ": "正在创建 ",
"Upload": "",
"Select upload location": "",
"No upload locations set!": "",
"Uploading": "",
"Upload successfull!": "",
"Upload failed!": "",
"View as text (unfinished)": "以文本形式查看(未完善)", "View as text (unfinished)": "以文本形式查看(未完善)",
"Upload": "上传",
"Select upload location": "选择上传位置",
"No upload locations set!": "未设置上传位置",
"Uploading": "正在上传",
"Upload successfull!": "上传成功!",
"Upload failed!": "上传失败!",
"Hash": "哈希",
"Hash Options": "哈希选项",
"Hashing": "正在哈希文件",
"Failed to hash file...": "哈希文件失败...",
"Ignore read only": "忽略只读", "Ignore read only": "忽略只读",
"Mount": "挂载", "Mount": "挂载",
"Sd": "SD卡", "Sd": "SD卡",
@@ -302,6 +331,7 @@
"Launch ": "启动 ", "Launch ": "启动 ",
"Launch option for: ": "启动选项:", "Launch option for: ": "启动选项:",
"Select launcher for: ": "选择启动器用于:", "Select launcher for: ": "选择启动器用于:",
"Close FileBrowser?": "是否关闭文件浏览器?",
"Sort By": "排序方式", "Sort By": "排序方式",
"Sort Options": "排序选项", "Sort Options": "排序选项",
@@ -328,32 +358,32 @@
"Ascending": "升序", "Ascending": "升序",
"Ascending (Up)": "升序", "Ascending (Up)": "升序",
"Asc": "升序", "Asc": "升序",
"Layout": "", "Layout": "布局",
"List": "", "List": "列表",
"Icon": "", "Icon": "图标",
"Grid": "", "Grid": "网格",
"Search": "搜索", "Search": "搜索",
"Options": "选项", "Options": "选项",
"Split": "拆分",
"OK": "确定", "OK": "确定",
"Back": "返回", "Back": "返回",
"Select": "选择", "Select": "选择",
"Open": "打开", "Open": "打开",
"Close": "关闭",
"Launch": "启动", "Launch": "启动",
"Restart": "重启", "Restart": "重启",
"Next": "下一项", "Next": "下一项",
"Prev": "上一项", "Prev": "上一项",
"Unstar": "取消星标",
"Star": "星标",
"Yes": "是", "Yes": "是",
"No": "否", "No": "否",
"On": "", "On": "",
"Off": "", "Off": "",
"Install": "安装", "Install": "安装",
"Install Selected files?": "安装所选文件?", "Install Selected files?": "安装所选文件?",
"Installing ": "正在安装 ", "Installing ": "正在安装 ",
"Installed ": "", "Installed ": "已安装",
"Installed!": "安装完成!", "Installed!": "安装完成!",
"Trying to load ": "尝试加载 ", "Trying to load ": "尝试加载 ",
"Checking MD5": "正在校验 MD5", "Checking MD5": "正在校验 MD5",
@@ -367,12 +397,13 @@
"Remove": "删除", "Remove": "删除",
"Completely remove ": "彻底删除 ", "Completely remove ": "彻底删除 ",
"Removing ": "正在移除 ", "Removing ": "正在移除 ",
"Removed ": "已移除 ",
"Uninstalling ": "正在卸载 ", "Uninstalling ": "正在卸载 ",
"Removed ": "已移除 ",
"Download": "下载", "Download": "下载",
"Downloading ": "正在下载 ", "Downloading ": "正在下载 ",
"Downloaded ": "已下载 ", "Downloaded ": "已下载 ",
"Download via the Network options!": "通过网络选项下载!",
"Update": "更新", "Update": "更新",
"Update avaliable: ": "有可用更新!", "Update avaliable: ": "有可用更新!",
@@ -380,9 +411,9 @@
"Updated to ": "更新至 ", "Updated to ": "更新至 ",
"Failed to download update": "更新下载失败", "Failed to download update": "更新下载失败",
"%zu hours %zu minutes remaining": "", "%zu hours %zu minutes remaining": "剩余 %zu 小时 %zu 分钟",
"%zu minutes %zu seconds remaining": "", "%zu minutes %zu seconds remaining": "剩余 %zu 分钟 %zu 秒",
"%zu seconds remaining": "", "%zu seconds remaining": "剩余 %zu 秒",
"Loading...": "加载中...", "Loading...": "加载中...",
"Loading": "加载中", "Loading": "加载中",

View File

@@ -3,6 +3,7 @@ background = 0x2d2d2d
grid = 0x46464630 grid = 0x46464630
popup = 0x2d2d2d popup = 0x2d2d2d
error = 0xfa5a3a error = 0xfa5a3a
focus = 0x000000b4
line = 0xfbfbfb line = 0xfbfbfb
line_separator = 0x707070 line_separator = 0x707070

View File

@@ -3,6 +3,7 @@ background = 0xebebeb
grid = 0xf0f0f0 grid = 0xf0f0f0
popup = 0xebebeb popup = 0xebebeb
error = 0xfa5a3a error = 0xfa5a3a
focus = 0xd3d3d3a0
line = 0x373737 line = 0x373737
line_separator = 0x6d787a line_separator = 0x6d787a

View File

@@ -0,0 +1,30 @@
[meta]
name=Default
author=ninstar
version=1.0.0
inherit=romfs:/themes/base_black_theme.ini
[theme]
background = 0x111f28
grid = 0x122430
popup = 0x143144
error = 0xfa3a5d
line = 0x335e77
line_separator = 0x163951
text = 0xfbfbfb
text_info = 0xbed0d6
text_selected = 0x32ffcf
selected_background = 0x143144
sidebar = 0x071013ef
scrollbar = 0x32ffcf
scrollbar_background = ; hide the background
progressbar = 0x32ffcf
progressbar_background = 0x0B1519
highlight_1 = 0x69ff8f
highlight_2 = 0x5cbeff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

BIN
assets/screenshots/web.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
set(sphaira_VERSION 0.11.3) set(sphaira_VERSION 0.12.0)
project(sphaira project(sphaira
VERSION ${sphaira_VERSION} VERSION ${sphaira_VERSION}
@@ -40,6 +40,7 @@ add_executable(sphaira
source/ui/menus/irs_menu.cpp source/ui/menus/irs_menu.cpp
source/ui/menus/main_menu.cpp source/ui/menus/main_menu.cpp
source/ui/menus/menu_base.cpp source/ui/menus/menu_base.cpp
source/ui/menus/save_menu.cpp
source/ui/menus/themezer.cpp source/ui/menus/themezer.cpp
source/ui/menus/ghdl.cpp source/ui/menus/ghdl.cpp
source/ui/menus/usb_menu.cpp source/ui/menus/usb_menu.cpp
@@ -79,6 +80,7 @@ add_executable(sphaira
source/i18n.cpp source/i18n.cpp
source/ftpsrv_helper.cpp source/ftpsrv_helper.cpp
source/threaded_file_transfer.cpp source/threaded_file_transfer.cpp
source/minizip_helper.cpp
source/usb/base.cpp source/usb/base.cpp
source/usb/usbds.cpp source/usb/usbds.cpp
@@ -178,7 +180,7 @@ FetchContent_Declare(stb
FetchContent_Declare(yyjson FetchContent_Declare(yyjson
GIT_REPOSITORY https://github.com/ibireme/yyjson.git GIT_REPOSITORY https://github.com/ibireme/yyjson.git
GIT_TAG 0.10.0 GIT_TAG 0.11.1
) )
FetchContent_Declare(minIni FetchContent_Declare(minIni
@@ -197,7 +199,19 @@ FetchContent_Declare(libusbhsfs
GIT_TAG db2bf2a GIT_TAG db2bf2a
) )
FetchContent_Declare(libnxtc
GIT_REPOSITORY https://github.com/ITotalJustice/libnxtc.git
GIT_TAG 0d369b8
)
FetchContent_Declare(nvjpg
GIT_REPOSITORY https://github.com/averne/oss-nvjpg.git
GIT_TAG fdcaba8
)
set(USE_NEW_ZSTD ON) set(USE_NEW_ZSTD ON)
# has issues with some homebrew and game icons (oxenfree, overwatch2).
set(USE_NVJPG ON)
set(ZSTD_BUILD_STATIC ON) set(ZSTD_BUILD_STATIC ON)
set(ZSTD_BUILD_SHARED OFF) set(ZSTD_BUILD_SHARED OFF)
@@ -249,6 +263,8 @@ FetchContent_MakeAvailable(
yyjson yyjson
zstd zstd
libusbhsfs libusbhsfs
libnxtc
nvjpg
) )
set(FTPSRV_LIB_BUILD TRUE) set(FTPSRV_LIB_BUILD TRUE)
@@ -259,7 +275,7 @@ set(FTPSRV_LIB_SESSIONS 16)
set(FTPSRV_LIB_BUF_SIZE 1024*64) set(FTPSRV_LIB_BUF_SIZE 1024*64)
set(FTPSRV_LIB_CUSTOM_DEFINES set(FTPSRV_LIB_CUSTOM_DEFINES
USE_VFS_SAVE=$<BOOL:TRUE> USE_VFS_SAVE=$<BOOL:FALSE>
USE_VFS_STORAGE=$<BOOL:TRUE> USE_VFS_STORAGE=$<BOOL:TRUE>
# disabled as it may conflict with the gamecard menu. # disabled as it may conflict with the gamecard menu.
USE_VFS_GC=$<BOOL:FALSE> USE_VFS_GC=$<BOOL:FALSE>
@@ -278,7 +294,6 @@ add_library(ftpsrv_helper
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_save.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_stdio.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_stdio.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_hdd.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_hdd.c
@@ -291,6 +306,23 @@ target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platfor
add_library(stb INTERFACE) add_library(stb INTERFACE)
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR}) target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
add_library(libnxtc
${libnxtc_SOURCE_DIR}/source/nxtc.c
${libnxtc_SOURCE_DIR}/source/nxtc_log.c
${libnxtc_SOURCE_DIR}/source/nxtc_utils.c
)
target_include_directories(libnxtc PUBLIC ${libnxtc_SOURCE_DIR}/include)
if (USE_NVJPG)
add_library(nvjpg
${nvjpg_SOURCE_DIR}/lib/decoder.cpp
${nvjpg_SOURCE_DIR}/lib/image.cpp
${nvjpg_SOURCE_DIR}/lib/surface.cpp
)
target_include_directories(nvjpg PUBLIC ${nvjpg_SOURCE_DIR}/include)
set_target_properties(nvjpg PROPERTIES CXX_STANDARD 26)
endif()
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
find_library(minizip_lib minizip REQUIRED) find_library(minizip_lib minizip REQUIRED)
find_path(minizip_inc minizip REQUIRED) find_path(minizip_inc minizip REQUIRED)
@@ -320,6 +352,7 @@ target_link_libraries(sphaira PRIVATE
stb stb
yyjson yyjson
# libusbhsfs # libusbhsfs
libnxtc
${minizip_lib} ${minizip_lib}
ZLIB::ZLIB ZLIB::ZLIB
@@ -336,6 +369,11 @@ else()
target_include_directories(sphaira PRIVATE ${zstd_inc}) target_include_directories(sphaira PRIVATE ${zstd_inc})
endif() endif()
if (USE_NVJPG)
target_link_libraries(sphaira PRIVATE nvjpg)
target_compile_definitions(sphaira PRIVATE USE_NVJPG)
endif()
target_include_directories(sphaira PRIVATE target_include_directories(sphaira PRIVATE
include include
${minizip_inc} ${minizip_inc}

View File

@@ -10,6 +10,9 @@
#include "fs.hpp" #include "fs.hpp"
#include "log.hpp" #include "log.hpp"
#ifdef USE_NVJPG
#include <nvjpg.hpp>
#endif
#include <switch.h> #include <switch.h>
#include <vector> #include <vector>
#include <string> #include <string>
@@ -193,7 +196,7 @@ public:
static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini"; static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini";
static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini"; static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini";
static constexpr inline auto INI_SECTION = "config"; static constexpr inline auto INI_SECTION = "config";
static constexpr inline auto DEFAULT_THEME_PATH = "romfs:/themes/abyss_theme.ini"; static constexpr inline auto DEFAULT_THEME_PATH = "romfs:/themes/default_theme.ini";
fs::FsPath m_app_path; fs::FsPath m_app_path;
u64 m_start_timestamp{}; u64 m_start_timestamp{};
@@ -272,6 +275,10 @@ public:
PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{}; PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
#ifdef USE_NVJPG
nj::Decoder m_decoder;
#endif
private: // from nanovg decko3d example by adubbz private: // from nanovg decko3d example by adubbz
static constexpr unsigned NumFramebuffers = 2; static constexpr unsigned NumFramebuffers = 2;
static constexpr unsigned StaticCmdSize = 0x1000; static constexpr unsigned StaticCmdSize = 0x1000;

View File

@@ -527,27 +527,37 @@ struct FsNativeSd final : FsNative {
#endif #endif
struct FsNativeBis final : FsNative { struct FsNativeBis final : FsNative {
FsNativeBis(FsBisPartitionId id, const FsPath& string, bool ignore_read_only = true) : FsNative{ignore_read_only} { FsNativeBis(FsBisPartitionId id, const FsPath& string) {
m_open_result = fsOpenBisFileSystem(&m_fs, id, string); m_open_result = fsOpenBisFileSystem(&m_fs, id, string);
} }
}; };
struct FsNativeImage final : FsNative { struct FsNativeImage final : FsNative {
FsNativeImage(FsImageDirectoryId id, bool ignore_read_only = true) : FsNative{ignore_read_only} { FsNativeImage(FsImageDirectoryId id) {
m_open_result = fsOpenImageDirectoryFileSystem(&m_fs, id); m_open_result = fsOpenImageDirectoryFileSystem(&m_fs, id);
} }
}; };
struct FsNativeContentStorage final : FsNative { struct FsNativeContentStorage final : FsNative {
FsNativeContentStorage(FsContentStorageId id, bool ignore_read_only = true) : FsNative{ignore_read_only} { FsNativeContentStorage(FsContentStorageId id) {
m_open_result = fsOpenContentStorageFileSystem(&m_fs, id); m_open_result = fsOpenContentStorageFileSystem(&m_fs, id);
} }
}; };
struct FsNativeGameCard final : FsNative { struct FsNativeGameCard final : FsNative {
FsNativeGameCard(const FsGameCardHandle* handle, FsGameCardPartition partition, bool ignore_read_only = true) : FsNative{ignore_read_only} { FsNativeGameCard(const FsGameCardHandle* handle, FsGameCardPartition partition) {
m_open_result = fsOpenGameCardFileSystem(&m_fs, handle, partition); m_open_result = fsOpenGameCardFileSystem(&m_fs, handle, partition);
} }
}; };
struct FsNativeSave final : FsNative {
FsNativeSave(FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr, bool read_only) {
if (read_only) {
m_open_result = fsOpenReadOnlySaveDataFileSystem(&m_fs, save_data_space_id, attr);
} else {
m_open_result = fsOpenSaveDataFileSystem(&m_fs, save_data_space_id, attr);
}
}
};
} // namespace fs } // namespace fs

View File

@@ -4,6 +4,7 @@
#include "ui/progress_box.hpp" #include "ui/progress_box.hpp"
#include <string> #include <string>
#include <memory> #include <memory>
#include <span>
#include <switch.h> #include <switch.h>
namespace sphaira::hash { namespace sphaira::hash {
@@ -26,5 +27,6 @@ auto GetTypeStr(Type type) -> const char*;
// returns the hash string. // returns the hash string.
Result Hash(ui::ProgressBox* pbox, Type type, std::shared_ptr<BaseSource> source, std::string& out); Result Hash(ui::ProgressBox* pbox, Type type, std::shared_ptr<BaseSource> source, std::string& out);
Result Hash(ui::ProgressBox* pbox, Type type, fs::Fs* fs, const fs::FsPath& path, std::string& out); Result Hash(ui::ProgressBox* pbox, Type type, fs::Fs* fs, const fs::FsPath& path, std::string& out);
Result Hash(ui::ProgressBox* pbox, Type type, std::span<const u8> data, std::string& out);
} // namespace sphaira::hash } // namespace sphaira::hash

View File

@@ -12,8 +12,14 @@ struct ImageResult {
int w, h; int w, h;
}; };
auto ImageLoadFromMemory(std::span<const u8> data) -> ImageResult; enum ImageFlag {
auto ImageLoadFromFile(const fs::FsPath& file) -> ImageResult; ImageFlag_None = 0,
// set this if the image is a jpeg, will use oss-nvjpg to load.
ImageFlag_JPEG = 1 << 0,
};
auto ImageLoadFromMemory(std::span<const u8> data, u32 flags = ImageFlag_None) -> ImageResult;
auto ImageLoadFromFile(const fs::FsPath& file, u32 flags = ImageFlag_None) -> ImageResult;
auto ImageResize(std::span<const u8> data, int inx, int iny, int outx, int outy) -> ImageResult; auto ImageResize(std::span<const u8> data, int inx, int iny, int outx, int outy) -> ImageResult;
auto ImageConvertToJpg(std::span<const u8> data, int x, int y) -> ImageResult; auto ImageConvertToJpg(std::span<const u8> data, int x, int y) -> ImageResult;

View File

@@ -0,0 +1,24 @@
#pragma once
#include <minizip/ioapi.h>
#include <vector>
#include <span>
#include <switch.h>
namespace sphaira::mz {
struct MzMem {
std::vector<u8> buf;
size_t offset;
};
struct MzSpan {
std::span<const u8> buf;
size_t offset;
};
void FileFuncMem(MzMem* mem, zlib_filefunc64_def* funcs);
void FileFuncSpan(MzSpan* span, zlib_filefunc64_def* funcs);
void FileFuncStdio(zlib_filefunc64_def* funcs);
} // namespace sphaira::mz

View File

@@ -6,6 +6,15 @@
namespace sphaira::thread { namespace sphaira::thread {
enum class Mode {
// default, always multi-thread.
MultiThreaded,
// always single-thread.
SingleThreaded,
// check buffer size, if smaller, single thread.
SingleThreadedIfSmaller,
};
using ReadCallback = std::function<Result(void* data, s64 off, s64 size, u64* bytes_read)>; using ReadCallback = std::function<Result(void* data, s64 off, s64 size, u64* bytes_read)>;
using WriteCallback = std::function<Result(const void* data, s64 off, s64 size)>; using WriteCallback = std::function<Result(const void* data, s64 off, s64 size)>;
@@ -23,10 +32,25 @@ using StartCallback = std::function<Result(PullCallback pull)>;
using StartCallback2 = std::function<Result(StartThreadCallback start, PullCallback pull)>; using StartCallback2 = std::function<Result(StartThreadCallback start, PullCallback pull)>;
// reads data from rfunc into wfunc. // reads data from rfunc into wfunc.
Result Transfer(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, WriteCallback wfunc); Result Transfer(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, WriteCallback wfunc, Mode mode = Mode::MultiThreaded);
// reads data from rfunc, pull data from provided pull() callback. // reads data from rfunc, pull data from provided pull() callback.
Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback sfunc); Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback sfunc, Mode mode = Mode::MultiThreaded);
Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback2 sfunc); Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback2 sfunc, Mode mode = Mode::MultiThreaded);
// helper for extract zips.
// this will multi-thread unzip if size >= 512KiB, otherwise it'll single pass.
Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, s64 size, u32 crc32 = 0);
// same as above but for zipping files.
Result TransferZip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, u32* crc32 = nullptr);
// passes the name inside the zip an final output path.
using UnzipAllFilter = std::function<bool(const fs::FsPath& name, fs::FsPath& path)>;
// helper all-in-one unzip function that unzips a zip (either open or path provided).
// the filter function can be used to modify the path and filter out unwanted files.
Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter = nullptr);
Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter = nullptr);
} // namespace sphaira::thread } // namespace sphaira::thread

View File

@@ -108,8 +108,11 @@ private:
std::shared_ptr<ScrollableText> m_details{}; std::shared_ptr<ScrollableText> m_details{};
std::shared_ptr<ScrollableText> m_changelog{}; std::shared_ptr<ScrollableText> m_changelog{};
std::shared_ptr<ScrollableText> m_detail_changelog{}; std::shared_ptr<ScrollableText> m_detail_changelog{};
std::unique_ptr<ScrollableText> m_manifest_list{};
bool m_show_changlog{}; bool m_show_changlog{};
bool m_show_file_list{};
ImageDownloadState m_file_list_state{};
}; };
enum Filter { enum Filter {

View File

@@ -181,6 +181,9 @@ struct FsView final : Widget {
void SetSide(ViewSide side); void SetSide(ViewSide side);
static auto get_collection(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
static auto get_collections(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
private: private:
void SetIndex(s64 index); void SetIndex(s64 index);
void InstallForwarder(); void InstallForwarder();
@@ -249,9 +252,6 @@ private:
void OnRenameCallback(); void OnRenameCallback();
auto CheckIfUpdateFolder() -> Result; auto CheckIfUpdateFolder() -> Result;
static auto get_collection(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
static auto get_collections(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
auto get_collection(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result; auto get_collection(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result; auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;

View File

@@ -6,6 +6,7 @@
#include "option.hpp" #include "option.hpp"
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <span>
namespace sphaira::ui::menu::game { namespace sphaira::ui::menu::game {
@@ -22,13 +23,12 @@ enum class NacpLoadStatus {
struct Entry { struct Entry {
u64 app_id{}; u64 app_id{};
char display_version[0x10]{};
NacpLanguageEntry lang{}; NacpLanguageEntry lang{};
int image{}; int image{};
bool selected{}; bool selected{};
std::shared_ptr<NsApplicationControlData> control{}; std::shared_ptr<NsApplicationControlData> control{};
u64 control_size{}; u64 jpeg_size{};
NacpLoadStatus status{NacpLoadStatus::None}; NacpLoadStatus status{NacpLoadStatus::None};
auto GetName() const -> const char* { auto GetName() const -> const char* {
@@ -38,35 +38,38 @@ struct Entry {
auto GetAuthor() const -> const char* { auto GetAuthor() const -> const char* {
return lang.author; return lang.author;
} }
auto GetDisplayVersion() const -> const char* {
return display_version;
}
}; };
struct ThreadResultData { struct ThreadResultData {
u64 id{}; u64 id{};
std::shared_ptr<NsApplicationControlData> control{}; std::shared_ptr<NsApplicationControlData> control{};
u64 control_size{}; u64 jpeg_size{};
char display_version[0x10]{};
NacpLanguageEntry lang{}; NacpLanguageEntry lang{};
NacpLoadStatus status{NacpLoadStatus::None}; NacpLoadStatus status{NacpLoadStatus::None};
}; };
struct ThreadData { struct ThreadData {
ThreadData(); ThreadData(bool title_cache);
auto IsRunning() const -> bool;
void Run(); void Run();
void Close(); void Close();
void Push(u64 id); void Push(u64 id);
void Push(std::span<const Entry> entries); void Push(std::span<const Entry> entries);
void Pop(std::vector<ThreadResultData>& out); void Pop(std::vector<ThreadResultData>& out);
auto IsRunning() const -> bool {
return m_running;
}
auto IsTitleCacheEnabled() const {
return m_title_cache;
}
private: private:
UEvent m_uevent{}; UEvent m_uevent{};
Mutex m_mutex_id{}; Mutex m_mutex_id{};
Mutex m_mutex_result{}; Mutex m_mutex_result{};
bool m_title_cache{};
// app_ids pushed to the queue, signal uevent when pushed. // app_ids pushed to the queue, signal uevent when pushed.
std::vector<u64> m_ids{}; std::vector<u64> m_ids{};
@@ -141,13 +144,14 @@ private:
bool m_is_reversed{}; bool m_is_reversed{};
bool m_dirty{}; bool m_dirty{};
ThreadData m_thread_data{}; std::unique_ptr<ThreadData> m_thread_data{};
Thread m_thread{}; Thread m_thread{};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated}; option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending}; option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail}; option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_Grid};
option::OptionBool m_hide_forwarders{INI_SECTION, "hide_forwarders", false}; option::OptionBool m_hide_forwarders{INI_SECTION, "hide_forwarders", false};
option::OptionBool m_title_cache{INI_SECTION, "title_cache", true};
}; };
} // namespace sphaira::ui::menu::game } // namespace sphaira::ui::menu::game

View File

@@ -0,0 +1,156 @@
#pragma once
#include "ui/menus/grid_menu_base.hpp"
#include "ui/list.hpp"
#include "fs.hpp"
#include "option.hpp"
#include <memory>
#include <vector>
#include <span>
namespace sphaira::ui::menu::save {
enum class NacpLoadStatus {
// not yet attempted to be loaded.
None,
// started loading.
Progress,
// loaded, ready to parse.
Loaded,
// failed to load, do not attempt to load again!
Error,
};
struct Entry final : FsSaveDataInfo {
NacpLanguageEntry lang{};
int image{};
bool selected{};
std::shared_ptr<NsApplicationControlData> control{};
u64 jpeg_size{};
NacpLoadStatus status{NacpLoadStatus::None};
auto GetName() const -> const char* {
return lang.name;
}
auto GetAuthor() const -> const char* {
return lang.author;
}
};
struct AccountEntry {
AccountUid uid;
AccountProfile profile;
AccountProfileBase base;
};
struct ThreadResultData {
u64 id{};
std::shared_ptr<NsApplicationControlData> control{};
u64 jpeg_size{};
NacpLanguageEntry lang{};
NacpLoadStatus status{NacpLoadStatus::None};
};
struct ThreadData {
ThreadData();
auto IsRunning() const -> bool;
void Run();
void Close();
void Push(u64 id);
void Push(std::span<const Entry> entries);
void Pop(std::vector<ThreadResultData>& out);
private:
UEvent m_uevent{};
Mutex m_mutex_id{};
Mutex m_mutex_result{};
// app_ids pushed to the queue, signal uevent when pushed.
std::vector<u64> m_ids{};
// control data pushed to the queue.
std::vector<ThreadResultData> m_result{};
std::atomic_bool m_running{};
};
enum SortType {
SortType_Updated,
};
enum OrderType {
OrderType_Descending,
OrderType_Ascending,
};
using LayoutType = grid::LayoutType;
struct Menu final : grid::Menu {
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "Saves"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
private:
void SetIndex(s64 index);
void ScanHomebrew();
void Sort();
void SortAndFindLastFile(bool scan);
void FreeEntries();
void OnLayoutChange();
auto GetSelectedEntries() const {
std::vector<Entry> out;
for (auto& e : m_entries) {
if (e.selected) {
out.emplace_back(e);
}
}
if (!m_entries.empty() && out.empty()) {
out.emplace_back(m_entries[m_index]);
}
return out;
}
void ClearSelection() {
for (auto& e : m_entries) {
e.selected = false;
}
m_selected_count = 0;
}
void BackupSaves(std::vector<std::reference_wrapper<Entry>>& entries);
void RestoreSave();
private:
static constexpr inline const char* INI_SECTION = "saves";
std::vector<Entry> m_entries{};
s64 m_index{}; // where i am in the array
s64 m_selected_count{};
std::unique_ptr<List> m_list{};
bool m_is_reversed{};
bool m_dirty{};
std::vector<AccountEntry> m_accounts{};
s64 m_account_index{};
ThreadData m_thread_data{};
Thread m_thread{};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_Grid};
option::OptionBool m_auto_backup_on_restore{INI_SECTION, "auto_backup_on_restore", true};
option::OptionBool m_compress_save_backup{INI_SECTION, "compress_save_backup", true};
};
} // namespace sphaira::ui::menu::save

View File

@@ -25,6 +25,7 @@ struct ProgressBox final : Widget {
auto Update(Controller* controller, TouchInfo* touch) -> void override; auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto SetActionName(const std::string& action) -> ProgressBox&;
auto SetTitle(const std::string& title) -> ProgressBox&; auto SetTitle(const std::string& title) -> ProgressBox&;
auto NewTransfer(const std::string& transfer) -> ProgressBox&; auto NewTransfer(const std::string& transfer) -> ProgressBox&;
auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&; auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;

View File

@@ -179,6 +179,8 @@ enum ThemeEntryID {
ThemeEntryID_TEXT_SELECTED, ThemeEntryID_TEXT_SELECTED,
// background colour of a selected item, can be an image (not recommended). // background colour of a selected item, can be an image (not recommended).
ThemeEntryID_SELECTED_BACKGROUND, ThemeEntryID_SELECTED_BACKGROUND,
// colour of the split screen and selected item.
ThemeEntryID_FOCUS,
// colour of line separators in a list. // colour of line separators in a list.
ThemeEntryID_LINE, ThemeEntryID_LINE,

View File

@@ -7,7 +7,7 @@
namespace sphaira::es { namespace sphaira::es {
enum { TicketModule = 522 }; enum { TicketModule = 507 };
enum : Result { enum : Result {
// found ticket has missmatching rights_id from it's name. // found ticket has missmatching rights_id from it's name.

View File

@@ -221,7 +221,7 @@ Result VerifyFixedKey(const Header& header);
// helpers that parse an nca. // helpers that parse an nca.
Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos); Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos);
Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out = nullptr, s64 nacp_size = 0, std::vector<u8>* icon_out = nullptr); Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out = nullptr, s64 nacp_size = 0, std::vector<u8>* icon_out = nullptr, s64 nacp_off = 0);
auto GetKeyGenStr(u8 key_gen) -> const char*; auto GetKeyGenStr(u8 key_gen) -> const char*;

View File

@@ -11,7 +11,7 @@
namespace sphaira::yati::source { namespace sphaira::yati::source {
struct Usb final : Base { struct Usb final : Base {
enum { USBModule = 523 }; enum { USBModule = 508 };
enum : Result { enum : Result {
Result_BadMagic = MAKERESULT(USBModule, 0), Result_BadMagic = MAKERESULT(USBModule, 0),

View File

@@ -16,8 +16,18 @@
namespace sphaira::yati { namespace sphaira::yati {
enum { YatiModule = 521 }; enum { YatiModule = 506 };
/*
Improving compression ratio via block splitting is now enabled by default for high compression levels (16+).
The amount of benefit varies depending on the workload.
Compressing archives comprised of heavily differing files will see more improvement than compression of single files that dont
vary much entropically (like text files/enwik). At levels 16+, we observe no measurable regression to compression speed.
The block splitter can be forcibly enabled on lower compression levels as well with the advanced parameter ZSTD_c_splitBlocks.
When forcibly enabled at lower levels, speed regressions can become more notable.
Additionally, since more compressed blocks may be produced, decompression speed on these blobs may also see small regressions.
*/
enum : Result { enum : Result {
// unkown container for the source provided. // unkown container for the source provided.
Result_ContainerNotFound = MAKERESULT(YatiModule, 10), Result_ContainerNotFound = MAKERESULT(YatiModule, 10),

View File

@@ -98,6 +98,7 @@ constexpr ThemeIdPair THEME_ENTRIES[] = {
{ "selected_background", ThemeEntryID_SELECTED_BACKGROUND, ElementType::Colour }, { "selected_background", ThemeEntryID_SELECTED_BACKGROUND, ElementType::Colour },
{ "error", ThemeEntryID_ERROR, ElementType::Colour }, { "error", ThemeEntryID_ERROR, ElementType::Colour },
{ "popup", ThemeEntryID_POPUP, ElementType::Colour }, { "popup", ThemeEntryID_POPUP, ElementType::Colour },
{ "focus", ThemeEntryID_FOCUS, ElementType::Colour },
{ "line", ThemeEntryID_LINE, ElementType::Colour }, { "line", ThemeEntryID_LINE, ElementType::Colour },
{ "line_separator", ThemeEntryID_LINE_SEPARATOR, ElementType::Colour }, { "line_separator", ThemeEntryID_LINE_SEPARATOR, ElementType::Colour },
{ "sidebar", ThemeEntryID_SIDEBAR, ElementType::Colour }, { "sidebar", ThemeEntryID_SIDEBAR, ElementType::Colour },
@@ -895,22 +896,18 @@ void App::SetTextScrollSpeed(long index) {
} }
auto App::Install(OwoConfig& config) -> Result { auto App::Install(OwoConfig& config) -> Result {
config.nro_path = nro_add_arg_file(config.nro_path); App::Push(std::make_shared<ui::ProgressBox>(0, "Installing Forwarder"_i18n, config.name, [config](auto pbox) mutable -> Result {
if (!config.icon.empty()) { return Install(pbox, config);
config.icon = GetNroIcon(config.icon); }, [](Result rc){
} App::PushErrorBox(rc, "Failed to install forwarder"_i18n);
const auto rc = install_forwarder(config, App::GetInstallSdEnable() ? NcmStorageId_SdCard : NcmStorageId_BuiltInUser); if (R_SUCCEEDED(rc)) {
if (R_FAILED(rc)) {
App::PlaySoundEffect(SoundEffect_Error);
App::Push(std::make_shared<ui::ErrorBox>(rc, "Failed to install forwarder"_i18n));
} else {
App::PlaySoundEffect(SoundEffect_Install); App::PlaySoundEffect(SoundEffect_Install);
App::Notify("Installed!"_i18n); App::Notify("Installed!"_i18n);
} }
}));
return rc; R_SUCCEED();
} }
auto App::Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result { auto App::Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result {
@@ -919,17 +916,15 @@ auto App::Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result {
config.icon = GetNroIcon(config.icon); config.icon = GetNroIcon(config.icon);
} }
const auto rc = install_forwarder(pbox, config, GetInstallSdEnable() ? NcmStorageId_SdCard : NcmStorageId_BuiltInUser); if (config.logo.empty()) {
fs::FsNativeSd().read_entire_file("/config/sphaira/logo/NintendoLogo.png", config.logo);
if (R_FAILED(rc)) {
App::PlaySoundEffect(SoundEffect_Error);
App::Push(std::make_shared<ui::ErrorBox>(rc, "Failed to install forwarder"_i18n));
} else {
App::PlaySoundEffect(SoundEffect_Install);
App::Notify("Installed!"_i18n);
} }
return rc; if (config.gif.empty()) {
fs::FsNativeSd().read_entire_file("/config/sphaira/logo/StartupMovie.gif", config.gif);
}
return install_forwarder(pbox, config, GetInstallSdEnable() ? NcmStorageId_SdCard : NcmStorageId_BuiltInUser);
} }
auto App::IsEmummc() -> bool { auto App::IsEmummc() -> bool {
@@ -1375,6 +1370,8 @@ App::App(const char* argv0) {
// loading each config one by one as it avoids re-opening the file multiple times. // loading each config one by one as it avoids re-opening the file multiple times.
ini_browse(cb, this, CONFIG_PATH); ini_browse(cb, this, CONFIG_PATH);
i18n::init(GetLanguage());
if (App::GetLogEnable()) { if (App::GetLogEnable()) {
log_file_init(); log_file_init();
log_write("hello world v%s\n", APP_VERSION_HASH); log_write("hello world v%s\n", APP_VERSION_HASH);
@@ -1403,6 +1400,12 @@ App::App(const char* argv0) {
curl::Init(); curl::Init();
#ifdef USE_NVJPG
// this has to be init before deko3d.
nj::initialize();
m_decoder.initialize();
#endif
// get current size of the framebuffer // get current size of the framebuffer
const auto fb = GetFrameBufferSize(); const auto fb = GetFrameBufferSize();
s_width = fb.size.x; s_width = fb.size.x;
@@ -1435,8 +1438,6 @@ App::App(const char* argv0) {
this->renderer.emplace(s_width, s_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); this->vg = nvgCreateDk(&*this->renderer, NVG_ANTIALIAS | NVG_STENCIL_STROKES);
i18n::init(GetLanguage());
// not sure if these are meant to be deleted or not... // not sure if these are meant to be deleted or not...
PlFontData font_standard, font_extended, font_lang; PlFontData font_standard, font_extended, font_lang;
plGetSharedFontByType(&font_standard, PlSharedFontType_Standard); plGetSharedFontByType(&font_standard, PlSharedFontType_Standard);
@@ -1643,7 +1644,7 @@ void App::DisplayMiscOptions(bool left_side) {
const auto index = *op_index; const auto index = *op_index;
if (index == items.size() - 1) { if (index == items.size() - 1) {
std::string out; std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, "Enter URL", "https://")) && !out.empty()) { if (R_SUCCEEDED(swkbd::ShowText(out, "Enter URL"_i18n.c_str(), "https://")) && !out.empty()) {
WebShow(out); WebShow(out);
} }
} else { } else {
@@ -1665,7 +1666,8 @@ void App::DisplayAdvancedOptions(bool left_side) {
text_scroll_speed_items.push_back("Normal"_i18n); text_scroll_speed_items.push_back("Normal"_i18n);
text_scroll_speed_items.push_back("Fast"_i18n); text_scroll_speed_items.push_back("Fast"_i18n);
std::vector<const char*> menu_names; std::vector<std::string> menu_names;
ui::SidebarEntryArray::Items menu_items;
for (auto& e : ui::menu::main::GetMiscMenuEntries()) { for (auto& e : ui::menu::main::GetMiscMenuEntries()) {
if (!e.IsShortcut()) { if (!e.IsShortcut()) {
continue; continue;
@@ -1676,16 +1678,7 @@ void App::DisplayAdvancedOptions(bool left_side) {
} }
menu_names.emplace_back(e.name); menu_names.emplace_back(e.name);
} menu_items.push_back(i18n::get(e.name));
ui::SidebarEntryArray::Items side_menu_items;
for (auto& str : menu_names) {
side_menu_items.push_back(i18n::get(str));
}
const auto it = std::find(menu_names.cbegin(), menu_names.cend(), g_app->m_right_menu.Get());
if (it == menu_names.cend()) {
g_app->m_right_menu.Set(menu_names[0]);
} }
options->Add(std::make_shared<ui::SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [](bool& enable){ options->Add(std::make_shared<ui::SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [](bool& enable){
@@ -1704,10 +1697,15 @@ void App::DisplayAdvancedOptions(bool left_side) {
App::SetTextScrollSpeed(index_out); App::SetTextScrollSpeed(index_out);
}, App::GetTextScrollSpeed())); }, App::GetTextScrollSpeed()));
options->Add(std::make_shared<ui::SidebarEntryArray>("Set left-side menu"_i18n, side_menu_items, [menu_names](s64& index_out){ options->Add(std::make_shared<ui::SidebarEntryArray>("Set left-side menu"_i18n, menu_items, [menu_names](s64& index_out){
const auto e = menu_names[index_out]; const auto e = menu_names[index_out];
if (g_app->m_left_menu.Get() != e) { if (g_app->m_left_menu.Get() != e) {
// swap menus around.
if (g_app->m_right_menu.Get() == e) {
g_app->m_right_menu.Set(g_app->m_left_menu.Get());
}
g_app->m_left_menu.Set(e); g_app->m_left_menu.Set(e);
App::Push(std::make_shared<ui::OptionBox>( App::Push(std::make_shared<ui::OptionBox>(
"Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){ "Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){
App::ExitRestart(); App::ExitRestart();
@@ -1716,10 +1714,15 @@ void App::DisplayAdvancedOptions(bool left_side) {
} }
}, i18n::get(g_app->m_left_menu.Get()))); }, i18n::get(g_app->m_left_menu.Get())));
options->Add(std::make_shared<ui::SidebarEntryArray>("Set right-side menu"_i18n, side_menu_items, [menu_names](s64& index_out){ options->Add(std::make_shared<ui::SidebarEntryArray>("Set right-side menu"_i18n, menu_items, [menu_names](s64& index_out){
const auto e = menu_names[index_out]; const auto e = menu_names[index_out];
if (g_app->m_right_menu.Get() != e) { if (g_app->m_right_menu.Get() != e) {
// swap menus around.
if (g_app->m_left_menu.Get() == e) {
g_app->m_left_menu.Set(g_app->m_right_menu.Get());
}
g_app->m_right_menu.Set(e); g_app->m_right_menu.Set(e);
App::Push(std::make_shared<ui::OptionBox>( App::Push(std::make_shared<ui::OptionBox>(
"Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){ "Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){
App::ExitRestart(); App::ExitRestart();
@@ -1852,6 +1855,7 @@ App::~App() {
ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal)); ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal));
log_write("starting to exit\n"); log_write("starting to exit\n");
TimeStamp ts;
i18n::exit(); i18n::exit();
curl::Exit(); curl::Exit();
@@ -1881,6 +1885,11 @@ App::~App() {
nvgDeleteDk(this->vg); nvgDeleteDk(this->vg);
this->renderer.reset(); this->renderer.reset();
#ifdef USE_NVJPG
m_decoder.finalize();
nj::finalize();
#endif
// backup hbmenu if it is not sphaira // backup hbmenu if it is not sphaira
if (App::GetReplaceHbmenuEnable() && !IsHbmenu()) { if (App::GetReplaceHbmenuEnable() && !IsHbmenu()) {
NacpStruct hbmenu_nacp; NacpStruct hbmenu_nacp;
@@ -1953,6 +1962,8 @@ App::~App() {
usbHsFsExit(); usbHsFsExit();
} }
log_write("\t[EXIT] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
if (App::GetLogEnable()) { if (App::GetLogEnable()) {
log_write("closing log\n"); log_write("closing log\n");
log_file_exit(); log_file_exit();

View File

@@ -332,7 +332,7 @@ struct ThreadQueue {
} }
auto Add(const Api& api, bool is_upload = false) -> bool { auto Add(const Api& api, bool is_upload = false) -> bool {
if (api.GetUrl().empty() || api.GetPath().empty() || !api.GetOnComplete()) { if (api.GetUrl().empty() || !api.GetOnComplete()) {
return false; return false;
} }

View File

@@ -207,14 +207,14 @@ Result DumpToUsbS2S(ui::ProgressBox* pbox, BaseSource* source, std::span<const f
while (!pbox->ShouldExit()) { while (!pbox->ShouldExit()) {
if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) { if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) {
pbox->NewTransfer("USB connected, sending file list"); pbox->NewTransfer("USB connected, sending file list"_i18n);
u8 flags = usb::tinfoil::USBFlag_NONE; u8 flags = usb::tinfoil::USBFlag_NONE;
if (App::GetApp()->m_dump_usb_transfer_stream.Get()) { if (App::GetApp()->m_dump_usb_transfer_stream.Get()) {
flags |= usb::tinfoil::USBFlag_STREAM; flags |= usb::tinfoil::USBFlag_STREAM;
} }
if (R_SUCCEEDED(usb->WaitForConnection(timeout, flags, file_list))) { if (R_SUCCEEDED(usb->WaitForConnection(timeout, flags, file_list))) {
pbox->NewTransfer("Sent file list, waiting for command..."); pbox->NewTransfer("Sent file list, waiting for command..."_i18n);
Result rc; Result rc;
if (flags & usb::tinfoil::USBFlag_STREAM) { if (flags & usb::tinfoil::USBFlag_STREAM) {
@@ -240,7 +240,7 @@ Result DumpToUsbS2S(ui::ProgressBox* pbox, BaseSource* source, std::span<const f
return rc; return rc;
} }
} else { } else {
pbox->NewTransfer("waiting for usb connection..."); pbox->NewTransfer("waiting for usb connection..."_i18n);
} }
} }

View File

@@ -118,6 +118,18 @@ Result CreateDirectory(FsFileSystem* fs, const FsPath& path, bool ignore_read_on
Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ignore_read_only) { Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ignore_read_only) {
R_UNLESS(ignore_read_only || !is_read_only_root(_path), Fs::ResultReadOnly); R_UNLESS(ignore_read_only || !is_read_only_root(_path), Fs::ResultReadOnly);
// try and create the directory / see if it already exists before the loop.
Result rc;
if (fs) {
rc = CreateDirectory(fs, _path, ignore_read_only);
} else {
rc = CreateDirectory(_path, ignore_read_only);
}
if (R_SUCCEEDED(rc) || rc == FsError_PathAlreadyExists) {
R_SUCCEED();
}
auto path_view = std::string_view{_path}; auto path_view = std::string_view{_path};
// todo: fix this for sdmc: and ums0: // todo: fix this for sdmc: and ums0:
FsPath path{"/"}; FsPath path{"/"};
@@ -134,7 +146,6 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig
std::strncat(path, dir.data(), dir.size()); std::strncat(path, dir.data(), dir.size());
log_write("[FS] dir creation path is now: %s\n", path.s); log_write("[FS] dir creation path is now: %s\n", path.s);
Result rc;
if (fs) { if (fs) {
rc = CreateDirectory(fs, path, ignore_read_only); rc = CreateDirectory(fs, path, ignore_read_only);
} else { } else {
@@ -155,31 +166,15 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig
Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& _path, bool ignore_read_only) { Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& _path, bool ignore_read_only) {
R_UNLESS(ignore_read_only || !is_read_only_root(_path), Fs::ResultReadOnly); R_UNLESS(ignore_read_only || !is_read_only_root(_path), Fs::ResultReadOnly);
size_t off = 0; // strip file name form path.
while (true) { const auto last_slash = std::strrchr(_path, '/');
const auto first = std::strchr(_path + off, '/'); if (!last_slash) {
if (!first) {
R_SUCCEED(); R_SUCCEED();
} }
off = (first - _path.s) + 1; FsPath new_path{};
FsPath path; std::snprintf(new_path, sizeof(new_path), "%.*s", (int)(last_slash - _path.s), _path.s);
std::strncpy(path, _path, off); return CreateDirectoryRecursively(fs, new_path, ignore_read_only);
Result rc;
if (fs) {
rc = CreateDirectory(fs, path, ignore_read_only);
} else {
rc = CreateDirectory(path, ignore_read_only);
}
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder recursively: %s\n", path.s);
return rc;
}
// log_write("created_directory recursively: %s\n", path);
}
} }
Result DeleteFile(FsFileSystem* fs, const FsPath& path, bool ignore_read_only) { Result DeleteFile(FsFileSystem* fs, const FsPath& path, bool ignore_read_only) {

View File

@@ -35,6 +35,25 @@ private:
bool m_is_file_based_emummc{}; bool m_is_file_based_emummc{};
}; };
struct MemSource final : BaseSource {
MemSource(std::span<const u8> data) : m_data{data} { }
Result Size(s64* out) {
*out = m_data.size();
R_SUCCEED();
}
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) {
size = std::min<s64>(size, m_data.size() - off);
std::memcpy(buf, m_data.data() + off, size);
*bytes_read = size;
R_SUCCEED();
}
private:
const std::span<const u8> m_data;
};
struct HashSource { struct HashSource {
virtual ~HashSource() = default; virtual ~HashSource() = default;
virtual void Update(const void* buf, s64 size) = 0; virtual void Update(const void* buf, s64 size) = 0;
@@ -181,4 +200,9 @@ Result Hash(ui::ProgressBox* pbox, Type type, fs::Fs* fs, const fs::FsPath& path
return Hash(pbox, type, source, out); return Hash(pbox, type, source, out);
} }
Result Hash(ui::ProgressBox* pbox, Type type, std::span<const u8> data, std::string& out) {
auto source = std::make_shared<MemSource>(data);
return Hash(pbox, type, source, out);
}
} // namespace sphaira::has } // namespace sphaira::has

View File

@@ -20,7 +20,11 @@
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#include "app.hpp"
#include "log.hpp" #include "log.hpp"
#ifdef USE_NVJPG
#include <nvjpg.hpp>
#endif
#include <cstring> #include <cstring>
namespace sphaira { namespace sphaira {
@@ -30,7 +34,6 @@ constexpr int BPP = 4;
auto ImageLoadInternal(stbi_uc* image_data, int x, int y) -> ImageResult { auto ImageLoadInternal(stbi_uc* image_data, int x, int y) -> ImageResult {
if (image_data) { if (image_data) {
log_write("loaded image: w: %d h: %d\n", x, y);
ImageResult result{}; ImageResult result{};
result.data.resize(x*y*BPP); result.data.resize(x*y*BPP);
result.w = x; result.w = x;
@@ -44,18 +47,84 @@ auto ImageLoadInternal(stbi_uc* image_data, int x, int y) -> ImageResult {
return {}; return {};
} }
#ifdef USE_NVJPG
auto ImageLoadInternal(nj::Image&& image) -> ImageResult {
if (!image.is_valid() || image.parse()) {
log_write("[NVJPG] failed to parse image\n");
return {};
}
nj::Surface surf{image.width, image.height};
if (surf.allocate()) {
log_write("[NVJPG] failed to allocate surf\n");
return {};
}
if (R_FAILED(App::GetApp()->m_decoder.render(image, surf, 255))) {
log_write("[NVJPG] failed to render\n");
return {};
}
if (R_FAILED(App::GetApp()->m_decoder.wait(surf))) {
log_write("[NVJPG] failed to wait\n");
return {};
}
ImageResult result{};
result.w = surf.width;
result.h = surf.height;
result.data.resize(surf.width * surf.height * surf.get_bpp());
// std::printf("[NVJPG] w: %zu h: %zu bpp: %u pitch: %zu size: %zu size2: %u\n", surf.width, surf.height, surf.get_bpp(), surf.pitch, surf.size(), 256*256*4);
if (surf.width * surf.get_bpp() == surf.pitch) [[likely]] {
std::memcpy(result.data.data(), surf.data(), result.data.size());
} else {
for (size_t i = 0; i < surf.height; i++) {
const auto src_pitch = surf.pitch;
const auto dst_pitch = surf.width * surf.get_bpp();
std::memcpy(result.data.data() + i * dst_pitch, surf.data() + i * src_pitch, dst_pitch);
}
}
return result;
}
#endif
} // namespace } // namespace
auto ImageLoadFromMemory(std::span<const u8> data) -> ImageResult { auto ImageLoadFromMemory(std::span<const u8> data, u32 flags) -> ImageResult {
#ifdef USE_NVJPG
if (flags & ImageFlag_JPEG) {
auto shared_vec = std::make_shared<std::vector<u8>>(data.size());
std::memcpy(shared_vec->data(), data.data(), shared_vec->size());
// don't make const as it prevents RTO.
auto result = ImageLoadInternal(nj::Image{shared_vec});
// if it failed, try again but without using oss-jpg.
return result.data.empty() ? ImageLoadFromMemory(data, 0) : result;
}
else
#endif
{
int x, y, channels; int x, y, channels;
return ImageLoadInternal(stbi_load_from_memory(data.data(), data.size(), &x, &y, &channels, BPP), x, y); return ImageLoadInternal(stbi_load_from_memory(data.data(), data.size(), &x, &y, &channels, BPP), x, y);
} }
}
auto ImageLoadFromFile(const fs::FsPath& file) -> ImageResult { auto ImageLoadFromFile(const fs::FsPath& file, u32 flags) -> ImageResult {
log_write("doing file load\n"); #ifdef USE_NVJPG
if (flags & ImageFlag_JPEG) {
// don't make const as it prevents RTO.
auto result = ImageLoadInternal(nj::Image{file});
// if it failed, try again but without using oss-jpg.
return result.data.empty() ? ImageLoadFromFile(file, 0) : result;
}
else
#endif
{
int x, y, channels; int x, y, channels;
return ImageLoadInternal(stbi_load(file, &x, &y, &channels, BPP), x, y); return ImageLoadInternal(stbi_load(file, &x, &y, &channels, BPP), x, y);
} }
}
auto ImageResize(std::span<const u8> data, int inx, int iny, int outx, int outy) -> ImageResult { auto ImageResize(std::span<const u8> data, int inx, int iny, int outx, int outy) -> ImageResult {
log_write("doing resize inx: %d iny: %d outx: %d outy: %d\n", inx, iny, outx, outy); log_write("doing resize inx: %d iny: %d outx: %d outy: %d\n", inx, iny, outx, outy);

View File

@@ -83,6 +83,10 @@ void userAppExit(void) {
psmExit(); psmExit();
plExit(); plExit();
socketExit(); socketExit();
// NOTE (DMC): prevents exfat corruption.
if (auto fs = fsdevGetDeviceFileSystem("sdmc:")) {
fsFsCommit(fs);
}
appletUnlockExit(); appletUnlockExit();
} }

View File

@@ -0,0 +1,210 @@
#include "minizip_helper.hpp"
#include <minizip/unzip.h>
#include <minizip/zip.h>
#include <cstring>
#include <cstdio>
namespace sphaira::mz {
namespace {
voidpf minizip_open_file_func_mem(voidpf opaque, const void* filename, int mode) {
return opaque;
}
ZPOS64_T minizip_tell_file_func_mem(voidpf opaque, voidpf stream) {
auto mem = static_cast<const MzMem*>(opaque);
return mem->offset;
}
long minizip_seek_file_func_mem(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) {
auto mem = static_cast<MzMem*>(opaque);
size_t new_offset = 0;
switch (origin) {
case ZLIB_FILEFUNC_SEEK_SET: new_offset = offset; break;
case ZLIB_FILEFUNC_SEEK_CUR: new_offset = mem->offset + offset; break;
case ZLIB_FILEFUNC_SEEK_END: new_offset = (mem->buf.size() - 1) + offset; break;
default: return -1;
}
if (new_offset > mem->buf.size()) {
return -1;
}
mem->offset = new_offset;
return 0;
}
uLong minizip_read_file_func_mem(voidpf opaque, voidpf stream, void* buf, uLong size) {
auto mem = static_cast<MzMem*>(opaque);
size = std::min(size, mem->buf.size() - mem->offset);
std::memcpy(buf, mem->buf.data() + mem->offset, size);
mem->offset += size;
return size;
}
uLong minizip_write_file_func_mem(voidpf opaque, voidpf stream, const void* buf, uLong size) {
auto mem = static_cast<MzMem*>(opaque);
// give it more memory
if (mem->buf.capacity() < mem->offset + size) {
mem->buf.reserve(mem->buf.capacity() + 1024*1024*64);
}
if (mem->buf.size() < mem->offset + size) {
mem->buf.resize(mem->offset + size);
}
std::memcpy(mem->buf.data() + mem->offset, buf, size);
mem->offset += size;
return size;
}
int minizip_close_file_func_mem(voidpf opaque, voidpf stream) {
return 0;
}
constexpr zlib_filefunc64_def zlib_filefunc_mem = {
.zopen64_file = minizip_open_file_func_mem,
.zread_file = minizip_read_file_func_mem,
.zwrite_file = minizip_write_file_func_mem,
.ztell64_file = minizip_tell_file_func_mem,
.zseek64_file = minizip_seek_file_func_mem,
.zclose_file = minizip_close_file_func_mem,
};
voidpf minizip_open_file_func_span(voidpf opaque, const void* filename, int mode) {
return opaque;
}
ZPOS64_T minizip_tell_file_func_span(voidpf opaque, voidpf stream) {
auto mem = static_cast<const MzSpan*>(opaque);
return mem->offset;
}
long minizip_seek_file_func_span(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) {
auto mem = static_cast<MzSpan*>(opaque);
size_t new_offset = 0;
switch (origin) {
case ZLIB_FILEFUNC_SEEK_SET: new_offset = offset; break;
case ZLIB_FILEFUNC_SEEK_CUR: new_offset = mem->offset + offset; break;
case ZLIB_FILEFUNC_SEEK_END: new_offset = (mem->buf.size() - 1) + offset; break;
default: return -1;
}
if (new_offset > mem->buf.size()) {
return -1;
}
mem->offset = new_offset;
return 0;
}
uLong minizip_read_file_func_span(voidpf opaque, voidpf stream, void* buf, uLong size) {
auto mem = static_cast<MzSpan*>(opaque);
size = std::min(size, mem->buf.size() - mem->offset);
std::memcpy(buf, mem->buf.data() + mem->offset, size);
mem->offset += size;
return size;
}
int minizip_close_file_func_span(voidpf opaque, voidpf stream) {
return 0;
}
constexpr zlib_filefunc64_def zlib_filefunc_span = {
.zopen64_file = minizip_open_file_func_span,
.zread_file = minizip_read_file_func_span,
.ztell64_file = minizip_tell_file_func_span,
.zseek64_file = minizip_seek_file_func_span,
.zclose_file = minizip_close_file_func_span,
};
voidpf minizip_open_file_func_stdio(voidpf opaque, const void* filename, int mode) {
const char* mode_fopen = NULL;
if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER) == ZLIB_FILEFUNC_MODE_READ) {
mode_fopen = "rb";
} else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) {
mode_fopen = "r+b";
} else if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
mode_fopen = "wb";
} else {
return NULL;
}
auto f = std::fopen((const char*)filename, mode_fopen);
if (f) {
std::setvbuf(f, nullptr, _IOFBF, 1024 * 512);
}
return f;
}
ZPOS64_T minizip_tell_file_func_stdio(voidpf opaque, voidpf stream) {
auto file = static_cast<std::FILE*>(stream);
return std::ftell(file);
}
long minizip_seek_file_func_stdio(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) {
auto file = static_cast<std::FILE*>(stream);
return std::fseek(file, offset, origin);
}
uLong minizip_read_file_func_stdio(voidpf opaque, voidpf stream, void* buf, uLong size) {
auto file = static_cast<std::FILE*>(stream);
return std::fread(buf, 1, size, file);
}
uLong minizip_write_file_func_stdio(voidpf opaque, voidpf stream, const void* buf, uLong size) {
auto file = static_cast<std::FILE*>(stream);
return std::fwrite(buf, 1, size, file);
}
int minizip_close_file_func_stdio(voidpf opaque, voidpf stream) {
auto file = static_cast<std::FILE*>(stream);
if (file) {
return std::fclose(file);
}
return 0;
}
int minizip_error_file_func_stdio(voidpf opaque, voidpf stream) {
auto file = static_cast<std::FILE*>(stream);
if (file) {
return std::ferror(file);
}
return 0;
}
constexpr zlib_filefunc64_def zlib_filefunc_stdio = {
.zopen64_file = minizip_open_file_func_stdio,
.zread_file = minizip_read_file_func_stdio,
.zwrite_file = minizip_write_file_func_stdio,
.ztell64_file = minizip_tell_file_func_stdio,
.zseek64_file = minizip_seek_file_func_stdio,
.zclose_file = minizip_close_file_func_stdio,
.zerror_file = minizip_error_file_func_stdio,
};
} // namespace
void FileFuncMem(MzMem* mem, zlib_filefunc64_def* funcs) {
*funcs = zlib_filefunc_mem;
funcs->opaque = mem;
}
void FileFuncSpan(MzSpan* span, zlib_filefunc64_def* funcs) {
*funcs = zlib_filefunc_span;
funcs->opaque = span;
}
void FileFuncStdio(zlib_filefunc64_def* funcs) {
*funcs = zlib_filefunc_stdio;
}
} // namespace sphaira::mz

View File

@@ -2,19 +2,25 @@
#include "log.hpp" #include "log.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "app.hpp" #include "app.hpp"
#include "minizip_helper.hpp"
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <minizip/unzip.h>
#include <minizip/zip.h>
namespace sphaira::thread { namespace sphaira::thread {
namespace { namespace {
constexpr u64 READ_BUFFER_MAX = 1024*1024*4; // used for file based emummc and zip/unzip.
constexpr u64 SMALL_BUFFER_SIZE = 1024 * 512;
// used for everything else.
constexpr u64 NORMAL_BUFFER_SIZE = 1024*1024*4;
struct ThreadBuffer { struct ThreadBuffer {
ThreadBuffer() { ThreadBuffer() {
buf.reserve(READ_BUFFER_MAX); buf.reserve(NORMAL_BUFFER_SIZE);
} }
std::vector<u8> buf; std::vector<u8> buf;
@@ -65,7 +71,7 @@ public:
}; };
struct ThreadData { struct ThreadData {
ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, WriteCallback _wfunc); ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, WriteCallback _wfunc, u64 buffer_size);
auto GetResults() -> Result; auto GetResults() -> Result;
void WakeAllThreads(); void WakeAllThreads();
@@ -104,9 +110,9 @@ private:
private: private:
// these need to be copied // these need to be copied
ui::ProgressBox* pbox{}; ui::ProgressBox* const pbox;
ReadCallback rfunc{}; const ReadCallback rfunc;
WriteCallback wfunc{}; const WriteCallback wfunc;
// these need to be created // these need to be created
Mutex mutex{}; Mutex mutex{};
@@ -121,21 +127,24 @@ private:
std::vector<u8> pull_buffer{}; std::vector<u8> pull_buffer{};
s64 pull_buffer_offset{}; s64 pull_buffer_offset{};
u64 read_buffer_size{}; const u64 read_buffer_size;
u64 max_buffer_size{}; const s64 write_size;
// these are shared between threads // these are shared between threads
volatile s64 read_offset{}; volatile s64 read_offset{};
volatile s64 write_offset{}; volatile s64 write_offset{};
volatile s64 write_size{};
volatile Result read_result{}; volatile Result read_result{};
volatile Result write_result{}; volatile Result write_result{};
volatile Result pull_result{}; volatile Result pull_result{};
}; };
ThreadData::ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, WriteCallback _wfunc) ThreadData::ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, WriteCallback _wfunc, u64 buffer_size)
: pbox{_pbox}, rfunc{_rfunc}, wfunc{_wfunc} { : pbox{_pbox}
, rfunc{_rfunc}
, wfunc{_wfunc}
, read_buffer_size{buffer_size}
, write_size{size} {
mutexInit(std::addressof(mutex)); mutexInit(std::addressof(mutex));
mutexInit(std::addressof(pull_mutex)); mutexInit(std::addressof(pull_mutex));
@@ -143,16 +152,6 @@ ThreadData::ThreadData(ui::ProgressBox* _pbox, s64 size, ReadCallback _rfunc, Wr
condvarInit(std::addressof(can_write)); condvarInit(std::addressof(can_write));
condvarInit(std::addressof(can_pull)); condvarInit(std::addressof(can_pull));
condvarInit(std::addressof(can_pull_write)); condvarInit(std::addressof(can_pull_write));
write_size = size;
if (App::IsFileBaseEmummc()) {
read_buffer_size = 1024 * 512;
max_buffer_size = 1024 * 512;
} else {
read_buffer_size = READ_BUFFER_MAX;
max_buffer_size = READ_BUFFER_MAX;
}
} }
auto ThreadData::GetResults() -> Result { auto ThreadData::GetResults() -> Result {
@@ -251,7 +250,7 @@ Result ThreadData::Pull(void* data, s64 size, u64* bytes_read) {
Result ThreadData::readFuncInternal() { Result ThreadData::readFuncInternal() {
// the main buffer which data is read into. // the main buffer which data is read into.
std::vector<u8> buf; std::vector<u8> buf;
buf.reserve(this->max_buffer_size); buf.reserve(this->read_buffer_size);
while (this->read_offset < this->write_size && R_SUCCEEDED(this->GetResults())) { while (this->read_offset < this->write_size && R_SUCCEEDED(this->GetResults())) {
// read more data // read more data
@@ -265,14 +264,14 @@ Result ThreadData::readFuncInternal() {
R_TRY(this->SetWriteBuf(buf, buf_size)); R_TRY(this->SetWriteBuf(buf, buf_size));
} }
log_write("read success\n"); log_write("finished read thread success!\n");
R_SUCCEED(); R_SUCCEED();
} }
// write thread writes data to the nca placeholder. // write thread writes data to the nca placeholder.
Result ThreadData::writeFuncInternal() { Result ThreadData::writeFuncInternal() {
std::vector<u8> buf; std::vector<u8> buf;
buf.reserve(this->max_buffer_size); buf.reserve(this->read_buffer_size);
while (this->write_offset < this->write_size && R_SUCCEEDED(this->GetResults())) { while (this->write_offset < this->write_size && R_SUCCEEDED(this->GetResults())) {
s64 dummy_off; s64 dummy_off;
@@ -288,7 +287,7 @@ Result ThreadData::writeFuncInternal() {
this->write_offset += size; this->write_offset += size;
} }
log_write("finished write thread!\n"); log_write("finished write thread success!\n");
R_SUCCEED(); R_SUCCEED();
} }
@@ -308,21 +307,55 @@ auto GetAlternateCore(int id) {
return id == 1 ? 2 : 1; return id == 1 ? 2 : 1;
} }
Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, WriteCallback wfunc, StartCallback2 sfunc) { Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, WriteCallback wfunc, StartCallback2 sfunc, Mode mode, u64 buffer_size = NORMAL_BUFFER_SIZE) {
App::SetAutoSleepDisabled(true); const auto is_file_based_emummc = App::IsFileBaseEmummc();
ON_SCOPE_EXIT(App::SetAutoSleepDisabled(false));
if (is_file_based_emummc) {
buffer_size = SMALL_BUFFER_SIZE;
}
if (mode == Mode::SingleThreadedIfSmaller) {
if (size <= buffer_size) {
mode = Mode::SingleThreaded;
} else {
mode = Mode::MultiThreaded;
}
}
// single threaded pull buffer is not supported.
R_UNLESS(mode != Mode::MultiThreaded || !sfunc, 0x1);
// todo: support single threaded pull buffer.
if (mode == Mode::SingleThreaded) {
std::vector<u8> buf(buffer_size);
s64 offset{};
while (offset < size) {
R_TRY(pbox->ShouldExitResult());
u64 bytes_read;
const auto rsize = std::min<s64>(buf.size(), size - offset);
R_TRY(rfunc(buf.data(), offset, rsize, &bytes_read));
R_TRY(wfunc(buf.data(), offset, bytes_read));
offset += bytes_read;
pbox->UpdateTransfer(offset, size);
}
R_SUCCEED();
}
else {
const auto WRITE_THREAD_CORE = sfunc ? pbox->GetCpuId() : GetAlternateCore(pbox->GetCpuId()); const auto WRITE_THREAD_CORE = sfunc ? pbox->GetCpuId() : GetAlternateCore(pbox->GetCpuId());
const auto READ_THREAD_CORE = GetAlternateCore(WRITE_THREAD_CORE); const auto READ_THREAD_CORE = GetAlternateCore(WRITE_THREAD_CORE);
ThreadData t_data{pbox, size, rfunc, wfunc}; ThreadData t_data{pbox, size, rfunc, wfunc, buffer_size};
Thread t_read{}; Thread t_read{};
R_TRY(threadCreate(&t_read, readFunc, std::addressof(t_data), nullptr, 1024*64, PRIO_PREEMPTIVE, READ_THREAD_CORE)); R_TRY(threadCreate(&t_read, readFunc, std::addressof(t_data), nullptr, 1024*256, 0x3B, READ_THREAD_CORE));
ON_SCOPE_EXIT(threadClose(&t_read)); ON_SCOPE_EXIT(threadClose(&t_read));
Thread t_write{}; Thread t_write{};
R_TRY(threadCreate(&t_write, writeFunc, std::addressof(t_data), nullptr, 1024*64, PRIO_PREEMPTIVE, WRITE_THREAD_CORE)); R_TRY(threadCreate(&t_write, writeFunc, std::addressof(t_data), nullptr, 1024*256, 0x3B, WRITE_THREAD_CORE));
ON_SCOPE_EXIT(threadClose(&t_write)); ON_SCOPE_EXIT(threadClose(&t_write));
const auto start_threads = [&]() -> Result { const auto start_threads = [&]() -> Result {
@@ -336,12 +369,16 @@ Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, Wri
ON_SCOPE_EXIT(threadWaitForExit(std::addressof(t_write))); ON_SCOPE_EXIT(threadWaitForExit(std::addressof(t_write)));
if (sfunc) { if (sfunc) {
log_write("[THREAD] doing sfuncn\n");
t_data.SetPullResult(sfunc(start_threads, [&](void* data, s64 size, u64* bytes_read) -> Result { t_data.SetPullResult(sfunc(start_threads, [&](void* data, s64 size, u64* bytes_read) -> Result {
R_TRY(t_data.GetResults()); R_TRY(t_data.GetResults());
return t_data.Pull(data, size, bytes_read); return t_data.Pull(data, size, bytes_read);
})); }));
} else { }
else {
log_write("[THREAD] doing normal\n");
R_TRY(start_threads()); R_TRY(start_threads());
log_write("[THREAD] started threads\n");
while (t_data.GetWriteOffset() != t_data.GetWriteSize() && R_SUCCEEDED(t_data.GetResults())) { while (t_data.GetWriteOffset() != t_data.GetWriteSize() && R_SUCCEEDED(t_data.GetResults())) {
pbox->UpdateTransfer(t_data.GetWriteOffset(), t_data.GetWriteSize()); pbox->UpdateTransfer(t_data.GetWriteOffset(), t_data.GetWriteSize());
@@ -371,24 +408,174 @@ Result TransferInternal(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, Wri
return t_data.GetResults(); return t_data.GetResults();
} }
log_write("returning from thread func\n");
return t_data.GetResults(); return t_data.GetResults();
} }
}
} // namespace } // namespace
Result Transfer(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, WriteCallback wfunc) { Result Transfer(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, WriteCallback wfunc, Mode mode) {
return TransferInternal(pbox, size, rfunc, wfunc, nullptr); return TransferInternal(pbox, size, rfunc, wfunc, nullptr, Mode::MultiThreaded);
} }
Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback sfunc) { Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback sfunc, Mode mode) {
return TransferInternal(pbox, size, rfunc, nullptr, [sfunc](StartThreadCallback start, PullCallback pull) -> Result { return TransferInternal(pbox, size, rfunc, nullptr, [sfunc](StartThreadCallback start, PullCallback pull) -> Result {
R_TRY(start()); R_TRY(start());
return sfunc(pull); return sfunc(pull);
}); }, Mode::MultiThreaded);
} }
Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback2 sfunc) { Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback2 sfunc, Mode mode) {
return TransferInternal(pbox, size, rfunc, nullptr, sfunc); return TransferInternal(pbox, size, rfunc, nullptr, sfunc, Mode::MultiThreaded);
}
Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, s64 size, u32 crc32) {
Result rc;
if (R_FAILED(rc = fs->CreateDirectoryRecursivelyWithPath(path)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder: %s 0x%04X\n", path.s, rc);
R_THROW(rc);
}
if (R_FAILED(rc = fs->CreateFile(path, size, 0)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create file: %s 0x%04X\n", path.s, rc);
R_THROW(rc);
}
fs::File f;
R_TRY(fs->OpenFile(path, FsOpenMode_Write, &f));
// only update the size if this is an existing file.
if (rc == FsError_PathAlreadyExists) {
R_TRY(f.SetSize(size));
}
// NOTES: do not use temp file with rename / delete after as it massively slows
// down small file transfers (RA 21s -> 50s).
u32 crc32_out{};
R_TRY(thread::TransferInternal(pbox, size,
[&](void* data, s64 off, s64 size, u64* bytes_read) -> Result {
const auto result = unzReadCurrentFile(zfile, data, size);
if (result <= 0) {
log_write("failed to read zip file: %s %d\n", path.s, result);
R_THROW(0x1);
}
if (crc32) {
crc32_out = crc32CalculateWithSeed(crc32_out, data, result);
}
*bytes_read = result;
R_SUCCEED();
},
[&](const void* data, s64 off, s64 size) -> Result {
return f.Write(off, data, size, FsWriteOption_None);
},
nullptr, Mode::SingleThreadedIfSmaller, SMALL_BUFFER_SIZE
));
// validate crc32 (if set in the info).
R_UNLESS(!crc32 || crc32 == crc32_out, 0x8);
R_SUCCEED();
}
Result TransferZip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, u32* crc32) {
fs::File f;
R_TRY(fs->OpenFile(path, FsOpenMode_Read, &f));
s64 file_size;
R_TRY(f.GetSize(&file_size));
if (crc32) {
*crc32 = 0;
}
return thread::TransferInternal(pbox, file_size,
[&](void* data, s64 off, s64 size, u64* bytes_read) -> Result {
const auto rc = f.Read(off, data, size, FsReadOption_None, bytes_read);
if (R_SUCCEEDED(rc) && crc32) {
*crc32 = crc32CalculateWithSeed(*crc32, data, *bytes_read);
}
return rc;
},
[&](const void* data, s64 off, s64 size) -> Result {
if (ZIP_OK != zipWriteInFileInZip(zfile, data, size)) {
log_write("failed to write zip file: %s\n", path.s);
R_THROW(0x1);
}
R_SUCCEED();
},
nullptr, Mode::SingleThreadedIfSmaller, SMALL_BUFFER_SIZE
);
}
Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter) {
unz_global_info64 ginfo;
if (UNZ_OK != unzGetGlobalInfo64(zfile, &ginfo)) {
R_THROW(0x1);
}
if (UNZ_OK != unzGoToFirstFile(zfile)) {
R_THROW(0x1);
}
for (s64 i = 0; i < ginfo.number_entry; i++) {
R_TRY(pbox->ShouldExitResult());
if (i > 0) {
if (UNZ_OK != unzGoToNextFile(zfile)) {
log_write("failed to unzGoToNextFile\n");
R_THROW(0x1);
}
}
if (UNZ_OK != unzOpenCurrentFile(zfile)) {
log_write("failed to open current file\n");
R_THROW(0x1);
}
ON_SCOPE_EXIT(unzCloseCurrentFile(zfile));
unz_file_info64 info;
fs::FsPath name;
if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, name, sizeof(name), 0, 0, 0, 0)) {
log_write("failed to get current info\n");
R_THROW(0x1);
}
// check if we should skip this file.
// don't make const as to allow the function to modify the path
// this function is used for the updater to change sphaira.nro to exe path.
auto path = fs::AppendPath(base_path, name);
if (filter && !filter(name, path)) {
continue;
}
pbox->NewTransfer(name);
if (path[std::strlen(path) -1] == '/') {
Result rc;
if (R_FAILED(rc = fs->CreateDirectoryRecursively(path)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder: %s 0x%04X\n", path.s, rc);
R_THROW(rc);
}
} else {
R_TRY(TransferUnzip(pbox, zfile, fs, path, info.uncompressed_size, info.crc));
}
}
R_SUCCEED();
}
Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter) {
zlib_filefunc64_def file_func;
mz::FileFuncStdio(&file_func);
auto zfile = unzOpen2_64(zip_out, &file_func);
R_UNLESS(zfile, 0x1);
ON_SCOPE_EXIT(unzClose(zfile));
return TransferUnzipAll(pbox, zfile, fs, base_path, filter);
} }
} // namespace::thread } // namespace::thread

View File

@@ -6,6 +6,7 @@
namespace sphaira::ui { namespace sphaira::ui {
ErrorBox::ErrorBox(const std::string& message) : m_message{message} { ErrorBox::ErrorBox(const std::string& message) : m_message{message} {
log_write("[ERROR] %s\n", m_message.c_str());
m_pos.w = 770.f; m_pos.w = 770.f;
m_pos.h = 430.f; m_pos.h = 430.f;
@@ -21,6 +22,7 @@ ErrorBox::ErrorBox(const std::string& message) : m_message{message} {
ErrorBox::ErrorBox(Result code, const std::string& message) : ErrorBox{message} { ErrorBox::ErrorBox(Result code, const std::string& message) : ErrorBox{message} {
m_code = code; m_code = code;
log_write("[ERROR] Code: 0x%X Module: %u Description: %u\n", R_VALUE(code), R_MODULE(code), R_DESCRIPTION(code));
} }
auto ErrorBox::Update(Controller* controller, TouchInfo* touch) -> void { auto ErrorBox::Update(Controller* controller, TouchInfo* touch) -> void {

View File

@@ -14,7 +14,10 @@
#include "swkbd.hpp" #include "swkbd.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "hasher.hpp" #include "hasher.hpp"
#include "threaded_file_transfer.hpp"
#include "nro.hpp" #include "nro.hpp"
#include "web.hpp"
#include "minizip_helper.hpp"
#include <minIni.h> #include <minIni.h>
#include <string> #include <string>
@@ -87,6 +90,12 @@ auto BuildBannerUrl(const Entry& e) -> std::string {
return out; return out;
} }
auto BuildManifestUrl(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 { auto BuildZipUrl(const Entry& e) -> std::string {
char out[0x100]; char out[0x100];
std::snprintf(out, sizeof(out), "%s/zips/%s.zip", URL_BASE, e.name.c_str()); std::snprintf(out, sizeof(out), "%s/zips/%s.zip", URL_BASE, e.name.c_str());
@@ -332,6 +341,7 @@ auto UninstallApp(ProgressBox* pbox, const Entry& entry) -> Result {
log_write("failed to delete file: %s\n", safe_buf.s); log_write("failed to delete file: %s\n", safe_buf.s);
} else { } else {
log_write("deleted file: %s\n", safe_buf.s); log_write("deleted file: %s\n", safe_buf.s);
svcSleepThread(1e+5);
// todo: delete empty directories! // todo: delete empty directories!
// fs::delete_directory(safe_buf); // fs::delete_directory(safe_buf);
} }
@@ -363,19 +373,30 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result {
fs::FsNativeSd fs; fs::FsNativeSd fs;
R_TRY(fs.GetFsOpenResult()); R_TRY(fs.GetFsOpenResult());
// check if we can download the entire zip to mem for faster download / extract times.
// current limit is 300MiB, or disabled for applet mode.
const auto file_download = App::IsApplet() || entry.filesize >= 1024 * 1024 * 300;
curl::ApiResult api_result{};
// 1. download the zip // 1. download the zip
if (!pbox->ShouldExit()) { if (!pbox->ShouldExit()) {
pbox->NewTransfer("Downloading "_i18n + entry.title); pbox->NewTransfer("Downloading "_i18n + entry.title);
log_write("starting download\n"); log_write("starting download\n");
const auto url = BuildZipUrl(entry); const auto url = BuildZipUrl(entry);
const auto result = curl::Api().ToFile( curl::Api api{
curl::Url{url}, curl::Url{url},
curl::Path{zip_out},
curl::OnProgress{pbox->OnDownloadProgressCallback()} curl::OnProgress{pbox->OnDownloadProgressCallback()}
); };
R_UNLESS(result.success, 0x1); if (file_download) {
api.SetOption(curl::Path{zip_out});
api_result = curl::ToFile(api);
} else {
api_result = curl::ToMemory(api);
}
R_UNLESS(api_result.success, 0x1);
} }
ON_SCOPE_EXIT(fs.DeleteFile(zip_out)); ON_SCOPE_EXIT(fs.DeleteFile(zip_out));
@@ -386,7 +407,11 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result {
log_write("starting md5 check\n"); log_write("starting md5 check\n");
std::string hash_out; std::string hash_out;
if (file_download) {
R_TRY(hash::Hash(pbox, hash::Type::Md5, &fs, zip_out, hash_out)); R_TRY(hash::Hash(pbox, hash::Type::Md5, &fs, zip_out, hash_out));
} else {
R_TRY(hash::Hash(pbox, hash::Type::Md5, api_result.data, hash_out));
}
if (strncasecmp(hash_out.data(), entry.md5.data(), entry.md5.length())) { if (strncasecmp(hash_out.data(), entry.md5.data(), entry.md5.length())) {
log_write("bad md5: %.*s vs %.*s\n", 32, hash_out.data(), 32, entry.md5.c_str()); log_write("bad md5: %.*s vs %.*s\n", 32, hash_out.data(), 32, entry.md5.c_str());
@@ -394,9 +419,17 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result {
} }
} }
mz::MzSpan mz_span{api_result.data};
zlib_filefunc64_def file_func;
if (!file_download) {
mz::FileFuncSpan(&mz_span, &file_func);
} else {
mz::FileFuncStdio(&file_func);
}
// 3. extract the zip // 3. extract the zip
if (!pbox->ShouldExit()) { if (!pbox->ShouldExit()) {
auto zfile = unzOpen64(zip_out); auto zfile = unzOpen2_64(zip_out, &file_func);
R_UNLESS(zfile, 0x1); R_UNLESS(zfile, 0x1);
ON_SCOPE_EXIT(unzClose(zfile)); ON_SCOPE_EXIT(unzClose(zfile));
@@ -434,43 +467,6 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result {
} }
} }
const auto unzip_to_file = [&](const unz_file_info64& info, const fs::FsPath& inzip, fs::FsPath output) -> Result {
if (output[0] != '/') {
output = fs::AppendPath("/", output);
}
// create directories
fs.CreateDirectoryRecursivelyWithPath(output);
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.s, rc);
R_THROW(rc);
}
fs::File f;
R_TRY(fs.OpenFile(output, FsOpenMode_Write, &f));
R_TRY(f.SetSize(info.uncompressed_size));
u64 offset{};
while (offset < info.uncompressed_size) {
R_TRY(pbox->ShouldExitResult());
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
if (bytes_read <= 0) {
log_write("failed to read zip file: %s\n", inzip.s);
R_THROW(0x1);
}
R_TRY(f.Write(offset, buf.data(), bytes_read, FsWriteOption_None));
pbox->UpdateTransfer(offset, info.uncompressed_size);
offset += bytes_read;
}
R_SUCCEED();
};
const auto unzip_to = [&](const fs::FsPath& inzip, const fs::FsPath& output) -> Result { const auto unzip_to = [&](const fs::FsPath& inzip, const fs::FsPath& output) -> Result {
pbox->NewTransfer(inzip); pbox->NewTransfer(inzip);
@@ -491,48 +487,28 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result {
R_THROW(0x1); R_THROW(0x1);
} }
return unzip_to_file(info, inzip, output); auto path = output;
if (path[0] != '/') {
path = fs::AppendPath("/", path);
}
return thread::TransferUnzip(pbox, zfile, &fs, path, info.uncompressed_size, info.crc);
}; };
const auto unzip_all = [&](std::span<const ManifestEntry> entries) -> Result { // unzip manifest, info and all entries.
unz_global_info64 ginfo; TimeStamp ts;
if (UNZ_OK != unzGetGlobalInfo64(zfile, &ginfo)) { #if 1
R_THROW(0x1); R_TRY(unzip_to("info.json", BuildInfoCachePath(entry)));
} R_TRY(unzip_to("manifest.install", BuildManifestCachePath(entry)));
#endif
if (UNZ_OK != unzGoToFirstFile(zfile)) { R_TRY(thread::TransferUnzipAll(pbox, zfile, &fs, "/", [&](const fs::FsPath& name, fs::FsPath& path) -> bool {
R_THROW(0x1); const auto it = std::ranges::find_if(new_manifest, [&name](auto& e){
}
for (s64 i = 0; i < ginfo.number_entry; i++) {
R_TRY(pbox->ShouldExitResult());
if (i > 0) {
if (UNZ_OK != unzGoToNextFile(zfile)) {
log_write("failed to unzGoToNextFile\n");
R_THROW(0x1);
}
}
if (UNZ_OK != unzOpenCurrentFile(zfile)) {
log_write("failed to open current file\n");
R_THROW(0x1);
}
ON_SCOPE_EXIT(unzCloseCurrentFile(zfile));
unz_file_info64 info;
char name[512];
if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, name, sizeof(name), 0, 0, 0, 0)) {
log_write("failed to get current info\n");
R_THROW(0x1);
}
const auto it = std::ranges::find_if(entries, [&name](auto& e){
return !strcasecmp(name, e.path); return !strcasecmp(name, e.path);
}); });
if (it == entries.end()) [[unlikely]] { if (it == new_manifest.end()) [[unlikely]] {
continue; return false;
} }
pbox->NewTransfer(it->path); pbox->NewTransfer(it->path);
@@ -540,30 +516,17 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result {
switch (it->command) { switch (it->command) {
case 'E': // both are the same? case 'E': // both are the same?
case 'U': case 'U':
break; return true;
case 'G': { // checks if file exists, if not, extract case 'G': // checks if file exists, if not, extract
if (fs.FileExists(fs::AppendPath("/", it->path))) { return !fs.FileExists(fs::AppendPath("/", it->path));
continue;
}
} break;
default: default:
log_write("bad command: %c\n", it->command); log_write("bad command: %c\n", it->command);
continue; return false;
} }
}));
R_TRY(unzip_to_file(info, it->path, it->path));
}
R_SUCCEED();
};
// unzip manifest, info and all entries.
TimeStamp ts;
R_TRY(unzip_to("info.json", BuildInfoCachePath(entry)));
R_TRY(unzip_to("manifest.install", BuildManifestCachePath(entry)));
R_TRY(unzip_all(new_manifest));
log_write("\n\t[APPSTORE] finished extract new, time taken: %.2fs %zums\n\n", ts.GetSecondsD(), ts.GetMs()); log_write("\n\t[APPSTORE] finished extract new, time taken: %.2fs %zums\n\n", ts.GetSecondsD(), ts.GetMs());
// finally finally, remove files no longer in the manifest // finally finally, remove files no longer in the manifest
@@ -583,6 +546,7 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> Result {
log_write("failed to delete: %s\n", safe_buf.s); log_write("failed to delete: %s\n", safe_buf.s);
} else { } else {
log_write("deleted file: %s\n", safe_buf.s); log_write("deleted file: %s\n", safe_buf.s);
svcSleepThread(1e+5);
} }
} }
} }
@@ -621,12 +585,15 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
} }
}}), }}),
std::make_pair(Button::X, Action{"Options"_i18n, [this](){ std::make_pair(Button::X, Action{"Options"_i18n, [this](){
auto sidebar = std::make_shared<Sidebar>("Options"_i18n, Sidebar::Side::RIGHT); auto options = std::make_shared<Sidebar>("Options"_i18n, Sidebar::Side::RIGHT);
sidebar->Add(std::make_shared<SidebarEntryCallback>("More by Author"_i18n, [this](){ ON_SCOPE_EXIT(App::Push(options));
options->Add(std::make_shared<SidebarEntryCallback>("More by Author"_i18n, [this](){
m_menu.SetAuthor(); m_menu.SetAuthor();
SetPop(); SetPop();
}, true)); }, true));
sidebar->Add(std::make_shared<SidebarEntryCallback>("Leave Feedback"_i18n, [this](){
options->Add(std::make_shared<SidebarEntryCallback>("Leave Feedback"_i18n, [this](){
std::string out; std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out)) && !out.empty()) { if (R_SUCCEEDED(swkbd::ShowText(out)) && !out.empty()) {
const auto post = "name=" "switch_user" "&package=" + m_entry.name + "&message=" + out; const auto post = "name=" "switch_user" "&package=" + m_entry.name + "&message=" + out;
@@ -647,10 +614,44 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
}); });
} }
}, true)); }, true));
App::Push(sidebar);
if (App::IsApplication() && !m_entry.url.empty()) {
options->Add(std::make_shared<SidebarEntryCallback>("Visit Website"_i18n, [this](){
WebShow(m_entry.url);
}));
}
}}), }}),
std::make_pair(Button::B, Action{"Back"_i18n, [this](){ std::make_pair(Button::B, Action{"Back"_i18n, [this](){
SetPop(); SetPop();
}}),
std::make_pair(Button::L2, Action{"Files"_i18n, [this](){
m_show_file_list ^= 1;
if (m_show_file_list && !m_manifest_list && m_file_list_state == ImageDownloadState::None) {
m_file_list_state = ImageDownloadState::Progress;
const auto path = BuildManifestCachePath(m_entry);
std::vector<u8> data;
if (R_SUCCEEDED(fs::read_entire_file(path, data))) {
m_file_list_state = ImageDownloadState::Done;
data.push_back('\0');
m_manifest_list = std::make_unique<ScrollableText>((const char*)data.data(), 0, 374, 250, 768, 18);
} else {
curl::Api().ToMemoryAsync(
curl::Url{BuildManifestUrl(m_entry)},
curl::StopToken{this->GetToken()},
curl::OnComplete{[this](auto& result){
if (result.success) {
m_file_list_state = ImageDownloadState::Done;
result.data.push_back('\0');
m_manifest_list = std::make_unique<ScrollableText>((const char*)result.data.data(), 0, 374, 250, 768, 18);
} else {
m_file_list_state = ImageDownloadState::Failed;
}
}}
);
}
}
}}) }})
); );
@@ -699,8 +700,14 @@ EntryMenu::~EntryMenu() {
void EntryMenu::Update(Controller* controller, TouchInfo* touch) { void EntryMenu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch); MenuBase::Update(controller, touch);
if (m_show_file_list) {
if (m_manifest_list) {
m_manifest_list->Update(controller, touch);
}
} else {
m_detail_changelog->Update(controller, touch); m_detail_changelog->Update(controller, touch);
} }
}
void EntryMenu::Draw(NVGcontext* vg, Theme* theme) { void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme); MenuBase::Draw(vg, theme);
@@ -754,12 +761,24 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
y -= block.h + 18; y -= block.h + 18;
} }
if (m_show_file_list) {
if (m_manifest_list) {
m_manifest_list->Draw(vg, theme);
} else if (m_file_list_state == ImageDownloadState::Progress) {
gfx::drawText(vg, 110, 374, 18, theme->GetColour(ThemeEntryID_TEXT), "Loading..."_i18n.c_str());
} else if (m_file_list_state == ImageDownloadState::Failed) {
gfx::drawText(vg, 110, 374, 18, theme->GetColour(ThemeEntryID_TEXT), "Failed to download manifest"_i18n.c_str());
}
} else {
m_detail_changelog->Draw(vg, theme); m_detail_changelog->Draw(vg, theme);
} }
}
void EntryMenu::ShowChangelogAction() { void EntryMenu::ShowChangelogAction() {
std::function<void()> func = std::bind(&EntryMenu::ShowChangelogAction, this); std::function<void()> func = std::bind(&EntryMenu::ShowChangelogAction, this);
m_show_changlog ^= 1; m_show_changlog ^= 1;
m_show_file_list = false;
if (m_show_changlog) { if (m_show_changlog) {
SetAction(Button::L, Action{"Details"_i18n, func}); SetAction(Button::L, Action{"Details"_i18n, func});

View File

@@ -21,6 +21,7 @@
#include "hasher.hpp" #include "hasher.hpp"
#include "location.hpp" #include "location.hpp"
#include "threaded_file_transfer.hpp" #include "threaded_file_transfer.hpp"
#include "minizip_helper.hpp"
#include "yati/yati.hpp" #include "yati/yati.hpp"
#include "yati/source/file.hpp" #include "yati/source/file.hpp"
@@ -435,7 +436,6 @@ FsView::~FsView() {
} }
void FsView::Update(Controller* controller, TouchInfo* touch) { void FsView::Update(Controller* controller, TouchInfo* touch) {
Widget::Update(controller, touch);
m_list->OnUpdate(controller, touch, m_index, m_entries_current.size(), [this](bool touch, auto i) { m_list->OnUpdate(controller, touch, m_index, m_entries_current.size(), [this](bool touch, auto i) {
if (touch && m_index == i) { if (touch && m_index == i) {
FireAction(Button::A); FireAction(Button::A);
@@ -561,7 +561,7 @@ void FsView::SetSide(ViewSide side) {
if (m_menu->IsSplitScreen()) { if (m_menu->IsSplitScreen()) {
if (m_side == ViewSide::Left) { if (m_side == ViewSide::Left) {
this->SetW(pos.w / 2 - pos.x / 2); this->SetW(pos.w / 2 - pos.x / 2);
this->SetX(pos.x / 2); this->SetX(pos.x / 2 + 20.f);
} else if (m_side == ViewSide::Right) { } else if (m_side == ViewSide::Right) {
this->SetW(pos.w / 2 - pos.x / 2); this->SetW(pos.w / 2 - pos.x / 2);
this->SetX(pos.x / 2 + SCREEN_WIDTH / 2); this->SetX(pos.x / 2 + SCREEN_WIDTH / 2);
@@ -571,7 +571,7 @@ void FsView::SetSide(ViewSide side) {
v.w -= v.x / 2; v.w -= v.x / 2;
if (m_side == ViewSide::Left) { if (m_side == ViewSide::Left) {
v.x = v.x / 2; v.x = v.x / 2 + 20.f;
} else if (m_side == ViewSide::Right) { } else if (m_side == ViewSide::Right) {
v.x = v.x / 2 + SCREEN_WIDTH / 2; v.x = v.x / 2 + SCREEN_WIDTH / 2;
} }
@@ -671,7 +671,19 @@ void FsView::InstallForwarder() {
config.icon = GetRomIcon(m_fs.get(), pbox, file_name, db_indexs, nro); config.icon = GetRomIcon(m_fs.get(), pbox, file_name, db_indexs, nro);
pbox->SetImageDataConst(config.icon); pbox->SetImageDataConst(config.icon);
if (!db_indexs.empty()) {
fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/NintendoLogo.png", config.logo);
fs::FsNativeSd().read_entire_file("/config/sphaira/logo/rom/StartupMovie.gif", config.gif);
}
return App::Install(pbox, config); return App::Install(pbox, config);
}, [this](Result rc){
App::PushErrorBox(rc, "Failed to install forwarder"_i18n);
if (R_SUCCEEDED(rc)) {
App::PlaySoundEffect(SoundEffect_Install);
App::Notify("Installed!"_i18n);
}
})); }));
} else { } else {
log_write("pressed B to skip launch...\n"); log_write("pressed B to skip launch...\n");
@@ -694,6 +706,8 @@ void FsView::InstallFiles() {
} }
R_SUCCEED(); R_SUCCEED();
}, [this](Result rc){
App::PushErrorBox(rc, "File install failed!"_i18n);
})); }));
} }
})); }));
@@ -708,75 +722,10 @@ void FsView::UnzipFiles(fs::FsPath dir_path) {
} }
App::Push(std::make_shared<ui::ProgressBox>(0, "Extracting "_i18n, "", [this, dir_path, targets](auto pbox) -> Result { App::Push(std::make_shared<ui::ProgressBox>(0, "Extracting "_i18n, "", [this, dir_path, targets](auto pbox) -> Result {
constexpr auto chunk_size = 1024 * 512; // 512KiB
for (auto& e : targets) { for (auto& e : targets) {
pbox->SetTitle(e.GetName()); pbox->SetTitle(e.GetName());
const auto zip_out = GetNewPath(e); const auto zip_out = GetNewPath(e);
auto zfile = unzOpen64(zip_out); R_TRY(thread::TransferUnzipAll(pbox, zip_out, m_fs.get(), dir_path));
R_UNLESS(zfile, 0x1);
ON_SCOPE_EXIT(unzClose(zfile));
unz_global_info64 pglobal_info;
if (UNZ_OK != unzGetGlobalInfo64(zfile, &pglobal_info)) {
R_THROW(0x1);
}
for (s64 i = 0; i < pglobal_info.number_entry; i++) {
if (i > 0) {
if (UNZ_OK != unzGoToNextFile(zfile)) {
log_write("failed to unzGoToNextFile\n");
R_THROW(0x1);
}
}
if (UNZ_OK != unzOpenCurrentFile(zfile)) {
log_write("failed to open current file\n");
R_THROW(0x1);
}
ON_SCOPE_EXIT(unzCloseCurrentFile(zfile));
unz_file_info64 info;
char name[512];
if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, name, sizeof(name), 0, 0, 0, 0)) {
log_write("failed to get current info\n");
R_THROW(0x1);
}
const auto file_path = fs::AppendPath(dir_path, name);
pbox->NewTransfer(name);
// create directories
m_fs->CreateDirectoryRecursivelyWithPath(file_path);
Result rc;
if (R_FAILED(rc = m_fs->CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create file: %s 0x%04X\n", file_path.s, rc);
R_THROW(rc);
}
fs::File f;
R_TRY(m_fs->OpenFile(file_path, FsOpenMode_Write, &f));
R_TRY(f.SetSize(info.uncompressed_size));
std::vector<char> buf(chunk_size);
s64 offset{};
while (offset < info.uncompressed_size) {
R_TRY(pbox->ShouldExitResult());
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
if (bytes_read <= 0) {
log_write("failed to read zip file: %s\n", name);
R_THROW(0x1);
}
R_TRY(f.Write(offset, buf.data(), bytes_read, FsWriteOption_None));
pbox->UpdateTransfer(offset, info.uncompressed_size);
offset += bytes_read;
}
}
} }
R_SUCCEED(); R_SUCCEED();
@@ -784,7 +733,7 @@ void FsView::UnzipFiles(fs::FsPath dir_path) {
App::PushErrorBox(rc, "Extract failed!"_i18n); App::PushErrorBox(rc, "Extract failed!"_i18n);
if (R_SUCCEEDED(rc)) { if (R_SUCCEEDED(rc)) {
App::Notify("Extract success!"); App::Notify("Extract success!"_i18n);
} }
Scan(m_path); Scan(m_path);
@@ -829,8 +778,6 @@ void FsView::ZipFiles(fs::FsPath zip_out) {
} }
App::Push(std::make_shared<ui::ProgressBox>(0, "Compressing "_i18n, "", [this, zip_out, targets](auto pbox) -> Result { App::Push(std::make_shared<ui::ProgressBox>(0, "Compressing "_i18n, "", [this, zip_out, targets](auto pbox) -> Result {
constexpr auto chunk_size = 1024 * 512; // 512KiB
const auto t = std::time(NULL); const auto t = std::time(NULL);
const auto tm = std::localtime(&t); const auto tm = std::localtime(&t);
@@ -843,7 +790,10 @@ void FsView::ZipFiles(fs::FsPath zip_out) {
zip_info.tmz_date.tm_mon = tm->tm_mon; zip_info.tmz_date.tm_mon = tm->tm_mon;
zip_info.tmz_date.tm_year = tm->tm_year; zip_info.tmz_date.tm_year = tm->tm_year;
auto zfile = zipOpen(zip_out, APPEND_STATUS_CREATE); zlib_filefunc64_def file_func;
mz::FileFuncStdio(&file_func);
auto zfile = zipOpen2_64(zip_out, APPEND_STATUS_CREATE, nullptr, &file_func);
R_UNLESS(zfile, 0x1); R_UNLESS(zfile, 0x1);
ON_SCOPE_EXIT(zipClose(zfile, "sphaira v" APP_VERSION_HASH)); ON_SCOPE_EXIT(zipClose(zfile, "sphaira v" APP_VERSION_HASH));
@@ -851,6 +801,11 @@ void FsView::ZipFiles(fs::FsPath zip_out) {
// the file name needs to be relative to the current directory. // the file name needs to be relative to the current directory.
const char* file_name_in_zip = file_path.s + std::strlen(m_path); const char* file_name_in_zip = file_path.s + std::strlen(m_path);
// strip root path (/ or ums0:)
if (!std::strncmp(file_name_in_zip, m_fs->Root(), std::strlen(m_fs->Root()))) {
file_name_in_zip += std::strlen(m_fs->Root());
}
// root paths are banned in zips, they will warn when extracting otherwise. // root paths are banned in zips, they will warn when extracting otherwise.
if (file_name_in_zip[0] == '/') { if (file_name_in_zip[0] == '/') {
file_name_in_zip++; file_name_in_zip++;
@@ -858,38 +813,13 @@ void FsView::ZipFiles(fs::FsPath zip_out) {
pbox->NewTransfer(file_name_in_zip); pbox->NewTransfer(file_name_in_zip);
const auto ext = std::strrchr(file_name_in_zip, '.'); if (ZIP_OK != zipOpenNewFileInZip(zfile, file_name_in_zip, &zip_info, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION)) {
const auto raw = ext && IsExtension(ext + 1, COMPRESSED_EXTENSIONS); log_write("failed to add zip for %s\n", file_path.s);
if (ZIP_OK != zipOpenNewFileInZip2(zfile, file_name_in_zip, &zip_info, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION, raw)) {
R_THROW(0x1); R_THROW(0x1);
} }
ON_SCOPE_EXIT(zipCloseFileInZip(zfile)); ON_SCOPE_EXIT(zipCloseFileInZip(zfile));
fs::File f; return thread::TransferZip(pbox, zfile, m_fs.get(), file_path);
R_TRY(m_fs->OpenFile(file_path, FsOpenMode_Read, &f));
s64 file_size;
R_TRY(f.GetSize(&file_size));
std::vector<char> buf(chunk_size);
s64 offset{};
while (offset < file_size) {
R_TRY(pbox->ShouldExitResult());
u64 bytes_read;
R_TRY(f.Read(offset, buf.data(), buf.size(), FsReadOption_None, &bytes_read));
if (ZIP_OK != zipWriteInFileInZip(zfile, buf.data(), bytes_read)) {
log_write("failed to write zip file: %s\n", file_path.s);
R_THROW(0x1);
}
pbox->UpdateTransfer(offset, file_size);
offset += bytes_read;
}
R_SUCCEED();
}; };
for (auto& e : targets) { for (auto& e : targets) {
@@ -915,7 +845,7 @@ void FsView::ZipFiles(fs::FsPath zip_out) {
App::PushErrorBox(rc, "Compress failed!"_i18n); App::PushErrorBox(rc, "Compress failed!"_i18n);
if (R_SUCCEEDED(rc)) { if (R_SUCCEEDED(rc)) {
App::Notify("Compress success!"); App::Notify("Compress success!"_i18n);
} }
Scan(m_path); Scan(m_path);
@@ -1498,9 +1428,11 @@ static Result DeleteAllCollections(ProgressBox* pbox, fs::Fs* fs, const Selected
if ((mode & FsDirOpenMode_ReadDirs) && p.type == FsDirEntryType_Dir) { if ((mode & FsDirOpenMode_ReadDirs) && p.type == FsDirEntryType_Dir) {
log_write("deleting dir: %s\n", full_path.s); log_write("deleting dir: %s\n", full_path.s);
R_TRY(fs->DeleteDirectory(full_path)); R_TRY(fs->DeleteDirectory(full_path));
svcSleepThread(1e+5);
} else if ((mode & FsDirOpenMode_ReadFiles) && p.type == FsDirEntryType_File) { } else if ((mode & FsDirOpenMode_ReadFiles) && p.type == FsDirEntryType_File) {
log_write("deleting file: %s\n", full_path.s); log_write("deleting file: %s\n", full_path.s);
R_TRY(fs->DeleteFile(full_path)); R_TRY(fs->DeleteFile(full_path));
svcSleepThread(1e+5);
} }
} }
@@ -1925,6 +1857,13 @@ Menu::~Menu() {
} }
void Menu::Update(Controller* controller, TouchInfo* touch) { void Menu::Update(Controller* controller, TouchInfo* touch) {
// workaround the buttons not being display properly.
// basically, inherit all actions from the view, draw them,
// then restore state after.
const auto actions_copy = GetActions();
ON_SCOPE_EXIT(m_actions = actions_copy);
m_actions.insert_range(view->GetActions());
MenuBase::Update(controller, touch); MenuBase::Update(controller, touch);
view->Update(controller, touch); view->Update(controller, touch);
} }
@@ -1944,9 +1883,9 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
view_right->Draw(vg, theme); view_right->Draw(vg, theme);
if (view == view_left) { if (view == view_left) {
gfx::drawRect(vg, view_right->GetPos(), nvgRGBA(0, 0, 0, 180), 5); gfx::drawRect(vg, view_right->GetPos(), theme->GetColour(ThemeEntryID_FOCUS), 5);
} else { } else {
gfx::drawRect(vg, view_left->GetPos(), nvgRGBA(0, 0, 0, 180), 5); gfx::drawRect(vg, view_left->GetPos(), theme->GetColour(ThemeEntryID_FOCUS), 5);
} }
gfx::drawRect(vg, SCREEN_WIDTH/2, GetY(), 1, GetH(), theme->GetColour(ThemeEntryID_LINE)); gfx::drawRect(vg, SCREEN_WIDTH/2, GetY(), 1, GetH(), theme->GetColour(ThemeEntryID_LINE));

View File

@@ -4,6 +4,7 @@
#include "dumper.hpp" #include "dumper.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "image.hpp"
#include "ui/menus/game_menu.hpp" #include "ui/menus/game_menu.hpp"
#include "ui/sidebar.hpp" #include "ui/sidebar.hpp"
@@ -23,6 +24,7 @@
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
#include <minIni.h> #include <minIni.h>
#include <nxtc.h>
namespace sphaira::ui::menu::game { namespace sphaira::ui::menu::game {
namespace { namespace {
@@ -30,6 +32,34 @@ namespace {
constexpr int THREAD_PRIO = PRIO_PREEMPTIVE; constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
constexpr int THREAD_CORE = 1; constexpr int THREAD_CORE = 1;
// taken from nxtc
constexpr u8 g_nacpLangTable[SetLanguage_Total] = {
[SetLanguage_JA] = 2,
[SetLanguage_ENUS] = 0,
[SetLanguage_FR] = 3,
[SetLanguage_DE] = 4,
[SetLanguage_IT] = 7,
[SetLanguage_ES] = 6,
[SetLanguage_ZHCN] = 14,
[SetLanguage_KO] = 12,
[SetLanguage_NL] = 8,
[SetLanguage_PT] = 10,
[SetLanguage_RU] = 11,
[SetLanguage_ZHTW] = 13,
[SetLanguage_ENGB] = 1,
[SetLanguage_FRCA] = 9,
[SetLanguage_ES419] = 5,
[SetLanguage_ZHHANS] = 14,
[SetLanguage_ZHHANT] = 13,
[SetLanguage_PTBR] = 15
};
auto GetNacpLangEntryIndex() -> u8 {
SetLanguage lang{SetLanguage_ENUS};
nxtcGetCacheLanguage(&lang);
return g_nacpLangTable[lang];
}
constexpr u32 ContentMetaTypeToContentFlag(u8 meta_type) { constexpr u32 ContentMetaTypeToContentFlag(u8 meta_type) {
if (meta_type & 0x80) { if (meta_type & 0x80) {
return 1 << (meta_type - 0x80); return 1 << (meta_type - 0x80);
@@ -284,31 +314,27 @@ void FakeNacpEntry(ThreadResultData& e) {
// fake the nacp entry // fake the nacp entry
std::strcpy(e.lang.name, "Corrupted"); std::strcpy(e.lang.name, "Corrupted");
std::strcpy(e.lang.author, "Corrupted"); std::strcpy(e.lang.author, "Corrupted");
std::strcpy(e.display_version, "0.0.0");
e.control.reset(); e.control.reset();
} }
bool LoadControlImage(Entry& e) { bool LoadControlImage(Entry& e) {
if (!e.image && e.control) { if (!e.image && e.control) {
ON_SCOPE_EXIT(e.control.reset());
TimeStamp ts; TimeStamp ts;
const auto jpeg_size = e.control_size - sizeof(NacpStruct); const auto image = ImageLoadFromMemory({e.control->icon, e.jpeg_size}, ImageFlag_JPEG);
e.image = nvgCreateImageMem(App::GetVg(), 0, e.control->icon, jpeg_size); if (!image.data.empty()) {
e.control.reset(); e.image = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
log_write("\t\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
return true; return true;
} }
}
return false; return false;
} }
Result LoadControlManual(u64 id, ThreadResultData& data) { Result GetControlPathFromStatus(const NsApplicationContentMetaStatus& status, u64* out_program_id, fs::FsPath* out_path) {
TimeStamp ts; const auto& ee = status;
MetaEntries entries;
R_TRY(GetMetaEntries(id, entries));
R_UNLESS(!entries.empty(), 0x1);
const auto& ee = entries.back();
if (ee.storageID != NcmStorageId_SdCard && ee.storageID != NcmStorageId_BuiltInUser && ee.storageID != NcmStorageId_GameCard) { if (ee.storageID != NcmStorageId_SdCard && ee.storageID != NcmStorageId_BuiltInUser && ee.storageID != NcmStorageId_GameCard) {
return 0x1; return 0x1;
} }
@@ -322,17 +348,28 @@ Result LoadControlManual(u64 id, ThreadResultData& data) {
NcmContentId content_id; NcmContentId content_id;
R_TRY(ncmContentMetaDatabaseGetContentIdByType(&db, &content_id, &key, NcmContentType_Control)); R_TRY(ncmContentMetaDatabaseGetContentIdByType(&db, &content_id, &key, NcmContentType_Control));
u64 program_id; R_TRY(ncmContentStorageGetProgramId(&cs, out_program_id, &content_id, FsContentAttributes_All));
R_TRY(ncmContentStorageGetProgramId(&cs, &program_id, &content_id, FsContentAttributes_All));
R_TRY(ncmContentStorageGetPath(&cs, out_path->s, sizeof(*out_path), &content_id));
R_SUCCEED();
}
Result LoadControlManual(u64 id, ThreadResultData& data) {
TimeStamp ts;
MetaEntries entries;
R_TRY(GetMetaEntries(id, entries));
R_UNLESS(!entries.empty(), 0x1);
u64 program_id;
fs::FsPath path; fs::FsPath path;
R_TRY(ncmContentStorageGetPath(&cs, path, sizeof(path), &content_id)); R_TRY(GetControlPathFromStatus(entries.back(), &program_id, &path));
std::vector<u8> icon; std::vector<u8> icon;
R_TRY(nca::ParseControl(path, program_id, &data.control->nacp, sizeof(data.control->nacp), &icon)); R_TRY(nca::ParseControl(path, program_id, &data.control->nacp.lang[GetNacpLangEntryIndex()], sizeof(NacpLanguageEntry), &icon));
std::memcpy(data.control->icon, icon.data(), icon.size()); std::memcpy(data.control->icon, icon.data(), icon.size());
data.control_size = sizeof(data.control->nacp) + icon.size(); data.jpeg_size = icon.size();
log_write("\t\t[manual control] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); log_write("\t\t[manual control] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
R_SUCCEED(); R_SUCCEED();
@@ -347,8 +384,10 @@ auto LoadControlEntry(u64 id) -> ThreadResultData {
bool manual_load = true; bool manual_load = true;
if (hosversionBefore(20,0,0)) { if (hosversionBefore(20,0,0)) {
TimeStamp ts; TimeStamp ts;
if (R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_CacheOnly, id, data.control.get(), sizeof(NsApplicationControlData), &data.control_size))) { u64 actual_size;
if (R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_CacheOnly, id, data.control.get(), sizeof(NsApplicationControlData), &actual_size))) {
manual_load = false; manual_load = false;
data.jpeg_size = actual_size - sizeof(NacpStruct);
log_write("\t\t[ns control cache] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); log_write("\t\t[ns control cache] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
} }
} }
@@ -360,18 +399,17 @@ auto LoadControlEntry(u64 id) -> ThreadResultData {
Result rc{}; Result rc{};
if (!manual_load) { if (!manual_load) {
TimeStamp ts; TimeStamp ts;
rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, id, data.control.get(), sizeof(NsApplicationControlData), &data.control_size); u64 actual_size;
if (R_SUCCEEDED(rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, id, data.control.get(), sizeof(NsApplicationControlData), &actual_size))) {
data.jpeg_size = actual_size - sizeof(NacpStruct);
log_write("\t\t[ns control storage] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs()); log_write("\t\t[ns control storage] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
} }
}
if (R_SUCCEEDED(rc)) { if (R_SUCCEEDED(rc)) {
NacpLanguageEntry* lang{}; data.lang = data.control->nacp.lang[GetNacpLangEntryIndex()];
if (R_SUCCEEDED(rc = nsGetApplicationDesiredLanguage(&data.control->nacp, &lang)) && lang) {
data.lang = *lang;
std::memcpy(data.display_version, data.control->nacp.display_version, sizeof(data.display_version));
data.status = NacpLoadStatus::Loaded; data.status = NacpLoadStatus::Loaded;
} }
}
if (R_FAILED(rc)) { if (R_FAILED(rc)) {
FakeNacpEntry(data); FakeNacpEntry(data);
@@ -383,8 +421,7 @@ auto LoadControlEntry(u64 id) -> ThreadResultData {
void LoadResultIntoEntry(Entry& e, const ThreadResultData& result) { void LoadResultIntoEntry(Entry& e, const ThreadResultData& result) {
e.status = result.status; e.status = result.status;
e.control = result.control; e.control = result.control;
e.control_size = result.control_size; e.jpeg_size= result.jpeg_size;
std::memcpy(e.display_version, result.display_version, sizeof(result.display_version));
e.lang = result.lang; e.lang = result.lang;
e.status = result.status; e.status = result.status;
} }
@@ -462,8 +499,16 @@ auto BuildNspPath(const Entry& e, const NsApplicationContentMetaStatus& status)
utilsReplaceIllegalCharacters(name_buf, true); utilsReplaceIllegalCharacters(name_buf, true);
char version[sizeof(NacpStruct::display_version) + 1]{}; char version[sizeof(NacpStruct::display_version) + 1]{};
// status.storageID
if (status.meta_type == NcmContentMetaType_Patch) { if (status.meta_type == NcmContentMetaType_Patch) {
std::snprintf(version, sizeof(version), "%s ", e.GetDisplayVersion()); u64 program_id;
fs::FsPath path;
if (R_SUCCEEDED(GetControlPathFromStatus(status, &program_id, &path))) {
char display_version[0x10];
if (R_SUCCEEDED(nca::ParseControl(path, program_id, display_version, sizeof(display_version), nullptr, offsetof(NacpStruct, display_version)))) {
std::snprintf(version, sizeof(version), "%s ", display_version);
}
}
} }
fs::FsPath path; fs::FsPath path;
@@ -619,6 +664,11 @@ void LaunchEntry(const Entry& e) {
void ThreadFunc(void* user) { void ThreadFunc(void* user) {
auto data = static_cast<ThreadData*>(user); auto data = static_cast<ThreadData*>(user);
if (data->IsTitleCacheEnabled() && !nxtcInitialize()) {
log_write("[NXTC] failed to init cache\n");
}
ON_SCOPE_EXIT(nxtcExit());
while (data->IsRunning()) { while (data->IsRunning()) {
data->Run(); data->Run();
} }
@@ -626,21 +676,23 @@ void ThreadFunc(void* user) {
} // namespace } // namespace
ThreadData::ThreadData() { ThreadData::ThreadData(bool title_cache) : m_title_cache{title_cache} {
ueventCreate(&m_uevent, true); ueventCreate(&m_uevent, true);
mutexInit(&m_mutex_id); mutexInit(&m_mutex_id);
mutexInit(&m_mutex_result); mutexInit(&m_mutex_result);
m_running = true; m_running = true;
} }
auto ThreadData::IsRunning() const -> bool {
return m_running;
}
void ThreadData::Run() { void ThreadData::Run() {
while (IsRunning()) {
const auto waiter = waiterForUEvent(&m_uevent); const auto waiter = waiterForUEvent(&m_uevent);
waitSingle(waiter, UINT64_MAX); while (IsRunning()) {
const auto rc = waitSingle(waiter, 3e+9);
// if we timed out, flush the cache and poll again.
if (R_FAILED(rc)) {
nxtcFlushCacheFile();
continue;
}
if (!IsRunning()) { if (!IsRunning()) {
return; return;
@@ -658,10 +710,28 @@ void ThreadData::Run() {
return; return;
} }
ThreadResultData result{ids[i]};
TimeStamp ts;
if (auto data = nxtcGetApplicationMetadataEntryById(ids[i])) {
log_write("[NXTC] loaded from cache time taken: %.2fs %zums %zuns\n", ts.GetSecondsD(), ts.GetMs(), ts.GetNs());
ON_SCOPE_EXIT(nxtcFreeApplicationMetadata(&data));
result.control = std::make_unique<NsApplicationControlData>();
result.status = NacpLoadStatus::Loaded;
std::strcpy(result.lang.name, data->name);
std::strcpy(result.lang.author, data->publisher);
std::memcpy(result.control->icon, data->icon_data, data->icon_size);
result.jpeg_size = data->icon_size;
} else {
// sleep after every other entry loaded. // sleep after every other entry loaded.
svcSleepThread(2e+6); // 2ms svcSleepThread(2e+6); // 2ms
const auto result = LoadControlEntry(ids[i]); result = LoadControlEntry(ids[i]);
if (result.status == NacpLoadStatus::Loaded) {
nxtcAddEntry(ids[i], &result.control->nacp, result.jpeg_size, result.control->icon, true);
}
}
mutexLock(&m_mutex_result); mutexLock(&m_mutex_result);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex_result)); ON_SCOPE_EXIT(mutexUnlock(&m_mutex_result));
m_result.emplace_back(result); m_result.emplace_back(result);
@@ -871,6 +941,10 @@ Menu::Menu(u32 flags) : grid::Menu{"Games"_i18n, flags} {
)); ));
}, true)); }, true));
} }
options->Add(std::make_shared<SidebarEntryBool>("Title cache"_i18n, m_title_cache.Get(), [this](bool& v_out){
m_title_cache.Set(v_out);
}));
}}) }})
); );
@@ -883,13 +957,14 @@ Menu::Menu(u32 flags) : grid::Menu{"Games"_i18n, flags} {
e.Open(); e.Open();
} }
threadCreate(&m_thread, ThreadFunc, &m_thread_data, nullptr, 1024*32, THREAD_PRIO, THREAD_CORE); m_thread_data = std::make_unique<ThreadData>(m_title_cache.Get());
threadCreate(&m_thread, ThreadFunc, m_thread_data.get(), nullptr, 1024*32, THREAD_PRIO, THREAD_CORE);
svcSetThreadCoreMask(m_thread.handle, THREAD_CORE, THREAD_AFFINITY_DEFAULT(THREAD_CORE)); svcSetThreadCoreMask(m_thread.handle, THREAD_CORE, THREAD_AFFINITY_DEFAULT(THREAD_CORE));
threadStart(&m_thread); threadStart(&m_thread);
} }
Menu::~Menu() { Menu::~Menu() {
m_thread_data.Close(); m_thread_data->Close();
for (auto& e : ncm_entries) { for (auto& e : ncm_entries) {
e.Close(); e.Close();
@@ -923,12 +998,17 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
void Menu::Draw(NVGcontext* vg, Theme* theme) { void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme); MenuBase::Draw(vg, theme);
if (m_entries.empty()) {
gfx::drawTextArgs(vg, GetX() + GetW() / 2.f, GetY() + GetH() / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Empty..."_i18n.c_str());
return;
}
// max images per frame, in order to not hit io / gpu too hard. // max images per frame, in order to not hit io / gpu too hard.
const int image_load_max = 2; const int image_load_max = 2;
int image_load_count = 0; int image_load_count = 0;
std::vector<ThreadResultData> data; std::vector<ThreadResultData> data;
m_thread_data.Pop(data); m_thread_data->Pop(data);
for (const auto& d : data) { for (const auto& d : data) {
const auto it = std::ranges::find_if(m_entries, [&d](auto& e) { const auto it = std::ranges::find_if(m_entries, [&d](auto& e) {
@@ -945,7 +1025,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
auto& e = m_entries[pos]; auto& e = m_entries[pos];
if (e.status == NacpLoadStatus::None) { if (e.status == NacpLoadStatus::None) {
m_thread_data.Push(e.app_id); m_thread_data->Push(e.app_id);
e.status = NacpLoadStatus::Progress; e.status = NacpLoadStatus::Progress;
} }
@@ -956,11 +1036,14 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} }
} }
char title_id[33];
std::snprintf(title_id, sizeof(title_id), "%016lX", e.app_id);
const auto selected = pos == m_index; const auto selected = pos == m_index;
DrawEntry(vg, theme, m_layout.Get(), v, selected, e.image, e.GetName(), e.GetAuthor(), e.GetDisplayVersion()); DrawEntry(vg, theme, m_layout.Get(), v, selected, e.image, e.GetName(), e.GetAuthor(), title_id);
if (e.selected) { if (e.selected) {
gfx::drawRect(vg, v, nvgRGBA(0, 0, 0, 180), 5); gfx::drawRect(vg, v, theme->GetColour(ThemeEntryID_FOCUS), 5);
gfx::drawText(vg, x + w / 2, y + h / 2, 24.f, "\uE14B", nullptr, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_SELECTED)); gfx::drawText(vg, x + w / 2, y + h / 2, 24.f, "\uE14B", nullptr, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_SELECTED));
} }
}); });
@@ -1018,7 +1101,7 @@ void Menu::ScanHomebrew() {
u64 unk_x11;// = e.unk_x11; u64 unk_x11;// = e.unk_x11;
memcpy(&unk_x0a, e.unk_x0a, sizeof(e.unk_x0a)); memcpy(&unk_x0a, e.unk_x0a, sizeof(e.unk_x0a));
memcpy(&unk_x11, e.unk_x11, sizeof(e.unk_x11)); memcpy(&unk_x11, e.unk_x11, sizeof(e.unk_x11));
log_write("ID: %016lx got type: %u unk_x09: %u unk_x0a: %zu unk_x10: %u unk_x11: %zu\n", e.application_id, e.type, log_write("ID: %016lx got type: %u unk_x09: %u unk_x0a: %zu unk_x10: %u unk_x11: %zu\n", e.app_id, e.type,
unk_x09, unk_x09,
unk_x0a, unk_x0a,
unk_x10, unk_x10,

View File

@@ -13,6 +13,7 @@
#include "i18n.hpp" #include "i18n.hpp"
#include "download.hpp" #include "download.hpp"
#include "dumper.hpp" #include "dumper.hpp"
#include "image.hpp"
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
@@ -528,7 +529,7 @@ Result Menu::GcMount() {
// the fs, same as mounting storage. // the fs, same as mounting storage.
for (u32 i = 0; i < REMOUNT_ATTEMPT_MAX; i++) { for (u32 i = 0; i < REMOUNT_ATTEMPT_MAX; i++) {
R_TRY(fsDeviceOperatorGetGameCardHandle(std::addressof(m_dev_op), std::addressof(m_handle))); R_TRY(fsDeviceOperatorGetGameCardHandle(std::addressof(m_dev_op), std::addressof(m_handle)));
m_fs = std::make_unique<fs::FsNativeGameCard>(std::addressof(m_handle), FsGameCardPartition_Secure, false); m_fs = std::make_unique<fs::FsNativeGameCard>(std::addressof(m_handle), FsGameCardPartition_Secure);
if (R_SUCCEEDED(m_fs->GetFsOpenResult())) { if (R_SUCCEEDED(m_fs->GetFsOpenResult())) {
break; break;
} }
@@ -664,7 +665,7 @@ Result Menu::GcMount() {
auto source = std::make_shared<GcSource>(m_entries[m_entry_index], m_fs.get()); auto source = std::make_shared<GcSource>(m_entries[m_entry_index], m_fs.get());
return yati::InstallFromCollections(pbox, source, source->m_collections, source->m_config); return yati::InstallFromCollections(pbox, source, source->m_collections, source->m_config);
}, [this](Result rc){ }, [this](Result rc){
App::PushErrorBox(rc, "Gc install failed"_i18n); App::PushErrorBox(rc, "Gc install failed!"_i18n);
if (R_SUCCEEDED(rc)) { if (R_SUCCEEDED(rc)) {
App::Notify("Gc install success!"_i18n); App::Notify("Gc install success!"_i18n);
@@ -965,7 +966,13 @@ void Menu::OnChangeIndex(s64 new_index) {
const auto& e = m_entries[m_entry_index]; const auto& e = m_entries[m_entry_index];
const auto jpeg_size = e.control_size - sizeof(NacpStruct); const auto jpeg_size = e.control_size - sizeof(NacpStruct);
m_icon = nvgCreateImageMem(App::GetVg(), 0, e.control->icon, jpeg_size);
TimeStamp ts;
const auto image = ImageLoadFromMemory({e.control->icon, jpeg_size}, ImageFlag_JPEG);
if (!image.data.empty()) {
m_icon = nvgCreateImageRGBA(App::GetVg(), image.w, image.h, 0, image.data.data());
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
}
} }
} }

View File

@@ -14,9 +14,9 @@
#include "download.hpp" #include "download.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "yyjson_helper.hpp" #include "yyjson_helper.hpp"
#include "threaded_file_transfer.hpp"
#include <minIni.h> #include <minIni.h>
#include <minizip/unzip.h>
#include <dirent.h> #include <dirent.h>
#include <cstring> #include <cstring>
#include <string> #include <string>
@@ -83,7 +83,6 @@ void from_json(const fs::FsPath& path, GhApiEntry& e) {
auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry* entry) -> Result { auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry* entry) -> Result {
static const fs::FsPath temp_file{"/switch/sphaira/cache/github/ghdl.temp"}; static const fs::FsPath temp_file{"/switch/sphaira/cache/github/ghdl.temp"};
constexpr auto chunk_size = 1024 * 512; // 512KiB
fs::FsNativeSd fs; fs::FsNativeSd fs;
R_TRY(fs.GetFsOpenResult()); R_TRY(fs.GetFsOpenResult());
@@ -113,77 +112,7 @@ auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry
// 3. extract the zip / file // 3. extract the zip / file
if (gh_asset.content_type.find("zip") != gh_asset.content_type.npos) { if (gh_asset.content_type.find("zip") != gh_asset.content_type.npos) {
log_write("found zip\n"); log_write("found zip\n");
auto zfile = unzOpen64(temp_file); R_TRY(thread::TransferUnzipAll(pbox, temp_file, &fs, root_path));
R_UNLESS(zfile, 0x1);
ON_SCOPE_EXIT(unzClose(zfile));
unz_global_info64 pglobal_info;
if (UNZ_OK != unzGetGlobalInfo64(zfile, &pglobal_info)) {
R_THROW(0x1);
}
for (int i = 0; i < pglobal_info.number_entry; i++) {
if (i > 0) {
if (UNZ_OK != unzGoToNextFile(zfile)) {
log_write("failed to unzGoToNextFile\n");
R_THROW(0x1);
}
}
if (UNZ_OK != unzOpenCurrentFile(zfile)) {
log_write("failed to open current file\n");
R_THROW(0x1);
}
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");
R_THROW(0x1);
}
file_path = fs::AppendPath(root_path, file_path);
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.s, rc);
R_THROW(rc);
}
} else {
if (R_FAILED(rc = fs.CreateDirectoryRecursivelyWithPath(file_path)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder: %s 0x%04X\n", file_path.s, rc);
R_THROW(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.s, rc);
R_THROW(rc);
}
fs::File f;
R_TRY(fs.OpenFile(file_path, FsOpenMode_Write, &f));
R_TRY(f.SetSize(info.uncompressed_size));
std::vector<char> buf(chunk_size);
s64 offset{};
while (offset < info.uncompressed_size) {
R_TRY(pbox->ShouldExitResult());
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
if (bytes_read <= 0) {
log_write("failed to read zip file: %s\n", file_path.s);
R_THROW(0x1);
}
R_TRY(f.Write(offset, buf.data(), bytes_read, FsWriteOption_None));
pbox->UpdateTransfer(offset, info.uncompressed_size);
offset += bytes_read;
}
}
}
} else { } else {
fs.CreateDirectoryRecursivelyWithPath(root_path); fs.CreateDirectoryRecursivelyWithPath(root_path);
fs.DeleteFile(root_path); fs.DeleteFile(root_path);

View File

@@ -10,6 +10,7 @@
#include "owo.hpp" #include "owo.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "image.hpp"
#include <minIni.h> #include <minIni.h>
#include <utility> #include <utility>
@@ -175,9 +176,17 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
// really, switch-tools should handle this by resizing the image before // really, switch-tools should handle this by resizing the image before
// adding it to the nro, as well as validate its a valid jpeg. // adding it to the nro, as well as validate its a valid jpeg.
const auto icon = nro_get_icon(e.path, e.icon_size, e.icon_offset); const auto icon = nro_get_icon(e.path, e.icon_size, e.icon_offset);
TimeStamp ts;
if (!icon.empty()) { if (!icon.empty()) {
e.image = nvgCreateImageMem(vg, 0, icon.data(), icon.size()); const auto image = ImageLoadFromMemory(icon, ImageFlag_None);
if (!image.data.empty()) {
e.image = nvgCreateImageRGBA(vg, image.w, image.h, 0, image.data.data());
log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
image_load_count++; image_load_count++;
} else {
// prevent loading of this icon again as it's already failed.
e.icon_offset = e.icon_size = 0;
}
} }
} }
} }

View File

@@ -118,12 +118,12 @@ Menu::Menu(u32 flags) : MenuBase{"Irs"_i18n, flags} {
is_negative_image_used_str.emplace_back("Negative image"_i18n); is_negative_image_used_str.emplace_back("Negative image"_i18n);
SidebarEntryArray::Items format_str; SidebarEntryArray::Items format_str;
format_str.emplace_back("320x240"_i18n); format_str.emplace_back("320\u00D7240");
format_str.emplace_back("160x120"_i18n); format_str.emplace_back("160\u00D7120");
format_str.emplace_back("80x60"_i18n); format_str.emplace_back("80\u00D760");
if (hosversionAtLeast(4,0,0)) { if (hosversionAtLeast(4,0,0)) {
format_str.emplace_back("40x30"_i18n); format_str.emplace_back("40\u00D730");
format_str.emplace_back("20x15"_i18n); format_str.emplace_back("20\u00D715");
} }
options->Add(std::make_shared<SidebarEntryArray>("Controller"_i18n, controller_str, [this](s64& index){ options->Add(std::make_shared<SidebarEntryArray>("Controller"_i18n, controller_str, [this](s64& index){

View File

@@ -15,6 +15,7 @@
#include "ui/menus/ftp_menu.hpp" #include "ui/menus/ftp_menu.hpp"
#include "ui/menus/gc_menu.hpp" #include "ui/menus/gc_menu.hpp"
#include "ui/menus/game_menu.hpp" #include "ui/menus/game_menu.hpp"
#include "ui/menus/save_menu.hpp"
#include "ui/menus/appstore.hpp" #include "ui/menus/appstore.hpp"
#include "app.hpp" #include "app.hpp"
@@ -22,9 +23,9 @@
#include "download.hpp" #include "download.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "threaded_file_transfer.hpp"
#include <cstring> #include <cstring>
#include <minizip/unzip.h>
#include <yyjson.h> #include <yyjson.h>
namespace sphaira::ui::menu::main { namespace sphaira::ui::menu::main {
@@ -49,6 +50,7 @@ const MiscMenuEntry MISC_MENU_ENTRIES[] = {
{ .name = "Appstore", .title = "Appstore", .func = MiscMenuFuncGenerator<ui::menu::appstore::Menu>, .flag = MiscMenuFlag_Shortcut }, { .name = "Appstore", .title = "Appstore", .func = MiscMenuFuncGenerator<ui::menu::appstore::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "Games", .title = "Games", .func = MiscMenuFuncGenerator<ui::menu::game::Menu>, .flag = MiscMenuFlag_Shortcut }, { .name = "Games", .title = "Games", .func = MiscMenuFuncGenerator<ui::menu::game::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "FileBrowser", .title = "FileBrowser", .func = MiscMenuFuncGenerator<ui::menu::filebrowser::Menu>, .flag = MiscMenuFlag_Shortcut }, { .name = "FileBrowser", .title = "FileBrowser", .func = MiscMenuFuncGenerator<ui::menu::filebrowser::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "Saves", .title = "Saves", .func = MiscMenuFuncGenerator<ui::menu::save::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "Themezer", .title = "Themezer", .func = MiscMenuFuncGenerator<ui::menu::themezer::Menu>, .flag = MiscMenuFlag_Shortcut }, { .name = "Themezer", .title = "Themezer", .func = MiscMenuFuncGenerator<ui::menu::themezer::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "GitHub", .title = "GitHub", .func = MiscMenuFuncGenerator<ui::menu::gh::Menu>, .flag = MiscMenuFlag_Shortcut }, { .name = "GitHub", .title = "GitHub", .func = MiscMenuFuncGenerator<ui::menu::gh::Menu>, .flag = MiscMenuFlag_Shortcut },
{ .name = "FTP", .title = "FTP Install", .func = MiscMenuFuncGenerator<ui::menu::ftp::Menu>, .flag = MiscMenuFlag_Install }, { .name = "FTP", .title = "FTP Install", .func = MiscMenuFuncGenerator<ui::menu::ftp::Menu>, .flag = MiscMenuFlag_Install },
@@ -59,7 +61,6 @@ const MiscMenuEntry MISC_MENU_ENTRIES[] = {
auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string version) -> Result { auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string version) -> Result {
static fs::FsPath zip_out{"/switch/sphaira/cache/update.zip"}; static fs::FsPath zip_out{"/switch/sphaira/cache/update.zip"};
constexpr auto chunk_size = 1024 * 512; // 512KiB
fs::FsNativeSd fs; fs::FsNativeSd fs;
R_TRY(fs.GetFsOpenResult()); R_TRY(fs.GetFsOpenResult());
@@ -82,83 +83,23 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
// 2. extract the zip // 2. extract the zip
if (!pbox->ShouldExit()) { if (!pbox->ShouldExit()) {
auto zfile = unzOpen64(zip_out); const auto exe_path = App::GetExePath();
R_UNLESS(zfile, 0x1); bool found_exe{};
ON_SCOPE_EXIT(unzClose(zfile));
unz_global_info64 pglobal_info; R_TRY(thread::TransferUnzipAll(pbox, zip_out, &fs, "/", [&](const fs::FsPath& name, fs::FsPath& path) -> bool {
if (UNZ_OK != unzGetGlobalInfo64(zfile, &pglobal_info)) { if (std::strstr(path, "sphaira.nro")) {
R_THROW(0x1); path = exe_path;
} found_exe = true;
for (s64 i = 0; i < pglobal_info.number_entry; i++) {
if (i > 0) {
if (UNZ_OK != unzGoToNextFile(zfile)) {
log_write("failed to unzGoToNextFile\n");
R_THROW(0x1);
}
}
if (UNZ_OK != unzOpenCurrentFile(zfile)) {
log_write("failed to open current file\n");
R_THROW(0x1);
}
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");
R_THROW(0x1);
}
if (file_path[0] != '/') {
file_path = fs::AppendPath("/", file_path);
}
if (std::strstr(file_path, "sphaira.nro")) {
file_path = App::GetExePath();
}
Result rc;
if (file_path[std::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.s, rc);
R_THROW(rc);
}
} 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.s, rc);
R_THROW(rc);
}
fs::File f;
R_TRY(fs.OpenFile(file_path, FsOpenMode_Write, &f));
R_TRY(f.SetSize(info.uncompressed_size));
std::vector<char> buf(chunk_size);
s64 offset{};
while (offset < info.uncompressed_size) {
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());
R_THROW(0x1);
}
R_TRY(f.Write(offset, buf.data(), bytes_read, FsWriteOption_None));
pbox->UpdateTransfer(offset, info.uncompressed_size);
offset += bytes_read;
}
} }
return true;
}));
// check if we have sphaira installed in other locations and update them. // check if we have sphaira installed in other locations and update them.
if (file_path == App::GetExePath()) { if (found_exe) {
for (auto& path : SPHAIRA_PATHS) { for (auto& path : SPHAIRA_PATHS) {
log_write("[UPD] checking path: %s\n", path.s); log_write("[UPD] checking path: %s\n", path.s);
// skip if we already updated this path. // skip if we already updated this path.
if (file_path == path) { if (exe_path == path) {
log_write("[UPD] skipped as already updated\n"); log_write("[UPD] skipped as already updated\n");
continue; continue;
} }
@@ -169,8 +110,7 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
if (R_SUCCEEDED(nro_get_nacp(path, nacp)) && !std::strcmp(nacp.lang[0].name, "sphaira")) { if (R_SUCCEEDED(nro_get_nacp(path, nacp)) && !std::strcmp(nacp.lang[0].name, "sphaira")) {
log_write("[UPD] found, updating\n"); log_write("[UPD] found, updating\n");
pbox->NewTransfer(path); pbox->NewTransfer(path);
R_TRY(pbox->CopyFile(&fs, file_path, path)); R_TRY(pbox->CopyFile(&fs, exe_path, path));
}
} }
} }
} }

View File

@@ -107,7 +107,7 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) {
gfx::drawTextArgs(vg, 80, start_y, 28.f, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str()); gfx::drawTextArgs(vg, 80, start_y, 28.f, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
m_scroll_title_sub_heading.Draw(vg, true, title_sub_x, start_y, text_w - title_sub_x, 16, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT_INFO), m_title_sub_heading.c_str()); m_scroll_title_sub_heading.Draw(vg, true, title_sub_x, start_y, text_w - title_sub_x, 16, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT_INFO), m_title_sub_heading.c_str());
m_scroll_sub_heading.Draw(vg, true, 80, 685, text_w - 80, 18, NVG_ALIGN_LEFT, theme->GetColour(ThemeEntryID_TEXT), m_sub_heading.c_str()); m_scroll_sub_heading.Draw(vg, true, 80, 685, text_w - 160, 18, NVG_ALIGN_LEFT, theme->GetColour(ThemeEntryID_TEXT), m_sub_heading.c_str());
} }
void MenuBase::SetTitle(std::string title) { void MenuBase::SetTitle(std::string title) {

File diff suppressed because it is too large Load Diff

View File

@@ -11,11 +11,12 @@
#include "ui/nvg_util.hpp" #include "ui/nvg_util.hpp"
#include "swkbd.hpp" #include "swkbd.hpp"
#include "i18n.hpp" #include "i18n.hpp"
#include "threaded_file_transfer.hpp"
#include "image.hpp"
#include <minIni.h> #include <minIni.h>
#include <stb_image.h> #include <stb_image.h>
#include <cstring> #include <cstring>
#include <minizip/unzip.h>
#include <yyjson.h> #include <yyjson.h>
#include "yyjson_helper.hpp" #include "yyjson_helper.hpp"
@@ -134,19 +135,14 @@ auto loadThemeImage(ThemeEntry& e) -> bool {
} }
auto vg = App::GetVg(); auto vg = App::GetVg();
fs::FsNativeSd fs;
std::vector<u8> image_buf;
const auto path = apiBuildIconCache(e); const auto path = apiBuildIconCache(e);
if (R_FAILED(fs.read_entire_file(path, image_buf))) { TimeStamp ts;
log_write("failed to load image from file: %s\n", path.s); const auto data = ImageLoadFromFile(path, ImageFlag_JPEG);
} else { if (!data.data.empty()) {
int channels_in_file; image.w = data.w;
auto buf = stbi_load_from_memory(image_buf.data(), image_buf.size(), &image.w, &image.h, &channels_in_file, 4); image.h = data.h;
if (buf) { image.image = nvgCreateImageRGBA(vg, data.w, data.h, 0, data.data.data());
ON_SCOPE_EXIT(stbi_image_free(buf)); log_write("\t[image load] time taken: %.2fs %zums\n", ts.GetSecondsD(), ts.GetMs());
image.image = nvgCreateImageRGBA(vg, image.w, image.h, 0, buf);
}
} }
if (!image.image) { if (!image.image) {
@@ -222,7 +218,6 @@ void from_json(const fs::FsPath& path, PackList& e) {
auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> Result { auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> Result {
static const fs::FsPath zip_out{"/switch/sphaira/cache/themezer/temp.zip"}; static const fs::FsPath zip_out{"/switch/sphaira/cache/themezer/temp.zip"};
constexpr auto chunk_size = 1024 * 512; // 512KiB
fs::FsNativeSd fs; fs::FsNativeSd fs;
R_TRY(fs.GetFsOpenResult()); R_TRY(fs.GetFsOpenResult());
@@ -272,66 +267,7 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> Result {
// 3. extract the zip // 3. extract the zip
if (!pbox->ShouldExit()) { if (!pbox->ShouldExit()) {
auto zfile = unzOpen64(zip_out); R_TRY(thread::TransferUnzipAll(pbox, zip_out, &fs, dir_path));
R_UNLESS(zfile, 0x1);
ON_SCOPE_EXIT(unzClose(zfile));
unz_global_info64 pglobal_info;
if (UNZ_OK != unzGetGlobalInfo64(zfile, &pglobal_info)) {
R_THROW(0x1);
}
for (int i = 0; i < pglobal_info.number_entry; i++) {
if (i > 0) {
if (UNZ_OK != unzGoToNextFile(zfile)) {
log_write("failed to unzGoToNextFile\n");
R_THROW(0x1);
}
}
if (UNZ_OK != unzOpenCurrentFile(zfile)) {
log_write("failed to open current file\n");
R_THROW(0x1);
}
ON_SCOPE_EXIT(unzCloseCurrentFile(zfile));
unz_file_info64 info;
char name[512];
if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &info, name, sizeof(name), 0, 0, 0, 0)) {
log_write("failed to get current info\n");
R_THROW(0x1);
}
const auto file_path = fs::AppendPath(dir_path, name);
pbox->NewTransfer(name);
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.s, rc);
R_THROW(rc);
}
fs::File f;
R_TRY(fs.OpenFile(file_path, FsOpenMode_Write, &f));
R_TRY(f.SetSize(info.uncompressed_size));
std::vector<char> buf(chunk_size);
s64 offset{};
while (offset < info.uncompressed_size) {
R_TRY(pbox->ShouldExitResult());
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());
R_THROW(0x1);
}
R_TRY(f.Write(offset, buf.data(), bytes_read, FsWriteOption_None));
pbox->UpdateTransfer(offset, info.uncompressed_size);
offset += bytes_read;
}
}
} }
log_write("finished install :)\n"); log_write("finished install :)\n");

View File

@@ -127,7 +127,7 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
R_SUCCEED(); R_SUCCEED();
}, [this](Result rc){ }, [this](Result rc){
App::PushErrorBox(rc, "USB install failed"_i18n); App::PushErrorBox(rc, "USB install failed!"_i18n);
if (R_SUCCEEDED(rc)) { if (R_SUCCEEDED(rc)) {
App::Notify("Usb install success!"_i18n); App::Notify("Usb install success!"_i18n);

View File

@@ -296,8 +296,8 @@ void drawScrollbar(NVGcontext* vg, const Theme* theme, float x, float y, float h
if (entry_total > max_entry_display) { if (entry_total > max_entry_display) {
const float sb_h = 1.f / (float)entry_total * h; const float sb_h = 1.f / (float)entry_total * h;
const float sb_y = SCROLL; const float sb_y = SCROLL;
gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), false); gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), 5);
gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(max_entry_display) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), false); gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(max_entry_display) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), 5);
} }
} }
@@ -318,8 +318,8 @@ void drawScrollbar2(NVGcontext* vg, const Theme* theme, float x, float y, float
if (count > page) { if (count > page) {
const float sb_h = 1.f / (float)count * h; const float sb_h = 1.f / (float)count * h;
const float sb_y = index_off; const float sb_y = index_off;
gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), false); gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), 5);
gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(page) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), false); gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(page) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), 5);
} }
} }
@@ -373,9 +373,7 @@ void drawAppLable(NVGcontext* vg, const Theme* theme, ScrollingText& st, float x
const float text_x = box_x + text_pad; const float text_x = box_x + text_pad;
const float text_y = y_offset + (box_h / 2.f); const float text_y = y_offset + (box_h / 2.f);
drawRect(vg, {x-4, y-4, w+8, w+8}, theme->GetColour(ThemeEntryID_GRID));
nvgBeginPath(vg); nvgBeginPath(vg);
nvgRoundedRect(vg, box_x, y_offset, box_w, box_h, 3.f); nvgRoundedRect(vg, box_x, y_offset, box_w, box_h, 3.f);
nvgFillColor(vg, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND)); nvgFillColor(vg, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND));
nvgFill(vg); nvgFill(vg);

View File

@@ -88,6 +88,7 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
m_last_offset = m_offset; m_last_offset = m_offset;
} }
const auto action = m_action;
const auto title = m_title; const auto title = m_title;
const auto transfer = m_transfer; const auto transfer = m_transfer;
const auto size = m_size; const auto size = m_size;
@@ -166,7 +167,7 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
gfx::drawTextArgs(vg, center_x, prog_bar.y + prog_bar.h + 30, 18, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%s (%s)", time_str, speed_str); gfx::drawTextArgs(vg, center_x, prog_bar.y + prog_bar.h + 30, 18, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%s (%s)", time_str, speed_str);
} }
gfx::drawTextArgs(vg, center_x, m_pos.y + 40, 24, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), m_action.c_str()); gfx::drawTextArgs(vg, center_x, m_pos.y + 40, 24, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), action.c_str());
const auto draw_text = [&](ScrollingText& scroll, const std::string& txt, float y, float size, float pad, ThemeEntryID id){ const auto draw_text = [&](ScrollingText& scroll, const std::string& txt, float y, float size, float pad, ThemeEntryID id){
float bounds[4]; float bounds[4];
@@ -187,6 +188,14 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
nvgRestore(vg); nvgRestore(vg);
} }
auto ProgressBox::SetActionName(const std::string& action) -> ProgressBox& {
mutexLock(&m_mutex);
m_action = action;
mutexUnlock(&m_mutex);
Yield();
return *this;
}
auto ProgressBox::SetTitle(const std::string& title) -> ProgressBox& { auto ProgressBox::SetTitle(const std::string& title) -> ProgressBox& {
mutexLock(&m_mutex); mutexLock(&m_mutex);
m_title = title; m_title = title;

View File

@@ -87,16 +87,8 @@ void ScrollableText::Draw(NVGcontext* vg, Theme* theme) {
// const Vec4 banner_vec(70, line_vec.y + 20, 848.f, 208.f); // const Vec4 banner_vec(70, line_vec.y + 20, 848.f, 208.f);
const Vec4 banner_vec(70, line_vec.y + 20, m_end_w + (110.0F), 208.f); const Vec4 banner_vec(70, line_vec.y + 20, m_end_w + (110.0F), 208.f);
// only draw scrollbar if needed
if ((m_bounds[3] - m_bounds[1]) > m_clip_y) {
const auto scrollbar_size = m_clip_y;
const auto max_index = (m_bounds[3] - m_bounds[1]) / m_step; const auto max_index = (m_bounds[3] - m_bounds[1]) / m_step;
const auto sb_h = 1.f / max_index * scrollbar_size; gfx::drawScrollbar2(vg, theme, banner_vec.w + 25, m_y_off_base, m_clip_y, m_index, max_index, 1, m_clip_y / m_step - 1);
const auto in_clip = m_clip_y / m_step - 1;
const auto sb_y = m_index;
gfx::drawRect(vg, banner_vec.w, m_y_off_base, 10, scrollbar_size, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND));
gfx::drawRect(vg, banner_vec.w+2, m_y_off_base + sb_h * sb_y, 10-4, sb_h + (sb_h * in_clip) - 4, theme->GetColour(ThemeEntryID_SCROLLBAR));
}
nvgSave(vg); nvgSave(vg);
nvgIntersectScissor(vg, 0, m_y_off_base - m_font_size, 1280, m_clip_y + m_font_size); // clip nvgIntersectScissor(vg, 0, m_y_off_base - m_font_size, 1280, m_clip_y + m_font_size); // clip

View File

@@ -293,12 +293,14 @@ Result UsbDs::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) {
} }
Result UsbDs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_urb_id) { Result UsbDs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_urb_id) {
if (ep == UsbSessionEndpoint_In) {
if (remaining == size && !(size % (u32)m_max_packet_size)) { if (remaining == size && !(size % (u32)m_max_packet_size)) {
log_write("[USBDS] SetZlt(true)\n"); log_write("[USBDS] SetZlt(true)\n");
R_TRY(usbDsEndpoint_SetZlt(m_endpoints[ep], true)); R_TRY(usbDsEndpoint_SetZlt(m_endpoints[ep], true));
} else { } else {
R_TRY(usbDsEndpoint_SetZlt(m_endpoints[ep], false)); R_TRY(usbDsEndpoint_SetZlt(m_endpoints[ep], false));
} }
}
return usbDsEndpoint_PostBufferAsync(m_endpoints[ep], buffer, size, out_urb_id); return usbDsEndpoint_PostBufferAsync(m_endpoints[ep], buffer, size, out_urb_id);
} }

View File

@@ -186,7 +186,7 @@ Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMet
R_SUCCEED(); R_SUCCEED();
} }
Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out, s64 nacp_size, std::vector<u8>* icon_out) { Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out, s64 nacp_size, std::vector<u8>* icon_out, s64 nacp_off) {
FsFileSystem fs; FsFileSystem fs;
R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentControl, path, FsContentAttributes_All)); R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, FsFileSystemType_ContentControl, path, FsContentAttributes_All));
ON_SCOPE_EXIT(fsFsClose(std::addressof(fs))); ON_SCOPE_EXIT(fsFsClose(std::addressof(fs)));
@@ -198,7 +198,7 @@ Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out, s64
ON_SCOPE_EXIT(fsFileClose(std::addressof(file))); ON_SCOPE_EXIT(fsFileClose(std::addressof(file)));
u64 bytes_read; u64 bytes_read;
R_TRY(fsFileRead(&file, 0, nacp_out, nacp_size, 0, &bytes_read)); R_TRY(fsFileRead(&file, nacp_off, nacp_out, nacp_size, 0, &bytes_read));
} }
// read icon. // read icon.

View File

@@ -433,7 +433,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
inflate_buf.reserve(t->max_buffer_size); inflate_buf.reserve(t->max_buffer_size);
s64 written{}; s64 written{};
s64 decompress_buf_off{}; s64 block_offset{};
std::vector<u8> buf{}; std::vector<u8> buf{};
buf.reserve(t->max_buffer_size); buf.reserve(t->max_buffer_size);
@@ -454,14 +454,15 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
} }
for (s64 off = 0; off < size;) { for (s64 off = 0; off < size;) {
// log_write("looking for section\n");
if (!ncz_section || !ncz_section->InRange(written)) { if (!ncz_section || !ncz_section->InRange(written)) {
log_write("[NCZ] looking for new section: %zu\n", written);
auto it = std::ranges::find_if(t->ncz_sections, [written](auto& e){ auto it = std::ranges::find_if(t->ncz_sections, [written](auto& e){
return e.InRange(written); return e.InRange(written);
}); });
R_UNLESS(it != t->ncz_sections.cend(), Result_NczSectionNotFound); R_UNLESS(it != t->ncz_sections.cend(), Result_NczSectionNotFound);
ncz_section = &(*it); ncz_section = &(*it);
log_write("[NCZ] found new section: %zu\n", written);
if (ncz_section->crypto_type >= nca::EncryptionType_AesCtr) { if (ncz_section->crypto_type >= nca::EncryptionType_AesCtr) {
const auto swp = std::byteswap(u64(written) >> 4); const auto swp = std::byteswap(u64(written) >> 4);
@@ -488,7 +489,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
// restore remaining data to the swapped buffer. // restore remaining data to the swapped buffer.
if (!temp_vector.empty()) { if (!temp_vector.empty()) {
log_write("storing data size: %zu\n", temp_vector.size()); log_write("[NCZ] storing data size: %zu\n", temp_vector.size());
inflate_buf = temp_vector; inflate_buf = temp_vector;
} }
@@ -496,6 +497,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
}; };
while (t->decompress_offset < t->write_size && R_SUCCEEDED(t->GetResults())) { while (t->decompress_offset < t->write_size && R_SUCCEEDED(t->GetResults())) {
s64 decompress_buf_off{};
R_TRY(t->GetDecompressBuf(buf, decompress_buf_off)); R_TRY(t->GetDecompressBuf(buf, decompress_buf_off));
// do we have an nsz? if so, setup buffers. // do we have an nsz? if so, setup buffers.
@@ -616,12 +618,14 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
// todo: blocks need to use read offset, as the offset + size is compressed range. // todo: blocks need to use read offset, as the offset + size is compressed range.
if (t->ncz_blocks.size()) { if (t->ncz_blocks.size()) {
if (!ncz_block || !ncz_block->InRange(decompress_buf_off)) { if (!ncz_block || !ncz_block->InRange(decompress_buf_off)) {
block_offset = 0;
log_write("[NCZ] looking for new block: %zu\n", decompress_buf_off);
auto it = std::ranges::find_if(t->ncz_blocks, [decompress_buf_off](auto& e){ auto it = std::ranges::find_if(t->ncz_blocks, [decompress_buf_off](auto& e){
return e.InRange(decompress_buf_off); return e.InRange(decompress_buf_off);
}); });
R_UNLESS(it != t->ncz_blocks.cend(), Result_NczBlockNotFound); R_UNLESS(it != t->ncz_blocks.cend(), Result_NczBlockNotFound);
// log_write("looking found block\n"); log_write("[NCZ] found new block: %zu off: %zd size: %zd\n", decompress_buf_off, it->offset, it->size);
ncz_block = &(*it); ncz_block = &(*it);
} }
@@ -629,7 +633,7 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
auto decompressedBlockSize = 1 << t->ncz_block_header.block_size_exponent; auto decompressedBlockSize = 1 << t->ncz_block_header.block_size_exponent;
// special handling for the last block to check it's actually compressed // special handling for the last block to check it's actually compressed
if (ncz_block->offset == t->ncz_blocks.back().offset) { if (ncz_block->offset == t->ncz_blocks.back().offset) {
log_write("last block special handling\n"); log_write("[NCZ] last block special handling\n");
decompressedBlockSize = t->ncz_block_header.decompressed_size % decompressedBlockSize; decompressedBlockSize = t->ncz_block_header.decompressed_size % decompressedBlockSize;
} }
@@ -637,12 +641,12 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
compressed = ncz_block->size < decompressedBlockSize; compressed = ncz_block->size < decompressedBlockSize;
// clip read size as blocks can be up to 32GB in size! // clip read size as blocks can be up to 32GB in size!
const auto size = std::min<u64>(buf.size() - buf_off, ncz_block->size); const auto size = std::min<u64>(buffer.size(), ncz_block->size - block_offset);
buffer = {buf.data() + buf_off, size}; buffer = buffer.subspan(0, size);
} }
if (compressed) { if (compressed) {
// log_write("COMPRESSED block\n"); log_write("[NCZ] COMPRESSED block\n");
ZSTD_inBuffer input = { buffer.data(), buffer.size(), 0 }; ZSTD_inBuffer input = { buffer.data(), buffer.size(), 0 };
while (input.pos < input.size) { while (input.pos < input.size) {
R_TRY(t->GetResults()); R_TRY(t->GetResults());
@@ -650,12 +654,15 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
inflate_buf.resize(inflate_offset + chunk_size); inflate_buf.resize(inflate_offset + chunk_size);
ZSTD_outBuffer output = { inflate_buf.data() + inflate_offset, chunk_size, 0 }; ZSTD_outBuffer output = { inflate_buf.data() + inflate_offset, chunk_size, 0 };
const auto res = ZSTD_decompressStream(dctx, std::addressof(output), std::addressof(input)); const auto res = ZSTD_decompressStream(dctx, std::addressof(output), std::addressof(input));
if (ZSTD_isError(res)) {
log_write("[NCZ] ZSTD_decompressStream() pos: %zu size: %zu res: %zd msg: %s\n", input.pos, input.size, res, ZSTD_getErrorName(res));
}
R_UNLESS(!ZSTD_isError(res), Result_InvalidNczZstdError); R_UNLESS(!ZSTD_isError(res), Result_InvalidNczZstdError);
t->decompress_offset += output.pos; t->decompress_offset += output.pos;
inflate_offset += output.pos; inflate_offset += output.pos;
if (inflate_offset >= INFLATE_BUFFER_MAX) { if (inflate_offset >= INFLATE_BUFFER_MAX) {
// log_write("flushing compressed data: %zd vs %zd diff: %zd\n", inflate_offset, INFLATE_BUFFER_MAX, inflate_offset - INFLATE_BUFFER_MAX); log_write("[NCZ] flushing compressed data: %zd vs %zd diff: %zd\n", inflate_offset, INFLATE_BUFFER_MAX, inflate_offset - INFLATE_BUFFER_MAX);
R_TRY(ncz_flush(INFLATE_BUFFER_MAX)); R_TRY(ncz_flush(INFLATE_BUFFER_MAX));
} }
} }
@@ -666,13 +673,14 @@ Result Yati::decompressFuncInternal(ThreadData* t) {
t->decompress_offset += buffer.size(); t->decompress_offset += buffer.size();
inflate_offset += buffer.size(); inflate_offset += buffer.size();
if (inflate_offset >= INFLATE_BUFFER_MAX) { if (inflate_offset >= INFLATE_BUFFER_MAX) {
// log_write("flushing copy data\n"); log_write("[NCZ] flushing copy data\n");
R_TRY(ncz_flush(INFLATE_BUFFER_MAX)); R_TRY(ncz_flush(INFLATE_BUFFER_MAX));
} }
} }
buf_off += buffer.size(); buf_off += buffer.size();
decompress_buf_off += buffer.size(); decompress_buf_off += buffer.size();
block_offset += buffer.size();
} }
} }
} }