Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
695102405b | ||
|
|
aeb6c64077 | ||
|
|
9050229b12 | ||
|
|
af4aa836a9 | ||
|
|
fb2272546a | ||
|
|
4b25b2c5eb | ||
|
|
c11d9edc4e | ||
|
|
e2a5454263 | ||
|
|
3ca82463cc | ||
|
|
793b36fd59 | ||
|
|
66fe526754 | ||
|
|
7c45d60e60 | ||
|
|
a9931a975d | ||
|
|
3e3ec71329 | ||
|
|
49abdc0590 | ||
|
|
4f931d2991 | ||
|
|
f7c5ccfa87 | ||
|
|
5ce23f29fa | ||
|
|
d13ad64099 | ||
|
|
be88bdb567 | ||
|
|
1bff57f9c9 | ||
|
|
4172d5d5b6 | ||
|
|
1cdea981de | ||
|
|
22ebfd4a82 | ||
|
|
798ac47487 | ||
|
|
fba8051007 | ||
|
|
15721b8e8a | ||
|
|
d43ca37875 | ||
|
|
1b5e7401f2 | ||
|
|
d8b2896bed | ||
|
|
6475f4316a | ||
|
|
93c38da742 | ||
|
|
8070268d2a | ||
|
|
2e6d757852 | ||
|
|
a91550174a | ||
|
|
52b166932d | ||
|
|
d50bcb650f | ||
|
|
654f3a1446 | ||
|
|
71415e5044 | ||
|
|
da33b9a6b9 | ||
|
|
fe2a1a3a80 | ||
|
|
a67171e2b8 | ||
|
|
cf908d63b9 | ||
|
|
ef25c3edc7 | ||
|
|
f956adabc3 | ||
|
|
4244be9592 | ||
|
|
a7fc19e28a | ||
|
|
cf192fca85 | ||
|
|
041bb2bbe5 | ||
|
|
df558d5dcc | ||
|
|
33de03a923 | ||
|
|
1000b9c8ec | ||
|
|
74fddecebc | ||
|
|
a64d4dce7a | ||
|
|
5daca4354c | ||
|
|
da9235f58e | ||
|
|
bd6566524c | ||
|
|
eadc46b0e4 | ||
|
|
71df5317be | ||
|
|
bd7eadc6a0 | ||
|
|
544272925d | ||
|
|
70a31be134 | ||
|
|
55ae2a63d9 | ||
|
|
5a53947a3e | ||
|
|
3bbb5ccb3c | ||
|
|
83472f1020 | ||
|
|
0167bf034c | ||
|
|
35abe363a6 | ||
|
|
97d3fd396e | ||
|
|
b98ccb927e | ||
|
|
db23f072a2 | ||
|
|
4d3d7e81d4 | ||
|
|
441807bc53 | ||
|
|
20e2d85843 | ||
|
|
e279a70606 | ||
|
|
5d9e24af31 | ||
|
|
078627e07b | ||
|
|
365ae2d0cb | ||
|
|
5b6e09b926 | ||
|
|
7072647611 | ||
|
|
30cf4826f8 | ||
|
|
ca47fc1f89 | ||
|
|
16a2c84edd | ||
|
|
df5e27dd06 | ||
|
|
d95226f8c0 | ||
|
|
164fec5b73 | ||
|
|
8dad96f39f | ||
|
|
2244e73c53 | ||
|
|
456cb02d2a | ||
|
|
f310704472 | ||
|
|
96e5a7081b | ||
|
|
1c93e18822 | ||
|
|
ac152454f0 | ||
|
|
7851f7f400 | ||
|
|
2b561dd438 | ||
|
|
3545f557fc | ||
|
|
8dfb9b9ba6 | ||
|
|
7cf36cd25f | ||
|
|
c53692022b | ||
|
|
0f3b7da0b2 | ||
|
|
e22daefb08 | ||
|
|
6fb5319da3 | ||
|
|
6970fec554 | ||
|
|
36be56647f | ||
|
|
cca6326314 | ||
|
|
9176c6780a | ||
|
|
b1a6b12cf3 | ||
|
|
c7cc11cc98 | ||
|
|
ec4b96b95d | ||
|
|
a2e343daa7 | ||
|
|
b811c9e3cd | ||
|
|
8ffaa56bc3 | ||
|
|
eca3358e57 | ||
|
|
757e380e08 | ||
|
|
6c1b5de932 | ||
|
|
d79ac126f7 | ||
|
|
2d7763444e | ||
|
|
1dafa2748c | ||
|
|
9f7bf9581c | ||
|
|
8f39acbaa2 | ||
|
|
81469d0ac9 | ||
|
|
1eae35f072 | ||
|
|
5b82e07b1c | ||
|
|
73886c28ae | ||
|
|
eea09f6e57 | ||
|
|
282c6e5493 | ||
|
|
2c2f602d14 | ||
|
|
f7f1254699 | ||
|
|
90f8a62823 | ||
|
|
e2a1c8b5e3 | ||
|
|
21f6f4b74d | ||
|
|
75d3b3ee0d | ||
|
|
0dde379932 | ||
|
|
9800bbecdf | ||
|
|
60e915c255 | ||
|
|
786f8a42fa | ||
|
|
5a4a0f75f2 | ||
|
|
5aca92a2cc | ||
|
|
7471885119 | ||
|
|
5038fb0c28 | ||
|
|
ff9f493460 | ||
|
|
89e82927ee | ||
|
|
651d9fa495 | ||
|
|
3141100457 |
3
.gitignore
vendored
@@ -11,6 +11,7 @@ old_code
|
||||
created_ncas
|
||||
assets/romfs/shaders
|
||||
.vscode/settings.json
|
||||
.idea
|
||||
info/
|
||||
romfs/shaders
|
||||
assets/unused
|
||||
@@ -22,3 +23,5 @@ libs/tweeny
|
||||
|
||||
compile_commands.json
|
||||
out
|
||||
|
||||
usb_test/
|
||||
|
||||
35
README.md
@@ -49,6 +49,40 @@ The `path` field is optional. If left out, it will use the name of the ini to fi
|
||||
|
||||
See `assets/romfs/assoc/` for more examples of file assoc entries.
|
||||
|
||||
## Installing (applications)
|
||||
|
||||
Sphaira can install applications (nsp, xci, nsz, xcz) from various sources (sd card, gamecard, ftp, usb).
|
||||
|
||||
For informantion about the install options, [see the wiki](https://github.com/ITotalJustice/sphaira/wiki/Install).
|
||||
|
||||
### Usb (install)
|
||||
|
||||
The USB protocol is the same as tinfoil, so tools such as [ns-usbloader](https://github.com/developersu/ns-usbloader) and [fluffy](https://github.com/fourminute/Fluffy) should work with sphaira. You may also use the provided python script found [here](tools/usb_install_pc.py).
|
||||
|
||||
### Ftp (install)
|
||||
|
||||
Once you have connected your ftp client to your switch, you can upload files to install into the `install` folder.
|
||||
|
||||
## Building from source
|
||||
|
||||
You will first need to install [devkitPro](https://devkitpro.org/wiki/Getting_Started).
|
||||
|
||||
Next you will need to install the dependencies:
|
||||
```sh
|
||||
sudo pacman -S switch-dev deko3d switch-cmake switch-curl switch-glm switch-zlib
|
||||
```
|
||||
|
||||
Once devkitPro and all dependencies are installed, you can now build sphaira.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/ITotalJustice/sphaira.git
|
||||
cd sphaira
|
||||
cmake --preset MinSizeRel
|
||||
cmake --build --preset MinSizeRel
|
||||
```
|
||||
|
||||
The output will be found in `build/MinSizeRel/sphaira.nro`
|
||||
|
||||
## Credits
|
||||
|
||||
- borealis
|
||||
@@ -62,4 +96,5 @@ See `assets/romfs/assoc/` for more examples of file assoc entries.
|
||||
- GBATemp
|
||||
- hb-appstore
|
||||
- haze
|
||||
- nxdumptool (for gamecard bin dumping and rsa verify code)
|
||||
- Everyone who has contributed to this project!
|
||||
|
||||
|
Before Width: | Height: | Size: 569 B After Width: | Height: | Size: 569 B |
|
Before Width: | Height: | Size: 703 B After Width: | Height: | Size: 703 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 783 B After Width: | Height: | Size: 783 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
2
assets/romfs/assoc/SwitchWave.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[config]
|
||||
supported_extensions=mp3|ogg|flac|wav|aac|ac3|aif|asf|mp4|mkv|m3u|m3u8|hls|vob|avi|dv|flv|m2ts|m2v|m4a|mov|mpeg|mpg|mts|swf|ts|vob|wma|wmv|png|jpg|jpeg|bmp|gif
|
||||
@@ -2,3 +2,4 @@
|
||||
path=/retroarch/cores/fbneo_libretro_libnx.nro
|
||||
supported_extensions=zip|7z|cue|ccd
|
||||
database=FBNeo - Arcade Games
|
||||
use_base_name=true
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
path=/retroarch/cores/mame2000_libretro_libnx.nro
|
||||
supported_extensions=zip|7z
|
||||
database=MAME 2000
|
||||
use_base_name=true
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
path=/retroarch/cores/mame2003_libretro_libnx.nro
|
||||
supported_extensions=zip
|
||||
database=MAME 2003
|
||||
use_base_name=true
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
path=/retroarch/cores/mame2003_plus_libretro_libnx.nro
|
||||
supported_extensions=zip
|
||||
database=MAME 2003-Plus
|
||||
use_base_name=true
|
||||
|
||||
2
assets/romfs/assoc/nxmp.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[config]
|
||||
supported_extensions=mp3|ogg|flac|wav|aac|ac3|aif|asf|mp4|mkv|m3u|m3u8|hls|vob|avi|dv|flv|m2ts|m2v|m4a|mov|mpeg|mpg|mts|swf|ts|vob|wma|wmv|png|jpg|jpeg|bmp|gif
|
||||
@@ -1,2 +0,0 @@
|
||||
[config]
|
||||
ext=nro
|
||||
@@ -2,3 +2,4 @@
|
||||
path=/retroarch/cores/xrick_libretro_libnx.nro
|
||||
supported_extensions=zip
|
||||
database=Rick Dangerous
|
||||
use_base_name=true
|
||||
|
||||
@@ -1,84 +1,35 @@
|
||||
{
|
||||
"[Applet Mode]": "[Applet-Modus]",
|
||||
"No Internet": "Keine Internetverbindung",
|
||||
"Files": "Dateien",
|
||||
"Apps": "Apps",
|
||||
"Store": "Store",
|
||||
"[Applet Mode]": " | Applet Modus |",
|
||||
"No Internet": "Kein Internet",
|
||||
"Switch-Handheld!": "Handheld!",
|
||||
"Switch-Docked!": "Angedockt!",
|
||||
"Audio disabled due to suspended game": "Audio deaktivert wegen Spielabbruch",
|
||||
"Are you sure you wish to cancel?": "Bist du sicher dass du abbrechen willst?",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Bei wiederholtem Auftreten bitte Issue erstellen.",
|
||||
|
||||
"Menu Options": " Menü | Optionen",
|
||||
"Menu": "Menü",
|
||||
"Options": "Optionen",
|
||||
"OK": "OK",
|
||||
"Back": "Zurück",
|
||||
"Select": "Auswählen",
|
||||
"Open": "Öffnen",
|
||||
"Launch": "Starten",
|
||||
"Info": "Info",
|
||||
"Install": "Installieren",
|
||||
"Delete": "Löschen",
|
||||
"Restart": "Neustart",
|
||||
"Changelog": "Changelog",
|
||||
"Details": "Details",
|
||||
"Update": "Update",
|
||||
"Remove": "Entfernen",
|
||||
"Restore": "Wiederherstellen",
|
||||
"Download": "Download",
|
||||
"Next Page": "Nächste Seite",
|
||||
"Prev Page": "Vorherige Seite",
|
||||
"Unstar": "Favorit entfernen",
|
||||
"Star": "Favorit",
|
||||
"System memory": "System-Speicher",
|
||||
"microSD card": "microSD-Karte",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "System-Speicher Bild",
|
||||
"Image microSD card": "microSD-Karten Bild",
|
||||
"Slow": "Langsam",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Schnell",
|
||||
"Yes": "Ja",
|
||||
"No": "Nein",
|
||||
"Enabled": "Aktiviert",
|
||||
"Disabled": "Deaktiviert",
|
||||
|
||||
"Sort By": "Sortieren nach",
|
||||
"Sort Options": "Sortieroptionen",
|
||||
"Filter": "Filter",
|
||||
"Sort": "Sortieren",
|
||||
"Order": "Reihenfolge",
|
||||
"Search": "Suchen",
|
||||
"Updated": "Aktualisiert",
|
||||
"Updated (Star)": "Aktualisiert (Favoriten)",
|
||||
"Downloads": "Downloads",
|
||||
"Size": "Größe",
|
||||
"Size (Star)": "Größe (Favoriten)",
|
||||
"Alphabetical": "Alphabetisch",
|
||||
"Alphabetical (Star)": "Alphabetisch (Favoriten)",
|
||||
"Likes": "Likes",
|
||||
"ID": "ID",
|
||||
"Descending": "Absteigend",
|
||||
"Descending (down)": "Absteigend",
|
||||
"Desc": "Abst.",
|
||||
"Ascending": "Aufsteigend",
|
||||
"Ascending (Up)": "Aufsteigend",
|
||||
"Asc": "Aufst.",
|
||||
|
||||
"Menu Options": "Menü-Optionen",
|
||||
"Theme": "Theme",
|
||||
"Theme Options": "Theme-Optionen",
|
||||
"Select Theme": "Theme auswählen",
|
||||
"Shuffle": "Zufällig",
|
||||
"Theme": "Themes",
|
||||
"Theme Options": " Themes | Optionen",
|
||||
"Select Theme": "Theme wählen",
|
||||
"Music": "Musik",
|
||||
"12 Hour Time": "",
|
||||
"Network": "Netzwerk",
|
||||
"Network Options": "Netzwerk-Optionen",
|
||||
"12 Hour Time": "12-Std Zeitformat",
|
||||
"Download Default Music": "",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "Konnektivität",
|
||||
"Network Options": "Konnektivität | Optionen",
|
||||
"Ftp": "FTP",
|
||||
"Mtp": "MTP",
|
||||
"Nxlink": "Nxlink",
|
||||
"Nxlink Connected": "Nxlink verbunden",
|
||||
"Nxlink Upload": "Nxlink Upload",
|
||||
"Nxlink Finished": "Nxlink abgeschlossen",
|
||||
"Switch-Handheld!": "Switch-Handheld!",
|
||||
"Switch-Docked!": "Switch-Dock-Modus!",
|
||||
"Nxlink": "NXLink",
|
||||
"Nxlink Connected": "NXLink | Verbunden",
|
||||
"Nxlink Upload": "NXLink | wird hochgeladen...",
|
||||
"Nxlink Finished": "NXLink | Hochladen beendet",
|
||||
|
||||
"Language": "Sprache",
|
||||
"Auto": "Auto",
|
||||
"Auto": "Systemsprache",
|
||||
"English": "English",
|
||||
"Japanese": "日本語",
|
||||
"French": "Français",
|
||||
@@ -87,92 +38,106 @@
|
||||
"Spanish": "Español",
|
||||
"Chinese": "中文",
|
||||
"Korean": "한국어",
|
||||
"Dutch": "Dutch",
|
||||
"Dutch": "Nederlands",
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "hbmenu beim Beenden ersetzen",
|
||||
"Misc": "Sonstiges",
|
||||
"Misc Options": "Weitere Optionen",
|
||||
"Web": "Web",
|
||||
"Install forwarders": "Forwarder installieren",
|
||||
"Install location": "Installationsort",
|
||||
"Show install warning": "Installationswarnung anzeigen",
|
||||
"Text scroll speed": "Textlaufgeschwindigkeit",
|
||||
"Vietnamese": "tiếng Việt",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"FileBrowser": "Datei-Browser",
|
||||
"%zd files": "%zd Dateien",
|
||||
"%zd dirs": "%zd Ordner",
|
||||
"File Options": "Datei-Optionen",
|
||||
"Show Hidden": "Versteckte anzeigen",
|
||||
"Folders First": "Ordner zuerst",
|
||||
"Hidden Last": "Versteckte zuletzt",
|
||||
"Cut": "Ausschneiden",
|
||||
"Copy": "Kopieren",
|
||||
"Paste": "Einfügen",
|
||||
"Paste ": "Einfügen ",
|
||||
" file(s)?": " Datei(en)?",
|
||||
"Rename": "Umbenennen",
|
||||
"Set New File Name": "Neuen Dateinamen eingeben",
|
||||
"Advanced": "Erweitert",
|
||||
"Advanced Options": "Erweiterte Optionen",
|
||||
"Create File": "Datei erstellen",
|
||||
"Set File Name": "Dateinamen eingeben",
|
||||
"Create Folder": "Ordner erstellen",
|
||||
"Set Folder Name": "Ordnernamen eingeben",
|
||||
"View as text (unfinished)": "Als Text anzeigen (Beta)",
|
||||
"Ignore read only": "Schreibschutz ignorieren",
|
||||
"Mount": "Einbinden",
|
||||
"Empty...": "Leer...",
|
||||
"Open with DayBreak?": "Mit DayBreak öffnen?",
|
||||
"Launch ": "Starten ",
|
||||
"Launch option for: ": "Startoption für: ",
|
||||
"Select launcher for: ": "Launcher auswählen für: ",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Homebrew-Optionen",
|
||||
"Hide Sphaira": "Sphaira ausblenden",
|
||||
"Install Forwarder": "Forwarder installieren",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "WARNUNG: Installation von Forwardern führt zum Ban!",
|
||||
"Installing Forwarder": "Installiere Forwarder",
|
||||
"Creating Program": "Erstelle Programm",
|
||||
"Creating Control": "Erstelle Control",
|
||||
"Creating Meta": "Erstelle Meta",
|
||||
"Writing Nca": "Schreibe NCA",
|
||||
"Updating ncm databse": "Aktualisiere NCM-Datenbank",
|
||||
"Pushing application record": "Übertrage Anwendungsdaten",
|
||||
"Installed!": "Installiert!",
|
||||
"Failed to install forwarder": "Forwarder-Installation fehlgeschlagen",
|
||||
"Unstarred ": "Favorit entfernt ",
|
||||
"Starred ": "Favorit hinzugefügt ",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortierung: %s | Reihenfolge: %s",
|
||||
"AppStore Options": "AppStore-Optionen",
|
||||
"All": "Alle",
|
||||
"Misc": "Extras",
|
||||
"Misc Options": " Extras | Optionen",
|
||||
"Games": "Spiele",
|
||||
"Emulators": "Emulatoren",
|
||||
"Tools": "Tools",
|
||||
"Themes": "Themes",
|
||||
"Legacy": "Legacy",
|
||||
"version: %s": "Version: %s",
|
||||
"updated: %s": "Aktualisiert: %s",
|
||||
"category: %s": "Kategorie: %s",
|
||||
"extracted: %.2f MiB": "Entpackt: %.2f MiB",
|
||||
"app_dls: %s": "Downloads: %s",
|
||||
"More by Author": "Mehr vom Entwickler",
|
||||
"Leave Feedback": "Feedback geben",
|
||||
"Game Options": "",
|
||||
"Hide forwarders": "",
|
||||
"Launch random game": "",
|
||||
"List meta records": "",
|
||||
"Entries": "",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/NSP/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Success": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
|
||||
"Themezer": "Themezer | NX Themes",
|
||||
"Themezer Options": " Themezer | Optionen",
|
||||
"Nsfw": "NSFW",
|
||||
"Page": "Seiten Nr. wählen ",
|
||||
"Page %zu / %zu": " %zu / %zu",
|
||||
"Enter Page Number": "Zu Seite Nr.: ___",
|
||||
"Bad Page": "Seite nicht gefunden",
|
||||
"Download theme?": "Theme herunterladen?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Lade JSON-File",
|
||||
"Select asset to download for ": "Wähle Asset für den Download von ",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
"Host:": "",
|
||||
"Port:": "",
|
||||
"Username:": "",
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "",
|
||||
"Waiting for connection...": "",
|
||||
"Transferring data...": "",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "",
|
||||
"Re-enabled MTP": "",
|
||||
"Installed via usb": "",
|
||||
"Usb install success!": "",
|
||||
"Usb install failed!": "",
|
||||
"Press B to exit...": "",
|
||||
"GameCard Install": "",
|
||||
"GameCard": "",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "",
|
||||
"Gc install failed!": "",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "",
|
||||
"IRS": "",
|
||||
"Irs": "IR-Sensor",
|
||||
"Ambient Noise Level: ": "Umgebungsrauschen: ",
|
||||
"Controller": "Controller",
|
||||
"Pad ": "Pad ",
|
||||
"HandHeld": "Handheld",
|
||||
" (Available)": " (Verfügbar)",
|
||||
" (Unsupported)": " (Nicht unterstützt)",
|
||||
" (Unconnected)": " (Nicht verbunden)",
|
||||
"HandHeld": "Handheld",
|
||||
"Rotation": "Rotation",
|
||||
"0 (Sideways)": "0° (Seitlich)",
|
||||
"90 (Flat)": "90° (Flach)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "Normal-Bild",
|
||||
"Negative image": "Negativ-Bild",
|
||||
"Format": "Format",
|
||||
"Trimming Format": "Beschnitt-Format",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "Beschnitt-Format",
|
||||
"External Light Filter": "Externes Lichtfilter",
|
||||
"Load Default": "Standard laden",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Themezer-Optionen",
|
||||
"Nsfw": "NSFW",
|
||||
"Page": "Seite",
|
||||
"Page %zu / %zu": "Seite %zu / %zu",
|
||||
"Enter Page Number": "Seitenzahl eingeben",
|
||||
"Bad Page": "Ungültige Seite",
|
||||
"Download theme?": "Theme herunterladen?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Lade JSON herunter",
|
||||
"Select asset to download for ": "Wähle Asset zum Download für ",
|
||||
|
||||
"Installing ": "Installiere ",
|
||||
"Uninstalling ": "Deinstalliere ",
|
||||
"Deleting ": "Lösche ",
|
||||
"Deleting": "Lösche",
|
||||
"Pasting ": "Füge ein ",
|
||||
"Pasting": "Füge ein",
|
||||
"Removing ": "Entferne ",
|
||||
"Scanning ": "Scanne ",
|
||||
"Creating ": "Erstelle ",
|
||||
"Copying ": "Kopiere ",
|
||||
"Trying to load ": "Lade ",
|
||||
"Downloading ": "Lade herunter ",
|
||||
"Downloaded ": "Heruntergeladen ",
|
||||
"Removed ": "Entfernt ",
|
||||
"Checking MD5": "Prüfe MD5",
|
||||
"Loading...": "Lade...",
|
||||
"Loading": "Lade",
|
||||
"Empty!": "Leer!",
|
||||
"Not Ready...": "Nicht bereit...",
|
||||
"Error loading page!": "Fehler beim Laden!",
|
||||
"Update avaliable: ": "Update verfügbar: ",
|
||||
"Download update: ": "Update herunterladen: ",
|
||||
"Updated to ": "Aktualisiert auf ",
|
||||
"Press OK to restart Sphaira": "OK drücken um Sphaira neuzustarten",
|
||||
"Restart Sphaira?": "Sphaira neustarten?",
|
||||
"Failed to download update": "Update-Download fehlgeschlagen",
|
||||
"Advanced": "Erweitert...",
|
||||
"Advanced Options": " Erweitert | Optionen",
|
||||
"Logging": "Protokollieren",
|
||||
"Replace hbmenu on exit": "hbmenu durch sphaira ersetzen",
|
||||
"Restore hbmenu?": "hbmenu wiederherstellen?",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Konnte /switch/hbmenu.nro nicht finden\nBitte hbmenu über den AppStore neu installieren",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Wiederherstellung fehlgeschlagen, bitte hbmenu neu herunterladen",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Wiederherstellung fehlgeschlagen, verwende stattdessen Sphaira",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu wiederhergestellt, Sphaira wird beendet",
|
||||
"Restored hbmenu": "hbmenu wiederhergestellt",
|
||||
"Delete Selected files?": "Ausgewählte Dateien löschen?",
|
||||
"Completely remove ": "Vollständig entfernen ",
|
||||
"Are you sure you want to delete ": "Wirklich löschen ",
|
||||
"Are you sure you wish to cancel?": "Wirklich abbrechen?",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Bei wiederholtem Auftreten bitte Issue erstellen."
|
||||
"Restore": "Wiederherstellen",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Die Datei /switch/hbmenu.nro fehlt.\nInstalliere hbmenu über den hb-AppStore.",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Fehler, hbmenu nicht wiederhergrstellt!\nInstalliere hbmenu über den hb-AppStore.",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Fehler, hbmenu nicht wiederhergrstellt!\nVerwende weiter sphaira",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu wurde wiederhergestellt, schließe sphaira",
|
||||
"Restored hbmenu": "hbmenu wurde wiederhergestellt",
|
||||
"Restart Sphaira?": "sphaira erneut starten?",
|
||||
"Press OK to restart Sphaira": "Drücke OK um sphaira erneut zustarten",
|
||||
"Text scroll speed": "Laufschrift Tempo",
|
||||
"Slow": "Niedrig",
|
||||
"Normal": "Mittel",
|
||||
"Fast": "Hoch",
|
||||
"Set right-side menu": "",
|
||||
"Install options": "",
|
||||
"Install Options": "",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "Warnungen anzeigen",
|
||||
"Install location": "Einhängepunkt",
|
||||
"System memory": "NAND Systemspeicher",
|
||||
"microSD card": "SD-Karte",
|
||||
"Boost CPU clock": "",
|
||||
"Allow downgrade": "",
|
||||
"Skip if already installed": "",
|
||||
"Ticket only": "",
|
||||
"Skip base": "",
|
||||
"Skip patch": "",
|
||||
"Skip dlc": "",
|
||||
"Skip data patch": "",
|
||||
"Skip ticket": "",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "",
|
||||
"Skip RSA NPDM verify": "",
|
||||
"Ignore distribution bit": "",
|
||||
"Convert to standard crypto": "",
|
||||
"Lower master key": "",
|
||||
"Lower system version": "",
|
||||
|
||||
"Homebrew": "hbmenu",
|
||||
"Apps": "hb-Apps",
|
||||
"Homebrew Options": " hbmenu | Optionen",
|
||||
"Hide Sphaira": "Verstecke sphaira",
|
||||
"Install Forwarder": "Forwarder installieren",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "Installiere Forwarder-NSP´s mit VORSICHT.\nEs erhöht das Risiko eines Konsolen-Banns!",
|
||||
"Installing Forwarder": "Installiere Forwarder",
|
||||
"Creating Program": "Erstelle Programm",
|
||||
"Creating Control": "Erstelle Control",
|
||||
"Creating Meta": "Erstelle Meta",
|
||||
"Writing Nca": "Schreibe NCA",
|
||||
"Updating ncm databse": "Aktualisiere NCM-Datenbank",
|
||||
"Pushing application record": "Übertrage Anwendungsdaten",
|
||||
"Failed to install forwarder": "Fehler beim installieren des Forwarders",
|
||||
"Unstarred ": "Favorit entfernt ",
|
||||
"Starred ": "Favorit ",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "hb-AppStore",
|
||||
"Appstore": "",
|
||||
"Store": "hb-Store",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Rubrik: %s | Sort.nach.: %s | Ordnung: %s",
|
||||
"AppStore Options": " hb-AppStore | Optionen",
|
||||
"Info": "Info",
|
||||
"Changelog": "Neuerungen",
|
||||
"Details": "Details",
|
||||
"version: %s": "Version: %s",
|
||||
"updated: %s": "Letztes Update am: %s",
|
||||
"category: %s": "Rubrik: %s",
|
||||
"extracted: %.2f MiB": "Größe: %.2f MiB",
|
||||
"app_dls: %s": "Anzahl Downloads: %s",
|
||||
"More by Author": "Weitere Apps des Entwicklers",
|
||||
"Leave Feedback": "Feedback hinterlassen",
|
||||
|
||||
"FileBrowser": "Datei-Manager",
|
||||
"Files": "Dateien",
|
||||
"%zd files": "%zd Dateien",
|
||||
"%zd dirs": "%zd Ordner",
|
||||
"File Options": "Datei - Ordner | Optionen",
|
||||
"Show Hidden": "Versteckte zeigen",
|
||||
"Folders First": "Ordner zuerst",
|
||||
"Hidden Last": "Versteckte zuletzt",
|
||||
"Cut": "Ausschneiden",
|
||||
"Copy": "Kopieren",
|
||||
"Copying ": "Kopiert wird: ",
|
||||
"Paste": "Einfügen",
|
||||
"Paste ": "Einfügen von: ",
|
||||
" file(s)?": " Datei/en?",
|
||||
"Pasting ": "Eingefügt wird: ",
|
||||
"Pasting": "Eingefügt wurde:",
|
||||
"Rename": "Umbenennen",
|
||||
"Set New File Name": "Neuen Dateinamen festlegen",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "Neue Datei",
|
||||
"Set File Name": "Dateiname festlegen",
|
||||
"Create Folder": "Neuer Ordner",
|
||||
"Set Folder Name": "Ordner umbenennen",
|
||||
"Creating ": "Erstellt wird: ",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "Als Text anzeigen",
|
||||
"Ignore read only": "Schreibschutz umgehen?",
|
||||
"Mount": "Einhängen",
|
||||
"Sd": "SD-Karte | Root-Verzeichnis",
|
||||
"Image System memory": "Album | NAND Systemspeicher",
|
||||
"Image microSD card": "Album | SD-Karte",
|
||||
"Empty...": "Keine Daten...",
|
||||
"Open with DayBreak?": "Mit Daybreak öffnen?",
|
||||
"Launch ": "Starte ",
|
||||
"Launch option for: ": "Start Option für: ",
|
||||
"Select launcher for: ": "Wähle Launcher für: ",
|
||||
|
||||
"Sort By": "Sortierung",
|
||||
"Sort Options": " Sortierung | Optionen",
|
||||
"Filter": "Rubrik",
|
||||
"All": "Alles anzeigen",
|
||||
"Emulators": "Emulatoren",
|
||||
"Tools": "Tools",
|
||||
"Themes": "Themes",
|
||||
"Legacy": "Älteres",
|
||||
"Sort": "Sortiert nach",
|
||||
"Size": "Größe",
|
||||
"Size (Star)": "Favorit | Größe",
|
||||
"Alphabetical": "Name",
|
||||
"Alphabetical (Star)": "Favorit | Name",
|
||||
"Updated": "zuletzt aktualisiert",
|
||||
"Updated (Star)": "Favorit | zuletzt aktualisiert",
|
||||
"Downloads": "Downloads",
|
||||
"Likes": "Beliebtheit",
|
||||
"ID": "Theme | Paket ID",
|
||||
"Order": "Anordnung",
|
||||
"Descending": "Absteigend ↓",
|
||||
"Descending (down)": "Absteigend ↓",
|
||||
"Desc": " ↓",
|
||||
"Ascending": "Aufsteigend ↑",
|
||||
"Ascending (Up)": "Aufsteigend ↑",
|
||||
"Asc": " ↑",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Search": "Suchen",
|
||||
|
||||
"Options": "Optionen",
|
||||
"OK": "OK",
|
||||
"Back": "Zurück",
|
||||
"Select": "Auswählen",
|
||||
"Open": "Öffne",
|
||||
"Launch": "Starte",
|
||||
"Restart": "Neustart",
|
||||
"Next": "",
|
||||
"Prev": "",
|
||||
"Unstar": "Kein Favorit",
|
||||
"Star": "Favorit",
|
||||
"Yes": "Ja",
|
||||
"No": "Nein",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
|
||||
"Install": "Installieren",
|
||||
"Install Selected files?": "",
|
||||
"Installing ": "Installiert wird: ",
|
||||
"Installed ": "",
|
||||
"Installed!": "Installiert!",
|
||||
"Trying to load ": "Versucht zu laden wird: ",
|
||||
"Checking MD5": "Checke MD5 Prüfsumme",
|
||||
|
||||
"Delete": "Löschen",
|
||||
"Delete Selected files?": "Ausgewähle Dateien löschen?",
|
||||
"Are you sure you want to delete ": "Bist du sicher zu löschen? Bestätige Löschung von: ",
|
||||
"Scanning ": "Gescannt wird: ",
|
||||
"Deleting ": "Gelöscht wird: ",
|
||||
"Deleting": "Gelöscht wurde:",
|
||||
"Remove": "Entfernen",
|
||||
"Completely remove ": "Komplett gelöscht wird: ",
|
||||
"Removing ": "Entfernt wird: ",
|
||||
"Removed ": "Entfernt wurde: ",
|
||||
"Uninstalling ": "Deinstalliert wird: ",
|
||||
|
||||
"Download": "Download",
|
||||
"Downloading ": "Heruntergeladen wird: ",
|
||||
"Downloaded ": "Heruntergeladen wurde: ",
|
||||
|
||||
"Update": "Update",
|
||||
"Update avaliable: ": "Update verfügbar: ",
|
||||
"Download update: ": " Herunterladen des Updates: ",
|
||||
"Updated to ": "Aktualisiert auf: ",
|
||||
"Failed to download update": "Herunterladen des Updates fehlgeschlagen!",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "Wird geladen...",
|
||||
"Loading": "Wird geladen",
|
||||
"Empty!": "Keine Daten!",
|
||||
"Not Ready...": "Nicht bereit...",
|
||||
"Error loading page!": "Ladefehler!"
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Applet Mode]",
|
||||
"No Internet": "No Internet",
|
||||
"Files": "Files",
|
||||
"Apps": "Apps",
|
||||
"Store": "Store",
|
||||
"Menu": "Menu",
|
||||
"Options": "Options",
|
||||
"OK": "OK",
|
||||
"Back": "Back",
|
||||
"Select": "Select",
|
||||
"Open": "Open",
|
||||
"Launch": "Launch",
|
||||
"Info": "Info",
|
||||
"Install": "Install",
|
||||
"Delete": "Delete",
|
||||
"Restart": "Restart",
|
||||
"Changelog": "Changelog",
|
||||
"Details": "Details",
|
||||
"Update": "Update",
|
||||
"Remove": "Remove",
|
||||
"Restore": "Restore",
|
||||
"Download": "Download",
|
||||
"Next Page": "Next Page",
|
||||
"Prev Page": "Prev Page",
|
||||
"Unstar": "Unstar",
|
||||
"Star": "Star",
|
||||
"System memory": "System memory",
|
||||
"microSD card": "microSD card",
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Image System memory",
|
||||
"Image microSD card": "Image microSD card",
|
||||
"Slow": "Slow",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Fast",
|
||||
"Yes": "Yes",
|
||||
"No": "No",
|
||||
"Enabled": "Enabled",
|
||||
"Disabled": "Disabled",
|
||||
|
||||
"Sort By": "Sort By",
|
||||
"Sort Options": "Sort Options",
|
||||
"Filter": "Filter",
|
||||
"Sort": "Sort",
|
||||
"Order": "Order",
|
||||
"Search": "Search",
|
||||
"Updated": "Updated",
|
||||
"Updated (Star)": "Updated (Star)",
|
||||
"Downloads": "Downloads",
|
||||
"Size": "Size",
|
||||
"Size (Star)": "Size (Star)",
|
||||
"Alphabetical": "Alphabetical",
|
||||
"Alphabetical (Star)": "Alphabetical (Star)",
|
||||
"Likes": "Likes",
|
||||
"ID": "ID",
|
||||
"Descending": "Descending",
|
||||
"Descending (down)": "Descending (down)",
|
||||
"Desc": "Desc",
|
||||
"Ascending": "Ascending",
|
||||
"Ascending (Up)": "Ascending (Up)",
|
||||
"Asc": "Asc",
|
||||
"Switch-Handheld!": "Switch-Handheld!",
|
||||
"Switch-Docked!": "Switch-Docked!",
|
||||
"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",
|
||||
"Menu": "Menu",
|
||||
"Theme": "Theme",
|
||||
"Theme Options": "Theme Options",
|
||||
"Select Theme": "Select Theme",
|
||||
"Shuffle": "Shuffle",
|
||||
"Music": "Music",
|
||||
"12 Hour Time": "12 Hour Time",
|
||||
"Download Default Music": "Download Default Music",
|
||||
"Failed to download default_music.bfstm, please try again": "Failed to download default_music.bfstm, please try again",
|
||||
"Overwrite current default music?": "Overwrite current default music?",
|
||||
|
||||
"Network": "Network",
|
||||
"Network Options": "Network Options",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "Nxlink Connected",
|
||||
"Nxlink Upload": "Nxlink Upload",
|
||||
"Nxlink Finished": "Nxlink Finished",
|
||||
"Switch-Handheld!": "Switch-Handheld!",
|
||||
"Switch-Docked!": "Switch-Docked!",
|
||||
|
||||
"Language": "Language",
|
||||
"Auto": "Auto",
|
||||
"English": "English",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "Replace hbmenu on exit",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Misc",
|
||||
"Misc Options": "Misc Options",
|
||||
"Web": "Web",
|
||||
"Install forwarders": "Install forwarders",
|
||||
"Install location": "Install location",
|
||||
"Show install warning": "Show install warning",
|
||||
"Text scroll speed": "Text scroll speed",
|
||||
|
||||
"FileBrowser": "FileBrowser",
|
||||
"%zd files": "%zd files",
|
||||
"%zd dirs": "%zd dirs",
|
||||
"File Options": "File Options",
|
||||
"Show Hidden": "Show Hidden",
|
||||
"Folders First": "Folders First",
|
||||
"Hidden Last": "Hidden Last",
|
||||
"Cut": "Cut",
|
||||
"Copy": "Copy",
|
||||
"Paste": "Paste",
|
||||
"Paste ": "Paste ",
|
||||
" file(s)?": " file(s)?",
|
||||
"Rename": "Rename",
|
||||
"Set New File Name": "Set New File Name",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Options": "Advanced Options",
|
||||
"Create File": "Create File",
|
||||
"Set File Name": "Set File Name",
|
||||
"Create Folder": "Create Folder",
|
||||
"Set Folder Name": "Set Folder Name",
|
||||
"View as text (unfinished)": "View as text (unfinished)",
|
||||
"Ignore read only": "Ignore read only",
|
||||
"Mount": "Mount",
|
||||
"Empty...": "Empty...",
|
||||
"Open with DayBreak?": "Open with DayBreak?",
|
||||
"Launch ": "Launch ",
|
||||
"Launch option for: ": "Launch option for: ",
|
||||
"Select launcher for: ": "Select launcher for: ",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Homebrew Options",
|
||||
"Hide Sphaira": "Hide 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",
|
||||
"Writing Nca": "Writing Nca",
|
||||
"Updating ncm databse": "Updating ncm databse",
|
||||
"Pushing application record": "Pushing application record",
|
||||
"Installed!": "Installed!",
|
||||
"Failed to install forwarder": "Failed to install forwarder",
|
||||
"Unstarred ": "Unstarred ",
|
||||
"Starred ": "Starred ",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sort: %s | Order: %s",
|
||||
"AppStore Options": "AppStore Options",
|
||||
"All": "All",
|
||||
"Games": "Games",
|
||||
"Emulators": "Emulators",
|
||||
"Tools": "Tools",
|
||||
"Themes": "Themes",
|
||||
"Legacy": "Legacy",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "More by Author",
|
||||
"Leave Feedback": "Leave Feedback",
|
||||
"Game Options": "Game Options",
|
||||
"Hide forwarders": "Hide forwarders",
|
||||
"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",
|
||||
"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",
|
||||
"Dump AddOnContent": "Dump AddOnContent",
|
||||
"Dump DataPatch": "Dump DataPatch",
|
||||
"Select dump location": "Select dump location",
|
||||
"microSD card (/dumps/NSP/)": "microSD card (/dumps/NSP/)",
|
||||
"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": "Success",
|
||||
"Delete successfull!": "Delete successfull!",
|
||||
"Delete failed!": "Delete failed!",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Themezer Options",
|
||||
"Nsfw": "Nsfw",
|
||||
"Page": "Page",
|
||||
"Page %zu / %zu": "Page %zu / %zu",
|
||||
"Enter Page Number": "Enter Page Number",
|
||||
"Bad Page": "Bad Page",
|
||||
"Download theme?": "Download theme?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Downloading json",
|
||||
"Select asset to download for ": "Select asset to download for ",
|
||||
|
||||
"FTP Install": "FTP Install",
|
||||
"FTP Install (EXPERIMENTAL)": "FTP Install (EXPERIMENTAL)",
|
||||
"Connection Type: WiFi | Strength: ": "Connection Type: WiFi | Strength: ",
|
||||
"Connection Type: Ethernet": "Connection Type: Ethernet",
|
||||
"Connection Type: None": "Connection Type: None",
|
||||
"Host:": "Host:",
|
||||
"Port:": "Port:",
|
||||
"Username:": "Username:",
|
||||
"Password:": "Password:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "Passphrase",
|
||||
"Failed to install via FTP, press B to exit...": "Failed to install via FTP, press to exit...",
|
||||
"Ftp install success!": "Ftp install success!",
|
||||
"Ftp install failed!": "Ftp install failed!",
|
||||
"USB Install": "USB Install",
|
||||
"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...": "Failed to init usb, press to exit...",
|
||||
"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...": "waiting for usb connection...",
|
||||
"Disable MTP for usb install": "Disable MTP for usb install",
|
||||
"Re-enabled MTP": "Re-enabled MTP",
|
||||
"Installed via usb": "Installed via usb",
|
||||
"Usb install success!": "Usb install success!",
|
||||
"Usb install failed!": "Usb install failed!",
|
||||
"Press B to exit...": "Press to exit...",
|
||||
"GameCard Install": "GameCard Install",
|
||||
"GameCard": "GameCard",
|
||||
"GC": "GC",
|
||||
"System memory %.1f GB": "System memory %.1f GB",
|
||||
"microSD card %.1f GB": "microSD card %.1f GB",
|
||||
"Nand Install": "Nand Install",
|
||||
"SD Card Install": "SD Card Install",
|
||||
"Exit": "Exit",
|
||||
"Gc install success!": "Gc install success!",
|
||||
"Gc install failed!": "Gc install failed!",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "IRS (Infrared Joycon Camera)",
|
||||
"IRS": "IRS",
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "Ambient Noise Level: ",
|
||||
"Controller": "Controller",
|
||||
"Pad ": "Pad ",
|
||||
"HandHeld": "HandHeld",
|
||||
" (Available)": " (Available)",
|
||||
" (Unsupported)": " (Unsupported)",
|
||||
" (Unconnected)": " (Unconnected)",
|
||||
"HandHeld": "HandHeld",
|
||||
"Rotation": "Rotation",
|
||||
"0 (Sideways)": "0 (Sideways)",
|
||||
"90 (Flat)": "90 (Flat)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"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",
|
||||
"Trimming Format": "Trimming Format",
|
||||
"External Light Filter": "External Light Filter",
|
||||
"Load Default": "Load Default",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Themezer Options",
|
||||
"Nsfw": "Nsfw",
|
||||
"Page": "Page",
|
||||
"Page %zu / %zu": "Page %zu / %zu",
|
||||
"Enter Page Number": "Enter Page Number",
|
||||
"Bad Page": "Bad Page",
|
||||
"Download theme?": "Download theme?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Downloading json",
|
||||
"Select asset to download for ": "Select asset to download for ",
|
||||
|
||||
"Installing ": "Installing ",
|
||||
"Uninstalling ": "Uninstalling ",
|
||||
"Deleting ": "Deleting ",
|
||||
"Deleting": "Deleting",
|
||||
"Pasting ": "Pasting ",
|
||||
"Pasting": "Pasting",
|
||||
"Removing ": "Removing ",
|
||||
"Scanning ": "Scanning ",
|
||||
"Creating ": "Creating ",
|
||||
"Copying ": "Copying ",
|
||||
"Trying to load ": "Trying to load ",
|
||||
"Downloading ": "Downloading ",
|
||||
"Downloaded ": "Downloaded ",
|
||||
"Removed ": "Removed ",
|
||||
"Checking MD5": "Checking MD5",
|
||||
"Loading...": "Loading...",
|
||||
"Loading": "Loading",
|
||||
"Empty!": "Empty!",
|
||||
"Not Ready...": "Not Ready...",
|
||||
"Error loading page!": "Error loading page!",
|
||||
"Update avaliable: ": "Update avaliable: ",
|
||||
"Download update: ": "Download update: ",
|
||||
"Updated to ": "Updated to ",
|
||||
"Press OK to restart Sphaira": "Press OK to restart Sphaira",
|
||||
"Restart Sphaira?": "Restart Sphaira?",
|
||||
"Failed to download update": "Failed to download update",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Options": "Advanced Options",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "Replace hbmenu on exit",
|
||||
"Restore hbmenu?": "Restore hbmenu?",
|
||||
"Restore": "Restore",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Failed to restore hbmenu, please re-download hbmenu",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Failed to restore hbmenu, using sphaira instead",
|
||||
"Restored hbmenu, closing sphaira": "Restored hbmenu, closing sphaira",
|
||||
"Restored hbmenu": "Restored hbmenu",
|
||||
"Restart Sphaira?": "Restart Sphaira?",
|
||||
"Press OK to restart Sphaira": "Press OK to restart Sphaira",
|
||||
"Text scroll speed": "Text scroll speed",
|
||||
"Slow": "Slow",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Fast",
|
||||
"Set right-side menu": "Set right-side menu",
|
||||
"Install options": "Install options",
|
||||
"Install Options": "Install Options",
|
||||
"Enable sysmmc": "Enable sysmmc",
|
||||
"Enable emummc": "Enable emummc",
|
||||
"Show install warning": "Show install warning",
|
||||
"Install location": "Install location",
|
||||
"System memory": "System memory",
|
||||
"microSD card": "microSD card",
|
||||
"Boost CPU clock": "Boost CPU clock",
|
||||
"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": "Skip dlc",
|
||||
"Skip data patch": "Skip data patch",
|
||||
"Skip ticket": "Skip ticket",
|
||||
"Skip NCA hash verify": "Skip NCA hash verify",
|
||||
"Skip RSA header verify": "Skip RSA header verify",
|
||||
"Skip RSA NPDM verify": "Skip RSA NPDM verify",
|
||||
"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",
|
||||
"Apps": "Apps",
|
||||
"Homebrew Options": "Homebrew Options",
|
||||
"Hide Sphaira": "Hide 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",
|
||||
"Writing Nca": "Writing Nca",
|
||||
"Updating ncm databse": "Updating ncm databse",
|
||||
"Pushing application record": "Pushing application record",
|
||||
"Failed to install forwarder": "Failed to install forwarder",
|
||||
"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": "Filter: %s | Sort: %s | Order: %s",
|
||||
"AppStore Options": "AppStore Options",
|
||||
"Info": "Info",
|
||||
"Changelog": "Changelog",
|
||||
"Details": "Details",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "More by Author",
|
||||
"Leave Feedback": "Leave Feedback",
|
||||
|
||||
"FileBrowser": "FileBrowser",
|
||||
"Files": "Files",
|
||||
"%zd files": "%zd files",
|
||||
"%zd dirs": "%zd dirs",
|
||||
"File Options": "File Options",
|
||||
"Show Hidden": "Show Hidden",
|
||||
"Folders First": "Folders First",
|
||||
"Hidden Last": "Hidden Last",
|
||||
"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",
|
||||
"Extract zip": "Extract zip",
|
||||
"Extract Options": "Extract Options",
|
||||
"Extract here": "Extract here",
|
||||
"Extract to root": "Extract to root",
|
||||
"Are you sure you want to extract to root?": "Are you sure you want to extract to root?",
|
||||
"Extract to...": "Extract to...",
|
||||
"Enter the path to the folder to extract into": "Enter the path to the folder to extract into",
|
||||
"Extracting ": "Extracting ",
|
||||
"Extract success!": "Extract success!",
|
||||
"Extract failed!": "Extract failed!",
|
||||
"Compress to zip": "Compress to 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": "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)": "View as text (unfinished)",
|
||||
"Ignore read only": "Ignore read only",
|
||||
"Mount": "Mount",
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Image System memory",
|
||||
"Image microSD card": "Image microSD card",
|
||||
"Empty...": "Empty...",
|
||||
"Open with DayBreak?": "Open with DayBreak?",
|
||||
"Launch ": "Launch ",
|
||||
"Launch option for: ": "Launch option for: ",
|
||||
"Select launcher for: ": "Select launcher for: ",
|
||||
|
||||
"Sort By": "Sort By",
|
||||
"Sort Options": "Sort Options",
|
||||
"Filter": "Filter",
|
||||
"All": "All",
|
||||
"Emulators": "Emulators",
|
||||
"Tools": "Tools",
|
||||
"Themes": "Themes",
|
||||
"Legacy": "Legacy",
|
||||
"Sort": "Sort",
|
||||
"Size": "Size",
|
||||
"Size (Star)": "Size (Star)",
|
||||
"Alphabetical": "Alphabetical",
|
||||
"Alphabetical (Star)": "Alphabetical (Star)",
|
||||
"Updated": "Updated",
|
||||
"Updated (Star)": "Updated (Star)",
|
||||
"Downloads": "Downloads",
|
||||
"Likes": "Likes",
|
||||
"ID": "ID",
|
||||
"Order": "Order",
|
||||
"Descending": "Descending",
|
||||
"Descending (down)": "Descending (down)",
|
||||
"Desc": "Desc",
|
||||
"Ascending": "Ascending",
|
||||
"Ascending (Up)": "Ascending (Up)",
|
||||
"Asc": "Asc",
|
||||
"Layout": "Layout",
|
||||
"List": "List",
|
||||
"Icon": "Icon",
|
||||
"Grid": "Grid",
|
||||
"Search": "Search",
|
||||
|
||||
"Options": "Options",
|
||||
"OK": "OK",
|
||||
"Back": "Back",
|
||||
"Select": "Select",
|
||||
"Open": "Open",
|
||||
"Launch": "Launch",
|
||||
"Restart": "Restart",
|
||||
"Next": "Next",
|
||||
"Prev": "Prev",
|
||||
"Unstar": "Unstar",
|
||||
"Star": "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": "Checking MD5",
|
||||
|
||||
"Delete": "Delete",
|
||||
"Delete Selected files?": "Delete Selected files?",
|
||||
"Completely remove ": "Completely remove ",
|
||||
"Are you sure you want to delete ": "Are you sure you want to delete ",
|
||||
"Are you sure you wish to cancel?": "Are you sure you wish to cancel?",
|
||||
"Audio disabled due to suspended game": "Audio disabled due to suspended game",
|
||||
"If this message appears repeatedly, please open an issue.": "If this message appears repeatedly, please open an issue."
|
||||
"Scanning ": "Scanning ",
|
||||
"Deleting ": "Deleting ",
|
||||
"Deleting": "Deleting",
|
||||
"Remove": "Remove",
|
||||
"Completely remove ": "Completely remove ",
|
||||
"Removing ": "Removing ",
|
||||
"Removed ": "Removed ",
|
||||
"Uninstalling ": "Uninstalling ",
|
||||
|
||||
"Download": "Download",
|
||||
"Downloading ": "Downloading ",
|
||||
"Downloaded ": "Downloaded ",
|
||||
|
||||
"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 hours %zu minutes remaining",
|
||||
"%zu minutes %zu seconds remaining": "%zu minutes %zu seconds remaining",
|
||||
"%zu seconds remaining": "%zu seconds remaining",
|
||||
|
||||
"Loading...": "Loading...",
|
||||
"Loading": "Loading",
|
||||
"Empty!": "Empty!",
|
||||
"Not Ready...": "Not Ready...",
|
||||
"Error loading page!": "Error loading page!"
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Modo Applet]",
|
||||
"No Internet": "Sin Internet",
|
||||
"Files": "Archivos",
|
||||
"Apps": "Apps",
|
||||
"Store": "Tienda",
|
||||
"Menu": "Menú",
|
||||
"Options": "Opciones",
|
||||
"OK": "OK",
|
||||
"Back": "Atrás",
|
||||
"Select": "Seleccionar",
|
||||
"Open": "Abrir",
|
||||
"Launch": "Ejecutar",
|
||||
"Info": "Información",
|
||||
"Install": "Instalar",
|
||||
"Delete": "Borrar",
|
||||
"Restart": "Reiniciar",
|
||||
"Changelog": "Log de cambios",
|
||||
"Details": "Detalles",
|
||||
"Update": "Actualizar",
|
||||
"Remove": "Borrar",
|
||||
"Restore": "Restaurar",
|
||||
"Download": "Descargar",
|
||||
"Next Page": "Página siguiente",
|
||||
"Prev Page": "Página anterior",
|
||||
"Unstar": "Quitar favorito",
|
||||
"Star": "Favorito",
|
||||
"System memory": "Memoria de sistema",
|
||||
"microSD card": "microSD",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Imagen memoria interna",
|
||||
"Image microSD card": "Imagen tarjeta microSD",
|
||||
"Slow": "Lento",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rápido",
|
||||
"Yes": "Sí",
|
||||
"No": "No",
|
||||
"Enabled": "Activado",
|
||||
"Disabled": "Desactivado",
|
||||
|
||||
"Sort By": "Ordenar por",
|
||||
"Sort Options": "Opciones de clasificación",
|
||||
"Filter": "Filtrar",
|
||||
"Sort": "Clasificar",
|
||||
"Order": "Orden",
|
||||
"Search": "Buscar",
|
||||
"Updated": "Actualizado",
|
||||
"Updated (Star)": "Actualizado (favorito)",
|
||||
"Downloads": "Descargas",
|
||||
"Size": "Tamaño",
|
||||
"Size (Star)": "Tamaño (favorito)",
|
||||
"Alphabetical": "Alfabético",
|
||||
"Alphabetical (Star)": "Alfabético (favorito)",
|
||||
"Likes": "Me Gusta",
|
||||
"ID": "ID",
|
||||
"Descending": "Descendente",
|
||||
"Descending (down)": "Descendente (abajo)",
|
||||
"Desc": "Descendente",
|
||||
"Ascending": "Ascendente",
|
||||
"Ascending (Up)": "Ascendente (arriba)",
|
||||
"Asc": "Ascendente",
|
||||
"Switch-Handheld!": "¡Switch-Modo-Portátil!",
|
||||
"Switch-Docked!": "¡Switch-Modo-TV!",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Si este mensaje aparece repetidamente, por favor abrir un 'issue'.",
|
||||
|
||||
"Menu Options": "Opciones de menú",
|
||||
"Menu": "Menú",
|
||||
"Theme": "Tema",
|
||||
"Theme Options": "Opciones de tema",
|
||||
"Select Theme": "Seleccionar tema",
|
||||
"Shuffle": "Barajar",
|
||||
"Music": "Música",
|
||||
"12 Hour Time": "",
|
||||
"Download Default Music": "",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "Red",
|
||||
"Network Options": "Opciones de red",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "NXlink conectado",
|
||||
"Nxlink Upload": "NXlink subida",
|
||||
"Nxlink Finished": "NXlink finalizado",
|
||||
"Switch-Handheld!": "¡Switch-Modo-Portátil!",
|
||||
"Switch-Docked!": "¡Switch-Modo-TV!",
|
||||
|
||||
"Language": "Idioma",
|
||||
"Auto": "Automático",
|
||||
"English": "English",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Registro",
|
||||
"Replace hbmenu on exit": "Reemplazar hbmenu",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Varios",
|
||||
"Misc Options": "Opciones varias",
|
||||
"Web": "Web",
|
||||
"Install forwarders": "Instalar forwarders",
|
||||
"Install location": "Dispositivo de instalación",
|
||||
"Show install warning": "Precaución de instalación",
|
||||
"Text scroll speed": "Velocidad de scroll",
|
||||
|
||||
"FileBrowser": "Explorador de archivos",
|
||||
"%zd files": "%zd archivos",
|
||||
"%zd dirs": "%zd carpetas",
|
||||
"File Options": "Opciones de archivo",
|
||||
"Show Hidden": "Mostrar archivos ocultos",
|
||||
"Folders First": "Carpetas primero",
|
||||
"Hidden Last": "Ocultos al final",
|
||||
"Cut": "Cortar",
|
||||
"Copy": "Copiar",
|
||||
"Paste": "Pegar",
|
||||
"Paste ": "Pegar ",
|
||||
" file(s)?": " ¿archivo(s)?",
|
||||
"Rename": "Renombrar",
|
||||
"Set New File Name": "Establecer nuevo nombre de archivo",
|
||||
"Advanced": "Avanzado",
|
||||
"Advanced Options": "Opciones avanzadas",
|
||||
"Create File": "Crear archivo",
|
||||
"Set File Name": "Establecer nombre de archivo",
|
||||
"Create Folder": "Crear carpeta",
|
||||
"Set Folder Name": "Establecer nombre de carpeta",
|
||||
"View as text (unfinished)": "Ver como texto (sin terminar)",
|
||||
"Ignore read only": "Ignorar sólo lectura",
|
||||
"Mount": "Montar",
|
||||
"Empty...": "Vacío...",
|
||||
"Open with DayBreak?": "¿Abrir con DayBreak?",
|
||||
"Launch ": "Abrir ",
|
||||
"Launch option for: ": "Opción de abrir con: ",
|
||||
"Select launcher for: ": "Seleccionar abrir con: ",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Opciones de Homebrew",
|
||||
"Hide Sphaira": "Ocultar Sphaira",
|
||||
"Install Forwarder": "Instalar Forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ADVERTENCIA: ¡La instalación de fordwarders podría producir un baneo de la consola!",
|
||||
"Installing Forwarder": "Instalando Forwarder",
|
||||
"Creating Program": "Creando Program",
|
||||
"Creating Control": "Creando Control",
|
||||
"Creating Meta": "Creando Meta",
|
||||
"Writing Nca": "Creando NCA",
|
||||
"Updating ncm databse": "Actualizando base de datos ncm",
|
||||
"Pushing application record": "Registro de aplicación",
|
||||
"Installed!": "¡Instalado!",
|
||||
"Failed to install forwarder": "Fallo al instalar forwarder",
|
||||
"Unstarred ": "Quitar Favorito",
|
||||
"Starred ": "Favorito",
|
||||
|
||||
"AppStore": "Tienda",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtrar: %s | Clasificar: %s | Orden: %s",
|
||||
"AppStore Options": "Opciones de la Tienda",
|
||||
"All": "Todo",
|
||||
"Games": "Juegos",
|
||||
"Emulators": "Emuladores",
|
||||
"Tools": "Herramientas",
|
||||
"Themes": "Temas",
|
||||
"Legacy": "Legado",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "actualizado: %s",
|
||||
"category: %s": "categoría: %s",
|
||||
"extracted: %.2f MiB": "extraído: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "Mostrar mas del Autor",
|
||||
"Leave Feedback": "Dejar Mensaje",
|
||||
"Game Options": "",
|
||||
"Hide forwarders": "",
|
||||
"Launch random game": "",
|
||||
"List meta records": "",
|
||||
"Entries": "",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/NSP/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Success": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Opciones de Themezer",
|
||||
"Nsfw": "NSFW",
|
||||
"Page": "Página",
|
||||
"Page %zu / %zu": "Pág. %zu / %zu",
|
||||
"Enter Page Number": "Ingresar Número de Página",
|
||||
"Bad Page": "Página Errónea",
|
||||
"Download theme?": "¿Descargar Tema?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Descargando json",
|
||||
"Select asset to download for ": "Seleccionar recurso a descargar para ",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
"Host:": "",
|
||||
"Port:": "",
|
||||
"Username:": "",
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "",
|
||||
"Waiting for connection...": "",
|
||||
"Transferring data...": "",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "",
|
||||
"Re-enabled MTP": "",
|
||||
"Installed via usb": "",
|
||||
"Usb install success!": "",
|
||||
"Usb install failed!": "",
|
||||
"Press B to exit...": "",
|
||||
"GameCard Install": "",
|
||||
"GameCard": "",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "",
|
||||
"Gc install failed!": "",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "",
|
||||
"IRS": "",
|
||||
"Irs": "IRS",
|
||||
"Ambient Noise Level: ": "Nivel de Ruido Ambiente",
|
||||
"Controller": "Control",
|
||||
"Pad ": "GamePad ",
|
||||
"HandHeld": "Portátil",
|
||||
" (Available)": " (Disponible)",
|
||||
" (Unsupported)": "(No Compatible)",
|
||||
" (Unconnected)": " (Desconectado)",
|
||||
"HandHeld": "Portátil",
|
||||
"Rotation": "Rotación",
|
||||
"0 (Sideways)": "0° (De lado)",
|
||||
"90 (Flat)": "90° (Plano)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "Imagen normal",
|
||||
"Negative image": "Imagen negativa",
|
||||
"Format": "Formato",
|
||||
"Trimming Format": "Formato de recorte",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "Formato de recorte",
|
||||
"External Light Filter": "Filtro de luz externa",
|
||||
"Load Default": "Cargar predeterminado",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Opciones de Themezer",
|
||||
"Nsfw": "NSFW",
|
||||
"Page": "Página",
|
||||
"Page %zu / %zu": "Pág. %zu / %zu",
|
||||
"Enter Page Number": "Ingresar Número de Página",
|
||||
"Bad Page": "Página Errónea",
|
||||
"Download theme?": "¿Descargar Tema?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Descargando json",
|
||||
"Select asset to download for ": "Seleccionar recurso a descargar para ",
|
||||
|
||||
"Installing ": "Instalando ",
|
||||
"Uninstalling ": "Desinstalando ",
|
||||
"Deleting ": "Borrando ",
|
||||
"Deleting": "Borrando",
|
||||
"Pasting ": "Pegando ",
|
||||
"Pasting": "Pegando",
|
||||
"Removing ": "Removiendo ",
|
||||
"Scanning ": "Escaneando ",
|
||||
"Creating ": "Creando ",
|
||||
"Copying ": "Copiando ",
|
||||
"Trying to load ": "Intentando cargar ",
|
||||
"Downloading ": "Descargando ",
|
||||
"Downloaded ": "Descargado ",
|
||||
"Removed ": "Removido ",
|
||||
"Checking MD5": "Chequeando MD5",
|
||||
"Loading...": "Cargando...",
|
||||
"Loading": "Cargando",
|
||||
"Empty!": "¡Vacío!",
|
||||
"Not Ready...": "No listo aún...",
|
||||
"Error loading page!": "¡Error cargando la página!",
|
||||
"Update avaliable: ": "Actualización disponible: ",
|
||||
"Download update: ": "Descargar actualización: ",
|
||||
"Updated to ": "Actualizado a ",
|
||||
"Press OK to restart Sphaira": "Presiona OK para reiniciar sphaira",
|
||||
"Restart Sphaira?": "¿Reiniciar sphaira?",
|
||||
"Failed to download update": "Fallo al descargar actualización",
|
||||
"Advanced": "Avanzado",
|
||||
"Advanced Options": "Opciones avanzadas",
|
||||
"Logging": "Registro",
|
||||
"Replace hbmenu on exit": "Reemplazar hbmenu",
|
||||
"Restore hbmenu?": "¿Restaurar hbmenu?",
|
||||
"Restore": "Restaurar",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Fallo al encontrar /switch/hbmenu.nro\nUsar la Tienda para reinstalar hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Fallo al restaurar hbmenu, por favor volver a descargar hbmenu",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Fallo al restaurar hbmenu, se usará sphaira",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu restaurado, cerrando sphaira",
|
||||
"Restored hbmenu": "hbmenu restaurado",
|
||||
"Restart Sphaira?": "¿Reiniciar sphaira?",
|
||||
"Press OK to restart Sphaira": "Presiona OK para reiniciar sphaira",
|
||||
"Text scroll speed": "Velocidad de scroll",
|
||||
"Slow": "Lento",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rápido",
|
||||
"Set right-side menu": "",
|
||||
"Install options": "",
|
||||
"Install Options": "",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "Precaución de instalación",
|
||||
"Install location": "Dispositivo de instalación",
|
||||
"System memory": "Memoria de sistema",
|
||||
"microSD card": "microSD",
|
||||
"Boost CPU clock": "",
|
||||
"Allow downgrade": "",
|
||||
"Skip if already installed": "",
|
||||
"Ticket only": "",
|
||||
"Skip base": "",
|
||||
"Skip patch": "",
|
||||
"Skip dlc": "",
|
||||
"Skip data patch": "",
|
||||
"Skip ticket": "",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "",
|
||||
"Skip RSA NPDM verify": "",
|
||||
"Ignore distribution bit": "",
|
||||
"Convert to standard crypto": "",
|
||||
"Lower master key": "",
|
||||
"Lower system version": "",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Apps": "Apps",
|
||||
"Homebrew Options": "Opciones de Homebrew",
|
||||
"Hide Sphaira": "Ocultar Sphaira",
|
||||
"Install Forwarder": "Instalar Forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ADVERTENCIA: ¡La instalación de fordwarders podría producir un baneo de la consola!",
|
||||
"Installing Forwarder": "Instalando Forwarder",
|
||||
"Creating Program": "Creando Program",
|
||||
"Creating Control": "Creando Control",
|
||||
"Creating Meta": "Creando Meta",
|
||||
"Writing Nca": "Creando NCA",
|
||||
"Updating ncm databse": "Actualizando base de datos ncm",
|
||||
"Pushing application record": "Registro de aplicación",
|
||||
"Failed to install forwarder": "Fallo al instalar forwarder",
|
||||
"Unstarred ": "Quitar Favorito",
|
||||
"Starred ": "Favorito",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "Tienda",
|
||||
"Appstore": "",
|
||||
"Store": "Tienda",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtrar: %s | Clasificar: %s | Orden: %s",
|
||||
"AppStore Options": "Opciones de la Tienda",
|
||||
"Info": "Información",
|
||||
"Changelog": "Log de cambios",
|
||||
"Details": "Detalles",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "actualizado: %s",
|
||||
"category: %s": "categoría: %s",
|
||||
"extracted: %.2f MiB": "extraído: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "Mostrar mas del Autor",
|
||||
"Leave Feedback": "Dejar Mensaje",
|
||||
|
||||
"FileBrowser": "Explorador de archivos",
|
||||
"Files": "Archivos",
|
||||
"%zd files": "%zd archivos",
|
||||
"%zd dirs": "%zd carpetas",
|
||||
"File Options": "Opciones de archivo",
|
||||
"Show Hidden": "Mostrar archivos ocultos",
|
||||
"Folders First": "Carpetas primero",
|
||||
"Hidden Last": "Ocultos al final",
|
||||
"Cut": "Cortar",
|
||||
"Copy": "Copiar",
|
||||
"Copying ": "Copiando ",
|
||||
"Paste": "Pegar",
|
||||
"Paste ": "Pegar ",
|
||||
" file(s)?": " ¿archivo(s)?",
|
||||
"Pasting ": "Pegando ",
|
||||
"Pasting": "Pegando",
|
||||
"Rename": "Renombrar",
|
||||
"Set New File Name": "Establecer nuevo nombre de archivo",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "Crear archivo",
|
||||
"Set File Name": "Establecer nombre de archivo",
|
||||
"Create Folder": "Crear carpeta",
|
||||
"Set Folder Name": "Establecer nombre de carpeta",
|
||||
"Creating ": "Creando ",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "Ver como texto (sin terminar)",
|
||||
"Ignore read only": "Ignorar sólo lectura",
|
||||
"Mount": "Montar",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Imagen memoria interna",
|
||||
"Image microSD card": "Imagen tarjeta microSD",
|
||||
"Empty...": "Vacío...",
|
||||
"Open with DayBreak?": "¿Abrir con DayBreak?",
|
||||
"Launch ": "Abrir ",
|
||||
"Launch option for: ": "Opción de abrir con: ",
|
||||
"Select launcher for: ": "Seleccionar abrir con: ",
|
||||
|
||||
"Sort By": "Ordenar por",
|
||||
"Sort Options": "Opciones de clasificación",
|
||||
"Filter": "Filtrar",
|
||||
"All": "Todo",
|
||||
"Emulators": "Emuladores",
|
||||
"Tools": "Herramientas",
|
||||
"Themes": "Temas",
|
||||
"Legacy": "Legado",
|
||||
"Sort": "Clasificar",
|
||||
"Size": "Tamaño",
|
||||
"Size (Star)": "Tamaño (favorito)",
|
||||
"Alphabetical": "Alfabético",
|
||||
"Alphabetical (Star)": "Alfabético (favorito)",
|
||||
"Updated": "Actualizado",
|
||||
"Updated (Star)": "Actualizado (favorito)",
|
||||
"Downloads": "Descargas",
|
||||
"Likes": "Me Gusta",
|
||||
"ID": "ID",
|
||||
"Order": "Orden",
|
||||
"Descending": "Descendente",
|
||||
"Descending (down)": "Descendente (abajo)",
|
||||
"Desc": "Descendente",
|
||||
"Ascending": "Ascendente",
|
||||
"Ascending (Up)": "Ascendente (arriba)",
|
||||
"Asc": "Ascendente",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Search": "Buscar",
|
||||
|
||||
"Options": "Opciones",
|
||||
"OK": "OK",
|
||||
"Back": "Atrás",
|
||||
"Select": "Seleccionar",
|
||||
"Open": "Abrir",
|
||||
"Launch": "Ejecutar",
|
||||
"Restart": "Reiniciar",
|
||||
"Next": "",
|
||||
"Prev": "",
|
||||
"Unstar": "Quitar favorito",
|
||||
"Star": "Favorito",
|
||||
"Yes": "Sí",
|
||||
"No": "No",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
|
||||
"Install": "Instalar",
|
||||
"Install Selected files?": "",
|
||||
"Installing ": "Instalando ",
|
||||
"Installed ": "",
|
||||
"Installed!": "¡Instalado!",
|
||||
"Trying to load ": "Intentando cargar ",
|
||||
"Checking MD5": "Chequeando MD5",
|
||||
|
||||
"Delete": "Borrar",
|
||||
"Delete Selected files?": "¿Eliminar archivos seleccionados?",
|
||||
"Completely remove ": "Eliminar completamente",
|
||||
"Are you sure you want to delete ": "¿Estás seguro que quieres eliminar? ",
|
||||
"Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Si este mensaje aparece repetidamente, por favor abrir un 'issue'."
|
||||
"Scanning ": "Escaneando ",
|
||||
"Deleting ": "Borrando ",
|
||||
"Deleting": "Borrando",
|
||||
"Remove": "Borrar",
|
||||
"Completely remove ": "Eliminar completamente",
|
||||
"Removing ": "Removiendo ",
|
||||
"Removed ": "Removido ",
|
||||
"Uninstalling ": "Desinstalando ",
|
||||
|
||||
"Download": "Descargar",
|
||||
"Downloading ": "Descargando ",
|
||||
"Downloaded ": "Descargado ",
|
||||
|
||||
"Update": "Actualizar",
|
||||
"Update avaliable: ": "Actualización disponible: ",
|
||||
"Download update: ": "Descargar actualización: ",
|
||||
"Updated to ": "Actualizado a ",
|
||||
"Failed to download update": "Fallo al descargar actualización",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "Cargando...",
|
||||
"Loading": "Cargando",
|
||||
"Empty!": "¡Vacío!",
|
||||
"Not Ready...": "No listo aún...",
|
||||
"Error loading page!": "¡Error cargando la página!"
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Mode Applet]",
|
||||
"No Internet": "Pas d'Internet",
|
||||
"Files": "Fichiers",
|
||||
"Apps": "Applications",
|
||||
"Store": "Magasin",
|
||||
"Menu": "Menu",
|
||||
"Options": "Options",
|
||||
"OK": "OK",
|
||||
"Back": "Retour",
|
||||
"Select": "Sélectionner",
|
||||
"Open": "Ouvrir",
|
||||
"Launch": "Exécuter",
|
||||
"Info": "Info.",
|
||||
"Install": "Installer",
|
||||
"Delete": "Supprimer",
|
||||
"Restart": "Redémarrer",
|
||||
"Changelog": "Changelog",
|
||||
"Details": "Détails",
|
||||
"Update": "Mise à jour",
|
||||
"Remove": "Supprimer",
|
||||
"Restore": "Restaurer",
|
||||
"Download": "Télécharger",
|
||||
"Next Page": "Page Suiv.",
|
||||
"Prev Page": "Page Préc.",
|
||||
"Unstar": "Retirer des favories",
|
||||
"Star": "Ajouter aux favories",
|
||||
"System memory": "Mémoire système",
|
||||
"microSD card": "Carte microSD",
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Image de la mémoire System",
|
||||
"Image microSD card": "Image de la Carte microSD",
|
||||
"Slow": "Lent",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rapide",
|
||||
"Yes": "Oui",
|
||||
"No": "Non",
|
||||
"Enabled": "Activé(e)",
|
||||
"Disabled": "Désactivé(e)",
|
||||
|
||||
"Sort By": "Tri Par",
|
||||
"Sort Options": "Options de Tri",
|
||||
"Filter": "Filtre",
|
||||
"Sort": "Tri",
|
||||
"Order": "Ordre",
|
||||
"Search": "Recherche",
|
||||
"Updated": "Mis à jour",
|
||||
"Updated (Star)": "Mis à jour (Favories)",
|
||||
"Downloads": "Téléchargements",
|
||||
"Size": "Taille",
|
||||
"Size (Star)": "Taille (Favories)",
|
||||
"Alphabetical": "Alphabétique",
|
||||
"Alphabetical (Star)": "Alphabétique (Favories)",
|
||||
"Likes": "Likes",
|
||||
"ID": "ID",
|
||||
"Descending": "Décroissant",
|
||||
"Descending (down)": "Décroissant",
|
||||
"Desc": "Décroissant",
|
||||
"Ascending": "Croissant",
|
||||
"Ascending (Up)": "Croissant",
|
||||
"Asc": "Croissant",
|
||||
"Switch-Handheld!": "Switch-Portable",
|
||||
"Switch-Docked!": "Switch-Dockée",
|
||||
"Audio disabled due to suspended game": "Audio désactivé à cause d'un jeu suspendu",
|
||||
"Are you sure you wish to cancel?": "Souhaitez-vous vraiment annuler?",
|
||||
"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.",
|
||||
|
||||
"Menu Options": "Options des Menus",
|
||||
"Menu": "Menu",
|
||||
"Theme": "Thème",
|
||||
"Theme Options": "Options de Thème",
|
||||
"Select Theme": "Choisir un Thème",
|
||||
"Shuffle": "Aléatoire",
|
||||
"Music": "Musique",
|
||||
"12 Hour Time": "Temps sur 12 heures",
|
||||
"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",
|
||||
"Overwrite current default music?": "Remplacer la musique actuelle par défaut?",
|
||||
|
||||
"Network": "Réseau",
|
||||
"Network Options": "Options Réseau",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,13 +27,12 @@
|
||||
"Nxlink Connected": "Nxlink Connecté",
|
||||
"Nxlink Upload": "Nxlink téléversement",
|
||||
"Nxlink Finished": "Nxlink terminé",
|
||||
"Switch-Handheld!": "Switch-Portable",
|
||||
"Switch-Docked!": "Switch-Dockée",
|
||||
|
||||
"Language": "Langue",
|
||||
"Auto": "Auto",
|
||||
"English": "English",
|
||||
"Japanese": "日本語",
|
||||
"French": "Français",
|
||||
"French": "Français",
|
||||
"German": "Deutsch",
|
||||
"Italian": "Italiano",
|
||||
"Spanish": "Español",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Journalisation",
|
||||
"Replace hbmenu on exit": "Remplacer hbmenu quand quitté",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Divers",
|
||||
"Misc Options": "Options Diverses",
|
||||
"Web": "Web",
|
||||
"Install forwarders": "Installer les Forwarders",
|
||||
"Install location": "Emplacement d'installation",
|
||||
"Show install warning": "Afficher l'avertissement d'installation",
|
||||
"Text scroll speed": "Vitesse de défilement du texte",
|
||||
|
||||
"FileBrowser": "Explorateur de Fichiers",
|
||||
"%zd files": "%zd fichiers",
|
||||
"%zd dirs": "%zd dossiers",
|
||||
"File Options": "Options de Fichier",
|
||||
"Show Hidden": "Afficher Masqués",
|
||||
"Folders First": "Dossiers en Premier",
|
||||
"Hidden Last": "Masqués en Dernier",
|
||||
"Cut": "Couper",
|
||||
"Copy": "Copier",
|
||||
"Paste": "Coller",
|
||||
"Paste ": "Coller ",
|
||||
" file(s)?": " fichier(s)?",
|
||||
"Rename": "Renommer",
|
||||
"Set New File Name": "Nouveau Nom Du Fichier",
|
||||
"Advanced": "Avancé",
|
||||
"Advanced Options": "Options Avancées",
|
||||
"Create File": "Créer un Fichier",
|
||||
"Set File Name": "Nommer Le Fichier",
|
||||
"Create Folder": "Créer un Dossier",
|
||||
"Set Folder Name": "Nommer Le Dossier",
|
||||
"View as text (unfinished)": "Afficher sous forme de texte (inachevé)",
|
||||
"Ignore read only": "Ignorer lecture seule",
|
||||
"Mount": "Monter",
|
||||
"Empty...": "Vide...",
|
||||
"Open with DayBreak?": "Ouvrir avec DayBreak?",
|
||||
"Launch ": "Lancer ",
|
||||
"Launch option for: ": "Option de lancement pour: ",
|
||||
"Select launcher for: ": "Sélectionner le lanceur pour: ",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Options Homebrew",
|
||||
"Hide Sphaira": "Masquer Sphaira",
|
||||
"Install Forwarder": "Installer le Forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ATTENTION: L'installation de forwarders entraînera un ban!",
|
||||
"Installing Forwarder": "Installation Du Forwarder",
|
||||
"Creating Program": "Création de Program",
|
||||
"Creating Control": "Création de Control",
|
||||
"Creating Meta": "Création de Meta",
|
||||
"Writing Nca": "Ecriture NCA",
|
||||
"Updating ncm databse": "Mise à jour de ncm databse",
|
||||
"Pushing application record": "Ajout de l'enregistrement de l'application",
|
||||
"Installed!": "Installé!",
|
||||
"Failed to install forwarder": "Echec de l'installation du forwarder",
|
||||
"Unstarred ": "Retiré des favories ",
|
||||
"Starred ": "Ajouté aux favories ",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtre: %s | Tri: %s | Ordre: %s",
|
||||
"AppStore Options": "Options de l'AppStore",
|
||||
"All": "Tous",
|
||||
"Games": "Jeux",
|
||||
"Emulators": "Émulateurs",
|
||||
"Tools": "Outils",
|
||||
"Themes": "Thèmes",
|
||||
"Legacy": "Legacy",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "Mis à jour: %s",
|
||||
"category: %s": "catégorie: %s",
|
||||
"extracted: %.2f MiB": "Extrait: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "Plus de cet Auteur",
|
||||
"Leave Feedback": "Laisser un avis",
|
||||
"Game Options": "Options de jeu",
|
||||
"Hide forwarders": "Masquer les forwarders",
|
||||
"Launch random game": "Lancer un jeu au hasard",
|
||||
"List meta records": "Lister les enregistrements meta",
|
||||
"Entries": "Entrées",
|
||||
"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",
|
||||
"Updating application record list": "Mise à jour de la liste d'enregistrement de l'application",
|
||||
"Dump": "Dumper",
|
||||
"Select content to dump": "Sélectionner un contenu à dumper",
|
||||
"Dump All": "Tout dumper",
|
||||
"Dump Application": "Dumper Application",
|
||||
"Dump Patch": "Dumper mise à jour",
|
||||
"Dump AddOnContent": "Dumper DLCs",
|
||||
"Dump DataPatch": "Dumper patch de données",
|
||||
"Select dump location": "Sélectionner l'emplacement du dump",
|
||||
"microSD card (/dumps/NSP/)": "carte microSD (/dumps/NSP/)",
|
||||
"USB transfer (Switch 2 Switch)": "Transfert USB (Switch à Switch",
|
||||
"/dev/null (Speed Test)": "/dev/null (test de vitesse)",
|
||||
"Dumping": "Dump en cours",
|
||||
"Dump successfull!": "Dump réussi!",
|
||||
"Dump failed!": "Dump échoué!",
|
||||
"Success": "Succès",
|
||||
"Delete successfull!": "Suprression réussie!",
|
||||
"Delete failed!": "Suprression échouée!",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Options Themezer",
|
||||
"Nsfw": "Nsfw",
|
||||
"Page": "Page",
|
||||
"Page %zu / %zu": "Page %zu / %zu",
|
||||
"Enter Page Number": "Entrez Un Numéro De Page",
|
||||
"Bad Page": "Page inexistante",
|
||||
"Download theme?": "Télécharger le thème?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Téléchargement du json",
|
||||
"Select asset to download for ": "Sélectionner l'asset à télécharger pour ",
|
||||
|
||||
"FTP Install": "Installation via FTP",
|
||||
"FTP Install (EXPERIMENTAL)": "Installation via FTP (EXPERIMENTAL)",
|
||||
"Connection Type: WiFi | Strength: ": "Type de connexion: WiFi, force du signal: ",
|
||||
"Connection Type: Ethernet": "Type de connexion: Ethernet",
|
||||
"Connection Type: None": "Type de connexion: Aucune",
|
||||
"Host:": "Hôte:",
|
||||
"Port:": "Port:",
|
||||
"Username:": "Nom d'utilisateur:",
|
||||
"Password:": "Mot de passe:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "Mot de passe:",
|
||||
"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 failed!": "Installation via FTP échouée!",
|
||||
"USB Install": "Installation via USB",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "Connecté, en attente de la liste des fichiers...",
|
||||
"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...",
|
||||
"Waiting for connection...": "En attente de la connexion...",
|
||||
"Transferring data...": "Transfère de données...",
|
||||
"USB connected, sending file list": "USB connecté, envoi de la liste des fichiers",
|
||||
"Sent file list, waiting for command...": "Liste des fichiers envoyée, attente d'une commande...",
|
||||
"waiting for usb connection...": "En attente d'une connexion USB...",
|
||||
"Disable MTP for usb install": "Désactivation du MTP pour l'installation via USB",
|
||||
"Re-enabled MTP": "Réactivation du MTP",
|
||||
"Installed via usb": "Installé via USB",
|
||||
"Usb install success!": "Installation via USB réussie!",
|
||||
"Usb install failed!": "Installation via USB échouée!",
|
||||
"Press B to exit...": "Appuyer sur B pour quitter...",
|
||||
"GameCard Install": "Installation de la cartouche",
|
||||
"GameCard": "Cartouche",
|
||||
"GC": "GC",
|
||||
"System memory %.1f GB": "Mémoire système %.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",
|
||||
"Gc install success!": "Installation de la cartouche réussie!",
|
||||
"Gc install failed!": "Installation de la cartouche échouée!",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "IRS (infrarouge de la caméra du Joycon",
|
||||
"IRS": "IRS",
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "Niveau De Bruit Ambiant: ",
|
||||
"Controller": "Contrôleur",
|
||||
"Pad ": "Manette ",
|
||||
"HandHeld": "Portable",
|
||||
" (Available)": " (Disponible)",
|
||||
" (Unsupported)": "Non supporté",
|
||||
" (Unconnected)": " (Non connectée)",
|
||||
"HandHeld": "Portable",
|
||||
"Rotation": "Rotation",
|
||||
"0 (Sideways)": "0 (Paysage)",
|
||||
"90 (Flat)": "90 (Portrait)",
|
||||
@@ -190,68 +155,238 @@
|
||||
"Dim group": "Groupe sombre",
|
||||
"None": "Aucun",
|
||||
"Gain": "Gain",
|
||||
"Negative Image": "Négatif",
|
||||
"Negative Image": "Image Négative",
|
||||
"Normal image": "Image normale",
|
||||
"Negative image": "Négatif",
|
||||
"Negative image": "Image négative",
|
||||
"Format": "Format",
|
||||
"Trimming Format": "Format de Découpe",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "Format de Découpe",
|
||||
"External Light Filter": "Filtre de Lumière Externe",
|
||||
"Load Default": "Charger par Défaut",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Options Themezer",
|
||||
"Nsfw": "Nsfw",
|
||||
"Page": "Page",
|
||||
"Page %zu / %zu": "Page %zu / %zu",
|
||||
"Enter Page Number": "Entrez Un Numéro De Page",
|
||||
"Bad Page": "Page inexistante",
|
||||
"Download theme?": "Télécharger le thème?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Téléchargement du json",
|
||||
"Select asset to download for ": "Sélectionner l'asset pour télécharger ",
|
||||
|
||||
"Installing ": "Installation ",
|
||||
"Uninstalling ": "Désinstallation ",
|
||||
"Deleting ": "Suppression ",
|
||||
"Deleting": "Suppression",
|
||||
"Pasting ": "Coller ",
|
||||
"Pasting": "Coller",
|
||||
"Removing ": "Suppression ",
|
||||
"Scanning ": "Scan ",
|
||||
"Creating ": "Création ",
|
||||
"Copying ": "Copie ",
|
||||
"Trying to load ": "Tente de charger ",
|
||||
"Downloading ": "Téléchargement ",
|
||||
"Downloaded ": "Téléchargé",
|
||||
"Removed ": "Supprimé ",
|
||||
"Checking MD5": "Vérification MD5",
|
||||
"Loading...": "Chargement...",
|
||||
"Loading": "Chargement",
|
||||
"Empty!": "Vide!",
|
||||
"Not Ready...": "Pas prêt",
|
||||
"Error loading page!": "Erreur du chargement de la page!",
|
||||
"Update avaliable: ": "Mise à jour disponible: ",
|
||||
"Download update: ": "Télécharger la mise à jour: ",
|
||||
"Updated to ": "Mis à jour vers ",
|
||||
"Press OK to restart Sphaira": "Appuyez sur OK pour redémarrer Sphaira",
|
||||
"Restart Sphaira?": "Redémarrer Sphaira?",
|
||||
"Failed to download update": "Echec du téléchargement de la mise à jour",
|
||||
"Advanced": "Avancé",
|
||||
"Advanced Options": "Options Avancées",
|
||||
"Logging": "Journalisation",
|
||||
"Replace hbmenu on exit": "Remplacer hbmenu quand quitté",
|
||||
"Restore hbmenu?": "Restaurer hbmenu?",
|
||||
"Restore": "Restaurer",
|
||||
"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",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Echec de la restauration de hbmenu, veuillez le réinstaller",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Echec 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?",
|
||||
"Press OK to restart Sphaira": "Appuyez sur OK pour redémarrer Sphaira",
|
||||
"Text scroll speed": "Vitesse de défilement du texte",
|
||||
"Slow": "Lent",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rapide",
|
||||
"Set right-side menu": "Configurer le menu de droite",
|
||||
"Install options": "Options d'installation",
|
||||
"Install Options": "Options d'Installation",
|
||||
"Enable sysmmc": "Activer sur la sysmmc",
|
||||
"Enable emummc": "Activer sur l'emummc",
|
||||
"Show install warning": "Afficher l'avertissement d'installation",
|
||||
"Install location": "Emplacement d'installation",
|
||||
"System memory": "Mémoire système",
|
||||
"microSD card": "Carte microSD",
|
||||
"Boost CPU clock": "Augmenter la vitesse de l'horloge CPU",
|
||||
"Allow downgrade": "Autoriser le downgrade",
|
||||
"Skip if already installed": "Ignorer si déjà installé",
|
||||
"Ticket only": "Seulement le ticket",
|
||||
"Skip base": "Ignorer base",
|
||||
"Skip patch": "Ignorer mise à jour",
|
||||
"Skip dlc": "Ignorer DLC",
|
||||
"Skip data patch": "Ignorer patch de données",
|
||||
"Skip ticket": "Ignorer ticket",
|
||||
"Skip NCA hash verify": "Ignorer la vérification du hash NCA",
|
||||
"Skip RSA header verify": "Ignorer la vérification de l'entête RSA",
|
||||
"Skip RSA NPDM verify": "Ignorer la vérification RSA NPDM",
|
||||
"Ignore distribution bit": "Ignorer le bit de distribution",
|
||||
"Convert to standard crypto": "Convertir vers la crypto standard",
|
||||
"Lower master key": "Abaisser la master key",
|
||||
"Lower system version": "Abaisser la version du système",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Apps": "Applications",
|
||||
"Homebrew Options": "Options Homebrew",
|
||||
"Hide Sphaira": "Masquer Sphaira",
|
||||
"Install Forwarder": "Installer le Forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ATTENTION: L'installation de forwarders entraînera un ban!",
|
||||
"Installing Forwarder": "Installation Du Forwarder",
|
||||
"Creating Program": "Création de Program",
|
||||
"Creating Control": "Création de Control",
|
||||
"Creating Meta": "Création de Meta",
|
||||
"Writing Nca": "Ecriture NCA",
|
||||
"Updating ncm databse": "Mise à jour de ncm databse",
|
||||
"Pushing application record": "Ajout de l'enregistrement de l'application",
|
||||
"Failed to install forwarder": "Echec de l'installation du forwarder",
|
||||
"Unstarred ": "Retiré des favories ",
|
||||
"Starred ": "Ajouté aux favories ",
|
||||
"Failed to remove old forwarder, please manually remove it!": "Supression de l'ancien forwarder échouée, supprimez-le manuellement!",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Appstore": "Magasin d'applications",
|
||||
"Store": "Magasin",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtre: %s | Tri: %s | Ordre: %s",
|
||||
"AppStore Options": "Options de l'AppStore",
|
||||
"Info": "Info.",
|
||||
"Changelog": "Journal des modifications",
|
||||
"Details": "Détails",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "Mis à jour: %s",
|
||||
"category: %s": "catégorie: %s",
|
||||
"extracted: %.2f MiB": "Extrait: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "Plus de cet Auteur",
|
||||
"Leave Feedback": "Laisser un avis",
|
||||
|
||||
"FileBrowser": "Explorateur de Fichiers",
|
||||
"Files": "Fichiers",
|
||||
"%zd files": "%zd fichiers",
|
||||
"%zd dirs": "%zd dossiers",
|
||||
"File Options": "Options de Fichier",
|
||||
"Show Hidden": "Afficher Masqués",
|
||||
"Folders First": "Dossiers en Premier",
|
||||
"Hidden Last": "Masqués en Dernier",
|
||||
"Cut": "Couper",
|
||||
"Copy": "Copier",
|
||||
"Copying ": "Copie en cours ",
|
||||
"Paste": "Coller",
|
||||
"Paste ": "Coller ",
|
||||
" file(s)?": " fichier(s)?",
|
||||
"Pasting ": "Collage en cours ",
|
||||
"Pasting": "Collage en cours",
|
||||
"Rename": "Renommer",
|
||||
"Set New File Name": "Nouveau Nom Du Fichier",
|
||||
"Extract zip": "Extraire le zip",
|
||||
"Extract Options": "Options d'extraction",
|
||||
"Extract here": "Extraire ici",
|
||||
"Extract to root": "Extraire à la racine",
|
||||
"Are you sure you want to extract to root?": "Souhaitez-vous vraiment extraire à la racine?",
|
||||
"Extract to...": "Extraire vers...",
|
||||
"Enter the path to the folder to extract into": "Entrer le chemin du répertoire vers lequel extraire",
|
||||
"Extracting ": "Extraction en cours ",
|
||||
"Extract success!": "Extraction réussie!",
|
||||
"Extract failed!": "Extraction échouée!",
|
||||
"Compress to zip": "Compresser en zip",
|
||||
"Compress Options": "Options de compression",
|
||||
"Compress": "Compresser",
|
||||
"Compress to...": "Compresser vers...",
|
||||
"Compressing ": "Compression en cours ",
|
||||
"Compress success!": "Compression réussie!",
|
||||
"Compress failed!": "Compression échouée!",
|
||||
"Create File": "Créer un Fichier",
|
||||
"Set File Name": "Nommer Le Fichier",
|
||||
"Create Folder": "Créer un Dossier",
|
||||
"Set Folder Name": "Nommer Le Dossier",
|
||||
"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é)",
|
||||
"Ignore read only": "Ignorer lecture seule",
|
||||
"Mount": "Monter",
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Image de la mémoire System",
|
||||
"Image microSD card": "Image de la Carte microSD",
|
||||
"Empty...": "Vide...",
|
||||
"Open with DayBreak?": "Ouvrir avec DayBreak?",
|
||||
"Launch ": "Lancer ",
|
||||
"Launch option for: ": "Option de lancement pour: ",
|
||||
"Select launcher for: ": "Sélectionner le lanceur pour: ",
|
||||
|
||||
"Sort By": "Tri Par",
|
||||
"Sort Options": "Options de Tri",
|
||||
"Filter": "Filtre",
|
||||
"All": "Tous",
|
||||
"Emulators": "Émulateurs",
|
||||
"Tools": "Outils",
|
||||
"Themes": "Thèmes",
|
||||
"Legacy": "Legacy",
|
||||
"Sort": "Tri",
|
||||
"Size": "Taille",
|
||||
"Size (Star)": "Taille (Favories)",
|
||||
"Alphabetical": "Alphabétique",
|
||||
"Alphabetical (Star)": "Alphabétique (Favories)",
|
||||
"Updated": "Mis à jour",
|
||||
"Updated (Star)": "Mis à jour (Favories)",
|
||||
"Downloads": "Téléchargements",
|
||||
"Likes": "Likes",
|
||||
"ID": "ID",
|
||||
"Order": "Ordre",
|
||||
"Descending": "Décroissant",
|
||||
"Descending (down)": "Décroissant (vers le bas)",
|
||||
"Desc": "Décroissant",
|
||||
"Ascending": "Croissant",
|
||||
"Ascending (Up)": "Croissant (vers le haut)",
|
||||
"Asc": "Croissant",
|
||||
"Layout": "Type d'affichage",
|
||||
"List": "Liste",
|
||||
"Icon": "Icône",
|
||||
"Grid": "Grille",
|
||||
"Search": "Recherche",
|
||||
|
||||
"Options": "Options",
|
||||
"OK": "OK",
|
||||
"Back": "Retour",
|
||||
"Select": "Sélectionner",
|
||||
"Open": "Ouvrir",
|
||||
"Launch": "Exécuter",
|
||||
"Restart": "Redémarrer",
|
||||
"Next": "Suivant",
|
||||
"Prev": "Précédent",
|
||||
"Unstar": "Retirer des favories",
|
||||
"Star": "Ajouter aux favories",
|
||||
"Yes": "Oui",
|
||||
"No": "Non",
|
||||
"On": "On",
|
||||
"Off": "Off",
|
||||
|
||||
"Install": "Installer",
|
||||
"Install Selected files?": "Installer les fichiers sélectionnés?",
|
||||
"Installing ": "Installation en cours ",
|
||||
"Installed ": "Installé ",
|
||||
"Installed!": "Installé!",
|
||||
"Trying to load ": "Tente de charger ",
|
||||
"Checking MD5": "Vérification MD5",
|
||||
|
||||
"Delete": "Supprimer",
|
||||
"Delete Selected files?": "Supprimer les fichiers sélectionnés?",
|
||||
"Completely remove ": "Supprimer totalement ",
|
||||
"Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ",
|
||||
"Are you sure you wish to cancel?": "Souhaitez-vous vraiment annuler?",
|
||||
"Audio disabled due to suspended game": "Audio désactivé à cause d'un jeu suspendu",
|
||||
"If this message appears repeatedly, please open an issue.": "Si ce message apparait en boucle veuillez ouvrir une issue."
|
||||
"Scanning ": "Scan en cours ",
|
||||
"Deleting ": "Suppression en cours ",
|
||||
"Deleting": "Suppression en cours",
|
||||
"Remove": "Supprimer",
|
||||
"Completely remove ": "Supprimer totalement ",
|
||||
"Removing ": "Suppression en cours ",
|
||||
"Removed ": "Supprimé ",
|
||||
"Uninstalling ": "Désinstallation en cours ",
|
||||
|
||||
"Download": "Télécharger",
|
||||
"Downloading ": "Téléchargement en cours ",
|
||||
"Downloaded ": "Téléchargé",
|
||||
|
||||
"Update": "Mise à jour",
|
||||
"Update avaliable: ": "Mise à jour disponible: ",
|
||||
"Download update: ": "Télécharger la mise à jour: ",
|
||||
"Updated to ": "Mis à jour vers ",
|
||||
"Failed to download update": "Echec du téléchargement de la mise à jour",
|
||||
|
||||
"%zu hours %zu minutes remaining": "%zu heures %zu minutes restantes",
|
||||
"%zu minutes %zu seconds remaining": "%zu minutes %zu secondes restantes",
|
||||
"%zu seconds remaining": "%zu secondes restantes",
|
||||
|
||||
"Loading...": "Chargement...",
|
||||
"Loading": "Chargement en cours",
|
||||
"Empty!": "Vide!",
|
||||
"Not Ready...": "Pas prêt",
|
||||
"Error loading page!": "Erreur du chargement de la page!"
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Modalità applet]",
|
||||
"No Internet": "Niente Internet",
|
||||
"Files": "File",
|
||||
"Apps": "App",
|
||||
"Store": "Store",
|
||||
"Menu": "Menu",
|
||||
"Options": "Opzioni",
|
||||
"OK": "OK",
|
||||
"Back": "Indietro",
|
||||
"Select": "Seleziona",
|
||||
"Open": "Apri",
|
||||
"Launch": "Lancia",
|
||||
"Info": "Informazioni",
|
||||
"Install": "Installa",
|
||||
"Delete": "Elimina",
|
||||
"Restart": "Riavvia",
|
||||
"Changelog": "Patch notes",
|
||||
"Details": "Dettagli",
|
||||
"Update": "Aggiorna",
|
||||
"Remove": "Rimuovi",
|
||||
"Restore": "Ripristina",
|
||||
"Download": "Download",
|
||||
"Next Page": "Pagina successiva",
|
||||
"Prev Page": "Pagina precedente",
|
||||
"Unstar": "Rimuovi dai preferiti",
|
||||
"Star": "Aggiungi ai preferiti",
|
||||
"System memory": "Memoria di sistema",
|
||||
"microSD card": "Scheda microSD",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Immagine memoria di sistema",
|
||||
"Image microSD card": "Immagine scheda microSD",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Sì",
|
||||
"No": "No",
|
||||
"Enabled": "Abilitato",
|
||||
"Disabled": "Disabilitato",
|
||||
|
||||
"Sort By": "Ordina per",
|
||||
"Sort Options": "Opzioni filtro",
|
||||
"Filter": "Filtro",
|
||||
"Sort": "Riordina",
|
||||
"Order": "Ordina",
|
||||
"Search": "Ricerca",
|
||||
"Updated": "Aggiornato",
|
||||
"Updated (Star)": "",
|
||||
"Downloads": "Download",
|
||||
"Size": "Dimensione",
|
||||
"Size (Star)": "Dimensione (Preferiti)",
|
||||
"Alphabetical": "Alfabetico",
|
||||
"Alphabetical (Star)": "Alfabetico (Preferiti)",
|
||||
"Likes": "Mi Piace",
|
||||
"ID": "ID",
|
||||
"Descending": "Decrescente",
|
||||
"Descending (down)": "Decrescente",
|
||||
"Desc": "Decrescente",
|
||||
"Ascending": "Crescente",
|
||||
"Ascending (Up)": "Crescente",
|
||||
"Asc": "Crescente",
|
||||
"Switch-Handheld!": "Switch Portatile",
|
||||
"Switch-Docked!": "Switch Dock",
|
||||
"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?",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Se questo messaggio appare frequentemente, segnala il bug.",
|
||||
|
||||
"Menu Options": "Opzioni menu",
|
||||
"Menu": "Menu",
|
||||
"Theme": "Tema",
|
||||
"Theme Options": "Opzioni tema",
|
||||
"Select Theme": "Seleziona tema",
|
||||
"Shuffle": "Mescola",
|
||||
"Music": "Musica",
|
||||
"12 Hour Time": "",
|
||||
"Download Default Music": "",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "Rete",
|
||||
"Network Options": "Opzioni di rete",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "Nxlink connesso",
|
||||
"Nxlink Upload": "Nxlink upload",
|
||||
"Nxlink Finished": "Nxlink finito",
|
||||
"Switch-Handheld!": "Switch Portatile",
|
||||
"Switch-Docked!": "Switch Dock",
|
||||
|
||||
"Language": "Lingua",
|
||||
"Auto": "Auto",
|
||||
"English": "English",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "Sostituisci hbmenu all'uscita",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Varie",
|
||||
"Misc Options": "Opzioni varie",
|
||||
"Web": "Rete",
|
||||
"Install forwarders": "Installa forwarder",
|
||||
"Install location": "Installa posizione",
|
||||
"Show install warning": "Mostra avvertimento installazione",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "FileBrowser",
|
||||
"%zd files": "%zd files",
|
||||
"%zd dirs": "%zd dirs",
|
||||
"File Options": "Opzioni file",
|
||||
"Show Hidden": "Mostra nascosto",
|
||||
"Folders First": "Prima le cartelle",
|
||||
"Hidden Last": "Ultimo nascosto",
|
||||
"Cut": "Taglia",
|
||||
"Copy": "Copia",
|
||||
"Paste": "Incolla",
|
||||
"Paste ": "Incolla ",
|
||||
" file(s)?": "(i)file?",
|
||||
"Rename": "Rinomina",
|
||||
"Set New File Name": "Imposta nuovo nome",
|
||||
"Advanced": "Avanzato",
|
||||
"Advanced Options": "Opzioni avanzate",
|
||||
"Create File": "Crea file",
|
||||
"Set File Name": "Imposta nome",
|
||||
"Create Folder": "Crea cartella",
|
||||
"Set Folder Name": "Imposta nome",
|
||||
"View as text (unfinished)": "Visualizza come testo (non finito)",
|
||||
"Ignore read only": "Ignora read only",
|
||||
"Mount": "Monta",
|
||||
"Empty...": "Vuoto...",
|
||||
"Open with DayBreak?": "Vuoi aprire con Daybreak?",
|
||||
"Launch ": "Lancia",
|
||||
"Launch option for: ": "Lancia opzione per",
|
||||
"Select launcher for: ": "Scegli launcher per",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Opzioni Homebrew",
|
||||
"Hide Sphaira": "Nascondi Sphaira",
|
||||
"Install Forwarder": "Installa forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ATTENZIONE: l'installazione di forwarder porterà al ban!",
|
||||
"Installing Forwarder": "",
|
||||
"Creating Program": "",
|
||||
"Creating Control": "",
|
||||
"Creating Meta": "",
|
||||
"Writing Nca": "",
|
||||
"Updating ncm databse": "",
|
||||
"Pushing application record": "",
|
||||
"Installed!": "",
|
||||
"Failed to install forwarder": "",
|
||||
"Unstarred ": "",
|
||||
"Starred ": "",
|
||||
|
||||
"AppStore": "",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Riordina: %s | Ordina: %s",
|
||||
"AppStore Options": "Opzioni dell'App Store",
|
||||
"All": "Tutto",
|
||||
"Games": "Giochi",
|
||||
"Emulators": "Emulatori",
|
||||
"Tools": "Strumenti",
|
||||
"Themes": "Temi",
|
||||
"Legacy": "Legacy",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "",
|
||||
"Leave Feedback": "",
|
||||
"Game Options": "",
|
||||
"Hide forwarders": "",
|
||||
"Launch random game": "",
|
||||
"List meta records": "",
|
||||
"Entries": "",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/NSP/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Success": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Impostazioni Themezer",
|
||||
"Nsfw": "NSFW",
|
||||
"Page": "Pagina",
|
||||
"Page %zu / %zu": "Pagina %zu / %zu",
|
||||
"Enter Page Number": "Inserisci il numero della pagina",
|
||||
"Bad Page": "Pagina invalida",
|
||||
"Download theme?": "Vuoi scaricare il tema?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Scaricamento json",
|
||||
"Select asset to download for ": "",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
"Host:": "",
|
||||
"Port:": "",
|
||||
"Username:": "",
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "",
|
||||
"Waiting for connection...": "",
|
||||
"Transferring data...": "",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "",
|
||||
"Re-enabled MTP": "",
|
||||
"Installed via usb": "",
|
||||
"Usb install success!": "",
|
||||
"Usb install failed!": "",
|
||||
"Press B to exit...": "",
|
||||
"GameCard Install": "",
|
||||
"GameCard": "",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "",
|
||||
"Gc install failed!": "",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "",
|
||||
"IRS": "",
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "",
|
||||
"Controller": "Controller",
|
||||
"Pad ": "Pad ",
|
||||
"HandHeld": "HandHeld",
|
||||
" (Available)": " (Disponibile)",
|
||||
" (Unsupported)": "",
|
||||
" (Unconnected)": " (Non connesso)",
|
||||
"HandHeld": "HandHeld",
|
||||
"Rotation": "Rotazione",
|
||||
"0 (Sideways)": "0 (Di lato)",
|
||||
"90 (Flat)": "90 (Piatto)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "Immagine normale",
|
||||
"Negative image": "Immagine negativa",
|
||||
"Format": "Formato",
|
||||
"Trimming Format": "Formato di ritaglio",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "Formato di ritaglio",
|
||||
"External Light Filter": "Filtro luce esterno",
|
||||
"Load Default": "Carica predefinito",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Impostazioni Themezer",
|
||||
"Nsfw": "NSFW",
|
||||
"Page": "Pagina",
|
||||
"Page %zu / %zu": "Pagina %zu / %zu",
|
||||
"Enter Page Number": "Inserisci il numero della pagina",
|
||||
"Bad Page": "Pagina invalida",
|
||||
"Download theme?": "Vuoi scaricare il tema?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Scaricamento json",
|
||||
"Select asset to download for": "Scegli l'asset da scaricare per",
|
||||
|
||||
"Installing ": "Installazione",
|
||||
"Uninstalling ": "Disinstallazione",
|
||||
"Deleting ": "Eliminazione",
|
||||
"Deleting": "Eliminazione",
|
||||
"Pasting ": "Incollo",
|
||||
"Pasting": "Incollo",
|
||||
"Removing ": "Rimozione",
|
||||
"Scanning ": "Scan",
|
||||
"Creating ": "Creazione",
|
||||
"Copying ": "Copio",
|
||||
"Trying to load ": "Cercando di caricare",
|
||||
"Downloading ": "Scaricando",
|
||||
"Downloaded ": "Scaricato",
|
||||
"Removed ": ""Rimosso,
|
||||
"Checking MD5": "Controllo MD5",
|
||||
"Loading...": "Caricamento...",
|
||||
"Loading": "Caricamento",
|
||||
"Empty!": "Vuoto!",
|
||||
"Not Ready...": "Non pronto...",
|
||||
"Error loading page!": "Errore nel caricare la pagina!",
|
||||
"Update avaliable: ": "Aggiornamento disponibile",
|
||||
"Download update: ": "Scarica aggiornamento",
|
||||
"Updated to ": "Aggiornato a",
|
||||
"Press OK to restart Sphaira": "Premi OK per riavviare Sphaira",
|
||||
"Restart Sphaira?": "Vuoi riavviare Sphaira?",
|
||||
"Failed to download update": "Download aggiornamento fallito",
|
||||
"Advanced": "Avanzato",
|
||||
"Advanced Options": "Opzioni avanzate",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "Sostituisci hbmenu all'uscita",
|
||||
"Restore hbmenu?": "Vuoi ripristinare hbmenu?",
|
||||
"Restore": "Ripristina",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Impossibile trovare /switch/hbmenu.nro\nUsa l'Appstore per reinstallare hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Impossibile ripristinare hbmenu, per favore riscaricalo",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Impossibile ripristinare hbmenu, uso Sphaira invece",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu ripristinato, chiudo Sphaira",
|
||||
"Restored hbmenu": "hbmenu ripristinato",
|
||||
"Restart Sphaira?": "Vuoi riavviare Sphaira?",
|
||||
"Press OK to restart Sphaira": "Premi OK per riavviare Sphaira",
|
||||
"Text scroll speed": "",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Set right-side menu": "",
|
||||
"Install options": "",
|
||||
"Install Options": "",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "Mostra avvertimento installazione",
|
||||
"Install location": "Installa posizione",
|
||||
"System memory": "Memoria di sistema",
|
||||
"microSD card": "Scheda microSD",
|
||||
"Boost CPU clock": "",
|
||||
"Allow downgrade": "",
|
||||
"Skip if already installed": "",
|
||||
"Ticket only": "",
|
||||
"Skip base": "",
|
||||
"Skip patch": "",
|
||||
"Skip dlc": "",
|
||||
"Skip data patch": "",
|
||||
"Skip ticket": "",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "",
|
||||
"Skip RSA NPDM verify": "",
|
||||
"Ignore distribution bit": "",
|
||||
"Convert to standard crypto": "",
|
||||
"Lower master key": "",
|
||||
"Lower system version": "",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Apps": "App",
|
||||
"Homebrew Options": "Opzioni Homebrew",
|
||||
"Hide Sphaira": "Nascondi Sphaira",
|
||||
"Install Forwarder": "Installa forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ATTENZIONE: l'installazione di forwarder porterà al ban!",
|
||||
"Installing Forwarder": "",
|
||||
"Creating Program": "",
|
||||
"Creating Control": "",
|
||||
"Creating Meta": "",
|
||||
"Writing Nca": "",
|
||||
"Updating ncm databse": "",
|
||||
"Pushing application record": "",
|
||||
"Failed to install forwarder": "",
|
||||
"Unstarred ": "",
|
||||
"Starred ": "",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "",
|
||||
"Appstore": "",
|
||||
"Store": "Store",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Riordina: %s | Ordina: %s",
|
||||
"AppStore Options": "Opzioni dell'App Store",
|
||||
"Info": "Informazioni",
|
||||
"Changelog": "Patch notes",
|
||||
"Details": "Dettagli",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "",
|
||||
"Leave Feedback": "",
|
||||
|
||||
"FileBrowser": "FileBrowser",
|
||||
"Files": "File",
|
||||
"%zd files": "%zd files",
|
||||
"%zd dirs": "%zd dirs",
|
||||
"File Options": "Opzioni file",
|
||||
"Show Hidden": "Mostra nascosto",
|
||||
"Folders First": "Prima le cartelle",
|
||||
"Hidden Last": "Ultimo nascosto",
|
||||
"Cut": "Taglia",
|
||||
"Copy": "Copia",
|
||||
"Copying ": "Copio",
|
||||
"Paste": "Incolla",
|
||||
"Paste ": "Incolla ",
|
||||
" file(s)?": "(i)file?",
|
||||
"Pasting ": "Incollo",
|
||||
"Pasting": "Incollo",
|
||||
"Rename": "Rinomina",
|
||||
"Set New File Name": "Imposta nuovo nome",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "Crea file",
|
||||
"Set File Name": "Imposta nome",
|
||||
"Create Folder": "Crea cartella",
|
||||
"Set Folder Name": "Imposta nome",
|
||||
"Creating ": "Creazione",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "Visualizza come testo (non finito)",
|
||||
"Ignore read only": "Ignora read only",
|
||||
"Mount": "Monta",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Immagine memoria di sistema",
|
||||
"Image microSD card": "Immagine scheda microSD",
|
||||
"Empty...": "Vuoto...",
|
||||
"Open with DayBreak?": "Vuoi aprire con Daybreak?",
|
||||
"Launch ": "Lancia",
|
||||
"Launch option for: ": "Lancia opzione per",
|
||||
"Select launcher for: ": "Scegli launcher per",
|
||||
|
||||
"Sort By": "Ordina per",
|
||||
"Sort Options": "Opzioni filtro",
|
||||
"Filter": "Filtro",
|
||||
"All": "Tutto",
|
||||
"Emulators": "Emulatori",
|
||||
"Tools": "Strumenti",
|
||||
"Themes": "Temi",
|
||||
"Legacy": "Legacy",
|
||||
"Sort": "Riordina",
|
||||
"Size": "Dimensione",
|
||||
"Size (Star)": "Dimensione (Preferiti)",
|
||||
"Alphabetical": "Alfabetico",
|
||||
"Alphabetical (Star)": "Alfabetico (Preferiti)",
|
||||
"Updated": "Aggiornato",
|
||||
"Updated (Star)": "",
|
||||
"Downloads": "Download",
|
||||
"Likes": "Mi Piace",
|
||||
"ID": "ID",
|
||||
"Order": "Ordina",
|
||||
"Descending": "Decrescente",
|
||||
"Descending (down)": "Decrescente",
|
||||
"Desc": "Decrescente",
|
||||
"Ascending": "Crescente",
|
||||
"Ascending (Up)": "Crescente",
|
||||
"Asc": "Crescente",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Search": "Ricerca",
|
||||
|
||||
"Options": "Opzioni",
|
||||
"OK": "OK",
|
||||
"Back": "Indietro",
|
||||
"Select": "Seleziona",
|
||||
"Open": "Apri",
|
||||
"Launch": "Lancia",
|
||||
"Restart": "Riavvia",
|
||||
"Next": "",
|
||||
"Prev": "",
|
||||
"Unstar": "Rimuovi dai preferiti",
|
||||
"Star": "Aggiungi ai preferiti",
|
||||
"Yes": "Sì",
|
||||
"No": "No",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
|
||||
"Install": "Installa",
|
||||
"Install Selected files?": "",
|
||||
"Installing ": "Installazione",
|
||||
"Installed ": "",
|
||||
"Installed!": "",
|
||||
"Trying to load ": "Cercando di caricare",
|
||||
"Checking MD5": "Controllo MD5",
|
||||
|
||||
"Delete": "Elimina",
|
||||
"Delete Selected files?": "Vuoi rimuovere i file selezionati?",
|
||||
"Completely remove ": "Elimina definitivamente",
|
||||
"Are you sure you want to delete ": "Sei sicuro di voler eliminare? ",
|
||||
"Are you sure you wish to cancel?": "Sei sicuro di voler annullare?",
|
||||
"Audio disabled due to suspended game": "Audio disabilitato poichè un app è in pausa",
|
||||
"If this message appears repeatedly, please open an issue.": "Se questo messaggio appare frequentemente, segnala il bug."
|
||||
}
|
||||
"Scanning ": "Scan",
|
||||
"Deleting ": "Eliminazione",
|
||||
"Deleting": "Eliminazione",
|
||||
"Remove": "Rimuovi",
|
||||
"Completely remove ": "Elimina definitivamente",
|
||||
"Removing ": "Rimozione",
|
||||
"Removed ": "Rimosso",
|
||||
"Uninstalling ": "Disinstallazione",
|
||||
|
||||
"Download": "Download",
|
||||
"Downloading ": "Scaricando",
|
||||
"Downloaded ": "Scaricato",
|
||||
|
||||
"Update": "Aggiorna",
|
||||
"Update avaliable: ": "Aggiornamento disponibile",
|
||||
"Download update: ": "Scarica aggiornamento",
|
||||
"Updated to ": "Aggiornato a",
|
||||
"Failed to download update": "Download aggiornamento fallito",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "Caricamento...",
|
||||
"Loading": "Caricamento",
|
||||
"Empty!": "Vuoto!",
|
||||
"Not Ready...": "Non pronto...",
|
||||
"Error loading page!": "Errore nel caricare la pagina!"
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Appletモード]",
|
||||
"No Internet": "インターネットなし",
|
||||
"Files": "ファイル",
|
||||
"Apps": "アプリ",
|
||||
"Store": "AppStore",
|
||||
"Menu": "メニュー",
|
||||
"Options": "設定",
|
||||
"OK": "確認",
|
||||
"Back": "戻る",
|
||||
"Select": "選択",
|
||||
"Open": "開く",
|
||||
"Launch": "起動",
|
||||
"Info": "情報",
|
||||
"Install": "インストール",
|
||||
"Delete": "削除",
|
||||
"Restart": "再起動",
|
||||
"Changelog": "リリースノート",
|
||||
"Details": "詳細",
|
||||
"Update": "アップデート",
|
||||
"Remove": "除去",
|
||||
"Restore": "復元",
|
||||
"Download": "ダウンロード",
|
||||
"Next Page": "次のページ",
|
||||
"Prev Page": "前のページ",
|
||||
"Unstar": "お気に入り解除",
|
||||
"Star": "お気に入り",
|
||||
"System memory": "システムメモリ",
|
||||
"microSD card": "SDメモリーカード",
|
||||
"Sd": "SDメモリーカード",
|
||||
"Image System memory": "システムメモリイメージ",
|
||||
"Image microSD card": "SDイメージ",
|
||||
"Slow": "遅い",
|
||||
"Normal": "普通",
|
||||
"Fast": "速い",
|
||||
"Yes": "はい",
|
||||
"No": "いいえ",
|
||||
"Enabled": "",
|
||||
"Disabled": "",
|
||||
|
||||
"Sort By": "並べ替え",
|
||||
"Sort Options": "並べ替え設定",
|
||||
"Filter": "フィルター",
|
||||
"Sort": "並べ替え",
|
||||
"Order": "順番",
|
||||
"Search": "検索",
|
||||
"Updated": "アップデート順",
|
||||
"Updated (Star)": "アップデート順(お気に入り)",
|
||||
"Downloads": "ダウンロード順",
|
||||
"Size": "ファイルサイズ",
|
||||
"Size (Star)": "ファイルサイズ(お気に入り)",
|
||||
"Alphabetical": "アルファベット順",
|
||||
"Alphabetical (Star)": "アルファベット順(お気に入り)",
|
||||
"Likes": "いいね順",
|
||||
"ID": "デベロッパー順",
|
||||
"Descending": "降順",
|
||||
"Descending (down)": "降順",
|
||||
"Desc": "降順",
|
||||
"Ascending": "上昇",
|
||||
"Ascending (Up)": "上昇",
|
||||
"Asc": "上昇",
|
||||
"Switch-Handheld!": "ハンドヘルド!",
|
||||
"Switch-Docked!": "ドック接続!",
|
||||
"Audio disabled due to suspended game": "ゲームが一時停止状態の場合、オーディオは無効になります",
|
||||
"Are you sure you wish to cancel?": "本当に取り消しますか?",
|
||||
"An error occurred": "不具合のお知らせ",
|
||||
"If this message appears repeatedly, please open an issue.": "このメッセージが繰り返し表示される場合は、問題を開いてください",
|
||||
|
||||
"Menu Options": "メニュー設定",
|
||||
"Menu": "メニュー",
|
||||
"Theme": "テーマ",
|
||||
"Theme Options": "テーマ設定",
|
||||
"Select Theme": "テーマを選ぶ",
|
||||
"Shuffle": "シャッフル",
|
||||
"Music": "BGM",
|
||||
"12 Hour Time": "",
|
||||
"12 Hour Time": "12時間表示",
|
||||
"Download Default Music": "基本BGMダウンロード",
|
||||
"Failed to download default_music.bfstm, please try again": "基本BGMのダウンロードに失敗しました、もう一度お試しください",
|
||||
"Overwrite current default music?": "BGMを書き換えますか?",
|
||||
|
||||
"Network": "ネットワーク",
|
||||
"Network Options": "ネットワーク設定",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "Nxlink 接続",
|
||||
"Nxlink Upload": "Nxlink アップロード",
|
||||
"Nxlink Finished": "Nxlink 終了",
|
||||
"Switch-Handheld!": "ハンドヘルド!",
|
||||
"Switch-Docked!": "ドック接続!",
|
||||
|
||||
"Language": "言語",
|
||||
"Auto": "自動",
|
||||
"English": "English",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "ログの取得",
|
||||
"Replace hbmenu on exit": "終了時に hbmenu を置き換える",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "その他",
|
||||
"Misc Options": "その他",
|
||||
"Web": "ウェブブラウザ",
|
||||
"Install forwarders": "Forwarderのインストール機能",
|
||||
"Install location": "インストール経路",
|
||||
"Show install warning": "警告文を示す",
|
||||
"Text scroll speed": "流れる文字の速さ",
|
||||
|
||||
"FileBrowser": "ファイルブラウザ",
|
||||
"%zd files": "%zd個のファイル",
|
||||
"%zd dirs": "%zd個のフォルダー",
|
||||
"File Options": "ファイル設定",
|
||||
"Show Hidden": "非表示ファイルを表示",
|
||||
"Folders First": "フォルダーを優先",
|
||||
"Hidden Last": "非表示ファイルを劣後",
|
||||
"Cut": "切り取り",
|
||||
"Copy": "コピー",
|
||||
"Paste": "ペースト",
|
||||
"Paste ": " ",
|
||||
" file(s)?": "個のファイルをペーストしますか?",
|
||||
"Rename": "名前の変更",
|
||||
"Set New File Name": "新しい名前を入力",
|
||||
"Advanced": "高度な",
|
||||
"Advanced Options": "高度設定",
|
||||
"Create File": "ファイルの作成",
|
||||
"Set File Name": "名前を入力",
|
||||
"Create Folder": "フォルダーの作成",
|
||||
"Set Folder Name": "名前を入力",
|
||||
"View as text (unfinished)": "テキストとして表示 (未完成)",
|
||||
"Ignore read only": "読み取り専用を無視する",
|
||||
"Mount": "マウント",
|
||||
"Empty...": "このフォルダーは空です",
|
||||
"Open with DayBreak?": "DayBreakで開きますか?",
|
||||
"Launch ": "起動しますか",
|
||||
"Launch option for: ": "起動設定: ",
|
||||
"Select launcher for: ": "起動ランチャーを選ぶ: ",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Homebrew設定",
|
||||
"Hide Sphaira": "Sphairaを非表示",
|
||||
"Install Forwarder": "Forwarderのインストール",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "警告: ForwarderをインストールするとBANされます。",
|
||||
"Installing Forwarder": "Forwarderのインストール中",
|
||||
"Creating Program": "プログラム作成中",
|
||||
"Creating Control": "コントロール作成中",
|
||||
"Creating Meta": "メター作成中",
|
||||
"Writing Nca": "Nca書き取り中",
|
||||
"Updating ncm databse": "ncmのDBをアップデート中",
|
||||
"Pushing application record": "アプリの記録をプッシュ中",
|
||||
"Installed!": "インストール完了",
|
||||
"Failed to install forwarder": "Forwarderのインストール失敗",
|
||||
"Unstarred ": "お気に入り解除: ",
|
||||
"Starred ": "お気に入りに登録: ",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Filter: %s | Sort: %s | Order: %s": "フィルター: %s | 並べ替え: %s | 順番: %s",
|
||||
"AppStore Options": "AppStoreの設定",
|
||||
"All": "全て",
|
||||
"Games": "ゲーム",
|
||||
"Emulators": "エミュレータ",
|
||||
"Tools": "ツール",
|
||||
"Themes": "テーマ",
|
||||
"Legacy": "レガシー",
|
||||
"version: %s": "バージョン: %s",
|
||||
"updated: %s": "更新日: %s",
|
||||
"category: %s": "カテゴリー: %s",
|
||||
"extracted: %.2f MiB": "容量: %.2f MiB",
|
||||
"app_dls: %s": "ダウンロード: %s",
|
||||
"More by Author": "ディベロッパーの他のアプリを見る",
|
||||
"Leave Feedback": "意見を残す",
|
||||
"Game Options": "ゲーム設定",
|
||||
"Hide forwarders": "Forwarderを隠す",
|
||||
"Launch random game": "ゲームをランダム起動",
|
||||
"List meta records": "メタレコード一覧",
|
||||
"Entries": "エントリ",
|
||||
"Failed to list application meta entries": "ゲームのメタ一覧を表示できませんでした",
|
||||
"No meta entries found...\n": "メタエントリが見つかりませんでした\n",
|
||||
"Updating application record list": "ゲームのレコードを更新しています",
|
||||
"Dump": "吸出し",
|
||||
"Select content to dump": "吸出すコンテンツを選択",
|
||||
"Dump All": "全て",
|
||||
"Dump Application": "ゲームのみ",
|
||||
"Dump Patch": "ゲームパッチのみ",
|
||||
"Dump AddOnContent": "DLCのみ",
|
||||
"Dump DataPatch": "DLCパッチのみ",
|
||||
"Select dump location": "吸出し位置を選択",
|
||||
"microSD card (/dumps/NSP/)": "SDカード (/dumps/NSP/)",
|
||||
"USB transfer (Switch 2 Switch)": "USB転送 (Switch 2 Switch)",
|
||||
"/dev/null (Speed Test)": "/dev/null (Speed Test)",
|
||||
"Dumping": "吸出し中",
|
||||
"Dump successfull!": "吸出し完了!",
|
||||
"Dump failed!": "吸出し失敗!",
|
||||
"Success": "完了",
|
||||
"Delete successfull!": "削除完了!",
|
||||
"Delete failed!": "削除失敗!",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Themezer設定",
|
||||
"Nsfw": "アダルトテーマ",
|
||||
"Page": "ページ",
|
||||
"Page %zu / %zu": "ページ %zu / %zu",
|
||||
"Enter Page Number": "ページの番号を入力",
|
||||
"Bad Page": "ページが見つかりません",
|
||||
"Download theme?": "テーマをインストールしますか?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "JSONからダウンロード",
|
||||
"Select asset to download for ": "ダウンロードアイテムを選択 ",
|
||||
|
||||
"FTP Install": "FTPでインストール",
|
||||
"FTP Install (EXPERIMENTAL)": "FTPでインストール(実験機能)",
|
||||
"Connection Type: WiFi | Strength: ": "接続: WiFi | 強度: ",
|
||||
"Connection Type: Ethernet": "接続: イーサネット",
|
||||
"Connection Type: None": "接続: なし",
|
||||
"Host:": "ホースと:",
|
||||
"Port:": "Port:",
|
||||
"Username:": "ユーザー名:",
|
||||
"Password:": "暗証番号:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "WiFi暗証番号:",
|
||||
"Failed to install via FTP, press B to exit...": "FTP経由でインストールできませんでした、を押して終了します",
|
||||
"Ftp install success!": "FTPインストール完了!",
|
||||
"Ftp install failed!": "FTPインストール失敗!",
|
||||
"USB Install": "USBインストール",
|
||||
"USB": "USBインストール",
|
||||
"Connected, waiting for file list...": "接続されました、ファイル リスト待機中",
|
||||
"Connected, starting transfer...": "接続されました、転送開始",
|
||||
"Failed to init usb, press B to exit...": "USB接続できませんでした、を押して終了します",
|
||||
"Waiting for connection...": "接続待機中",
|
||||
"Transferring data...": "データ転送中",
|
||||
"USB connected, sending file list": "接続されました、ファイルリスト送信中",
|
||||
"Sent file list, waiting for command...": "ファイルリストを送信しました、入力待機中",
|
||||
"waiting for usb connection...": "USB接続待機中",
|
||||
"Disable MTP for usb install": "USBインストールのため、MTPを無効にします",
|
||||
"Re-enabled MTP": "MTPに再接続します",
|
||||
"Installed via usb": "USBインストールに成功しました",
|
||||
"Usb install success!": "USBインストール完了!",
|
||||
"Usb install failed!": "USBインストール失敗!",
|
||||
"Press B to exit...": "を押して終了します",
|
||||
"GameCard Install": "ゲームカードインストール",
|
||||
"GameCard": "ゲームカードインストール",
|
||||
"GC": "ゲームカード",
|
||||
"System memory %.1f GB": "本体保存メモリー %.1f GB",
|
||||
"microSD card %.1f GB": "SDカード %.1f GB",
|
||||
"Nand Install": "本体保存メモリーにインストール",
|
||||
"SD Card Install": "SDカードにインストール",
|
||||
"Exit": "もどる",
|
||||
"Gc install success!": "ゲームカードインストール完了!",
|
||||
"Gc install failed!": "ゲームカードインストール失敗!",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "Joy-Con IRカメラ",
|
||||
"IRS": "Joy-Con IRカメラ",
|
||||
"Irs": "Joy-Con IRカメラ",
|
||||
"Ambient Noise Level: ": "ノイズレベル: ",
|
||||
"Controller": "コントローラー",
|
||||
"Pad ": "Joy-Con ",
|
||||
"HandHeld": "ハンドヘルド",
|
||||
" (Available)": " (利用可能)",
|
||||
" (Unsupported)": " (未対応)",
|
||||
" (Unconnected)": " (未接続)",
|
||||
"HandHeld": "ハンドヘルド",
|
||||
"Rotation": "回転",
|
||||
"0 (Sideways)": "0 (横)",
|
||||
"90 (Flat)": "90 (フラット)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "通常画像",
|
||||
"Negative image": "ネガティブなイメージ",
|
||||
"Format": "解像度",
|
||||
"Trimming Format": "トリミングされた解像度",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "トリミングされた解像度",
|
||||
"External Light Filter": "外光フィルター",
|
||||
"Load Default": "基本設定に戻す",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Themezer設定",
|
||||
"Nsfw": "アダルトテーマ",
|
||||
"Page": "ページ",
|
||||
"Page %zu / %zu": "ページ %zu / %zu",
|
||||
"Enter Page Number": "ページの番号を入力",
|
||||
"Bad Page": "ページが見つかりません",
|
||||
"Download theme?": "テーマをインストールしますか?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "JSONからダウンロード",
|
||||
"Select asset to download for ": "ダウンロードアイテムを選択 ",
|
||||
|
||||
"Installing ": "インストール中 ",
|
||||
"Uninstalling ": "アンインストール中 ",
|
||||
"Deleting ": "削除中 ",
|
||||
"Deleting": "削除中",
|
||||
"Pasting ": "ペースト中 ",
|
||||
"Pasting": "ペースト中",
|
||||
"Removing ": "除去中 ",
|
||||
"Scanning ": "スキャン中 ",
|
||||
"Creating ": "作成中 ",
|
||||
"Copying ": "コピー中 ",
|
||||
"Trying to load ": "サムネイルを取得中 ",
|
||||
"Downloading ": "ダウンロード中 ",
|
||||
"Downloaded ": "ダウンロード完了 ",
|
||||
"Removed ": "除去完了 ",
|
||||
"Checking MD5": "MD5を確認中 ",
|
||||
"Loading...": "ロード中",
|
||||
"Loading": "ロード中",
|
||||
"Empty!": "何も見つかりません",
|
||||
"Not Ready...": "準備ができていません",
|
||||
"Error loading page!": "ページのロードエラー",
|
||||
"Update avaliable: ": "アップデート可能: ",
|
||||
"Download update: ": "アップデートをダウンロード: ",
|
||||
"Updated to ": "アップデート: ",
|
||||
"Press OK to restart Sphaira": "確認ボタンを押してSphairaを再起動",
|
||||
"Restart Sphaira?": "Sphairaを再起動しますか?",
|
||||
"Failed to download update": "アップデートのダウンロード失敗",
|
||||
"Advanced": "高度な",
|
||||
"Advanced Options": "高度設定",
|
||||
"Logging": "ログの取得",
|
||||
"Replace hbmenu on exit": "終了時に hbmenu を置き換える",
|
||||
"Restore hbmenu?": "hbmenuに戻しますか?",
|
||||
"Restore": "復元",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmemu.nroが見つかりません\nAppstoreから再インストールしてください",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "hbmenuを復元できませんでした、再インストールしてください",
|
||||
"Failed to restore hbmenu, using sphaira instead": "hbmenuを復元できませんでした、sphairaを引き続き使います",
|
||||
"Restored hbmenu, closing sphaira": "hbmenuに復元されました、sphairaを終了します",
|
||||
"Restored hbmenu": "hbmenuに復元されました",
|
||||
"Restart Sphaira?": "Sphairaを再起動しますか?",
|
||||
"Press OK to restart Sphaira": "確認ボタンを押してSphairaを再起動",
|
||||
"Text scroll speed": "流れる文字の速さ",
|
||||
"Slow": "遅い",
|
||||
"Normal": "普通",
|
||||
"Fast": "速い",
|
||||
"Set right-side menu": "右側メニュー設定",
|
||||
"Install options": "インストール設定",
|
||||
"Install Options": "インストール設定",
|
||||
"Enable sysmmc": "SYSNANDを有効化",
|
||||
"Enable emummc": "EMUNANDを有効化",
|
||||
"Show install warning": "警告文を示す",
|
||||
"Install location": "インストール経路",
|
||||
"System memory": "本体保存メモリー",
|
||||
"microSD card": "SDカード",
|
||||
"Boost CPU clock": "CPUクロックをブースト",
|
||||
"Allow downgrade": "ダウングレード許可",
|
||||
"Skip if already installed": "既にインストールされている場合はスキップします",
|
||||
"Ticket only": "チケットのみ設置",
|
||||
"Skip base": "ゲームをスキップ",
|
||||
"Skip patch": "ゲームパッチをスキップ",
|
||||
"Skip dlc": "DLCをスキップ",
|
||||
"Skip data patch": "DLCパッチをスキップ",
|
||||
"Skip ticket": "チケットをスキップ",
|
||||
"Skip NCA hash verify": "NCAハッシュ検証をスキップ",
|
||||
"Skip RSA header verify": "RSAヘッダー検証をスキップ",
|
||||
"Skip RSA NPDM verify": "RSA NPDM検証をスキップ",
|
||||
"Ignore distribution bit": "分散ビットを無視",
|
||||
"Convert to standard crypto": "標準暗号に変換",
|
||||
"Lower master key": "下位マスターキーを許容",
|
||||
"Lower system version": "下位システムバージョンを許容",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Apps": "アプリ",
|
||||
"Homebrew Options": "Homebrew設定",
|
||||
"Hide Sphaira": "Sphairaを非表示",
|
||||
"Install Forwarder": "Forwarderのインストール",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "警告: ForwarderをインストールするとBANされます",
|
||||
"Installing Forwarder": "Forwarderのインストール中",
|
||||
"Creating Program": "プログラム作成中",
|
||||
"Creating Control": "コントロール作成中",
|
||||
"Creating Meta": "メター作成中",
|
||||
"Writing Nca": "Nca書き取り中",
|
||||
"Updating ncm databse": "ncmのDBをアップデート中",
|
||||
"Pushing application record": "アプリの記録をプッシュ中",
|
||||
"Failed to install forwarder": "Forwarderのインストール失敗",
|
||||
"Unstarred ": "お気に入り解除: ",
|
||||
"Starred ": "お気に入りに登録: ",
|
||||
"Failed to remove old forwarder, please manually remove it!": "古いForwarderを削除できませんでした、手動で削除してください!",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Appstore": "AppStore",
|
||||
"Store": "AppStore",
|
||||
"Filter: %s | Sort: %s | Order: %s": "フィルター: %s | 並べ替え: %s | 順番: %s",
|
||||
"AppStore Options": "AppStoreの設定",
|
||||
"Info": "情報",
|
||||
"Changelog": "リリースノート",
|
||||
"Details": "詳細",
|
||||
"version: %s": "バージョン: %s",
|
||||
"updated: %s": "更新日: %s",
|
||||
"category: %s": "カテゴリー: %s",
|
||||
"extracted: %.2f MiB": "容量: %.2f MiB",
|
||||
"app_dls: %s": "ダウンロード: %s",
|
||||
"More by Author": "ディベロッパーの他のアプリを見る",
|
||||
"Leave Feedback": "意見を残す",
|
||||
|
||||
"FileBrowser": "ファイルブラウザ",
|
||||
"Files": "ファイル",
|
||||
"%zd files": "%zd個のファイル",
|
||||
"%zd dirs": "%zd個のフォルダー",
|
||||
"File Options": "ファイル設定",
|
||||
"Show Hidden": "非表示ファイルを表示",
|
||||
"Folders First": "フォルダーを優先",
|
||||
"Hidden Last": "非表示ファイルを劣後",
|
||||
"Cut": "切り取り",
|
||||
"Copy": "コピー",
|
||||
"Copying ": "コピー中 ",
|
||||
"Paste": "ペースト",
|
||||
"Paste ": " ",
|
||||
" file(s)?": "個のファイルをペーストしますか?",
|
||||
"Pasting ": "ペースト中 ",
|
||||
"Pasting": "ペースト中",
|
||||
"Rename": "名前の変更",
|
||||
"Set New File Name": "新しい名前を入力",
|
||||
"Extract zip": "ZIPファイルを解凍",
|
||||
"Extract Options": "解凍設定",
|
||||
"Extract here": "ここに解凍",
|
||||
"Extract to root": "最上位ルートに解凍",
|
||||
"Are you sure you want to extract to root?": "最上位ルートに解凍してよろしいですか?",
|
||||
"Extract to...": "指定ルート",
|
||||
"Enter the path to the folder to extract into": "解凍したいルートを指定してください",
|
||||
"Extracting ": "解凍します ",
|
||||
"Extract success!": "解凍完了!",
|
||||
"Extract failed!": "解凍失敗!",
|
||||
"Compress to zip": "ZIPファイルに圧縮",
|
||||
"Compress Options": "圧縮設定",
|
||||
"Compress": "ZIPファイルに圧縮",
|
||||
"Compress to...": "指定ルート",
|
||||
"Compressing ": "圧縮します ",
|
||||
"Compress success!": "圧縮完了!",
|
||||
"Compress failed!": "圧縮失敗!",
|
||||
"Create File": "ファイルの作成",
|
||||
"Set File Name": "名前を入力",
|
||||
"Create Folder": "フォルダーの作成",
|
||||
"Set Folder Name": "名前を入力",
|
||||
"Creating ": "作成中 ",
|
||||
"Upload": "アップロード",
|
||||
"Select upload location": "アップロードの位置を設定",
|
||||
"No upload locations set!": "アップロードの位置が設定されていません",
|
||||
"Uploading": "アップロード中",
|
||||
"Upload successfull!": "アップロード完了!",
|
||||
"Upload failed!": "アップロード失敗!",
|
||||
"View as text (unfinished)": "テキストとして表示 (未完成)",
|
||||
"Ignore read only": "読み取り専用を無視する",
|
||||
"Mount": "マウント",
|
||||
"Sd": "SDメモリーカード",
|
||||
"Image System memory": "システムメモリイメージ",
|
||||
"Image microSD card": "SDイメージ",
|
||||
"Empty...": "このフォルダーは空です",
|
||||
"Open with DayBreak?": "DayBreakで開きますか?",
|
||||
"Launch ": "起動しますか",
|
||||
"Launch option for: ": "起動設定: ",
|
||||
"Select launcher for: ": "起動ランチャーを選ぶ: ",
|
||||
|
||||
"Sort By": "並べ替え",
|
||||
"Sort Options": "並べ替え設定",
|
||||
"Filter": "フィルター",
|
||||
"All": "全て",
|
||||
"Emulators": "エミュレータ",
|
||||
"Tools": "ツール",
|
||||
"Themes": "テーマ",
|
||||
"Legacy": "レガシー",
|
||||
"Sort": "並べ替え",
|
||||
"Size": "ファイルサイズ",
|
||||
"Size (Star)": "ファイルサイズ(お気に入り)",
|
||||
"Alphabetical": "アルファベット順",
|
||||
"Alphabetical (Star)": "アルファベット順(お気に入り)",
|
||||
"Updated": "アップデート順",
|
||||
"Updated (Star)": "アップデート順(お気に入り)",
|
||||
"Downloads": "ダウンロード順",
|
||||
"Likes": "いいね順",
|
||||
"ID": "デベロッパー順",
|
||||
"Order": "順番",
|
||||
"Descending": "降順",
|
||||
"Descending (down)": "降順",
|
||||
"Desc": "降順",
|
||||
"Ascending": "上昇",
|
||||
"Ascending (Up)": "上昇",
|
||||
"Asc": "上昇",
|
||||
"Layout": "レイアウト",
|
||||
"List": "リスト",
|
||||
"Icon": "アイコン",
|
||||
"Grid": "グリッド",
|
||||
"Search": "検索",
|
||||
|
||||
"Options": "設定",
|
||||
"OK": "確認",
|
||||
"Back": "戻る",
|
||||
"Select": "選択",
|
||||
"Open": "開く",
|
||||
"Launch": "起動",
|
||||
"Restart": "再起動",
|
||||
"Next": "次へ",
|
||||
"Prev": "前へ",
|
||||
"Unstar": "お気に入り解除",
|
||||
"Star": "お気に入り",
|
||||
"Yes": "はい",
|
||||
"No": "いいえ",
|
||||
"On": "オン",
|
||||
"Off": "オフ",
|
||||
|
||||
"Install": "インストール",
|
||||
"Install Selected files?": "選択したファイルをインストールしますか?",
|
||||
"Installing ": "インストール中 ",
|
||||
"Installed ": "インストールしました ",
|
||||
"Installed!": "インストール完了",
|
||||
"Trying to load ": "サムネイルを取得中 ",
|
||||
"Checking MD5": "MD5を確認中 ",
|
||||
|
||||
"Delete": "削除",
|
||||
"Delete Selected files?": "本当に削除しますか?",
|
||||
"Completely remove ": "除去しますか ",
|
||||
"Are you sure you want to delete ": "消去してもよろしいですか ",
|
||||
"Are you sure you wish to cancel?": "本当に取り消しますか?",
|
||||
"Audio disabled due to suspended game": "ゲームが一時停止状態の場合、オーディオは無効になります",
|
||||
"If this message appears repeatedly, please open an issue.": "このメッセージが繰り返し表示される場合は、問題を開いてください。"
|
||||
"Scanning ": "スキャン中 ",
|
||||
"Deleting ": "削除中 ",
|
||||
"Deleting": "削除中",
|
||||
"Remove": "除去",
|
||||
"Completely remove ": "除去しますか ",
|
||||
"Removing ": "除去中 ",
|
||||
"Removed ": "除去完了 ",
|
||||
"Uninstalling ": "アンインストール中 ",
|
||||
|
||||
"Download": "ダウンロード",
|
||||
"Downloading ": "ダウンロード中 ",
|
||||
"Downloaded ": "ダウンロード完了 ",
|
||||
|
||||
"Update": "アップデート",
|
||||
"Update avaliable: ": "アップデート可能: ",
|
||||
"Download update: ": "アップデートをダウンロード: ",
|
||||
"Updated to ": "アップデート: ",
|
||||
"Failed to download update": "アップデートのダウンロード失敗",
|
||||
|
||||
"%zu hours %zu minutes remaining": "残り %zu 時間 %zu 分",
|
||||
"%zu minutes %zu seconds remaining": "残り %zu 分",
|
||||
"%zu seconds remaining": "残り %zu 秒",
|
||||
|
||||
"Loading...": "ロード中",
|
||||
"Loading": "ロード中",
|
||||
"Empty!": "何も見つかりません",
|
||||
"Not Ready...": "準備ができていません",
|
||||
"Error loading page!": "ページのロードエラー"
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[ 애플릿 모드 ]",
|
||||
"No Internet": "인터넷 연결 없음",
|
||||
"Files": "파일 탐색기",
|
||||
"Apps": "홈브류",
|
||||
"Store": "앱스토어",
|
||||
"Menu": "메뉴",
|
||||
"Options": "설정",
|
||||
"OK": "확인",
|
||||
"Back": "뒤로",
|
||||
"Select": "선택",
|
||||
"Open": "열기",
|
||||
"Launch": "실행",
|
||||
"Info": "정보",
|
||||
"Install": "설치",
|
||||
"Delete": "삭제",
|
||||
"Restart": "재시작",
|
||||
"Changelog": "변경 내역",
|
||||
"Details": "상세",
|
||||
"Update": "업데이트",
|
||||
"Remove": "제거",
|
||||
"Restore": "복원",
|
||||
"Download": "다운로드",
|
||||
"Next Page": "다음 페이지",
|
||||
"Prev Page": "이전 페이지",
|
||||
"Unstar": "즐겨찾기 해제",
|
||||
"Star": "즐겨찾기",
|
||||
"System memory": "낸드 저장소",
|
||||
"microSD card": "SD 카드",
|
||||
"Sd": "SD 카드",
|
||||
"Image System memory": "낸드 이미지",
|
||||
"Image microSD card": "SD 이미지",
|
||||
"Slow": "느림",
|
||||
"Normal": "보통",
|
||||
"Fast": "빠름",
|
||||
"Yes": "예",
|
||||
"No": "아니요",
|
||||
"Enabled": "",
|
||||
"Disabled": "",
|
||||
|
||||
"Sort By": "정렬",
|
||||
"Sort Options": "정렬 옵션",
|
||||
"Filter": "필터",
|
||||
"Sort": "분류",
|
||||
"Order": "정렬",
|
||||
"Search": "검색",
|
||||
"Updated": "업데이트순",
|
||||
"Updated (Star)": "업데이트순 (즐겨찾기)",
|
||||
"Downloads": "다운로드순",
|
||||
"Size": "크기순",
|
||||
"Size (Star)": "크기순 (즐겨찾기)",
|
||||
"Alphabetical": "알파벳순",
|
||||
"Alphabetical (Star)": "알파벳순 (즐겨찾기)",
|
||||
"Likes": "좋아요순",
|
||||
"ID": "ID순",
|
||||
"Descending": "내림차순",
|
||||
"Descending (down)": "내림차순",
|
||||
"Desc": "내림차순",
|
||||
"Ascending": "오름차순",
|
||||
"Ascending (Up)": "오름차순",
|
||||
"Asc": "오름차순",
|
||||
"Switch-Handheld!": "휴대모드로 전환됨!",
|
||||
"Switch-Docked!": "독 모드로 전환됨!",
|
||||
"Audio disabled due to suspended game": "게임 실행 중에는 BGM이 비활성화 됩니다",
|
||||
"Are you sure you wish to cancel?": "정말 취소할까요?",
|
||||
"An error occurred": "오류가 발생했습니다!",
|
||||
"If this message appears repeatedly, please open an issue.": "해당 메시지가 반복해서 나타나는 경우, 이슈를 등록하세요.",
|
||||
|
||||
"Menu Options": "메뉴",
|
||||
"Menu": "메뉴",
|
||||
"Theme": "테마",
|
||||
"Theme Options": "테마 옵션",
|
||||
"Select Theme": "테마 선택",
|
||||
"Shuffle": "셔플",
|
||||
"Music": "BGM",
|
||||
"12 Hour Time": "",
|
||||
"12 Hour Time": "12 시간제",
|
||||
"Download Default Music": "기본 BGM 다운로드",
|
||||
"Failed to download default_music.bfstm, please try again": "BGM 파일 다운로드에 실패했습니다, 다시 시도하세요",
|
||||
"Overwrite current default music?": "BGM 파일을 덮어쓸까요?",
|
||||
|
||||
"Network": "네트워크",
|
||||
"Network Options": "네트워크 옵션",
|
||||
"Ftp": "FTP (무선)",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "Nxlink 연결됨",
|
||||
"Nxlink Upload": "Nxlink 업로드",
|
||||
"Nxlink Finished": "Nxlink 종료됨",
|
||||
"Switch-Handheld!": "휴대모드로 전환됨!",
|
||||
"Switch-Docked!": "독 모드로 전환됨!",
|
||||
|
||||
"Language": "언어",
|
||||
"Auto": "자동",
|
||||
"English": "English",
|
||||
@@ -92,98 +43,112 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "로깅",
|
||||
"Replace hbmenu on exit": "종료 시 hbmenu 교체",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "기타",
|
||||
"Misc Options": "기타 옵션",
|
||||
"Web": "웹 브라우저",
|
||||
"Install forwarders": "바로가기 설치",
|
||||
"Install location": "설치 위치",
|
||||
"Show install warning": "경고 메시지",
|
||||
"Text scroll speed": "흐르는 텍스트 속도",
|
||||
|
||||
"FileBrowser": "파일 탐색기",
|
||||
"%zd files": "%zd 개 파일",
|
||||
"%zd dirs": "%zd 개 폴더",
|
||||
"File Options": "파일 옵션",
|
||||
"Show Hidden": "숨겨진 항목 표시",
|
||||
"Folders First": "폴더 우선 정렬",
|
||||
"Hidden Last": "숨겨진 항목 후순 정렬",
|
||||
"Cut": "잘라내기",
|
||||
"Copy": "복사",
|
||||
"Paste": "붙여넣기",
|
||||
"Paste ": " ",
|
||||
" file(s)?": "개 항목을 붙여넣을까요?",
|
||||
"Rename": "이름 바꾸기",
|
||||
"Set New File Name": "새 파일명 입력",
|
||||
"Advanced": "고급",
|
||||
"Advanced Options": "고급 옵션",
|
||||
"Create File": "새 파일",
|
||||
"Set File Name": "파일명 입력",
|
||||
"Create Folder": "새 폴더",
|
||||
"Set Folder Name": "폴더명 입력",
|
||||
"View as text (unfinished)": "텍스트로 보기 (미완성)",
|
||||
"Ignore read only": "읽기 전용 설정 무시",
|
||||
"Mount": "마운트",
|
||||
"Empty...": "비어있음...",
|
||||
"Open with DayBreak?": "DayBreak로 열까요?",
|
||||
"Launch ": "실행할까요 ",
|
||||
"Launch option for: ": "실행 옵션: ",
|
||||
"Select launcher for: ": "실행 런처: ",
|
||||
|
||||
"Homebrew": "홈브류",
|
||||
"Homebrew Options": "홈브류 옵션",
|
||||
"Hide Sphaira": "Sphaira 숨기기",
|
||||
"Install Forwarder": "바로가기 설치",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "경고: 시스낸드에 바로가기 설치 시, 밴 위험이 있습니다!",
|
||||
"Installing Forwarder": "바로가기 설치",
|
||||
"Creating Program": "프로그램 생성",
|
||||
"Creating Control": "컨트롤 생성",
|
||||
"Creating Meta": "메타 생성",
|
||||
"Writing Nca": "Nca 쓰기",
|
||||
"Updating ncm databse": "Ncm 데이터베이스 업데이트",
|
||||
"Pushing application record": "응용 프로그램 기록 푸싱",
|
||||
"Installed!": "설치 완료!",
|
||||
"Failed to install forwarder": "바로가기 설치 실패",
|
||||
"Unstarred ": "즐겨찾기 해제: ",
|
||||
"Starred ": "즐겨찾기 적용: ",
|
||||
|
||||
"AppStore": "앱스토어",
|
||||
"Filter: %s | Sort: %s | Order: %s": "필터: %s | 분류: %s | 정렬: %s",
|
||||
"AppStore Options": "앱스토어 옵션",
|
||||
"All": "모두",
|
||||
"Games": "게임",
|
||||
"Emulators": "에뮬레이터",
|
||||
"Tools": "도구",
|
||||
"Themes": "테마",
|
||||
"Legacy": "레거시",
|
||||
"version: %s": "버전: %s",
|
||||
"updated: %s": "업데이트: %s",
|
||||
"category: %s": "카테고리: %s",
|
||||
"extracted: %.2f MiB": "용량: %.2f MiB",
|
||||
"app_dls: %s": "다운로드 횟수: %s",
|
||||
"More by Author": "개발자의 다른 앱 더 보기",
|
||||
"Leave Feedback": "피드백 남기기",
|
||||
"Game Options": "게임 옵션",
|
||||
"Hide forwarders": "바로가기 앱 숨기기",
|
||||
"Launch random game": "게임 랜덤 실행",
|
||||
"List meta records": "메타 기록",
|
||||
"Entries": "목록",
|
||||
"Failed to list application meta entries": "메타 항목 나열에 실패했습니다",
|
||||
"No meta entries found...\n": "메타 항목을 찾을 수 없습니다...\n",
|
||||
"Updating application record list": "앱 기록 업데이트 중",
|
||||
"Dump": "덤프",
|
||||
"Select content to dump": "덤프 옵션",
|
||||
"Dump All": "모든 콘텐츠",
|
||||
"Dump Application": "게임",
|
||||
"Dump Patch": "게임 패치",
|
||||
"Dump AddOnContent": "DLC",
|
||||
"Dump DataPatch": "DLC 패치",
|
||||
"Select dump location": "덤프 위치",
|
||||
"microSD card (/dumps/NSP/)": "SD 카드 (sdmc:/dumps/NSP/)",
|
||||
"USB transfer (Switch 2 Switch)": "USB 전송 (Switch 2 Switch)",
|
||||
"/dev/null (Speed Test)": "/dev/null (Speed Test)",
|
||||
"Dumping": "덤프 중",
|
||||
"Dump successfull!": "덤프 완료!",
|
||||
"Dump failed!": "덤프 실패!",
|
||||
"Success": "완료!",
|
||||
"Delete successfull!": "삭제 완료!",
|
||||
"Delete failed!": "삭제 실패!",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Themezer 옵션",
|
||||
"Nsfw": "선정성 테마",
|
||||
"Page": "페이지",
|
||||
"Page %zu / %zu": "페이지 %zu / %zu",
|
||||
"Enter Page Number": "페이지 번호 입력",
|
||||
"Bad Page": "잘못된 페이지 입력됨",
|
||||
"Download theme?": "테마를 다운로드할까요?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "JSON에서 다운로드",
|
||||
"Select asset to download for ": "다운로드 아이템: ",
|
||||
|
||||
"FTP Install": "FTP 설치",
|
||||
"FTP Install (EXPERIMENTAL)": "FTP 설치 (실험실 기능)",
|
||||
"Connection Type: WiFi | Strength: ": "상태: WiFi | 신호 세기: ",
|
||||
"Connection Type: Ethernet": "상태: 이더넷",
|
||||
"Connection Type: None": "상태: 연결 없음",
|
||||
"Host:": "호스트:",
|
||||
"Port:": "포트:",
|
||||
"Username:": "사용자명:",
|
||||
"Password:": "비밀번호:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "WiFi 암호:",
|
||||
"Failed to install via FTP, press B to exit...": "FTP 설치 실패함, 종료하려면 버튼을 입력하세요...",
|
||||
"Ftp install success!": "FTP 설치 완료!",
|
||||
"Ftp install failed!": "FTP 설치 실패!",
|
||||
"USB Install": "USB 설치",
|
||||
"USB": "USB 설치",
|
||||
"Connected, waiting for file list...": "연결됨, 파일 목록 대기 중...",
|
||||
"Connected, starting transfer...": "연결됨, 전송 시작 중...",
|
||||
"Failed to init usb, press B to exit...": "USB 연결 실패함, 종료하려면 버튼을 입력하세요...",
|
||||
"Waiting for connection...": "연결 대기 중...",
|
||||
"Transferring data...": "데이터 전송 중...",
|
||||
"USB connected, sending file list": "USB 연결됨, 파일 목록 전송 중",
|
||||
"Sent file list, waiting for command...": "파일 목록 전송됨, 입력 대기 중...",
|
||||
"waiting for usb connection...": "USB 연결 대기 중...",
|
||||
"Disable MTP for usb install": "USB 설치를 위해 MTP 비활성화됨",
|
||||
"Re-enabled MTP": "MTP 재활성화됨",
|
||||
"Installed via usb": "USB를 통해 설치됨",
|
||||
"Usb install success!": "USB 설치 완료!",
|
||||
"Usb install failed!": "USB 설치 실패!",
|
||||
"Press B to exit...": " 버튼으로 나가기",
|
||||
"GameCard Install": "카트리지 설치",
|
||||
"GameCard": "카트리지 설치",
|
||||
"GC": "카트리지",
|
||||
"System memory %.1f GB": "본체 저장 메모리 %.1f GB",
|
||||
"microSD card %.1f GB": "SD 카드 %.1f GB",
|
||||
"Nand Install": "본체 저장 메모리에 설치",
|
||||
"SD Card Install": "SD 카드에 설치",
|
||||
"Exit": "나가기",
|
||||
"Gc install success!": "카트리지 설치 완료!",
|
||||
"Gc install failed!": "카트리지 설치 실패!",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "조이콘 적외선 카메라",
|
||||
"IRS": "조이콘 적외선 카메라",
|
||||
"Irs": "조이콘 적외선 카메라",
|
||||
"Ambient Noise Level: ": "주변 노이즈 레벨: ",
|
||||
"Controller": "컨트롤러",
|
||||
"Pad ": "조이콘 ",
|
||||
"HandHeld": "본체 연결",
|
||||
" (Available)": " (사용 가능)",
|
||||
" (Unsupported)": " (지원 안됨)",
|
||||
" (Unconnected)": " (연결 없음)",
|
||||
"HandHeld": "본체 연결",
|
||||
"Rotation": "화면 회전",
|
||||
"0 (Sideways)": "반시계방향 90° 회전",
|
||||
"90 (Flat)": "정방향",
|
||||
"180 (-Sideways)": "시계방향 90° 회전",
|
||||
"270 (Upside down)": "상하반전",
|
||||
"Colour": "색상",
|
||||
"Grey": "회색",
|
||||
"Colour": "색상 팔레트",
|
||||
"Grey": "그레이스케일",
|
||||
"Ironbow": "아이언보우",
|
||||
"Green": "초록색",
|
||||
"Red": "빨간색",
|
||||
"Blue": "파란색",
|
||||
"Green": "그린",
|
||||
"Red": "레드",
|
||||
"Blue": "블루",
|
||||
"Light Target": "반사 표적",
|
||||
"All leds": "모든 LED 켜기",
|
||||
"Bright group": "Bright LED 켜기",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "일반",
|
||||
"Negative image": "반전",
|
||||
"Format": "해상도",
|
||||
"Trimming Format": "트리밍 해상도",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "트리밍 해상도",
|
||||
"External Light Filter": "외부 조명 필터",
|
||||
"Load Default": "기본값으로 설정",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Themezer 옵션",
|
||||
"Nsfw": "선정성 테마",
|
||||
"Page": "페이지",
|
||||
"Page %zu / %zu": "페이지 %zu / %zu",
|
||||
"Enter Page Number": "페이지 번호 입력",
|
||||
"Bad Page": "잘못된 페이지",
|
||||
"Download theme?": "테마를 다운로드할까요?",
|
||||
"Advanced": "고급",
|
||||
"Advanced Options": "고급 옵션",
|
||||
"Logging": "로깅",
|
||||
"Replace hbmenu on exit": "종료 시 hbmenu 교체",
|
||||
"Restore hbmenu?": "hbmenu로 복원할까요?",
|
||||
"Restore": "복원",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmemu.nro를 찾을 수 없습니다\n앱스토어에서 다시 설치하세요",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "hbmenu 복원 실패함, hbmenu를 다시 다운로드하세요",
|
||||
"Failed to restore hbmenu, using sphaira instead": "hbmenu 복원 실패함, sphaira를 계속 사용합니다",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu 복원됨, sphaira를 종료합니다",
|
||||
"Restored hbmenu": "hbmenu 복원됨",
|
||||
"Restart Sphaira?": "Sphaira를 재시작할까요?",
|
||||
"Press OK to restart Sphaira": "확인 버튼 입력하여 Sphaira 재시작",
|
||||
"Text scroll speed": "긴 텍스트 표시 속도",
|
||||
"Slow": "천천히",
|
||||
"Normal": "보통",
|
||||
"Fast": "빠르게",
|
||||
"Set right-side menu": "우측 메뉴 설정",
|
||||
"Install options": "설치 옵션",
|
||||
"Install Options": "설치 옵션",
|
||||
"Enable sysmmc": "시스낸드 활성화",
|
||||
"Enable emummc": "에뮤낸드 활성화",
|
||||
"Show install warning": "설치 경고 표시",
|
||||
"Install location": "설치 위치",
|
||||
"System memory": "본체 저장 메모리",
|
||||
"microSD card": "SD 카드",
|
||||
"Boost CPU clock": "CPU 클럭 향상",
|
||||
"Allow downgrade": "다운그레이드 허용",
|
||||
"Skip if already installed": "설치된 항목 건너뛰기",
|
||||
"Ticket only": "티켓만 설치",
|
||||
"Skip base": "게임 건너뛰기",
|
||||
"Skip patch": "게임 패치 건너뛰기",
|
||||
"Skip dlc": "DLC 건너뛰기",
|
||||
"Skip data patch": "DLC 패치 건너뛰기",
|
||||
"Skip ticket": "티켓 건너뛰기",
|
||||
"Skip NCA hash verify": "NCA 해시 확인 건너뛰기",
|
||||
"Skip RSA header verify": "RSA 헤더 확인 건너뛰기",
|
||||
"Skip RSA NPDM verify": "RSA NPDM 확인 건너뛰기",
|
||||
"Ignore distribution bit": "배포 비트 무시",
|
||||
"Convert to standard crypto": "표준 암호화로 변환",
|
||||
"Lower master key": "하위 마스터 키 지원",
|
||||
"Lower system version": "하위 시스템 버전 지원",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "JSON에서 다운로드",
|
||||
"Select asset to download for ": "다운로드 아이템: ",
|
||||
"Homebrew": "홈브류",
|
||||
"Apps": "홈브류",
|
||||
"Homebrew Options": "홈브류 옵션",
|
||||
"Hide Sphaira": "Sphaira 숨기기",
|
||||
"Install Forwarder": "바로가기 설치",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "경고: 시스낸드에 설치 시, 밴 위험이 있습니다!",
|
||||
"Installing Forwarder": "바로가기 설치",
|
||||
"Creating Program": "프로그램 생성",
|
||||
"Creating Control": "컨트롤 생성",
|
||||
"Creating Meta": "메타 생성",
|
||||
"Writing Nca": "Nca 쓰기",
|
||||
"Updating ncm databse": "Ncm 데이터베이스 업데이트",
|
||||
"Pushing application record": "응용 프로그램 기록 푸싱",
|
||||
"Failed to install forwarder": "바로가기 설치 실패함",
|
||||
"Unstarred ": "즐겨찾기 해제: ",
|
||||
"Starred ": "즐겨찾기 등록: ",
|
||||
"Failed to remove old forwarder, please manually remove it!": "바로가기 제거 실패함, 직접 제거해주세요!",
|
||||
|
||||
"Installing ": "설치 ",
|
||||
"Uninstalling ": "설치 제거 ",
|
||||
"Deleting ": "삭제 ",
|
||||
"Deleting": "삭제",
|
||||
"Pasting ": "붙여넣기 ",
|
||||
"AppStore": "앱스토어",
|
||||
"Appstore": "앱스토어",
|
||||
"Store": "앱스토어",
|
||||
"Filter: %s | Sort: %s | Order: %s": "필터: %s | 분류: %s | 정렬: %s",
|
||||
"AppStore Options": "앱스토어 옵션",
|
||||
"Info": "정보",
|
||||
"Changelog": "변경사항",
|
||||
"Details": "상세",
|
||||
"version: %s": "버전: %s",
|
||||
"updated: %s": "업데이트: %s",
|
||||
"category: %s": "카테고리: %s",
|
||||
"extracted: %.2f MiB": "용량: %.2f MiB",
|
||||
"app_dls: %s": "다운로드 횟수: %s",
|
||||
"More by Author": "개발자의 다른 앱 더 보기",
|
||||
"Leave Feedback": "피드백 남기기",
|
||||
|
||||
"FileBrowser": "파일 탐색기",
|
||||
"Files": "파일 탐색기",
|
||||
"%zd files": "%zd 개 파일",
|
||||
"%zd dirs": "%zd 개 폴더",
|
||||
"File Options": "파일 옵션",
|
||||
"Show Hidden": "숨겨진 항목 표시",
|
||||
"Folders First": "폴더 우선 정렬",
|
||||
"Hidden Last": "숨겨진 항목 후순 정렬",
|
||||
"Cut": "잘라내기",
|
||||
"Copy": "복사",
|
||||
"Copying ": "복사 중 ",
|
||||
"Paste": "붙여넣기",
|
||||
"Paste ": " ",
|
||||
" file(s)?": "개 항목을 붙여넣을까요?",
|
||||
"Pasting ": "붙여넣는 중 ",
|
||||
"Pasting": "붙여넣기",
|
||||
"Removing ": "제거 ",
|
||||
"Scanning ": "스캔 ",
|
||||
"Creating ": "생성 ",
|
||||
"Copying ": "복사 ",
|
||||
"Trying to load ": "썸네일 받아오는 중... ",
|
||||
"Downloading ": "다운로드 ",
|
||||
"Rename": "이름 바꾸기",
|
||||
"Set New File Name": "새 파일명 입력",
|
||||
"Extract zip": "압축 해제",
|
||||
"Extract Options": "압축 해제 옵션",
|
||||
"Extract here": "여기에 풀기",
|
||||
"Extract to root": "최상위 경로에 풀기",
|
||||
"Are you sure you want to extract to root?": "최상위 경로에 압축 해제할까요?",
|
||||
"Extract to...": "경로 지정",
|
||||
"Enter the path to the folder to extract into": "압축 해제할 경로를 입력하세요",
|
||||
"Extracting ": "압축 해제 중 ",
|
||||
"Extract success!": "압축 해제 완료!",
|
||||
"Extract failed!": "압축 해제 실패!",
|
||||
"Compress to zip": "압축",
|
||||
"Compress Options": "압축 옵션",
|
||||
"Compress": "ZIP 파일로 압축",
|
||||
"Compress to...": "경로 지정",
|
||||
"Compressing ": "압축 중 ",
|
||||
"Compress success!": "압축 완료!",
|
||||
"Compress failed!": "압축 실패!",
|
||||
"Create File": "새 파일",
|
||||
"Set File Name": "파일명 입력",
|
||||
"Create Folder": "새 폴더",
|
||||
"Set Folder Name": "폴더명 입력",
|
||||
"Creating ": "생성 중 ",
|
||||
"Upload": "업로드",
|
||||
"Select upload location": "업로드 위치 선택",
|
||||
"No upload locations set!": "업로드 위치가 설정되지 않았습니다!",
|
||||
"Uploading": "업로드 중 ",
|
||||
"Upload successfull!": "업로드 완료!",
|
||||
"Upload failed!": "업로드 실패!",
|
||||
"View as text (unfinished)": "텍스트로 보기 (미완성)",
|
||||
"Ignore read only": "읽기 전용 설정 무시",
|
||||
"Mount": "마운트",
|
||||
"Sd": "SD 카드",
|
||||
"Image System memory": "시스낸드 앨범",
|
||||
"Image microSD card": "에뮤낸드 앨범",
|
||||
"Empty...": "비어있음...",
|
||||
"Open with DayBreak?": "DayBreak로 열까요?",
|
||||
"Launch ": "실행할까요 ",
|
||||
"Launch option for: ": "실행 옵션: ",
|
||||
"Select launcher for: ": "실행 런처: ",
|
||||
|
||||
"Sort By": "정렬",
|
||||
"Sort Options": "정렬 옵션",
|
||||
"Filter": "필터",
|
||||
"All": "전체",
|
||||
"Emulators": "에뮬레이터",
|
||||
"Tools": "도구",
|
||||
"Themes": "테마",
|
||||
"Legacy": "레거시",
|
||||
"Sort": "분류",
|
||||
"Size": "크기순",
|
||||
"Size (Star)": "크기순 (즐겨찾기)",
|
||||
"Alphabetical": "알파벳순",
|
||||
"Alphabetical (Star)": "알파벳순 (즐겨찾기)",
|
||||
"Updated": "업데이트순",
|
||||
"Updated (Star)": "업데이트순 (즐겨찾기)",
|
||||
"Downloads": "다운로드순",
|
||||
"Likes": "좋아요순",
|
||||
"ID": "ID순",
|
||||
"Order": "정렬",
|
||||
"Descending": "내림차순",
|
||||
"Descending (down)": "내림차순",
|
||||
"Desc": "내림차순",
|
||||
"Ascending": "오름차순",
|
||||
"Ascending (Up)": "오름차순",
|
||||
"Asc": "오름차순",
|
||||
"Layout": "레이아웃",
|
||||
"List": "목록",
|
||||
"Icon": "아이콘",
|
||||
"Grid": "격자",
|
||||
"Search": "검색",
|
||||
|
||||
"Options": "설정",
|
||||
"OK": "확인",
|
||||
"Back": "뒤로",
|
||||
"Select": "선택",
|
||||
"Open": "열기",
|
||||
"Launch": "실행",
|
||||
"Restart": "재시작",
|
||||
"Next": "다음",
|
||||
"Prev": "이전",
|
||||
"Unstar": "즐겨찾기 해제",
|
||||
"Star": "즐겨찾기",
|
||||
"Yes": "예",
|
||||
"No": "아니요",
|
||||
"On": "켬",
|
||||
"Off": "끔",
|
||||
|
||||
"Install": "설치",
|
||||
"Install Selected files?": "선택한 파일을 설치할까요?",
|
||||
"Installing ": "설치 중 ",
|
||||
"Installed ": "설치됨 ",
|
||||
"Installed!": "설치 완료!",
|
||||
"Trying to load ": "정보 취득 중 ",
|
||||
"Checking MD5": "MD5 확인 중",
|
||||
|
||||
"Delete": "삭제",
|
||||
"Delete Selected files?": "선택한 파일을 삭제할까요?",
|
||||
"Are you sure you want to delete ": "정말 삭제할까요 ",
|
||||
"Scanning ": "스캔 중 ",
|
||||
"Deleting ": "삭제 중 ",
|
||||
"Deleting": "삭제 중",
|
||||
"Remove": "제거",
|
||||
"Completely remove ": "정말 삭제할까요 ",
|
||||
"Removing ": "제거 중 ",
|
||||
"Removed ": "제거됨: ",
|
||||
"Uninstalling ": "설치 제거됨: ",
|
||||
|
||||
"Download": "다운로드",
|
||||
"Downloading ": "다운로드 중 ",
|
||||
"Downloaded ": "다운로드 완료: ",
|
||||
"Removed ": "제거 됨: ",
|
||||
"Checking MD5": "MD5 확인",
|
||||
|
||||
"Update": "업데이트",
|
||||
"Update avaliable: ": "업데이트 가능: ",
|
||||
"Download update: ": "업데이트 다운로드: ",
|
||||
"Updated to ": "업데이트됨: ",
|
||||
"Failed to download update": "업데이트 다운로드 실패함",
|
||||
|
||||
"%zu hours %zu minutes remaining": "%zu 시간 %zu 분 남음",
|
||||
"%zu minutes %zu seconds remaining": "%zu 분 %zu 초 남음",
|
||||
"%zu seconds remaining": "%zu 초 남음",
|
||||
|
||||
"Loading...": "로딩 중...",
|
||||
"Loading": "로딩 중...",
|
||||
"Empty!": "찾을 수 없습니다!",
|
||||
"Not Ready...": "준비되지 않음...",
|
||||
"Error loading page!": "페이지 로딩 오류!",
|
||||
"Update avaliable: ": "업데이트 가능: ",
|
||||
"Download update: ": "업데이트 다운로드: ",
|
||||
"Updated to ": "업데이트: ",
|
||||
"Press OK to restart Sphaira": "확인 버튼 입력하여 Sphaira 재시작",
|
||||
"Restart Sphaira?": "Sphaira를 재시작할까요?",
|
||||
"Failed to download update": "업데이트 다운로드 실패함",
|
||||
"Restore hbmenu?": "hbmenu로 교체할까요?",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmemu.nro를 찾을 수 없습니다\n앱스토어에서 다시 설치하세요",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "hbmenu 교체 실패함, hbmenu를 다시 다운로드하세요",
|
||||
"Failed to restore hbmenu, using sphaira instead": "hbmenu 교체 실패함, sphaira를 계속 사용합니다",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu로 교체됨, sphaira를 종료합니다",
|
||||
"Restored hbmenu": "hbmenu로 교체됨",
|
||||
"Delete Selected files?": "선택한 파일을 삭제할까요?",
|
||||
"Completely remove ": "정말 삭제할까요 ",
|
||||
"Are you sure you want to delete ": "정말 삭제할까요 ",
|
||||
"Are you sure you wish to cancel?": "정말 취소할까요?",
|
||||
"Audio disabled due to suspended game": "게임 실행 중에는 BGM이 비활성화 됩니다",
|
||||
"If this message appears repeatedly, please open an issue.": "해당 메시지가 반복해서 나타나는 경우, 이슈를 등록하세요."
|
||||
"Error loading page!": "페이지 로딩 오류!"
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Applet-modus]",
|
||||
"No Internet": "Geen internet",
|
||||
"Files": "",
|
||||
"Apps": "",
|
||||
"Store": "",
|
||||
"Menu": "Menu",
|
||||
"Options": "Opties",
|
||||
"OK": "",
|
||||
"Back": "Terug",
|
||||
"Select": "",
|
||||
"Open": "Open",
|
||||
"Launch": "Launch",
|
||||
"Info": "Info",
|
||||
"Install": "Installeren",
|
||||
"Delete": "Verwijderen",
|
||||
"Restart": "",
|
||||
"Changelog": "",
|
||||
"Details": "",
|
||||
"Update": "",
|
||||
"Remove": "",
|
||||
"Restore": "",
|
||||
"Download": "Downloaden",
|
||||
"Next Page": "Volgende pagina",
|
||||
"Prev Page": "Vorige pagina",
|
||||
"Unstar": "",
|
||||
"Star": "",
|
||||
"System memory": "",
|
||||
"microSD card": "",
|
||||
"Sd": "",
|
||||
"Image System memory": "",
|
||||
"Image microSD card": "",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Ja",
|
||||
"No": "Nee",
|
||||
"Enabled": "Ingeschakeld",
|
||||
"Disabled": "Gehandicapt",
|
||||
|
||||
"Sort By": "Sorteer op",
|
||||
"Sort Options": "Sorteeropties",
|
||||
"Filter": "Filter",
|
||||
"Sort": "Soort",
|
||||
"Order": "Volgorde",
|
||||
"Search": "Zoekopdracht",
|
||||
"Updated": "Bijgewerkt",
|
||||
"Updated (Star)": "",
|
||||
"Downloads": "Downloads",
|
||||
"Size": "Maat",
|
||||
"Size (Star)": "",
|
||||
"Alphabetical": "Alfabetisch",
|
||||
"Alphabetical (Star)": "",
|
||||
"Likes": "",
|
||||
"ID": "",
|
||||
"Descending": "Aflopend",
|
||||
"Descending (down)": "Aflopend",
|
||||
"Desc": "Aflopend",
|
||||
"Ascending": "Oplopend",
|
||||
"Ascending (Up)": "Oplopend",
|
||||
"Asc": "Oplopend",
|
||||
"Switch-Handheld!": "",
|
||||
"Switch-Docked!": "",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"Are you sure you wish to cancel?": "",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "",
|
||||
|
||||
"Menu Options": "Menu-opties",
|
||||
"Menu": "Menu",
|
||||
"Theme": "Thema",
|
||||
"Theme Options": "Thema Opties",
|
||||
"Select Theme": "Selecteer Thema",
|
||||
"Shuffle": "Schudden",
|
||||
"Music": "Muziek",
|
||||
"12 Hour Time": "",
|
||||
"Download Default Music": "",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "Netwerk",
|
||||
"Network Options": "Netwerkopties",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "",
|
||||
"Nxlink Upload": "",
|
||||
"Nxlink Finished": "",
|
||||
"Switch-Handheld!": "",
|
||||
"Switch-Docked!": "",
|
||||
|
||||
"Language": "Taal",
|
||||
"Auto": "",
|
||||
"English": "English",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Loggen",
|
||||
"Replace hbmenu on exit": "Vervang hbmenu bij afsluiten",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Diversen",
|
||||
"Misc Options": "Diverse opties",
|
||||
"Web": "Web",
|
||||
"Install forwarders": "",
|
||||
"Install location": "",
|
||||
"Show install warning": "",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "Bestandsbrowser",
|
||||
"%zd files": "%zd files",
|
||||
"%zd dirs": "%zd dirs",
|
||||
"File Options": "Bestandsopties",
|
||||
"Show Hidden": "Toon verborgen",
|
||||
"Folders First": "Mappen eerst",
|
||||
"Hidden Last": "Verborgen laatste",
|
||||
"Cut": "Snee",
|
||||
"Copy": "Kopiëren",
|
||||
"Paste": "",
|
||||
"Paste ": "",
|
||||
" file(s)?": "",
|
||||
"Rename": "Hernoemen",
|
||||
"Set New File Name": "",
|
||||
"Advanced": "Geavanceerd",
|
||||
"Advanced Options": "Bestand maken",
|
||||
"Create File": "Bestand maken",
|
||||
"Set File Name": "",
|
||||
"Create Folder": "Map maken",
|
||||
"Set Folder Name": "",
|
||||
"View as text (unfinished)": "Bekijk als tekst (onvoltooid)",
|
||||
"Ignore read only": "",
|
||||
"Mount": "",
|
||||
"Empty...": "",
|
||||
"Open with DayBreak?": "",
|
||||
"Launch ": "",
|
||||
"Launch option for: ": "",
|
||||
"Select launcher for: ": "",
|
||||
|
||||
"Homebrew": "Zelf brouwen",
|
||||
"Homebrew Options": "Homebrew-opties",
|
||||
"Hide Sphaira": "Verberg Sphaira",
|
||||
"Install Forwarder": "Forwarder installeren",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "WAARSCHUWING: Het installeren van forwarders leidt tot een ban!",
|
||||
"Installing Forwarder": "",
|
||||
"Creating Program": "",
|
||||
"Creating Control": "",
|
||||
"Creating Meta": "",
|
||||
"Writing Nca": "",
|
||||
"Updating ncm databse": "",
|
||||
"Pushing application record": "",
|
||||
"Installed!": "",
|
||||
"Failed to install forwarder": "",
|
||||
"Unstarred ": "",
|
||||
"Starred ": "",
|
||||
|
||||
"AppStore": "",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Soort: %s | Volgorde: %s",
|
||||
"AppStore Options": "AppStore-opties",
|
||||
"All": "Alle",
|
||||
"Games": "Spellen",
|
||||
"Emulators": "Emulators",
|
||||
"Tools": "Hulpmiddelen",
|
||||
"Themes": "Thema's",
|
||||
"Legacy": "Nalatenschap",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "",
|
||||
"Leave Feedback": "",
|
||||
"Game Options": "",
|
||||
"Hide forwarders": "",
|
||||
"Launch random game": "",
|
||||
"List meta records": "",
|
||||
"Entries": "",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/NSP/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Success": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
|
||||
"Themezer": "Themamaker",
|
||||
"Themezer Options": "",
|
||||
"Nsfw": "",
|
||||
"Page": "",
|
||||
"Page %zu / %zu": "Page %zu / %zu",
|
||||
"Enter Page Number": "",
|
||||
"Bad Page": "",
|
||||
"Download theme?": "",
|
||||
|
||||
"GitHub": "",
|
||||
"Downloading json": "",
|
||||
"Select asset to download for ": "",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
"Host:": "",
|
||||
"Port:": "",
|
||||
"Username:": "",
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "",
|
||||
"Waiting for connection...": "",
|
||||
"Transferring data...": "",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "",
|
||||
"Re-enabled MTP": "",
|
||||
"Installed via usb": "",
|
||||
"Usb install success!": "",
|
||||
"Usb install failed!": "",
|
||||
"Press B to exit...": "",
|
||||
"GameCard Install": "",
|
||||
"GameCard": "",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "",
|
||||
"Gc install failed!": "",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "",
|
||||
"IRS": "",
|
||||
"Irs": "Ir",
|
||||
"Ambient Noise Level: ": "",
|
||||
"Controller": "Controleur",
|
||||
"Pad ": "Pad ",
|
||||
"HandHeld": "Handbediende",
|
||||
" (Available)": " (Beschikbaar)",
|
||||
" (Unsupported)": "",
|
||||
" (Unconnected)": " (Niet verbonden)",
|
||||
"HandHeld": "Handbediende",
|
||||
"Rotation": "Rotatie",
|
||||
"0 (Sideways)": "0 (zijwaarts)",
|
||||
"90 (Flat)": "90 (plat)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "Normaal beeld",
|
||||
"Negative image": "Negatief beeld",
|
||||
"Format": "Formaat",
|
||||
"Trimming Format": "Trimformaat",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "Trimformaat",
|
||||
"External Light Filter": "Extern lichtfilter",
|
||||
"Load Default": "Standaard laden",
|
||||
|
||||
"Themezer": "Themamaker",
|
||||
"Themezer Options": "",
|
||||
"Nsfw": "",
|
||||
"Page": "",
|
||||
"Page %zu / %zu": "Page %zu / %zu",
|
||||
"Enter Page Number": "",
|
||||
"Bad Page": "",
|
||||
"Download theme?": "",
|
||||
|
||||
"GitHub": "",
|
||||
"Downloading json": "",
|
||||
"Select asset to download for ": "",
|
||||
|
||||
"Installing ": "",
|
||||
"Uninstalling ": "",
|
||||
"Deleting ": "",
|
||||
"Deleting": "",
|
||||
"Pasting ": "",
|
||||
"Pasting": "",
|
||||
"Removing ": "",
|
||||
"Scanning ": "",
|
||||
"Creating ": "",
|
||||
"Copying ": "",
|
||||
"Trying to load ": "",
|
||||
"Downloading ": "",
|
||||
"Downloaded ": "",
|
||||
"Removed ": "",
|
||||
"Checking MD5": "",
|
||||
"Loading...": "",
|
||||
"Loading": "",
|
||||
"Empty!": "",
|
||||
"Not Ready...": "",
|
||||
"Error loading page!": "",
|
||||
"Update avaliable: ": "",
|
||||
"Download update: ": "",
|
||||
"Updated to ": "",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "",
|
||||
"Failed to download update": "",
|
||||
"Advanced": "Geavanceerd",
|
||||
"Advanced Options": "Bestand maken",
|
||||
"Logging": "Loggen",
|
||||
"Replace hbmenu on exit": "Vervang hbmenu bij afsluiten",
|
||||
"Restore hbmenu?": "",
|
||||
"Restore": "",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "",
|
||||
"Failed to restore hbmenu, using sphaira instead": "",
|
||||
"Restored hbmenu, closing sphaira": "",
|
||||
"Restored hbmenu": "",
|
||||
"Restart Sphaira?": "",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Text scroll speed": "",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Set right-side menu": "",
|
||||
"Install options": "",
|
||||
"Install Options": "",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "",
|
||||
"Install location": "",
|
||||
"System memory": "",
|
||||
"microSD card": "",
|
||||
"Boost CPU clock": "",
|
||||
"Allow downgrade": "",
|
||||
"Skip if already installed": "",
|
||||
"Ticket only": "",
|
||||
"Skip base": "",
|
||||
"Skip patch": "",
|
||||
"Skip dlc": "",
|
||||
"Skip data patch": "",
|
||||
"Skip ticket": "",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "",
|
||||
"Skip RSA NPDM verify": "",
|
||||
"Ignore distribution bit": "",
|
||||
"Convert to standard crypto": "",
|
||||
"Lower master key": "",
|
||||
"Lower system version": "",
|
||||
|
||||
"Homebrew": "Zelf brouwen",
|
||||
"Apps": "",
|
||||
"Homebrew Options": "Homebrew-opties",
|
||||
"Hide Sphaira": "Verberg Sphaira",
|
||||
"Install Forwarder": "Forwarder installeren",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "WAARSCHUWING: Het installeren van forwarders leidt tot een ban!",
|
||||
"Installing Forwarder": "",
|
||||
"Creating Program": "",
|
||||
"Creating Control": "",
|
||||
"Creating Meta": "",
|
||||
"Writing Nca": "",
|
||||
"Updating ncm databse": "",
|
||||
"Pushing application record": "",
|
||||
"Failed to install forwarder": "",
|
||||
"Unstarred ": "",
|
||||
"Starred ": "",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "",
|
||||
"Appstore": "",
|
||||
"Store": "",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Soort: %s | Volgorde: %s",
|
||||
"AppStore Options": "AppStore-opties",
|
||||
"Info": "Info",
|
||||
"Changelog": "",
|
||||
"Details": "",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "",
|
||||
"Leave Feedback": "",
|
||||
|
||||
"FileBrowser": "Bestandsbrowser",
|
||||
"Files": "",
|
||||
"%zd files": "%zd files",
|
||||
"%zd dirs": "%zd dirs",
|
||||
"File Options": "Bestandsopties",
|
||||
"Show Hidden": "Toon verborgen",
|
||||
"Folders First": "Mappen eerst",
|
||||
"Hidden Last": "Verborgen laatste",
|
||||
"Cut": "Snee",
|
||||
"Copy": "Kopiëren",
|
||||
"Copying ": "",
|
||||
"Paste": "",
|
||||
"Paste ": "",
|
||||
" file(s)?": "",
|
||||
"Pasting ": "",
|
||||
"Pasting": "",
|
||||
"Rename": "Hernoemen",
|
||||
"Set New File Name": "",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "Bestand maken",
|
||||
"Set File Name": "",
|
||||
"Create Folder": "Map maken",
|
||||
"Set Folder Name": "",
|
||||
"Creating ": "",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "Bekijk als tekst (onvoltooid)",
|
||||
"Ignore read only": "",
|
||||
"Mount": "",
|
||||
"Sd": "",
|
||||
"Image System memory": "",
|
||||
"Image microSD card": "",
|
||||
"Empty...": "",
|
||||
"Open with DayBreak?": "",
|
||||
"Launch ": "",
|
||||
"Launch option for: ": "",
|
||||
"Select launcher for: ": "",
|
||||
|
||||
"Sort By": "Sorteer op",
|
||||
"Sort Options": "Sorteeropties",
|
||||
"Filter": "Filter",
|
||||
"All": "Alle",
|
||||
"Emulators": "Emulators",
|
||||
"Tools": "Hulpmiddelen",
|
||||
"Themes": "Thema's",
|
||||
"Legacy": "Nalatenschap",
|
||||
"Sort": "Soort",
|
||||
"Size": "Maat",
|
||||
"Size (Star)": "",
|
||||
"Alphabetical": "Alfabetisch",
|
||||
"Alphabetical (Star)": "",
|
||||
"Updated": "Bijgewerkt",
|
||||
"Updated (Star)": "",
|
||||
"Downloads": "Downloads",
|
||||
"Likes": "",
|
||||
"ID": "",
|
||||
"Order": "Volgorde",
|
||||
"Descending": "Aflopend",
|
||||
"Descending (down)": "Aflopend",
|
||||
"Desc": "Aflopend",
|
||||
"Ascending": "Oplopend",
|
||||
"Ascending (Up)": "Oplopend",
|
||||
"Asc": "Oplopend",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Search": "Zoekopdracht",
|
||||
|
||||
"Options": "Opties",
|
||||
"OK": "",
|
||||
"Back": "Terug",
|
||||
"Select": "",
|
||||
"Open": "Open",
|
||||
"Launch": "Launch",
|
||||
"Restart": "",
|
||||
"Next": "",
|
||||
"Prev": "",
|
||||
"Unstar": "",
|
||||
"Star": "",
|
||||
"Yes": "Ja",
|
||||
"No": "Nee",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
|
||||
"Install": "Installeren",
|
||||
"Install Selected files?": "",
|
||||
"Installing ": "",
|
||||
"Installed ": "",
|
||||
"Installed!": "",
|
||||
"Trying to load ": "",
|
||||
"Checking MD5": "",
|
||||
|
||||
"Delete": "Verwijderen",
|
||||
"Delete Selected files?": "",
|
||||
"Completely remove ": "",
|
||||
"Are you sure you want to delete ": "Weet u zeker dat u wilt verwijderen ",
|
||||
"Are you sure you wish to cancel?": "",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"If this message appears repeatedly, please open an issue.": ""
|
||||
"Scanning ": "",
|
||||
"Deleting ": "",
|
||||
"Deleting": "",
|
||||
"Remove": "",
|
||||
"Completely remove ": "",
|
||||
"Removing ": "",
|
||||
"Removed ": "",
|
||||
"Uninstalling ": "",
|
||||
|
||||
"Download": "Downloaden",
|
||||
"Downloading ": "",
|
||||
"Downloaded ": "",
|
||||
|
||||
"Update": "",
|
||||
"Update avaliable: ": "",
|
||||
"Download update: ": "",
|
||||
"Updated to ": "",
|
||||
"Failed to download update": "",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "",
|
||||
"Loading": "",
|
||||
"Empty!": "",
|
||||
"Not Ready...": "",
|
||||
"Error loading page!": ""
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Modo Applet]",
|
||||
"No Internet": "Sem Internet",
|
||||
"Files": "Arquivos",
|
||||
"Apps": "Aplicativos",
|
||||
"Store": "Loja",
|
||||
"Menu": "Menu",
|
||||
"Options": "Opções",
|
||||
"OK": "OK",
|
||||
"Back": "Voltar",
|
||||
"Select": "Selecionar",
|
||||
"Open": "Abrir",
|
||||
"Launch": "Iniciar",
|
||||
"Info": "Informações",
|
||||
"Install": "Instalar",
|
||||
"Delete": "Excluir",
|
||||
"Restart": "Reiniciar",
|
||||
"Changelog": "Alterações",
|
||||
"Details": "Detalhes",
|
||||
"Update": "Atualizar",
|
||||
"Remove": "Remover",
|
||||
"Restore": "Restaurar",
|
||||
"Download": "Baixar",
|
||||
"Next Page": "Próxima página",
|
||||
"Prev Page": "Página anterior",
|
||||
"Unstar": "Desfavoritar",
|
||||
"Star": "Favoritar",
|
||||
"System memory": "Memória do console",
|
||||
"microSD card": "Cartão microSD",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Imagem (memória do console)",
|
||||
"Image microSD card": "Imagem (cartão microSD)",
|
||||
"Slow": "Lenta",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rápida",
|
||||
"Yes": "Sim",
|
||||
"No": "Não",
|
||||
"Enabled": "Sim",
|
||||
"Disabled": "Não",
|
||||
|
||||
"Sort By": "Ordernar/Organizar",
|
||||
"Sort Options": "Ordernar/Organizar",
|
||||
"Filter": "Filtro",
|
||||
"Sort": "Organizar por",
|
||||
"Order": "Ordem",
|
||||
"Search": "Buscar",
|
||||
"Updated": "Atualizado",
|
||||
"Updated (Star)": "Atualizado (favoritos)",
|
||||
"Downloads": "Nº de downloads",
|
||||
"Size": "Tamanho",
|
||||
"Size (Star)": "Tamanho (favoritos)",
|
||||
"Alphabetical": "Ordem alfabética",
|
||||
"Alphabetical (Star)": "Ordem alfabética (favoritos)",
|
||||
"Likes": "Nº de curtidas",
|
||||
"ID": "ID",
|
||||
"Descending": "Decrescente",
|
||||
"Descending (down)": "Decrescente (baixo)",
|
||||
"Desc": "Decr.",
|
||||
"Ascending": "Ascendente",
|
||||
"Ascending (Up)": "Ascendente (cima)",
|
||||
"Asc": "Asc.",
|
||||
"[Applet Mode]": "[Modo applet]",
|
||||
"No Internet": "Sem internet",
|
||||
"Switch-Handheld!": "Switch-Portátil",
|
||||
"Switch-Docked!": "Switch-Docado",
|
||||
"Audio disabled due to suspended game": "Áudio desativado devido ao software suspenso.",
|
||||
"Are you sure you wish to cancel?": "Você tem certeza que quer cancelar?",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Se esta mensagem aparecer repetidamente, abra um issue.",
|
||||
|
||||
"Menu Options": "Opções do menu",
|
||||
"Menu": "Menu",
|
||||
"Theme": "Tema",
|
||||
"Theme Options": "Opções de tema",
|
||||
"Select Theme": "Tema atual",
|
||||
"Shuffle": "Embaralhar temas",
|
||||
"Music": "Música",
|
||||
"12 Hour Time": "",
|
||||
"12 Hour Time": "Relógio de 12 horas",
|
||||
"Download Default Music": "Baixar música padrão",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "Rede",
|
||||
"Network Options": "Opções de rede",
|
||||
"Ftp": "Servidor FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "Nxlink conectado",
|
||||
"Nxlink Upload": "Envio Nxlink",
|
||||
"Nxlink Finished": "Nxlink finalizado",
|
||||
"Switch-Handheld!": "Switch-Portátil",
|
||||
"Switch-Docked!": "Switch-Docado",
|
||||
|
||||
"Language": "Idioma",
|
||||
"Auto": "Automático",
|
||||
"English": "English",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Registro de depuração",
|
||||
"Replace hbmenu on exit": "Substituir hbmenu ao sair",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Diversos",
|
||||
"Misc Options": "Opções diversas",
|
||||
"Web": "Navegador de internet",
|
||||
"Install forwarders": "Instalar atalhos (forwarders)",
|
||||
"Install location": "Local de instalação",
|
||||
"Show install warning": "Mostrar aviso de instalação",
|
||||
"Text scroll speed": "Rolagem do texto",
|
||||
"Games": "Softwares",
|
||||
"Game Options": "Opções de software",
|
||||
"Hide forwarders": "Ocultar atalhos (forwarders)",
|
||||
"Launch random game": "Iniciar um software aleatório",
|
||||
"List meta records": "Registro de conteúdos",
|
||||
"Entries": "Entradas",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "Exportar",
|
||||
"Select content to dump": "Exportação de conteúdo",
|
||||
"Dump All": "Exportar tudo",
|
||||
"Dump Application": "Exportar software base",
|
||||
"Dump Patch": "Exportar atualização",
|
||||
"Dump AddOnContent": "Exportar DLCs",
|
||||
"Dump DataPatch": "Exportar patch de dados",
|
||||
"Select dump location": "Selecione o local de exportação",
|
||||
"microSD card (/dumps/NSP/)": "Cartão microSD (/dump/NSP/)",
|
||||
"USB transfer (Switch 2 Switch)": "Transferência via USB (Switch 2 Switch)",
|
||||
"/dev/null (Speed Test)": "Teste de velocidade (/dev/null)",
|
||||
"Dumping": "Extraindo...",
|
||||
"Dump successfull!": "Extração foi concluída com sucesso.",
|
||||
"Dump failed!": "Extração falhou.",
|
||||
"Success": "",
|
||||
"Delete successfull!": "Exclusão foi concluída com sucesso.",
|
||||
"Delete failed!": "Exclusão falhou",
|
||||
|
||||
"FileBrowser": "Arquivos",
|
||||
"%zd files": "%zd arquivo(s)",
|
||||
"%zd dirs": "%zd diretório(s)",
|
||||
"File Options": "Opções de arquivo",
|
||||
"Show Hidden": "Mostrar ocultos",
|
||||
"Folders First": "Pastas primeiro",
|
||||
"Hidden Last": "Ocultos por último",
|
||||
"Cut": "Recortar",
|
||||
"Copy": "Copiar",
|
||||
"Paste": "Colar",
|
||||
"Paste ": "Colar ",
|
||||
" file(s)?": " arquivo(s)?",
|
||||
"Rename": "Renomear",
|
||||
"Set New File Name": "Defina o nome do novo arquivo",
|
||||
"Advanced": "Avançado",
|
||||
"Advanced Options": "Opções avançadas",
|
||||
"Create File": "Criar arquivo",
|
||||
"Set File Name": "Defina o nome do arquivo",
|
||||
"Create Folder": "Criar pasta",
|
||||
"Set Folder Name": "Defina o nome da pasta",
|
||||
"View as text (unfinished)": "Ver como texto (inacabado)",
|
||||
"Ignore read only": "Ignorar somente leitura",
|
||||
"Mount": "Montar",
|
||||
"Empty...": "Vazio...",
|
||||
"Open with DayBreak?": "Abrir com DayBreak?",
|
||||
"Launch ": "Iniciar ",
|
||||
"Launch option for: ": "Opções de inicialização para: ",
|
||||
"Select launcher for: ": "Selecionar launcher para: ",
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Opções do Themezer",
|
||||
"Nsfw": "Temas 18+ (NSFW)",
|
||||
"Page": "Ir para página",
|
||||
"Page %zu / %zu": "Página %zu / %zu",
|
||||
"Enter Page Number": "Número da página",
|
||||
"Bad Page": "Página inválida",
|
||||
"Download theme?": "Baixar tema?",
|
||||
|
||||
"Homebrew": "Aplicativos",
|
||||
"Homebrew Options": "Opções do aplicativo",
|
||||
"Hide Sphaira": "Esconder sphaira",
|
||||
"Install Forwarder": "Instalar atalho (forwarder)",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "AVISO: Instalar atalhos pode\nresultar em um banimento!",
|
||||
"Installing Forwarder": "Instalando forwarder",
|
||||
"Creating Program": "Criando Program",
|
||||
"Creating Control": "Criando Control",
|
||||
"Creating Meta": "Criando Meta",
|
||||
"Writing Nca": "Escrevendo NCA",
|
||||
"Updating ncm databse": "Atualizando base de dados NCM",
|
||||
"Pushing application record": "Aplicando registro do aplicativo",
|
||||
"Installed!": "Instalado!",
|
||||
"Failed to install forwarder": "Falha ao instalar forwarder",
|
||||
"Unstarred ": "Desfavoritado ",
|
||||
"Starred ": "Favoritado ",
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Baixando JSON",
|
||||
"Select asset to download for ": "Selecione o recurso para baixar de ",
|
||||
|
||||
"AppStore": "Loja",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Por: %s | Ordem: %s",
|
||||
"AppStore Options": "Opções da loja",
|
||||
"All": "Todos",
|
||||
"Games": "Jogos",
|
||||
"Emulators": "Emuladores",
|
||||
"Tools": "Ferramentas",
|
||||
"Themes": "Temas",
|
||||
"Legacy": "Legado",
|
||||
"version: %s": "versão: %s",
|
||||
"updated: %s": "atualizado: %s",
|
||||
"category: %s": "categoria: %s",
|
||||
"extracted: %.2f MiB": "tam. extraído: %.2f MiB",
|
||||
"app_dls: %s": "downloads: %s",
|
||||
"More by Author": "Mais deste autor",
|
||||
"Leave Feedback": "Deixar um feedback",
|
||||
"FTP Install": "Instalação via FTP",
|
||||
"FTP Install (EXPERIMENTAL)": "Instalação via FTP (EXPERIMENTAL)",
|
||||
"Connection Type: WiFi | Strength: ": "Conexão por rede Wi-Fi | Intensidade do sinal: ",
|
||||
"Connection Type: Ethernet": "Conexão por cabo (ethernet)",
|
||||
"Connection Type: None": "Sem conexão",
|
||||
"Host:": "Host:",
|
||||
"Port:": "Porta:",
|
||||
"Username:": "Nome de usuário:",
|
||||
"Password:": "Senha:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "Senha:",
|
||||
"Failed to install via FTP, press B to exit...": "Falha ao instalar via FTP,\naperte B para sair.",
|
||||
"Ftp install success!": "Instalação via FTP concluída com sucesso.",
|
||||
"Ftp install failed!": "Instalação via FTP falhou.",
|
||||
"USB Install": "Instalação via USB",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "Falha ao instalar via USB,\naperte B para sair.",
|
||||
"Waiting for connection...": "Aguardando conexão...",
|
||||
"Transferring data...": "Transferindo dados...",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "Escuta MTP desabilitada temporáriamente.",
|
||||
"Re-enabled MTP": "Escuta MTP reabilitada.",
|
||||
"Installed via usb": "Instalado via USB",
|
||||
"Usb install success!": "Instalação via USB concluída com sucesso.",
|
||||
"Usb install failed!": "Instalação via USB falhou.",
|
||||
"Press B to exit...": "Aperte B para sair.",
|
||||
"GameCard Install": "Instalação de cartão de jogo",
|
||||
"GameCard": "Cartão de jogo",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "Instalação de cartão de jogo concluída com sucesso.",
|
||||
"Gc install failed!": "Instalação de cartão de jogo falhou.",
|
||||
|
||||
"Irs": "Sensor infravermelho",
|
||||
"IRS (Infrared Joycon Camera)": "Câmera de movimento IR",
|
||||
"IRS": "Câmera de movimento IR",
|
||||
"Irs": "Câmera de movimento IR",
|
||||
"Ambient Noise Level: ": "Nível de ruído ambiente: ",
|
||||
"Controller": "Controle",
|
||||
"Pad ": "Pad ",
|
||||
"HandHeld": "Portátil",
|
||||
" (Available)": " (disponível)",
|
||||
" (Unsupported)": "(não suportado)",
|
||||
" (Unconnected)": " (desconectado)",
|
||||
"HandHeld": "Portátil",
|
||||
"Rotation": "Rotação",
|
||||
"0 (Sideways)": "0 (lateralmente)",
|
||||
"90 (Flat)": "90 (plano)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "Imagem normal",
|
||||
"Negative image": "Imagem negativa",
|
||||
"Format": "Formato",
|
||||
"Trimming Format": "Formato do recorte",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "Formato do recorte",
|
||||
"External Light Filter": "Filtro de luz externa",
|
||||
"Load Default": "Restaurar padrão",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Opções do Themezer",
|
||||
"Nsfw": "Temas 18+ (NSFW)",
|
||||
"Page": "Ir para página",
|
||||
"Page %zu / %zu": "Página %zu / %zu",
|
||||
"Enter Page Number": "Número da página",
|
||||
"Bad Page": "Página inválida",
|
||||
"Download theme?": "Baixar tema?",
|
||||
"Advanced": "Avançado",
|
||||
"Advanced Options": "Opções avançadas",
|
||||
"Logging": "Registro de depuração",
|
||||
"Replace hbmenu on exit": "Substituir hbmenu ao sair",
|
||||
"Restore hbmenu?": "Restaurar hbmenu?",
|
||||
"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 restore hbmenu, please re-download hbmenu": "Falha ao restaurar o hbmenu, baixe o hbmenu novamente.",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Falha ao restaurar hbmenu, usando sphaira.",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu restaurado, fechando sphaira.",
|
||||
"Restored hbmenu": "hbmenu restaurado.",
|
||||
"Restart Sphaira?": "Reiniciar sphaira?",
|
||||
"Press OK to restart Sphaira": "Selecione OK para reiniciar o sphaira.",
|
||||
"Text scroll speed": "Rolagem do texto",
|
||||
"Slow": "Lenta",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rápida",
|
||||
"Set right-side menu": "Menu do botão R",
|
||||
"Install options": "Opções de instalação",
|
||||
"Install Options": "Opções de instalação",
|
||||
"Enable sysmmc": "Habilitar sysMMC",
|
||||
"Enable emummc": "Habilitar emuMMC",
|
||||
"Show install warning": "Mostrar aviso de instalação",
|
||||
"Install location": "Local de instalação",
|
||||
"System memory": "Memória do console",
|
||||
"microSD card": "Cartão microSD",
|
||||
"Boost CPU clock": "Aumentar clock da CPU",
|
||||
"Allow downgrade": "Permitir downgrade",
|
||||
"Skip if already installed": "Pular se já instalado",
|
||||
"Ticket only": "Instalar apenas ticket",
|
||||
"Skip base": "Pular software base",
|
||||
"Skip patch": "Pular atualizações",
|
||||
"Skip dlc": "Pular DLCs",
|
||||
"Skip data patch": "Pular patch de dados",
|
||||
"Skip ticket": "Pular ticket",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "Pular checagem de header RSA",
|
||||
"Skip RSA NPDM verify": "Pular checagem de NPDM RSA",
|
||||
"Ignore distribution bit": "Ignorar bit de distribuição",
|
||||
"Convert to standard crypto": "Convertr para crypto padrão",
|
||||
"Lower master key": "Reduzir a master key",
|
||||
"Lower system version": "Reduzir versão do sistema",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Baixando JSON",
|
||||
"Select asset to download for ": "Selecione o recurso para baixar de ",
|
||||
"Homebrew": "Aplicativos",
|
||||
"Apps": "Aplicativos",
|
||||
"Homebrew Options": "Opções de aplicativo",
|
||||
"Hide Sphaira": "Esconder sphaira",
|
||||
"Install Forwarder": "Instalar atalho (forwarder)",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "AVISO: Instalar atalhos pode resultar em um banimento!",
|
||||
"Installing Forwarder": "Instalando forwarder...",
|
||||
"Creating Program": "Criando Program",
|
||||
"Creating Control": "Criando Control",
|
||||
"Creating Meta": "Criando Meta",
|
||||
"Writing Nca": "Escrevendo NCA",
|
||||
"Updating ncm databse": "Atualizando base de dados NCM",
|
||||
"Pushing application record": "Aplicando registro do aplicativo",
|
||||
"Failed to install forwarder": "Falha ao instalar forwarder",
|
||||
"Unstarred ": "Desfavoritado ",
|
||||
"Starred ": "Favoritado ",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"Installing ": "Instalando ",
|
||||
"Uninstalling ": "Desinstalando ",
|
||||
"Deleting ": "Excluindo ",
|
||||
"Deleting": "Excluindo",
|
||||
"AppStore": "Loja",
|
||||
"Appstore": "Loja",
|
||||
"Store": "Loja",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Por: %s | Ordem: %s",
|
||||
"AppStore Options": "Opções da loja",
|
||||
"Info": "Informações",
|
||||
"Changelog": "Alterações",
|
||||
"Details": "Detalhes",
|
||||
"version: %s": "versão: %s",
|
||||
"updated: %s": "atualizado: %s",
|
||||
"category: %s": "categoria: %s",
|
||||
"extracted: %.2f MiB": "tam. extraído: %.2f MiB",
|
||||
"app_dls: %s": "downloads: %s",
|
||||
"More by Author": "Mais deste autor",
|
||||
"Leave Feedback": "Deixar um feedback",
|
||||
|
||||
"FileBrowser": "Arquivos",
|
||||
"Files": "Arquivos",
|
||||
"%zd files": "%zd arquivo(s)",
|
||||
"%zd dirs": "%zd diretório(s)",
|
||||
"File Options": "Opções de arquivo",
|
||||
"Show Hidden": "Mostrar ocultos",
|
||||
"Folders First": "Pastas primeiro",
|
||||
"Hidden Last": "Ocultos por último",
|
||||
"Cut": "Recortar",
|
||||
"Copy": "Copiar",
|
||||
"Copying ": "Copiando ",
|
||||
"Paste": "Colar",
|
||||
"Paste ": "Colar ",
|
||||
" file(s)?": " arquivo(s)?",
|
||||
"Pasting ": "Colando ",
|
||||
"Pasting": "Colando ",
|
||||
"Removing ": "Removendo ",
|
||||
"Scanning ": "Analisando ",
|
||||
"Rename": "Renomear",
|
||||
"Set New File Name": "Defina o nome do novo arquivo",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "Comprimir em zip",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "Criar arquivo",
|
||||
"Set File Name": "Defina o nome do arquivo",
|
||||
"Create Folder": "Criar pasta",
|
||||
"Set Folder Name": "Defina o nome da pasta",
|
||||
"Creating ": "Criando ",
|
||||
"Copying ": "Copiando ",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "Ver como texto (inacabado)",
|
||||
"Ignore read only": "Ignorar somente leitura",
|
||||
"Mount": "Montar",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Imagem (memória do console)",
|
||||
"Image microSD card": "Imagem (cartão microSD)",
|
||||
"Empty...": "Vazio",
|
||||
"Open with DayBreak?": "Abrir com DayBreak?",
|
||||
"Launch ": "Iniciar ",
|
||||
"Launch option for: ": "Opções de inicialização para: ",
|
||||
"Select launcher for: ": "Selecionar launcher para: ",
|
||||
|
||||
"Sort By": "Ordernar/Organizar",
|
||||
"Sort Options": "Ordernar/Organizar",
|
||||
"Filter": "Filtro",
|
||||
"All": "Todos",
|
||||
"Emulators": "Emuladores",
|
||||
"Tools": "Ferramentas",
|
||||
"Themes": "Temas",
|
||||
"Legacy": "Legado",
|
||||
"Sort": "Organizar por",
|
||||
"Size": "Tamanho",
|
||||
"Size (Star)": "Tamanho (favoritos)",
|
||||
"Alphabetical": "Ordem alfabética",
|
||||
"Alphabetical (Star)": "Ordem alfabética (favoritos)",
|
||||
"Updated": "Atualizado",
|
||||
"Updated (Star)": "Atualizado (favoritos)",
|
||||
"Downloads": "Nº de downloads",
|
||||
"Likes": "Nº de curtidas",
|
||||
"ID": "ID",
|
||||
"Order": "Ordem",
|
||||
"Descending": "Decrescente",
|
||||
"Descending (down)": "Decrescente (baixo)",
|
||||
"Desc": "Decr.",
|
||||
"Ascending": "Ascendente",
|
||||
"Ascending (Up)": "Ascendente (cima)",
|
||||
"Asc": "Asc.",
|
||||
"Layout": "Exibição",
|
||||
"List": "Lista",
|
||||
"Icon": "Ícones",
|
||||
"Grid": "Grade",
|
||||
"Search": "Buscar",
|
||||
|
||||
"Options": "Opções",
|
||||
"OK": "OK",
|
||||
"Back": "Voltar",
|
||||
"Select": "Selecionar",
|
||||
"Open": "Abrir",
|
||||
"Launch": "Iniciar",
|
||||
"Restart": "Reiniciar",
|
||||
"Next": "Prómixo",
|
||||
"Prev": "Anterior",
|
||||
"Unstar": "Desfavoritar",
|
||||
"Star": "Favoritar",
|
||||
"Yes": "Sim",
|
||||
"No": "Não",
|
||||
"On": "Sim",
|
||||
"Off": "Não",
|
||||
|
||||
"Install": "Instalar",
|
||||
"Install Selected files?": "Instalar os arquivos selecionados?",
|
||||
"Installing ": "Instalando ",
|
||||
"Installed ": "Instalado ",
|
||||
"Installed!": "Instalado!",
|
||||
"Trying to load ": "Tentando carregar ",
|
||||
"Checking MD5": "Checando MD5",
|
||||
|
||||
"Delete": "Excluir",
|
||||
"Delete Selected files?": "Excluir os arquivos selecionados?",
|
||||
"Are you sure you want to delete ": "Você tem certeza que quer excluir ",
|
||||
"Scanning ": "Analisando ",
|
||||
"Deleting ": "Excluindo ",
|
||||
"Deleting": "Excluindo...",
|
||||
"Remove": "Remover",
|
||||
"Completely remove ": "Remover completamente ",
|
||||
"Removing ": "Removendo ",
|
||||
"Removed ": "Removido ",
|
||||
"Uninstalling ": "Desinstalando ",
|
||||
|
||||
"Download": "Baixar",
|
||||
"Downloading ": "Baixando ",
|
||||
"Downloaded ": "Baixado ",
|
||||
"Removed ": "Removido ",
|
||||
"Checking MD5": "Checando MD5",
|
||||
|
||||
"Update": "Atualizar",
|
||||
"Update avaliable: ": "Atualização disponível: ",
|
||||
"Download update: ": "Baixar autalização: ",
|
||||
"Updated to ": "Atualizado para ",
|
||||
"Failed to download update": "Falha ao baixar a atualização.",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "Carregando...",
|
||||
"Loading": "Carregando",
|
||||
"Empty!": "Vazio",
|
||||
"Not Ready...": "Não está pronto...",
|
||||
"Error loading page!": "Erro ao carregar página!",
|
||||
"Update avaliable: ": "Atualização disponível: ",
|
||||
"Download update: ": "Baixar autalização: ",
|
||||
"Updated to ": "Atualizado para ",
|
||||
"Press OK to restart Sphaira": "Selecione OK para reiniciar o sphaira",
|
||||
"Restart Sphaira?": "Reiniciar sphaira?",
|
||||
"Failed to download update": "Falha ao baixar a atualização",
|
||||
"Restore hbmenu?": "Restaurar hbmenu?",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Falha ao buscar /switch/hbmenu.nro\nUse a AppStore para reinstalar o hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Falha ao restaurar o hbmenu, baixe o hbmenu novamente",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Falha ao restaurar hbmenu, usando sphaira",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu restaurado, fechando sphaira",
|
||||
"Restored hbmenu": "hbmenu restaurado",
|
||||
"Delete Selected files?": "Excluir os arquivos selecionados?",
|
||||
"Completely remove ": "Remover completamente ",
|
||||
"Are you sure you want to delete ": "Você tem certeza que quer excluir ",
|
||||
"Are you sure you wish to cancel?": "Você tem certeza que quer cancelar?",
|
||||
"Audio disabled due to suspended game": "Áudio desativado devido ao software suspenso",
|
||||
"If this message appears repeatedly, please open an issue.": "Se esta mensagem aparecer repetidamente, abra um issue."
|
||||
}
|
||||
"Error loading page!": "Erro ao carregar página."
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Режим апплета]",
|
||||
"No Internet": "Нет Интернета",
|
||||
"Files": "",
|
||||
"Apps": "",
|
||||
"Store": "",
|
||||
"Menu": "Меню",
|
||||
"Options": "Параметры темы",
|
||||
"OK": "",
|
||||
"Back": "Назад",
|
||||
"Select": "",
|
||||
"Open": "Открыть",
|
||||
"Launch": "Запуск",
|
||||
"Info": "Информация",
|
||||
"Install": "Установить",
|
||||
"Delete": "Удалить",
|
||||
"Restart": "",
|
||||
"Changelog": "",
|
||||
"Details": "",
|
||||
"Update": "",
|
||||
"Remove": "",
|
||||
"Restore": "",
|
||||
"Download": "Скачать",
|
||||
"Next Page": "Следующая страница",
|
||||
"Prev Page": "Предыдущая страница",
|
||||
"Unstar": "",
|
||||
"Star": "",
|
||||
"System memory": "",
|
||||
"microSD card": "",
|
||||
"Sd": "",
|
||||
"Image System memory": "",
|
||||
"Image microSD card": "",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Да",
|
||||
"No": "Нет",
|
||||
"Enabled": "Включено",
|
||||
"Disabled": "Отключено",
|
||||
|
||||
"Sort By": "Сортировать по",
|
||||
"Sort Options": "Параметры сортировки",
|
||||
"Filter": "Фильтр",
|
||||
"Sort": "Сортировать",
|
||||
"Order": "Порядок",
|
||||
"Search": "Поиск",
|
||||
"Updated": "Обновлено",
|
||||
"Updated (Star)": "",
|
||||
"Downloads": "Загрузки",
|
||||
"Size": "Размер",
|
||||
"Size (Star)": "",
|
||||
"Alphabetical": "По наименованию",
|
||||
"Alphabetical (Star)": "",
|
||||
"Likes": "",
|
||||
"ID": "",
|
||||
"Descending": "По убыванию",
|
||||
"Descending (down)": "По убыванию",
|
||||
"Desc": "По убыванию",
|
||||
"Ascending": "По возрастанию",
|
||||
"Ascending (Up)": "По возрастанию",
|
||||
"Asc": "По возрастанию",
|
||||
"Switch-Handheld!": "",
|
||||
"Switch-Docked!": "",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"Are you sure you wish to cancel?": "",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "",
|
||||
|
||||
"Menu Options": "Параметры меню",
|
||||
"Menu": "Меню",
|
||||
"Theme": "Тема",
|
||||
"Theme Options": "Параметры темы",
|
||||
"Select Theme": "Выберите тему",
|
||||
"Shuffle": "Перетасовать",
|
||||
"Music": "Музыка",
|
||||
"12 Hour Time": "",
|
||||
"Download Default Music": "",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "Сеть",
|
||||
"Network Options": "Параметры сети",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "",
|
||||
"Nxlink Upload": "",
|
||||
"Nxlink Finished": "",
|
||||
"Switch-Handheld!": "",
|
||||
"Switch-Docked!": "",
|
||||
|
||||
"Language": "Язык",
|
||||
"Auto": "",
|
||||
"English": "English",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Журналирование",
|
||||
"Replace hbmenu on exit": "Заменить hbmenu при выходе",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Прочее",
|
||||
"Misc Options": "Прочие параметры",
|
||||
"Web": "Интернет",
|
||||
"Install forwarders": "",
|
||||
"Install location": "",
|
||||
"Show install warning": "",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "Файловый менеджер",
|
||||
"%zd files": "%zd files",
|
||||
"%zd dirs": "%zd dirs",
|
||||
"File Options": "Параметры файла",
|
||||
"Show Hidden": "Показать скрытые",
|
||||
"Folders First": "Папки в первую очередь",
|
||||
"Hidden Last": "Скрытые в последнюю очередь",
|
||||
"Cut": "Вырезать",
|
||||
"Copy": "Копировать",
|
||||
"Paste": "",
|
||||
"Paste ": "",
|
||||
" file(s)?": "",
|
||||
"Rename": "Переименовать",
|
||||
"Set New File Name": "",
|
||||
"Advanced": "Продвинутые",
|
||||
"Advanced Options": "Расширенные параметры",
|
||||
"Create File": "Создать файл",
|
||||
"Set File Name": "",
|
||||
"Create Folder": "Создать папку",
|
||||
"Set Folder Name": "",
|
||||
"View as text (unfinished)": "Посмотреть как текст (незакончено)",
|
||||
"Ignore read only": "",
|
||||
"Mount": "",
|
||||
"Empty...": "",
|
||||
"Open with DayBreak?": "",
|
||||
"Launch ": "",
|
||||
"Launch option for: ": "",
|
||||
"Select launcher for: ": "",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Параметры Homebrew",
|
||||
"Hide Sphaira": "Скрыть Sphaira",
|
||||
"Install Forwarder": "Установить форвардер",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ВНИМАНИЕ: Установка форвардеров приведет к бану!",
|
||||
"Installing Forwarder": "Установить форвардер",
|
||||
"Creating Program": "",
|
||||
"Creating Control": "",
|
||||
"Creating Meta": "",
|
||||
"Writing Nca": "",
|
||||
"Updating ncm databse": "",
|
||||
"Pushing application record": "",
|
||||
"Installed!": "",
|
||||
"Failed to install forwarder": "",
|
||||
"Unstarred ": "",
|
||||
"Starred ": "",
|
||||
|
||||
"AppStore": "",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Фильтр: %s | Сортировать: %s | Порядок: %s",
|
||||
"AppStore Options": "Параметры магазина приложений",
|
||||
"All": "Все",
|
||||
"Games": "Игры",
|
||||
"Emulators": "Эмуляторы",
|
||||
"Tools": "Инструменты",
|
||||
"Themes": "Темы",
|
||||
"Legacy": "Легаси",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "",
|
||||
"Leave Feedback": "",
|
||||
"Game Options": "",
|
||||
"Hide forwarders": "",
|
||||
"Launch random game": "",
|
||||
"List meta records": "",
|
||||
"Entries": "",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/NSP/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Success": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "",
|
||||
"Nsfw": "",
|
||||
"Page": "",
|
||||
"Page %zu / %zu": "Page %zu / %zu",
|
||||
"Enter Page Number": "",
|
||||
"Bad Page": "",
|
||||
"Download theme?": "",
|
||||
|
||||
"GitHub": "",
|
||||
"Downloading json": "",
|
||||
"Select asset to download for ": "",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
"Host:": "",
|
||||
"Port:": "",
|
||||
"Username:": "",
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "",
|
||||
"Waiting for connection...": "",
|
||||
"Transferring data...": "",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "",
|
||||
"Re-enabled MTP": "",
|
||||
"Installed via usb": "",
|
||||
"Usb install success!": "",
|
||||
"Usb install failed!": "",
|
||||
"Press B to exit...": "",
|
||||
"GameCard Install": "",
|
||||
"GameCard": "",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "",
|
||||
"Gc install failed!": "",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "",
|
||||
"IRS": "",
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "",
|
||||
"Controller": "Контроллер",
|
||||
"Pad ": "Pad ",
|
||||
"HandHeld": "Портативный",
|
||||
" (Available)": " (Доступно)",
|
||||
" (Unsupported)": "",
|
||||
" (Unconnected)": " (Не подключено)",
|
||||
"HandHeld": "Портативный",
|
||||
"Rotation": "Вращение",
|
||||
"0 (Sideways)": "0 (набок)",
|
||||
"90 (Flat)": "90 (ровно)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "Обычное изображение",
|
||||
"Negative image": "Негативное изображение",
|
||||
"Format": "Формат",
|
||||
"Trimming Format": "Формат обрезки",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "Формат обрезки",
|
||||
"External Light Filter": "Внешний светофильтр",
|
||||
"Load Default": "Загрузить умолчания",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "",
|
||||
"Nsfw": "",
|
||||
"Page": "",
|
||||
"Page %zu / %zu": "Page %zu / %zu",
|
||||
"Enter Page Number": "",
|
||||
"Bad Page": "",
|
||||
"Download theme?": "",
|
||||
|
||||
"GitHub": "",
|
||||
"Downloading json": "",
|
||||
"Select asset to download for ": "",
|
||||
|
||||
"Installing ": "",
|
||||
"Uninstalling ": "",
|
||||
"Deleting ": "",
|
||||
"Deleting": "",
|
||||
"Pasting ": "",
|
||||
"Pasting": "",
|
||||
"Removing ": "",
|
||||
"Scanning ": "",
|
||||
"Creating ": "",
|
||||
"Copying ": "",
|
||||
"Trying to load ": "",
|
||||
"Downloading ": "",
|
||||
"Downloaded ": "",
|
||||
"Removed ": "",
|
||||
"Checking MD5": "",
|
||||
"Loading...": "",
|
||||
"Loading": "",
|
||||
"Empty!": "",
|
||||
"Not Ready...": "",
|
||||
"Error loading page!": "",
|
||||
"Update avaliable: ": "",
|
||||
"Download update: ": "",
|
||||
"Updated to ": "",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "",
|
||||
"Failed to download update": "",
|
||||
"Advanced": "Продвинутые",
|
||||
"Advanced Options": "Расширенные параметры",
|
||||
"Logging": "Журналирование",
|
||||
"Replace hbmenu on exit": "Заменить hbmenu при выходе",
|
||||
"Restore hbmenu?": "",
|
||||
"Restore": "",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "",
|
||||
"Failed to restore hbmenu, using sphaira instead": "",
|
||||
"Restored hbmenu, closing sphaira": "",
|
||||
"Restored hbmenu": "",
|
||||
"Restart Sphaira?": "",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Text scroll speed": "",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Set right-side menu": "",
|
||||
"Install options": "",
|
||||
"Install Options": "",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "",
|
||||
"Install location": "",
|
||||
"System memory": "",
|
||||
"microSD card": "",
|
||||
"Boost CPU clock": "",
|
||||
"Allow downgrade": "",
|
||||
"Skip if already installed": "",
|
||||
"Ticket only": "",
|
||||
"Skip base": "",
|
||||
"Skip patch": "",
|
||||
"Skip dlc": "",
|
||||
"Skip data patch": "",
|
||||
"Skip ticket": "",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "",
|
||||
"Skip RSA NPDM verify": "",
|
||||
"Ignore distribution bit": "",
|
||||
"Convert to standard crypto": "",
|
||||
"Lower master key": "",
|
||||
"Lower system version": "",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Apps": "",
|
||||
"Homebrew Options": "Параметры Homebrew",
|
||||
"Hide Sphaira": "Скрыть Sphaira",
|
||||
"Install Forwarder": "Установить форвардер",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ВНИМАНИЕ: Установка форвардеров приведет к бану!",
|
||||
"Installing Forwarder": "Установить форвардер",
|
||||
"Creating Program": "",
|
||||
"Creating Control": "",
|
||||
"Creating Meta": "",
|
||||
"Writing Nca": "",
|
||||
"Updating ncm databse": "",
|
||||
"Pushing application record": "",
|
||||
"Failed to install forwarder": "",
|
||||
"Unstarred ": "",
|
||||
"Starred ": "",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "",
|
||||
"Appstore": "",
|
||||
"Store": "",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Фильтр: %s | Сортировать: %s | Порядок: %s",
|
||||
"AppStore Options": "Параметры магазина приложений",
|
||||
"Info": "Информация",
|
||||
"Changelog": "",
|
||||
"Details": "",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "",
|
||||
"Leave Feedback": "",
|
||||
|
||||
"FileBrowser": "Файловый менеджер",
|
||||
"Files": "",
|
||||
"%zd files": "%zd files",
|
||||
"%zd dirs": "%zd dirs",
|
||||
"File Options": "Параметры файла",
|
||||
"Show Hidden": "Показать скрытые",
|
||||
"Folders First": "Папки в первую очередь",
|
||||
"Hidden Last": "Скрытые в последнюю очередь",
|
||||
"Cut": "Вырезать",
|
||||
"Copy": "Копировать",
|
||||
"Copying ": "",
|
||||
"Paste": "",
|
||||
"Paste ": "",
|
||||
" file(s)?": "",
|
||||
"Pasting ": "",
|
||||
"Pasting": "",
|
||||
"Rename": "Переименовать",
|
||||
"Set New File Name": "",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "Создать файл",
|
||||
"Set File Name": "",
|
||||
"Create Folder": "Создать папку",
|
||||
"Set Folder Name": "",
|
||||
"Creating ": "",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "Посмотреть как текст (незакончено)",
|
||||
"Ignore read only": "",
|
||||
"Mount": "",
|
||||
"Sd": "",
|
||||
"Image System memory": "",
|
||||
"Image microSD card": "",
|
||||
"Empty...": "",
|
||||
"Open with DayBreak?": "",
|
||||
"Launch ": "",
|
||||
"Launch option for: ": "",
|
||||
"Select launcher for: ": "",
|
||||
|
||||
"Sort By": "Сортировать по",
|
||||
"Sort Options": "Параметры сортировки",
|
||||
"Filter": "Фильтр",
|
||||
"All": "Все",
|
||||
"Emulators": "Эмуляторы",
|
||||
"Tools": "Инструменты",
|
||||
"Themes": "Темы",
|
||||
"Legacy": "Легаси",
|
||||
"Sort": "Сортировать",
|
||||
"Size": "Размер",
|
||||
"Size (Star)": "",
|
||||
"Alphabetical": "По наименованию",
|
||||
"Alphabetical (Star)": "",
|
||||
"Updated": "Обновлено",
|
||||
"Updated (Star)": "",
|
||||
"Downloads": "Загрузки",
|
||||
"Likes": "",
|
||||
"ID": "",
|
||||
"Order": "Порядок",
|
||||
"Descending": "По убыванию",
|
||||
"Descending (down)": "По убыванию",
|
||||
"Desc": "По убыванию",
|
||||
"Ascending": "По возрастанию",
|
||||
"Ascending (Up)": "По возрастанию",
|
||||
"Asc": "По возрастанию",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Search": "Поиск",
|
||||
|
||||
"Options": "Параметры темы",
|
||||
"OK": "",
|
||||
"Back": "Назад",
|
||||
"Select": "",
|
||||
"Open": "Открыть",
|
||||
"Launch": "Запуск",
|
||||
"Restart": "",
|
||||
"Next": "",
|
||||
"Prev": "",
|
||||
"Unstar": "",
|
||||
"Star": "",
|
||||
"Yes": "Да",
|
||||
"No": "Нет",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
|
||||
"Install": "Установить",
|
||||
"Install Selected files?": "",
|
||||
"Installing ": "",
|
||||
"Installed ": "",
|
||||
"Installed!": "",
|
||||
"Trying to load ": "",
|
||||
"Checking MD5": "",
|
||||
|
||||
"Delete": "Удалить",
|
||||
"Delete Selected files?": "",
|
||||
"Completely remove ": "",
|
||||
"Are you sure you want to delete ": "Вы уверены, что хотите удалить ",
|
||||
"Are you sure you wish to cancel?": "",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"If this message appears repeatedly, please open an issue.": ""
|
||||
"Scanning ": "",
|
||||
"Deleting ": "",
|
||||
"Deleting": "",
|
||||
"Remove": "",
|
||||
"Completely remove ": "",
|
||||
"Removing ": "",
|
||||
"Removed ": "",
|
||||
"Uninstalling ": "",
|
||||
|
||||
"Download": "Скачать",
|
||||
"Downloading ": "",
|
||||
"Downloaded ": "",
|
||||
|
||||
"Update": "",
|
||||
"Update avaliable: ": "",
|
||||
"Download update: ": "",
|
||||
"Updated to ": "",
|
||||
"Failed to download update": "",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "",
|
||||
"Loading": "",
|
||||
"Empty!": "",
|
||||
"Not Ready...": "",
|
||||
"Error loading page!": ""
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Applet-läge]",
|
||||
"No Internet": "Ingen Internetanslutning",
|
||||
"Files": "Filer",
|
||||
"Apps": "Appar",
|
||||
"Store": "Butik",
|
||||
"Menu": "Meny",
|
||||
"Options": "Alternativ",
|
||||
"OK": "OK",
|
||||
"Back": "Tillbaka",
|
||||
"Select": "Välj",
|
||||
"Open": "Öppna",
|
||||
"Launch": "Starta",
|
||||
"Info": "Info",
|
||||
"Install": "Installera",
|
||||
"Delete": "Radera",
|
||||
"Restart": "Starta om",
|
||||
"Changelog": "Ändringslogg",
|
||||
"Details": "Detaljer",
|
||||
"Update": "Uppdatera",
|
||||
"Remove": "Ta bort",
|
||||
"Restore": "Återställ",
|
||||
"Download": "Ladda ner",
|
||||
"Next Page": "Nästa sida",
|
||||
"Prev Page": "Föregående sida",
|
||||
"Unstar": "Avmarkera stjärna",
|
||||
"Star": "Markera stjärna",
|
||||
"System memory": "Systemminne",
|
||||
"microSD card": "microSD-kort",
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Avbild Systemminne",
|
||||
"Image microSD card": "Avbild microSD-kort",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Ja",
|
||||
"No": "Nej",
|
||||
"Enabled": "Aktiverad",
|
||||
"Disabled": "Inaktiverad",
|
||||
|
||||
"Sort By": "Sortera efter",
|
||||
"Sort Options": "Sorteringsalternativ",
|
||||
"Filter": "Filtrera",
|
||||
"Sort": "Sortera",
|
||||
"Order": "Ordning",
|
||||
"Search": "Sök",
|
||||
"Updated": "Uppdaterad",
|
||||
"Updated (Star)": "Uppdaterad (Stjärna)",
|
||||
"Downloads": "Nedladdningar",
|
||||
"Size": "Storlek",
|
||||
"Size (Star)": "Storlek (Stjärna)",
|
||||
"Alphabetical": "Alfabetisk",
|
||||
"Alphabetical (Star)": "Alfabetisk (Stjärna)",
|
||||
"Likes": "Gillar",
|
||||
"ID": "ID",
|
||||
"Descending": "Fallande",
|
||||
"Descending (down)": "Fallande (nedåt)",
|
||||
"Desc": "Fall",
|
||||
"Ascending": "Stigande",
|
||||
"Ascending (Up)": "Stigande (uppåt)",
|
||||
"Asc": "Stig",
|
||||
"Switch-Handheld!": "Switch Handhållen!",
|
||||
"Switch-Docked!": "Switch Dockad!",
|
||||
"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?",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Om detta meddelande visas upprepade gånger, vänligen öppna en felanmälan.",
|
||||
|
||||
"Menu Options": "Menyalternativ",
|
||||
"Menu": "Meny",
|
||||
"Theme": "Tema",
|
||||
"Theme Options": "Temaalternativ",
|
||||
"Select Theme": "Välj tema",
|
||||
"Shuffle": "Blanda",
|
||||
"Music": "Musik",
|
||||
"12 Hour Time": "",
|
||||
"Download Default Music": "",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "Nätverk",
|
||||
"Network Options": "Nätverksalternativ",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "Nxlink ansluten",
|
||||
"Nxlink Upload": "Nxlink överför",
|
||||
"Nxlink Finished": "Nxlink klar",
|
||||
"Switch-Handheld!": "Switch Handhållen!",
|
||||
"Switch-Docked!": "Switch Dockad!",
|
||||
|
||||
"Language": "Språk",
|
||||
"Auto": "Auto",
|
||||
"English": "Engelska",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Ryska",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "Loggning",
|
||||
"Replace hbmenu on exit": "Ersätt hbmenu vid avslut",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Övrigt",
|
||||
"Misc Options": "Övriga alternativ",
|
||||
"Web": "Webb",
|
||||
"Install forwarders": "Installera genvägar",
|
||||
"Install location": "Installationsplats",
|
||||
"Show install warning": "Visa installationsvarning",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "Filbläddrare",
|
||||
"%zd files": "%zd filer",
|
||||
"%zd dirs": "%zd kataloger",
|
||||
"File Options": "Filalternativ",
|
||||
"Show Hidden": "Visa dolda",
|
||||
"Folders First": "Mappar först",
|
||||
"Hidden Last": "Dolda sist",
|
||||
"Cut": "Klipp ut",
|
||||
"Copy": "Kopiera",
|
||||
"Paste": "Klistra in",
|
||||
"Paste ": "Klistra in ",
|
||||
" file(s)?": " fil(er)?",
|
||||
"Rename": "Byt namn",
|
||||
"Set New File Name": "Ange nytt filnamn",
|
||||
"Advanced": "Avancerat",
|
||||
"Advanced Options": "Avancerade alternativ",
|
||||
"Create File": "Skapa fil",
|
||||
"Set File Name": "Ange filnamn",
|
||||
"Create Folder": "Skapa mapp",
|
||||
"Set Folder Name": "Ange mappnamn",
|
||||
"View as text (unfinished)": "Visa som text (ofärdig)",
|
||||
"Ignore read only": "Ignorera skrivskydd",
|
||||
"Mount": "Montera",
|
||||
"Empty...": "Tom...",
|
||||
"Open with DayBreak?": "Öppna med DayBreak?",
|
||||
"Launch ": "Starta ",
|
||||
"Launch option for: ": "Startalternativ för: ",
|
||||
"Select launcher for: ": "Välj startprogram för: ",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Homebrew-alternativ",
|
||||
"Hide Sphaira": "Dölj Sphaira",
|
||||
"Install Forwarder": "Installera genväg",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "VARNING: Att installera genvägar kan leda till avstängning!",
|
||||
"Installing Forwarder": "Installerar genväg",
|
||||
"Creating Program": "Skapar program",
|
||||
"Creating Control": "Skapar kontroll",
|
||||
"Creating Meta": "Skapar metadata",
|
||||
"Writing Nca": "Skriver Nca",
|
||||
"Updating ncm databse": "Uppdaterar ncm-databas",
|
||||
"Pushing application record": "Skickar programpost",
|
||||
"Installed!": "Installerad!",
|
||||
"Failed to install forwarder": "Misslyckades att installera genväg",
|
||||
"Unstarred ": "Avmarkerad ",
|
||||
"Starred ": "Markerad ",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortering: %s | Ordning: %s",
|
||||
"AppStore Options": "AppStore-alternativ",
|
||||
"All": "Alla",
|
||||
"Games": "Spel",
|
||||
"Emulators": "Emulatorer",
|
||||
"Tools": "Verktyg",
|
||||
"Themes": "Teman",
|
||||
"Legacy": "Legacy",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "uppdaterad: %s",
|
||||
"category: %s": "kategori: %s",
|
||||
"extracted: %.2f MiB": "extraherad: %.2f MiB",
|
||||
"app_dls: %s": "app_nedladdningar: %s",
|
||||
"More by Author": "Mer av författaren",
|
||||
"Leave Feedback": "Lämna feedback",
|
||||
"Game Options": "",
|
||||
"Hide forwarders": "",
|
||||
"Launch random game": "",
|
||||
"List meta records": "",
|
||||
"Entries": "",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/NSP/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Success": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Themezer-alternativ",
|
||||
"Nsfw": "Nsfw",
|
||||
"Page": "Sida",
|
||||
"Page %zu / %zu": "Sida %zu / %zu",
|
||||
"Enter Page Number": "Ange sidnummer",
|
||||
"Bad Page": "Ogiltig sida",
|
||||
"Download theme?": "Ladda ner tema?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Laddar ner JSON",
|
||||
"Select asset to download for ": "Välj tillgång att ladda ner för ",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
"Host:": "",
|
||||
"Port:": "",
|
||||
"Username:": "",
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "",
|
||||
"Waiting for connection...": "",
|
||||
"Transferring data...": "",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "",
|
||||
"Re-enabled MTP": "",
|
||||
"Installed via usb": "",
|
||||
"Usb install success!": "",
|
||||
"Usb install failed!": "",
|
||||
"Press B to exit...": "",
|
||||
"GameCard Install": "",
|
||||
"GameCard": "",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "",
|
||||
"Gc install failed!": "",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "",
|
||||
"IRS": "",
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "Omgivningsljudnivå: ",
|
||||
"Controller": "Kontroller",
|
||||
"Pad ": "Handkontroll ",
|
||||
"HandHeld": "Handhållen",
|
||||
" (Available)": " (Tillgänglig)",
|
||||
" (Unsupported)": " (Ej stödd)",
|
||||
" (Unconnected)": " (Ej ansluten)",
|
||||
"HandHeld": "Handhållen",
|
||||
"Rotation": "Rotation",
|
||||
"0 (Sideways)": "0 (Sidan)",
|
||||
"90 (Flat)": "90 (Platt)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "Normal bild",
|
||||
"Negative image": "Negativ bild",
|
||||
"Format": "Format",
|
||||
"Trimming Format": "Trimningsformat",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "Trimningsformat",
|
||||
"External Light Filter": "Externt ljusfilter",
|
||||
"Load Default": "Ladda standard",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Themezer-alternativ",
|
||||
"Nsfw": "Nsfw",
|
||||
"Page": "Sida",
|
||||
"Page %zu / %zu": "Sida %zu / %zu",
|
||||
"Enter Page Number": "Ange sidnummer",
|
||||
"Bad Page": "Ogiltig sida",
|
||||
"Download theme?": "Ladda ner tema?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Laddar ner JSON",
|
||||
"Select asset to download for ": "Välj tillgång att ladda ner för ",
|
||||
|
||||
"Installing ": "Installerar ",
|
||||
"Uninstalling ": "Avinstallerar ",
|
||||
"Deleting ": "Raderar ",
|
||||
"Deleting": "Raderar",
|
||||
"Pasting ": "Klistrar in ",
|
||||
"Pasting": "Klistrar in",
|
||||
"Removing ": "Tar bort ",
|
||||
"Scanning ": "Skannar ",
|
||||
"Creating ": "Skapar ",
|
||||
"Copying ": "Kopierar ",
|
||||
"Trying to load ": "Försöker ladda ",
|
||||
"Downloading ": "Laddar ner ",
|
||||
"Downloaded ": "Nedladdad ",
|
||||
"Removed ": "Borttagen ",
|
||||
"Checking MD5": "Kontrollerar MD5",
|
||||
"Loading...": "Laddar...",
|
||||
"Loading": "Laddar",
|
||||
"Empty!": "Tomt!",
|
||||
"Not Ready...": "Inte redo...",
|
||||
"Error loading page!": "Fel vid laddning av sida!",
|
||||
"Update avaliable: ": "Uppdatering tillgänglig: ",
|
||||
"Download update: ": "Ladda ner uppdatering: ",
|
||||
"Updated to ": "Uppdaterad till ",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "Starta om Sphaira?",
|
||||
"Failed to download update": "Misslyckades att ladda ner uppdatering",
|
||||
"Advanced": "Avancerat",
|
||||
"Advanced Options": "Avancerade alternativ",
|
||||
"Logging": "Loggning",
|
||||
"Replace hbmenu on exit": "Ersätt hbmenu vid avslut",
|
||||
"Restore hbmenu?": "Återställ hbmenu?",
|
||||
"Restore": "Återställ",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Kunde inte hitta /switch/hbmenu.nro\nInstallera om hbmenu från Appstore ",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Misslyckades med att återställa hbmenu, vänligen ladda ner hbmenu igen.",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Misslyckades med att återställa hbmenu, använder istället sphaira.",
|
||||
"Restored hbmenu, closing sphaira": "Återställde hbmenu, stänger sphaira.",
|
||||
"Restored hbmenu": "Återställde hbmenu.",
|
||||
"Restart Sphaira?": "Starta om Sphaira?",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Text scroll speed": "",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Set right-side menu": "",
|
||||
"Install options": "",
|
||||
"Install Options": "",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "Visa installationsvarning",
|
||||
"Install location": "Installationsplats",
|
||||
"System memory": "Systemminne",
|
||||
"microSD card": "microSD-kort",
|
||||
"Boost CPU clock": "",
|
||||
"Allow downgrade": "",
|
||||
"Skip if already installed": "",
|
||||
"Ticket only": "",
|
||||
"Skip base": "",
|
||||
"Skip patch": "",
|
||||
"Skip dlc": "",
|
||||
"Skip data patch": "",
|
||||
"Skip ticket": "",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "",
|
||||
"Skip RSA NPDM verify": "",
|
||||
"Ignore distribution bit": "",
|
||||
"Convert to standard crypto": "",
|
||||
"Lower master key": "",
|
||||
"Lower system version": "",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Apps": "Appar",
|
||||
"Homebrew Options": "Homebrew-alternativ",
|
||||
"Hide Sphaira": "Dölj Sphaira",
|
||||
"Install Forwarder": "Installera genväg",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "VARNING: Att installera genvägar kan leda till avstängning!",
|
||||
"Installing Forwarder": "Installerar genväg",
|
||||
"Creating Program": "Skapar program",
|
||||
"Creating Control": "Skapar kontroll",
|
||||
"Creating Meta": "Skapar metadata",
|
||||
"Writing Nca": "Skriver Nca",
|
||||
"Updating ncm databse": "Uppdaterar ncm-databas",
|
||||
"Pushing application record": "Skickar programpost",
|
||||
"Failed to install forwarder": "Misslyckades att installera genväg",
|
||||
"Unstarred ": "Avmarkerad ",
|
||||
"Starred ": "Markerad ",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Appstore": "",
|
||||
"Store": "Butik",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortering: %s | Ordning: %s",
|
||||
"AppStore Options": "AppStore-alternativ",
|
||||
"Info": "Info",
|
||||
"Changelog": "Ändringslogg",
|
||||
"Details": "Detaljer",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "uppdaterad: %s",
|
||||
"category: %s": "kategori: %s",
|
||||
"extracted: %.2f MiB": "extraherad: %.2f MiB",
|
||||
"app_dls: %s": "app_nedladdningar: %s",
|
||||
"More by Author": "Mer av författaren",
|
||||
"Leave Feedback": "Lämna feedback",
|
||||
|
||||
"FileBrowser": "Filbläddrare",
|
||||
"Files": "Filer",
|
||||
"%zd files": "%zd filer",
|
||||
"%zd dirs": "%zd kataloger",
|
||||
"File Options": "Filalternativ",
|
||||
"Show Hidden": "Visa dolda",
|
||||
"Folders First": "Mappar först",
|
||||
"Hidden Last": "Dolda sist",
|
||||
"Cut": "Klipp ut",
|
||||
"Copy": "Kopiera",
|
||||
"Copying ": "Kopierar ",
|
||||
"Paste": "Klistra in",
|
||||
"Paste ": "Klistra in ",
|
||||
" file(s)?": " fil(er)?",
|
||||
"Pasting ": "Klistrar in ",
|
||||
"Pasting": "Klistrar in",
|
||||
"Rename": "Byt namn",
|
||||
"Set New File Name": "Ange nytt filnamn",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "Skapa fil",
|
||||
"Set File Name": "Ange filnamn",
|
||||
"Create Folder": "Skapa mapp",
|
||||
"Set Folder Name": "Ange mappnamn",
|
||||
"Creating ": "Skapar ",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "Visa som text (ofärdig)",
|
||||
"Ignore read only": "Ignorera skrivskydd",
|
||||
"Mount": "Montera",
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Avbild Systemminne",
|
||||
"Image microSD card": "Avbild microSD-kort",
|
||||
"Empty...": "Tom...",
|
||||
"Open with DayBreak?": "Öppna med DayBreak?",
|
||||
"Launch ": "Starta ",
|
||||
"Launch option for: ": "Startalternativ för: ",
|
||||
"Select launcher for: ": "Välj startprogram för: ",
|
||||
|
||||
"Sort By": "Sortera efter",
|
||||
"Sort Options": "Sorteringsalternativ",
|
||||
"Filter": "Filtrera",
|
||||
"All": "Alla",
|
||||
"Emulators": "Emulatorer",
|
||||
"Tools": "Verktyg",
|
||||
"Themes": "Teman",
|
||||
"Legacy": "Legacy",
|
||||
"Sort": "Sortera",
|
||||
"Size": "Storlek",
|
||||
"Size (Star)": "Storlek (Stjärna)",
|
||||
"Alphabetical": "Alfabetisk",
|
||||
"Alphabetical (Star)": "Alfabetisk (Stjärna)",
|
||||
"Updated": "Uppdaterad",
|
||||
"Updated (Star)": "Uppdaterad (Stjärna)",
|
||||
"Downloads": "Nedladdningar",
|
||||
"Likes": "Gillar",
|
||||
"ID": "ID",
|
||||
"Order": "Ordning",
|
||||
"Descending": "Fallande",
|
||||
"Descending (down)": "Fallande (nedåt)",
|
||||
"Desc": "Fall",
|
||||
"Ascending": "Stigande",
|
||||
"Ascending (Up)": "Stigande (uppåt)",
|
||||
"Asc": "Stig",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Search": "Sök",
|
||||
|
||||
"Options": "Alternativ",
|
||||
"OK": "OK",
|
||||
"Back": "Tillbaka",
|
||||
"Select": "Välj",
|
||||
"Open": "Öppna",
|
||||
"Launch": "Starta",
|
||||
"Restart": "Starta om",
|
||||
"Next": "",
|
||||
"Prev": "",
|
||||
"Unstar": "Avmarkera stjärna",
|
||||
"Star": "Markera stjärna",
|
||||
"Yes": "Ja",
|
||||
"No": "Nej",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
|
||||
"Install": "Installera",
|
||||
"Install Selected files?": "",
|
||||
"Installing ": "Installerar ",
|
||||
"Installed ": "",
|
||||
"Installed!": "Installerad!",
|
||||
"Trying to load ": "Försöker ladda ",
|
||||
"Checking MD5": "Kontrollerar MD5",
|
||||
|
||||
"Delete": "Radera",
|
||||
"Delete Selected files?": "Radera valda filer?",
|
||||
"Completely remove ": "Ta bort helt ",
|
||||
"Are you sure you want to delete ": "Är du säker på att du vill radera ",
|
||||
"Are you sure you wish to cancel?": "Är du säker på att du vill avbryta?",
|
||||
"Audio disabled due to suspended game": "Ljud är avstängt på grund av bakgrundsprogram",
|
||||
"If this message appears repeatedly, please open an issue.": "Om detta meddelande visas upprepade gånger, vänligen öppna en felanmälan.",
|
||||
"Scanning ": "Skannar ",
|
||||
"Deleting ": "Raderar ",
|
||||
"Deleting": "Raderar",
|
||||
"Remove": "Ta bort",
|
||||
"Completely remove ": "Ta bort helt ",
|
||||
"Removing ": "Tar bort ",
|
||||
"Removed ": "Borttagen ",
|
||||
"Uninstalling ": "Avinstallerar ",
|
||||
|
||||
"Download": "Ladda ner",
|
||||
"Downloading ": "Laddar ner ",
|
||||
"Downloaded ": "Nedladdad ",
|
||||
|
||||
"Update": "Uppdatera",
|
||||
"Update avaliable: ": "Uppdatering tillgänglig: ",
|
||||
"Download update: ": "Ladda ner uppdatering: ",
|
||||
"Updated to ": "Uppdaterad till ",
|
||||
"Failed to download update": "Misslyckades att ladda ner uppdatering",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "Laddar...",
|
||||
"Loading": "Laddar",
|
||||
"Empty!": "Tomt!",
|
||||
"Not Ready...": "Inte redo...",
|
||||
"Error loading page!": "Fel vid laddning av sida!"
|
||||
}
|
||||
392
assets/romfs/i18n/uk.json
Normal file
@@ -0,0 +1,392 @@
|
||||
{
|
||||
"[Applet Mode]": "[Режим Аплету]",
|
||||
"No Internet": "Без інтернету",
|
||||
"Switch-Handheld!": "Switch - Портатив!",
|
||||
"Switch-Docked!": "Switch - Докований!",
|
||||
"Audio disabled due to suspended game": "Аудіо вимкнено через призупинену програму",
|
||||
"Are you sure you wish to cancel?": "Ви впевнені, що хочете скасувати?",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Якщо це повідомлення з'являється повторно, будь ласка, повідомте про проблему.",
|
||||
|
||||
"Menu Options": "Опції меню",
|
||||
"Menu": "Меню",
|
||||
"Theme": "Тема",
|
||||
"Theme Options": "Опції теми",
|
||||
"Select Theme": "Вибрати тему",
|
||||
"Music": "Музика",
|
||||
"12 Hour Time": "12-годинний формат часу",
|
||||
"Download Default Music": "Завантажити музику за замовчуванням",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "Мережа",
|
||||
"Network Options": "Опції мережі",
|
||||
"Ftp": "FTP",
|
||||
"Mtp": "MTP",
|
||||
"Nxlink": "Nxlink",
|
||||
"Nxlink Connected": "Nxlink підключено",
|
||||
"Nxlink Upload": "Nxlink | Завантаження",
|
||||
"Nxlink Finished": "Nxlink | Завершено",
|
||||
|
||||
"Language": "Мова",
|
||||
"Auto": "Автоматично",
|
||||
"English": "English",
|
||||
"Japanese": "日本語",
|
||||
"French": "Français",
|
||||
"German": "Deutsch",
|
||||
"Italian": "Italiano",
|
||||
"Spanish": "Español",
|
||||
"Chinese": "中文",
|
||||
"Korean": "한국어",
|
||||
"Dutch": "Nederlands",
|
||||
"Portuguese": "Português",
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Tiếng Việt",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Різне",
|
||||
"Misc Options": "Опції різного",
|
||||
"Games": "Ігри",
|
||||
"Game Options": "Опції ігор",
|
||||
"Hide forwarders": "Приховати форвардери",
|
||||
"Launch random game": "Запустити випадкову гру",
|
||||
"List meta records": "Список метаданих записів",
|
||||
"Entries": "Записи",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/NSP/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Success": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Опції Themezer",
|
||||
"Nsfw": "NSFW",
|
||||
"Page": "Сторінка",
|
||||
"Page %zu / %zu": "Сторінка %zu / %zu",
|
||||
"Enter Page Number": "Введіть номер сторінки",
|
||||
"Bad Page": "Неправильна сторінка",
|
||||
"Download theme?": "Завантажити тему?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Завантаження JSON",
|
||||
"Select asset to download for ": "Виберіть ресурс для завантаження для ",
|
||||
|
||||
"FTP Install": "Встановлення через FTP",
|
||||
"FTP Install (EXPERIMENTAL)": "Встановлення через FTP (ЕКСПЕРИМЕНТАЛЬНО)",
|
||||
"Connection Type: WiFi | Strength: ": "Тип підключення: WiFi | Сила сигналу: ",
|
||||
"Connection Type: Ethernet": "Тип підключення: Ethernet",
|
||||
"Connection Type: None": "Тип підключення: Немає",
|
||||
"Host:": "Хост:",
|
||||
"Port:": "Порт:",
|
||||
"Username:": "Ім'я користувача:",
|
||||
"Password:": "Пароль:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "Кодова фраза:",
|
||||
"Failed to install via FTP, press B to exit...": "Не вдалося встановити через FTP, натисніть B для виходу...",
|
||||
"Ftp install success!": "Встановлення через FTP успішно завершено.",
|
||||
"Ftp install failed!": "Встановлення через FTP не вдалося.",
|
||||
"USB Install": "Встановлення через USB",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "Не вдалося ініціалізувати USB, натисніть B для виходу...",
|
||||
"Waiting for connection...": "Очікування підключення...",
|
||||
"Transferring data...": "Передача даних...",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "Вимкнути MTP для встановлення через USB",
|
||||
"Re-enabled MTP": "MTP знову увімкнено",
|
||||
"Installed via usb": "Встановлено через USB",
|
||||
"Usb install success!": "Встановлення через USB успішно завершено.",
|
||||
"Usb install failed!": "Встановлення через USB не вдалося.",
|
||||
"Press B to exit...": "Натисніть B для виходу...",
|
||||
"GameCard Install": "Встановлення з картриджа",
|
||||
"GameCard": "Картридж",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "Встановлення з картриджа успішно завершено.",
|
||||
"Gc install failed!": "Встановлення з картриджа не вдалося.",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "ІЧ (Інфрачервона камера Joycon)",
|
||||
"IRS": "",
|
||||
"Irs": "ІЧ-сенсор",
|
||||
"Ambient Noise Level: ": "Рівень навколишнього шуму: ",
|
||||
"Controller": "Контролер",
|
||||
"Pad ": "Геймпад ",
|
||||
"HandHeld": "Портативний режим",
|
||||
" (Available)": " (Доступно)",
|
||||
" (Unsupported)": " (Не підтримується)",
|
||||
" (Unconnected)": " (Не підключено)",
|
||||
"Rotation": "Обертання",
|
||||
"0 (Sideways)": "0° (Збоку)",
|
||||
"90 (Flat)": "90° (Плоско)",
|
||||
"180 (-Sideways)": "180° (-Збоку)",
|
||||
"270 (Upside down)": "270° (Догори дном)",
|
||||
"Colour": "Колір",
|
||||
"Grey": "Сірий",
|
||||
"Ironbow": "Ironbow",
|
||||
"Green": "Зелений",
|
||||
"Red": "Червоний",
|
||||
"Blue": "Синій",
|
||||
"Light Target": "Ціль освітлення",
|
||||
"All leds": "Всі світлодіоди",
|
||||
"Bright group": "Яскрава група",
|
||||
"Dim group": "Тьмяна група",
|
||||
"None": "Немає",
|
||||
"Gain": "Підсилення",
|
||||
"Negative Image": "Негативне зображення",
|
||||
"Normal image": "Нормальне зображення",
|
||||
"Negative image": "",
|
||||
"Format": "Формат",
|
||||
"Trimming Format": "Формат обрізки",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"External Light Filter": "Фільтр зовнішнього освітлення",
|
||||
"Load Default": "Завантажити типові",
|
||||
|
||||
"Advanced": "Додатково",
|
||||
"Advanced Options": "Додаткові опції",
|
||||
"Logging": "Логування",
|
||||
"Replace hbmenu on exit": "Заміна hbmenu при виході",
|
||||
"Restore hbmenu?": "Відновити hbmenu?",
|
||||
"Restore": "Відновити",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Не вдалося знайти /switch/hbmenu.nro\nВикористовуйте Магазин програм для перевстановлення hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Не вдалося відновити hbmenu, будь ласка, завантажте hbmenu знову",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Не вдалося відновити hbmenu, замість цього використовується Sphaira",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu відновлено, закриття Sphaira",
|
||||
"Restored hbmenu": "hbmenu відновлено",
|
||||
"Restart Sphaira?": "Перезапустити Sphaira?",
|
||||
"Press OK to restart Sphaira": "Натисніть OK для перезапуску Sphaira",
|
||||
"Text scroll speed": "Швидк. прокрутки",
|
||||
"Slow": "Повільно",
|
||||
"Normal": "Нормально",
|
||||
"Fast": "Швидко",
|
||||
"Set right-side menu": "Праве меню",
|
||||
"Install options": "Опції встановлення",
|
||||
"Install Options": "Опції встановлення",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "Попередж. при встанов.",
|
||||
"Install location": "Місце встановлення",
|
||||
"System memory": "Пам'ять консолі",
|
||||
"microSD card": "SD-карта",
|
||||
"Boost CPU clock": "Розігнати CPU",
|
||||
"Allow downgrade": "Дозволити відкат",
|
||||
"Skip if already installed": "Пропуск, якщо встановл.",
|
||||
"Ticket only": "Тільки тікет",
|
||||
"Skip base": "Пропустити базу",
|
||||
"Skip patch": "Пропустити патч",
|
||||
"Skip dlc": "Пропустити DLC",
|
||||
"Skip data patch": "Пропустити патч даних",
|
||||
"Skip ticket": "Пропустити тікет",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "Пропуск перевірку заголовка RSA",
|
||||
"Skip RSA NPDM verify": "Пропуск перевірку NPDM RSA",
|
||||
"Ignore distribution bit": "Ігнорувати біт розподілу",
|
||||
"Convert to standard crypto": "Конвертувати у стандартне шифрування",
|
||||
"Lower master key": "Знизити майстер-ключ",
|
||||
"Lower system version": "Знизити версію системи",
|
||||
|
||||
"Homebrew": "Домашні програми",
|
||||
"Apps": "Програми",
|
||||
"Homebrew Options": "Опції домашніх програм",
|
||||
"Hide Sphaira": "Приховати Sphaira",
|
||||
"Install Forwarder": "Встановити форвардер",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "УВАГА: Встановлення форвардерів може призвести до бану!",
|
||||
"Installing Forwarder": "Встановлення форвардера",
|
||||
"Creating Program": "Створення програми",
|
||||
"Creating Control": "Створення контролера",
|
||||
"Creating Meta": "Створення метаданих",
|
||||
"Writing Nca": "Запис NCA",
|
||||
"Updating ncm databse": "Оновлення бази даних NCM",
|
||||
"Pushing application record": "Запис даних програми",
|
||||
"Failed to install forwarder": "Не вдалося встановити форвардер",
|
||||
"Unstarred ": "Знято зірку з ",
|
||||
"Starred ": "Позначено зіркою ",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "Магазин програм",
|
||||
"Appstore": "",
|
||||
"Store": "Магазин",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Фільтр: %s | Сорт.: %s | Порядок: %s",
|
||||
"AppStore Options": "Опції магазину програм",
|
||||
"Info": "Інфо",
|
||||
"Changelog": "Журнал змін",
|
||||
"Details": "Деталі",
|
||||
"version: %s": "версія: %s",
|
||||
"updated: %s": "оновлено: %s",
|
||||
"category: %s": "категорія: %s",
|
||||
"extracted: %.2f MiB": "розмір: %.2f MiB",
|
||||
"app_dls: %s": "завантажень: %s",
|
||||
"More by Author": "Більше від автора",
|
||||
"Leave Feedback": "Залишити відгук",
|
||||
|
||||
"FileBrowser": "Файловий менеджер",
|
||||
"Files": "Файли",
|
||||
"%zd files": "%zd файл(и)",
|
||||
"%zd dirs": "%zd тек(и)",
|
||||
"File Options": "Опції файлів",
|
||||
"Show Hidden": "Показати приховані",
|
||||
"Folders First": "Теки спочатку",
|
||||
"Hidden Last": "Приховані в кінці",
|
||||
"Cut": "Вирізати",
|
||||
"Copy": "Копіювати",
|
||||
"Copying ": "Копіювання ",
|
||||
"Paste": "Вставити",
|
||||
"Paste ": "Вставити: ",
|
||||
" file(s)?": " файл(и)?",
|
||||
"Pasting ": "Вставлення ",
|
||||
"Pasting": "Вставлення",
|
||||
"Rename": "Перейменувати",
|
||||
"Set New File Name": "Введіть нове ім'я файлу",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "Стиснути в zip",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "Створити файл",
|
||||
"Set File Name": "Введіть ім'я файлу",
|
||||
"Create Folder": "Створити теку",
|
||||
"Set Folder Name": "Введіть ім'я теки",
|
||||
"Creating ": "Створення ",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "Переглянути як текст (незавершено)",
|
||||
"Ignore read only": "Ігнорувати лише читання",
|
||||
"Mount": "Монтувати",
|
||||
"Sd": "SD-карта",
|
||||
"Image System memory": "Фото | Пам'ять консолі",
|
||||
"Image microSD card": "Фото | SD-карта",
|
||||
"Empty...": "Пусто...",
|
||||
"Open with DayBreak?": "Відкрити за допомогою DayBreak?",
|
||||
"Launch ": "Запустити ",
|
||||
"Launch option for: ": "Опція запуску для: ",
|
||||
"Select launcher for: ": "Виберіть лаунчер для: ",
|
||||
|
||||
"Sort By": "Сортувати за",
|
||||
"Sort Options": "Опції сортування",
|
||||
"Filter": "Фільтр",
|
||||
"All": "Всі",
|
||||
"Emulators": "Емулятори",
|
||||
"Tools": "Інструменти",
|
||||
"Themes": "Теми",
|
||||
"Legacy": "Доступні оновлення",
|
||||
"Sort": "Сортування",
|
||||
"Size": "Розмір",
|
||||
"Size (Star)": "Розмір (Зірка)",
|
||||
"Alphabetical": "За алфавітом",
|
||||
"Alphabetical (Star)": "За алфавітом (Зірка)",
|
||||
"Updated": "Оновлено",
|
||||
"Updated (Star)": "Оновлено (Зірка)",
|
||||
"Downloads": "Завантаження",
|
||||
"Likes": "Вподобання",
|
||||
"ID": "ID",
|
||||
"Order": "Порядок",
|
||||
"Descending": "За спаданням",
|
||||
"Descending (down)": "За спаданням (вниз)",
|
||||
"Desc": "Спад.",
|
||||
"Ascending": "За зростанням",
|
||||
"Ascending (Up)": "За зростанням (вгору)",
|
||||
"Asc": "Зрост.",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Search": "Пошук",
|
||||
|
||||
"Options": "Налаштування",
|
||||
"OK": "ОК",
|
||||
"Back": "Назад",
|
||||
"Select": "Вибрати",
|
||||
"Open": "Відкрити",
|
||||
"Launch": "Запустити",
|
||||
"Restart": "Перезапустити",
|
||||
"Next": "Наступний",
|
||||
"Prev": "Попередній",
|
||||
"Unstar": "Прибрати з обраного",
|
||||
"Star": "Позначити зіркою",
|
||||
"Yes": "Так",
|
||||
"No": "Ні",
|
||||
"On": "Увімк.",
|
||||
"Off": "Вимк.",
|
||||
|
||||
"Install": "Встановити",
|
||||
"Install Selected files?": "Встановити вибрані файли?",
|
||||
"Installing ": "Встановлення ",
|
||||
"Installed ": "Встановлено ",
|
||||
"Installed!": "Встановлено!",
|
||||
"Trying to load ": "Спроба завантажити ",
|
||||
"Checking MD5": "Перевірка MD5",
|
||||
|
||||
"Delete": "Видалити",
|
||||
"Delete Selected files?": "Видалити вибрані файли?",
|
||||
"Are you sure you want to delete ": "Ви впевнені, що хочете видалити ",
|
||||
"Scanning ": "Сканування ",
|
||||
"Deleting ": "Видалення ",
|
||||
"Deleting": "Видалення",
|
||||
"Remove": "Видалити",
|
||||
"Completely remove ": "Повністю видалити ",
|
||||
"Removing ": "Видалення ",
|
||||
"Removed ": "Видалено ",
|
||||
"Uninstalling ": "Видалення ",
|
||||
|
||||
"Download": "Завантажити",
|
||||
"Downloading ": "Завантаження ",
|
||||
"Downloaded ": "Завантажено ",
|
||||
|
||||
"Update": "Оновити",
|
||||
"Update avaliable: ": "Доступне оновлення: ",
|
||||
"Download update: ": "Завантажити оновлення: ",
|
||||
"Updated to ": "Оновлено до ",
|
||||
"Failed to download update": "Не вдалося завантажити оновлення",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "Завантаження...",
|
||||
"Loading": "Завантаження",
|
||||
"Empty!": "Пусто!",
|
||||
"Not Ready...": "Не готово...",
|
||||
"Error loading page!": "Помилка завантаження сторінки!"
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[Applet Mode]",
|
||||
"No Internet": "Không có Internet",
|
||||
"Files": "Tập tin",
|
||||
"Apps": "Ứng dụng",
|
||||
"Store": "Cửa hàng",
|
||||
"Menu": "Menu",
|
||||
"Options": "Tuỳ chọn",
|
||||
"OK": "OK",
|
||||
"Back": "Trở về",
|
||||
"Select": "Chọn",
|
||||
"Open": "Mở",
|
||||
"Launch": "Chạy",
|
||||
"Info": "Thông tin",
|
||||
"Install": "Cài đặt",
|
||||
"Delete": "Xoá",
|
||||
"Restart": "Khởi động lại",
|
||||
"Changelog": "Thay đổi",
|
||||
"Details": "Chi tiết",
|
||||
"Update": "Cập nhật",
|
||||
"Remove": "Gỡ",
|
||||
"Restore": "Khôi phục",
|
||||
"Download": "Tải về",
|
||||
"Next Page": "Trang kế",
|
||||
"Prev Page": "Trang trước",
|
||||
"Unstar": "Xoá yêu thích",
|
||||
"Star": "Yêu thích",
|
||||
"System memory": "Bộ nhớ máy",
|
||||
"microSD card": "Thẻ nhớ",
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Bộ nhớ hệ thống hình ảnh",
|
||||
"Image microSD card": "Thẻ nhớ hệ thống hình ảnh",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Yes": "Có",
|
||||
"No": "Không",
|
||||
"Enabled": "Bật",
|
||||
"Disabled": "Tắt",
|
||||
|
||||
"Sort By": "Sắp xếp bởi",
|
||||
"Sort Options": "Tuỳ chọn sắp xếp",
|
||||
"Filter": "Lọc",
|
||||
"Sort": "Sắp xếp",
|
||||
"Order": "Thứ tự",
|
||||
"Search": "Tìm kiếm",
|
||||
"Updated": "Updated",
|
||||
"Updated (Star)": "Đã cập nhật (Yêu thích)",
|
||||
"Downloads": "Danh sách tải về",
|
||||
"Size": "Kích thước",
|
||||
"Size (Star)": "Kích thước (Yêu thích)",
|
||||
"Alphabetical": "A-Z",
|
||||
"Alphabetical (Star)": "A-Z (Yêu thích)",
|
||||
"Likes": "Thích",
|
||||
"ID": "ID",
|
||||
"Descending": "Giảm dần",
|
||||
"Descending (down)": "Giảm dần (xuống)",
|
||||
"Desc": "Giảm",
|
||||
"Ascending": "Tăng dần",
|
||||
"Ascending (Up)": "Tăng dần (lên)",
|
||||
"Asc": "Tăng",
|
||||
"Switch-Handheld!": "Switch-Handheld!",
|
||||
"Switch-Docked!": "Switch-Docked!",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"Are you sure you wish to cancel?": "Bạn có chắn muốn huỷ không?",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Nếu thấy tin nhắn này, hãy báo lỗi.",
|
||||
|
||||
"Menu Options": "Menu tuỳ chọn",
|
||||
"Menu": "Menu",
|
||||
"Theme": "Theme",
|
||||
"Theme Options": "Theme tuỳ chọn",
|
||||
"Select Theme": "Chọn Theme",
|
||||
"Shuffle": "Trộn",
|
||||
"Music": "Âm nhạc",
|
||||
"12 Hour Time": "",
|
||||
"Download Default Music": "",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "Mạng",
|
||||
"Network Options": "Tuỳ chọn mạng",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "Nxlink Kết Nối",
|
||||
"Nxlink Upload": "Nxlink Đăng Tải",
|
||||
"Nxlink Finished": "Nxlink Hoàn Thành",
|
||||
"Switch-Handheld!": "Switch-Handheld!",
|
||||
"Switch-Docked!": "Switch-Docked!",
|
||||
|
||||
"Language": "Ngôn ngữ",
|
||||
"Auto": "Tự động",
|
||||
"English": "English",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Việt Nam",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "Thay thế hbmenu khi thoát",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Tiện ích",
|
||||
"Misc Options": "Tiện ích mở rộng",
|
||||
"Web": "Web",
|
||||
"Install forwarders": "Cài ra màn hình",
|
||||
"Install location": "Vị trí cài đặt",
|
||||
"Show install warning": "Hiển thị cảnh báo cài đặt",
|
||||
"Text scroll speed": "",
|
||||
|
||||
"FileBrowser": "Duyệt tập tin",
|
||||
"%zd files": "%zd tập tin",
|
||||
"%zd dirs": "%zd thư mục",
|
||||
"File Options": "Tuỳ chọn tập tin",
|
||||
"Show Hidden": "Hiển thị tập tin ẩn",
|
||||
"Folders First": "Thư mục đầu tiên",
|
||||
"Hidden Last": "Ẩn cuối",
|
||||
"Cut": "Cắt",
|
||||
"Copy": "Sao chép",
|
||||
"Paste": "Dán",
|
||||
"Paste ": "Paste ",
|
||||
" file(s)?": " tập tin(nhiều)?",
|
||||
"Rename": "Đổi tên",
|
||||
"Set New File Name": "Đặt tên mới cho tập tin",
|
||||
"Advanced": "Mở rộng",
|
||||
"Advanced Options": "Tuỳ chọn mở rộng",
|
||||
"Create File": "Tạo tập tin",
|
||||
"Set File Name": "Đặt tên cho tập tin",
|
||||
"Create Folder": "Tạo thư mục",
|
||||
"Set Folder Name": "Đặt tên thư mục",
|
||||
"View as text (unfinished)": "Xem dạng văn bản (chưa xong)",
|
||||
"Ignore read only": "Bỏ qua chỉ đọc",
|
||||
"Mount": "Gắn",
|
||||
"Empty...": "Rỗng...",
|
||||
"Open with DayBreak?": "Mở với DayBreak?",
|
||||
"Launch ": "Chạy ",
|
||||
"Launch option for: ": "Chạy với tuỳ chọn cho: ",
|
||||
"Select launcher for: ": "Chọn trình chạy cho: ",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Homebrew Options": "Tuỳ chọn Homebrew",
|
||||
"Hide Sphaira": "Ẩn Sphaira",
|
||||
"Install Forwarder": "Cài ra ngoài màn hình",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "CẢNH BÁO: Bạn có chắn muốn cài ra ngoài màn hình!",
|
||||
"Installing Forwarder": "Đang cài đặt ra ngoài màn hình",
|
||||
"Creating Program": "Tạo chương trình",
|
||||
"Creating Control": "Tạo điều khiển",
|
||||
"Creating Meta": "Tạo Meta",
|
||||
"Writing Nca": "Ghi Nca",
|
||||
"Updating ncm databse": "Cập nhật ncm databse",
|
||||
"Pushing application record": "Đẩy ứng dụng",
|
||||
"Installed!": "Đã cài xong!",
|
||||
"Failed to install forwarder": "Cài đặt ra ngoài màn hình thất bại",
|
||||
"Unstarred ": "Bỏ yêu thích ",
|
||||
"Starred ": "Đã yêu thích ",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Lọc: %s | Sắp xếp: %s | Thứ tự: %s",
|
||||
"AppStore Options": "Tuỳ chọn AppStore",
|
||||
"All": "Tất cả",
|
||||
"Games": "Games",
|
||||
"Emulators": "Emulators",
|
||||
"Tools": "Tools",
|
||||
"Themes": "Themes",
|
||||
"Legacy": "Legacy",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "Xem thêm tác giả",
|
||||
"Leave Feedback": "Để lại phản hồi",
|
||||
"Game Options": "",
|
||||
"Hide forwarders": "",
|
||||
"Launch random game": "",
|
||||
"List meta records": "",
|
||||
"Entries": "",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/NSP/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Success": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Tuỳ chọn Themezer",
|
||||
"Nsfw": "18+",
|
||||
"Page": "Trang",
|
||||
"Page %zu / %zu": "Trang %zu / %zu",
|
||||
"Enter Page Number": "Nhập số trang",
|
||||
"Bad Page": "Trang không tồn tại",
|
||||
"Download theme?": "Tải theme?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Đang tải json",
|
||||
"Select asset to download for ": "Chọn nội dung để tải xuống cho ",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
"Host:": "",
|
||||
"Port:": "",
|
||||
"Username:": "",
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "",
|
||||
"Waiting for connection...": "",
|
||||
"Transferring data...": "",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "",
|
||||
"Re-enabled MTP": "",
|
||||
"Installed via usb": "",
|
||||
"Usb install success!": "",
|
||||
"Usb install failed!": "",
|
||||
"Press B to exit...": "",
|
||||
"GameCard Install": "",
|
||||
"GameCard": "",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "",
|
||||
"Gc install failed!": "",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "",
|
||||
"IRS": "",
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "Mức ồn xung quanh: ",
|
||||
"Controller": "Điều khiển",
|
||||
"Pad ": "Pad ",
|
||||
"HandHeld": "Cầm tay",
|
||||
" (Available)": " (Có sẵn)",
|
||||
" (Unsupported)": " (Không hỗ trợ)",
|
||||
" (Unconnected)": " (Không kết nối)",
|
||||
"HandHeld": "Cầm tay",
|
||||
"Rotation": "Xoay",
|
||||
"0 (Sideways)": "0 (Đi ngang)",
|
||||
"90 (Flat)": "90 (Phẳng)",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "Ảnh bình thường",
|
||||
"Negative image": "Ảnh âm bản",
|
||||
"Format": "Định dạng",
|
||||
"Trimming Format": "Định dạng cắt tỉa",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "Định dạng cắt tỉa",
|
||||
"External Light Filter": "Bộ lộc ánh sáng bên ngoài",
|
||||
"Load Default": "Tải mặc định",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Tuỳ chọn Themezer",
|
||||
"Nsfw": "18+",
|
||||
"Page": "Trang",
|
||||
"Page %zu / %zu": "Trang %zu / %zu",
|
||||
"Enter Page Number": "Nhập số trang",
|
||||
"Bad Page": "Trang không tồn tại",
|
||||
"Download theme?": "Tải theme?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Đang tải json",
|
||||
"Select asset to download for ": "Chọn nội dung để tải xuống cho ",
|
||||
|
||||
"Installing ": "Đang cài đặt ",
|
||||
"Uninstalling ": "Đang gỡ cài đặt ",
|
||||
"Deleting ": "Đang xoá ",
|
||||
"Deleting": "Đang xoá",
|
||||
"Pasting ": "Đang dán ",
|
||||
"Pasting": "Đang dán",
|
||||
"Removing ": "Đang gỡ ",
|
||||
"Scanning ": "Đang quét ",
|
||||
"Creating ": "Đang tạo ",
|
||||
"Copying ": "Đang sao chép ",
|
||||
"Trying to load ": "Đang cố gắn mở ",
|
||||
"Downloading ": "Đang tải xuống ",
|
||||
"Downloaded ": "Đã tải xong ",
|
||||
"Removed ": "Đã gỡ ",
|
||||
"Checking MD5": "Kiểm tra MD5",
|
||||
"Loading...": "Đang tải...",
|
||||
"Loading": "Đang tải",
|
||||
"Empty!": "Trống!",
|
||||
"Not Ready...": "Chưa sẵn sàng...",
|
||||
"Error loading page!": "Lỗi tải trang!",
|
||||
"Update avaliable: ": "Cập nhậc có sẵn: ",
|
||||
"Download update: ": "Tải cập nhật: ",
|
||||
"Updated to ": "Đã cập nhật ",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "Khởi động lại Sphaira?",
|
||||
"Failed to download update": "Cập nhật thất bại",
|
||||
"Advanced": "Mở rộng",
|
||||
"Advanced Options": "Tuỳ chọn mở rộng",
|
||||
"Logging": "Logging",
|
||||
"Replace hbmenu on exit": "Thay thế hbmenu khi thoát",
|
||||
"Restore hbmenu?": "Khôi phục hbmenu?",
|
||||
"Restore": "Khôi phục",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Không tìm thấy /switch/hbmenu.nro\nSử dụng AppStore để cài lại hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Không thể khôi phục hbmenu, Vui lòng tải lại hbmenu",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Không thể khôi phục hbmenu, thay vào đó sử dụng Sphira",
|
||||
"Restored hbmenu, closing sphaira": "Khôi mục hbmenu thành công, đóng sphaira",
|
||||
"Restored hbmenu": "Đã khôi phục hbmenu",
|
||||
"Restart Sphaira?": "Khởi động lại Sphaira?",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Text scroll speed": "",
|
||||
"Slow": "",
|
||||
"Normal": "",
|
||||
"Fast": "",
|
||||
"Set right-side menu": "",
|
||||
"Install options": "",
|
||||
"Install Options": "",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "Hiển thị cảnh báo cài đặt",
|
||||
"Install location": "Vị trí cài đặt",
|
||||
"System memory": "Bộ nhớ máy",
|
||||
"microSD card": "Thẻ nhớ",
|
||||
"Boost CPU clock": "",
|
||||
"Allow downgrade": "",
|
||||
"Skip if already installed": "",
|
||||
"Ticket only": "",
|
||||
"Skip base": "",
|
||||
"Skip patch": "",
|
||||
"Skip dlc": "",
|
||||
"Skip data patch": "",
|
||||
"Skip ticket": "",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "",
|
||||
"Skip RSA NPDM verify": "",
|
||||
"Ignore distribution bit": "",
|
||||
"Convert to standard crypto": "",
|
||||
"Lower master key": "",
|
||||
"Lower system version": "",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Apps": "Ứng dụng",
|
||||
"Homebrew Options": "Tuỳ chọn Homebrew",
|
||||
"Hide Sphaira": "Ẩn Sphaira",
|
||||
"Install Forwarder": "Cài ra ngoài màn hình",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "CẢNH BÁO: Bạn có chắn muốn cài ra ngoài màn hình!",
|
||||
"Installing Forwarder": "Đang cài đặt ra ngoài màn hình",
|
||||
"Creating Program": "Tạo chương trình",
|
||||
"Creating Control": "Tạo điều khiển",
|
||||
"Creating Meta": "Tạo Meta",
|
||||
"Writing Nca": "Ghi Nca",
|
||||
"Updating ncm databse": "Cập nhật ncm databse",
|
||||
"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",
|
||||
"Unstarred ": "Bỏ yêu thích ",
|
||||
"Starred ": "Đã yêu thích ",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "AppStore",
|
||||
"Appstore": "",
|
||||
"Store": "Cửa hàng",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Lọc: %s | Sắp xếp: %s | Thứ tự: %s",
|
||||
"AppStore Options": "Tuỳ chọn AppStore",
|
||||
"Info": "Thông tin",
|
||||
"Changelog": "Thay đổi",
|
||||
"Details": "Chi tiết",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "updated: %s",
|
||||
"category: %s": "category: %s",
|
||||
"extracted: %.2f MiB": "extracted: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "Xem thêm tác giả",
|
||||
"Leave Feedback": "Để lại phản hồi",
|
||||
|
||||
"FileBrowser": "Duyệt tập tin",
|
||||
"Files": "Tập tin",
|
||||
"%zd files": "%zd tập tin",
|
||||
"%zd dirs": "%zd thư mục",
|
||||
"File Options": "Tuỳ chọn tập tin",
|
||||
"Show Hidden": "Hiển thị tập tin ẩn",
|
||||
"Folders First": "Thư mục đầu tiên",
|
||||
"Hidden Last": "Ẩn cuối",
|
||||
"Cut": "Cắt",
|
||||
"Copy": "Sao chép",
|
||||
"Copying ": "Đang sao chép ",
|
||||
"Paste": "Dán",
|
||||
"Paste ": "Paste ",
|
||||
" file(s)?": " tập tin(nhiều)?",
|
||||
"Pasting ": "Đang dán ",
|
||||
"Pasting": "Đang dán",
|
||||
"Rename": "Đổi tên",
|
||||
"Set New File Name": "Đặt tên mới cho tập tin",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "Tạo tập tin",
|
||||
"Set File Name": "Đặt tên cho tập tin",
|
||||
"Create Folder": "Tạo thư mục",
|
||||
"Set Folder Name": "Đặt tên thư mục",
|
||||
"Creating ": "Đang tạo ",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "Xem dạng văn bản (chưa xong)",
|
||||
"Ignore read only": "Bỏ qua chỉ đọc",
|
||||
"Mount": "Gắn",
|
||||
"Sd": "Sd",
|
||||
"Image System memory": "Bộ nhớ hệ thống hình ảnh",
|
||||
"Image microSD card": "Thẻ nhớ hệ thống hình ảnh",
|
||||
"Empty...": "Rỗng...",
|
||||
"Open with DayBreak?": "Mở với DayBreak?",
|
||||
"Launch ": "Chạy ",
|
||||
"Launch option for: ": "Chạy với tuỳ chọn cho: ",
|
||||
"Select launcher for: ": "Chọn trình chạy cho: ",
|
||||
|
||||
"Sort By": "Sắp xếp bởi",
|
||||
"Sort Options": "Tuỳ chọn sắp xếp",
|
||||
"Filter": "Lọc",
|
||||
"All": "Tất cả",
|
||||
"Emulators": "Emulators",
|
||||
"Tools": "Tools",
|
||||
"Themes": "Themes",
|
||||
"Legacy": "Legacy",
|
||||
"Sort": "Sắp xếp",
|
||||
"Size": "Kích thước",
|
||||
"Size (Star)": "Kích thước (Yêu thích)",
|
||||
"Alphabetical": "A-Z",
|
||||
"Alphabetical (Star)": "A-Z (Yêu thích)",
|
||||
"Updated": "Updated",
|
||||
"Updated (Star)": "Đã cập nhật (Yêu thích)",
|
||||
"Downloads": "Danh sách tải về",
|
||||
"Likes": "Thích",
|
||||
"ID": "ID",
|
||||
"Order": "Thứ tự",
|
||||
"Descending": "Giảm dần",
|
||||
"Descending (down)": "Giảm dần (xuống)",
|
||||
"Desc": "Giảm",
|
||||
"Ascending": "Tăng dần",
|
||||
"Ascending (Up)": "Tăng dần (lên)",
|
||||
"Asc": "Tăng",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Search": "Tìm kiếm",
|
||||
|
||||
"Options": "Tuỳ chọn",
|
||||
"OK": "OK",
|
||||
"Back": "Trở về",
|
||||
"Select": "Chọn",
|
||||
"Open": "Mở",
|
||||
"Launch": "Chạy",
|
||||
"Restart": "Khởi động lại",
|
||||
"Next": "",
|
||||
"Prev": "",
|
||||
"Unstar": "Xoá yêu thích",
|
||||
"Star": "Yêu thích",
|
||||
"Yes": "Có",
|
||||
"No": "Không",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
|
||||
"Install": "Cài đặt",
|
||||
"Install Selected files?": "",
|
||||
"Installing ": "Đang cài đặt ",
|
||||
"Installed ": "",
|
||||
"Installed!": "Đã cài xong!",
|
||||
"Trying to load ": "Đang cố gắn mở ",
|
||||
"Checking MD5": "Kiểm tra MD5",
|
||||
|
||||
"Delete": "Xoá",
|
||||
"Delete Selected files?": "Xoá những tập tin được chọn?",
|
||||
"Completely remove ": "Đã gỡ thành công ",
|
||||
"Are you sure you want to delete ": "Bạn có muốn xoá ",
|
||||
"Are you sure you wish to cancel?": "Bạn có chắn muốn huỷ không?",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Nếu thấy tin nhắn này, hãy báo lỗi."
|
||||
"Scanning ": "Đang quét ",
|
||||
"Deleting ": "Đang xoá ",
|
||||
"Deleting": "Đang xoá",
|
||||
"Remove": "Gỡ",
|
||||
"Completely remove ": "Đã gỡ thành công ",
|
||||
"Removing ": "Đang gỡ ",
|
||||
"Removed ": "Đã gỡ ",
|
||||
"Uninstalling ": "Đang gỡ cài đặt ",
|
||||
|
||||
"Download": "Tải về",
|
||||
"Downloading ": "Đang tải xuống ",
|
||||
"Downloaded ": "Đã tải xong ",
|
||||
|
||||
"Update": "Cập nhật",
|
||||
"Update avaliable: ": "Cập nhậc có sẵn: ",
|
||||
"Download update: ": "Tải cập nhật: ",
|
||||
"Updated to ": "Đã cập nhật ",
|
||||
"Failed to download update": "Cập nhật thất bại",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "Đang tải...",
|
||||
"Loading": "Đang tải",
|
||||
"Empty!": "Trống!",
|
||||
"Not Ready...": "Chưa sẵn sàng...",
|
||||
"Error loading page!": "Lỗi tải trang!"
|
||||
}
|
||||
@@ -1,72 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": "[小程序模式]",
|
||||
"No Internet": "网络未连接",
|
||||
"Files": "文件",
|
||||
"Apps": "应用",
|
||||
"Store": "商店",
|
||||
"Menu": "菜单",
|
||||
"Options": "选项",
|
||||
"OK": "确定",
|
||||
"Back": "返回",
|
||||
"Select": "选择",
|
||||
"Open": "打开",
|
||||
"Launch": "启动",
|
||||
"Info": "信息",
|
||||
"Install": "安装",
|
||||
"Delete": "删除",
|
||||
"Restart": "重启",
|
||||
"Changelog": "更新日志",
|
||||
"Details": "详情",
|
||||
"Update": "更新",
|
||||
"Remove": "删除",
|
||||
"Restore": "恢复",
|
||||
"Download": "下载",
|
||||
"Next Page": "下一页",
|
||||
"Prev Page": "上一页",
|
||||
"Unstar": "取消星标",
|
||||
"Star": "星标",
|
||||
"System memory": "主机内存",
|
||||
"microSD card": "SD卡",
|
||||
"Sd": "SD卡",
|
||||
"Image System memory": "主机内存图像",
|
||||
"Image microSD card": "SD卡图像",
|
||||
"Slow": "慢",
|
||||
"Normal": "正常",
|
||||
"Fast": "快",
|
||||
"Yes": "是",
|
||||
"No": "否",
|
||||
"Enabled": "启用",
|
||||
"Disabled": "禁用",
|
||||
|
||||
"Sort By": "排序方式",
|
||||
"Sort Options": "排序选项",
|
||||
"Filter": "筛选",
|
||||
"Sort": "排序",
|
||||
"Order": "顺序",
|
||||
"Search": "搜索",
|
||||
"Updated": "最近使用",
|
||||
"Updated (Star)": "最近更新(星标优先)",
|
||||
"Downloads": "下载",
|
||||
"Size": "按大小",
|
||||
"Size (Star)": "按大小(星标优先)",
|
||||
"Alphabetical": "按字母顺序",
|
||||
"Alphabetical (Star)": "按字母顺序(星标优先)",
|
||||
"Likes": "点赞量",
|
||||
"ID": "ID",
|
||||
"Descending": "降序",
|
||||
"Descending (down)": "降序",
|
||||
"Desc": "降序",
|
||||
"Ascending": "升序",
|
||||
"Ascending (Up)": "升序",
|
||||
"Asc": "升序",
|
||||
"Switch-Handheld!": "切换至掌机模式!",
|
||||
"Switch-Docked!": "切换至底座模式!",
|
||||
"Audio disabled due to suspended game": "由于游戏暂停,音频已禁用",
|
||||
"Are you sure you wish to cancel?": "您确定要取消吗?",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "若此消息反复出现,请提交问题报告。",
|
||||
|
||||
"Menu Options": "菜单选项",
|
||||
"Menu": "菜单",
|
||||
"Theme": "主题",
|
||||
"Theme Options": "主题选项",
|
||||
"Select Theme": "选择主题",
|
||||
"Shuffle": "随机播放",
|
||||
"Music": "音乐",
|
||||
"12 Hour Time": "",
|
||||
"12 Hour Time": "12小时制时间",
|
||||
"Download Default Music": "下载默认音乐",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
|
||||
"Network": "网络",
|
||||
"Network Options": "网络选项",
|
||||
"Ftp": "FTP",
|
||||
@@ -75,8 +27,7 @@
|
||||
"Nxlink Connected": "Nxlink 已连接",
|
||||
"Nxlink Upload": "Nxlink 上传中",
|
||||
"Nxlink Finished": "Nxlink 已结束",
|
||||
"Switch-Handheld!": "切换至掌机模式!",
|
||||
"Switch-Docked!": "切换至底座模式!",
|
||||
|
||||
"Language": "语言",
|
||||
"Auto": "自动",
|
||||
"English": "English",
|
||||
@@ -92,87 +43,101 @@
|
||||
"Russian": "Русский",
|
||||
"Swedish": "Svenska",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Logging": "日志",
|
||||
"Replace hbmenu on exit": "退出后用Sphaira替换hbmenu",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "拓展",
|
||||
"Misc Options": "拓展设置",
|
||||
"Web": "网页浏览器",
|
||||
"Install forwarders": "允许安装前端应用",
|
||||
"Install location": "安装位置",
|
||||
"Show install warning": "显示安装警告",
|
||||
"Text scroll speed": "文本滚动速度",
|
||||
|
||||
"FileBrowser": "文件浏览",
|
||||
"%zd files": "%zd 个文件",
|
||||
"%zd dirs": "%zd 个文件夹",
|
||||
"File Options": "文件选项",
|
||||
"Show Hidden": "显示隐藏项目",
|
||||
"Folders First": "文件夹靠前",
|
||||
"Hidden Last": "隐藏项目置后",
|
||||
"Cut": "剪切",
|
||||
"Copy": "复制",
|
||||
"Paste": "粘贴",
|
||||
"Paste ": "粘贴 ",
|
||||
" file(s)?": "个文件(夹)?",
|
||||
"Rename": "重命名",
|
||||
"Set New File Name": "输入新命名",
|
||||
"Advanced": "高级",
|
||||
"Advanced Options": "高级选项",
|
||||
"Create File": "新建文件",
|
||||
"Set File Name": "输入文件名",
|
||||
"Create Folder": "新建文件夹",
|
||||
"Set Folder Name": "输入文件夹名",
|
||||
"View as text (unfinished)": "以文本形式查看(未完善)",
|
||||
"Ignore read only": "忽略只读",
|
||||
"Mount": "挂载",
|
||||
"Empty...": "空...",
|
||||
"Open with DayBreak?": "使用DayBreak打开?",
|
||||
"Launch ": "启动 ",
|
||||
"Launch option for: ": "启动选项:",
|
||||
"Select launcher for: ": "选择启动器用于:",
|
||||
|
||||
"Homebrew": "应用列表",
|
||||
"Homebrew Options": "应用选项",
|
||||
"Hide Sphaira": "在应用列表中隐藏Sphaira",
|
||||
"Install Forwarder": "安装前端应用",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "警告:安装前端应用可能导致ban机!",
|
||||
"Installing Forwarder": "正在生成前端应用",
|
||||
"Creating Program": "正在创建程序",
|
||||
"Creating Control": "正在创建控制器",
|
||||
"Creating Meta": "正在创建元数据",
|
||||
"Writing Nca": "正在写入Nca",
|
||||
"Updating ncm databse": "正在更新ncm数据库",
|
||||
"Pushing application record": "正在推送应用记录",
|
||||
"Installed!": "安装完成!",
|
||||
"Failed to install forwarder": "前端应用安装失败",
|
||||
"Unstarred ": "取消星标 ",
|
||||
"Starred ": "已星标 ",
|
||||
|
||||
"AppStore": "应用商店",
|
||||
"Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s",
|
||||
"AppStore Options": "应用商店选项",
|
||||
"All": "全部",
|
||||
"Games": "游戏",
|
||||
"Emulators": "模拟器",
|
||||
"Tools": "工具",
|
||||
"Themes": "主题",
|
||||
"Legacy": "可更新",
|
||||
"version: %s": "版本: %s",
|
||||
"updated: %s": "更新时间: %s",
|
||||
"category: %s": "分类: %s",
|
||||
"extracted: %.2f MiB": "应用大小: %.2f MiB",
|
||||
"app_dls: %s": "下载量: %s",
|
||||
"More by Author": "作者更多作品",
|
||||
"Leave Feedback": "留言反馈",
|
||||
"Game Options": "游戏选项",
|
||||
"Hide forwarders": "隐藏前端启动",
|
||||
"Launch random game": "开启随机游戏",
|
||||
"List meta records": "列出元数据记录",
|
||||
"Entries": "条目",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/NSP/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Success": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
|
||||
"Themezer": "在线主题",
|
||||
"Themezer Options": "在线主题选项",
|
||||
"Nsfw": "公共场合不宜的主题",
|
||||
"Page": "页面",
|
||||
"Page %zu / %zu": "页面 %zu / %zu",
|
||||
"Enter Page Number": "输入跳转的页码",
|
||||
"Bad Page": "错误的页面",
|
||||
"Download theme?": "下载该主题?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "正在下载 json",
|
||||
"Select asset to download for ": "选择要下载的资源用于 ",
|
||||
|
||||
"FTP Install": "通过 FTP 安装",
|
||||
"FTP Install (EXPERIMENTAL)": "通过 FTP 安装(实验性)",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "连接类型:以太网",
|
||||
"Connection Type: None": "连接类型:无",
|
||||
"Host:": "主机:",
|
||||
"Port:": "端口:",
|
||||
"Username:": "用户名:",
|
||||
"Password:": "密码:",
|
||||
"SSID:": "网络名称:",
|
||||
"Passphrase:": "密码:",
|
||||
"Failed to install via FTP, press B to exit...": "通过 FTP 安装失败,按 B 键退出...",
|
||||
"Ftp install success!": "通过 FTP 安装成功。",
|
||||
"Ftp install failed!": "通过 FTP 安装失败。",
|
||||
"USB Install": "通过 USB 安装",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "USB 初始化失败,按 B 键退出...",
|
||||
"Waiting for connection...": "等待连接中...",
|
||||
"Transferring data...": "正在传输数据...",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "暂时禁用 USB 安装的 MTP 功能",
|
||||
"Re-enabled MTP": "重新启用 MTP",
|
||||
"Installed via usb": "通过 USB 安装",
|
||||
"Usb install success!": "通过 USB 安装成功。",
|
||||
"Usb install failed!": "通过 USB 安装失败。",
|
||||
"Press B to exit...": "按 B 键退出...",
|
||||
"GameCard Install": "卡带安装",
|
||||
"GameCard": "卡带",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Nand Install": "",
|
||||
"SD Card Install": "",
|
||||
"Exit": "",
|
||||
"Gc install success!": "游戏安装成功。",
|
||||
"Gc install failed!": "游戏安装失败。",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "",
|
||||
"IRS": "",
|
||||
"Irs": "红外成像",
|
||||
"Ambient Noise Level: ": "环境噪声等级:",
|
||||
"Controller": "控制器",
|
||||
"Pad ": "手柄 ",
|
||||
" (Available)": " (可用的)",
|
||||
" (Unsupported)": "",
|
||||
" (Unconnected)": " (未连接)",
|
||||
"HandHeld": "掌机模式",
|
||||
" (Available)": " (可用的)",
|
||||
" (Unsupported)": " (不支持的)",
|
||||
" (Unconnected)": " (未连接)",
|
||||
"Rotation": "旋转",
|
||||
"0 (Sideways)": "0度",
|
||||
"90 (Flat)": "90度",
|
||||
@@ -194,64 +159,234 @@
|
||||
"Normal image": "正常图像",
|
||||
"Negative image": "负片图像",
|
||||
"Format": "格式",
|
||||
"Trimming Format": "裁剪格式",
|
||||
"320x240": "320×240",
|
||||
"160x120": "160×120",
|
||||
"80x60": "80×60",
|
||||
"40x30": "40×30",
|
||||
"20x15": "20×15",
|
||||
"Trimming Format": "裁剪格式",
|
||||
"External Light Filter": "外部光滤镜",
|
||||
"Load Default": "加载默认值",
|
||||
|
||||
"Themezer": "在线主题",
|
||||
"Themezer Options": "在线主题选项",
|
||||
"Nsfw": "公共场合不宜的主题",
|
||||
"Page": "页面",
|
||||
"Page %zu / %zu": "页面 %zu / %zu",
|
||||
"Enter Page Number": "输入跳转的页码",
|
||||
"Bad Page": "错误的页面",
|
||||
"Download theme?": "下载该主题?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "正在下载 json",
|
||||
"Select asset to download for ": "选择要下载的资源用于 ",
|
||||
|
||||
"Installing ": "正在安装 ",
|
||||
"Uninstalling ": "正在卸载 ",
|
||||
"Deleting ": "正在删除 ",
|
||||
"Deleting": "正在删除",
|
||||
"Pasting ": "正在粘贴 ",
|
||||
"Pasting": "正在粘贴",
|
||||
"Removing ": "正在移除 ",
|
||||
"Scanning ": "正在扫描 ",
|
||||
"Creating ": "正在创建 ",
|
||||
"Copying ": "正在复制 ",
|
||||
"Trying to load ": "尝试加载 ",
|
||||
"Downloading ": "正在下载 ",
|
||||
"Downloaded ": "已下载 ",
|
||||
"Removed ": "已移除 ",
|
||||
"Checking MD5": "正在校验 MD5",
|
||||
"Loading...": "加载中...",
|
||||
"Loading": "加载中",
|
||||
"Empty!": "空空如野!",
|
||||
"Not Ready...": "尚未准备好...",
|
||||
"Error loading page!": "页面加载失败!",
|
||||
"Update avaliable: ": "有可用更新!",
|
||||
"Download update: ": "下载更新:",
|
||||
"Updated to ": "更新至 ",
|
||||
"Press OK to restart Sphaira": "",
|
||||
"Restart Sphaira?": "重启 Sphaira?",
|
||||
"Failed to download update": "更新下载失败",
|
||||
"Advanced": "高级",
|
||||
"Advanced Options": "高级选项",
|
||||
"Logging": "日志",
|
||||
"Replace hbmenu on exit": "退出后用Sphaira替换hbmenu",
|
||||
"Restore hbmenu?": "恢复 hbmenu?",
|
||||
"Restore": "恢复",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "未能找到 /switch/hbmenu.nro\n请使用应用商店重新安装 hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "恢复 hbmenu 失败,请重新下载 hbmenu",
|
||||
"Failed to restore hbmenu, using sphaira instead": "恢复 hbmenu 失败,改用 Sphaira",
|
||||
"Restored hbmenu, closing sphaira": "已恢复 hbmenu,正在关闭 Sphaira",
|
||||
"Restored hbmenu": "已恢复 hbmenu",
|
||||
"Restart Sphaira?": "重启 Sphaira?",
|
||||
"Press OK to restart Sphaira": "按OK键以重启shphaira菜单",
|
||||
"Text scroll speed": "文本滚动速度",
|
||||
"Slow": "慢",
|
||||
"Normal": "正常",
|
||||
"Fast": "快",
|
||||
"Set right-side menu": "",
|
||||
"Install options": "安装选项",
|
||||
"Install Options": "安装选项",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "显示安装警告",
|
||||
"Install location": "安装位置",
|
||||
"System memory": "主机内存",
|
||||
"microSD card": "SD卡",
|
||||
"Boost CPU clock": "提升 CPU 频率",
|
||||
"Allow downgrade": "允许降级",
|
||||
"Skip if already installed": "若已安装则跳过",
|
||||
"Ticket only": "仅安装票据",
|
||||
"Skip base": "跳过基础部分",
|
||||
"Skip patch": "跳过补丁",
|
||||
"Skip dlc": "跳过 DLC(可下载内容)",
|
||||
"Skip data patch": "跳过数据补丁",
|
||||
"Skip ticket": "跳过票据",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "跳过 RSA 头部验证",
|
||||
"Skip RSA NPDM verify": "跳过 RSA NPDM 验证",
|
||||
"Ignore distribution bit": "忽略分布位",
|
||||
"Convert to standard crypto": "转换为标准加密方式",
|
||||
"Lower master key": "降低主密钥",
|
||||
"Lower system version": "降低系统版本",
|
||||
|
||||
"Homebrew": "应用列表",
|
||||
"Apps": "应用",
|
||||
"Homebrew Options": "应用选项",
|
||||
"Hide Sphaira": "在应用列表中隐藏Sphaira",
|
||||
"Install Forwarder": "安装前端应用",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "警告:安装前端应用可能导致ban机!",
|
||||
"Installing Forwarder": "正在生成前端应用",
|
||||
"Creating Program": "正在创建程序",
|
||||
"Creating Control": "正在创建控制器",
|
||||
"Creating Meta": "正在创建元数据",
|
||||
"Writing Nca": "正在写入Nca",
|
||||
"Updating ncm databse": "正在更新ncm数据库",
|
||||
"Pushing application record": "正在推送应用记录",
|
||||
"Failed to install forwarder": "前端应用安装失败",
|
||||
"Unstarred ": "取消星标 ",
|
||||
"Starred ": "已星标 ",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "应用商店",
|
||||
"Appstore": "",
|
||||
"Store": "商店",
|
||||
"Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s",
|
||||
"AppStore Options": "应用商店选项",
|
||||
"Info": "信息",
|
||||
"Changelog": "更新日志",
|
||||
"Details": "详情",
|
||||
"version: %s": "版本: %s",
|
||||
"updated: %s": "更新时间: %s",
|
||||
"category: %s": "分类: %s",
|
||||
"extracted: %.2f MiB": "应用大小: %.2f MiB",
|
||||
"app_dls: %s": "下载量: %s",
|
||||
"More by Author": "作者更多作品",
|
||||
"Leave Feedback": "留言反馈",
|
||||
|
||||
"FileBrowser": "文件浏览",
|
||||
"Files": "文件",
|
||||
"%zd files": "%zd 个文件",
|
||||
"%zd dirs": "%zd 个文件夹",
|
||||
"File Options": "文件选项",
|
||||
"Show Hidden": "显示隐藏项目",
|
||||
"Folders First": "文件夹靠前",
|
||||
"Hidden Last": "隐藏项目置后",
|
||||
"Cut": "剪切",
|
||||
"Copy": "复制",
|
||||
"Copying ": "正在复制 ",
|
||||
"Paste": "粘贴",
|
||||
"Paste ": "粘贴 ",
|
||||
" file(s)?": "个文件(夹)?",
|
||||
"Pasting ": "正在粘贴 ",
|
||||
"Pasting": "正在粘贴",
|
||||
"Rename": "重命名",
|
||||
"Set New File Name": "输入新命名",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "压缩到zip",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Create File": "新建文件",
|
||||
"Set File Name": "输入文件名",
|
||||
"Create Folder": "新建文件夹",
|
||||
"Set Folder Name": "输入文件夹名",
|
||||
"Creating ": "正在创建 ",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"View as text (unfinished)": "以文本形式查看(未完善)",
|
||||
"Ignore read only": "忽略只读",
|
||||
"Mount": "挂载",
|
||||
"Sd": "SD卡",
|
||||
"Image System memory": "主机内存图像",
|
||||
"Image microSD card": "SD卡图像",
|
||||
"Empty...": "空...",
|
||||
"Open with DayBreak?": "使用DayBreak打开?",
|
||||
"Launch ": "启动 ",
|
||||
"Launch option for: ": "启动选项:",
|
||||
"Select launcher for: ": "选择启动器用于:",
|
||||
|
||||
"Sort By": "排序方式",
|
||||
"Sort Options": "排序选项",
|
||||
"Filter": "筛选",
|
||||
"All": "全部",
|
||||
"Emulators": "模拟器",
|
||||
"Tools": "工具",
|
||||
"Themes": "主题",
|
||||
"Legacy": "可更新",
|
||||
"Sort": "排序",
|
||||
"Size": "按大小",
|
||||
"Size (Star)": "按大小(星标优先)",
|
||||
"Alphabetical": "按字母顺序",
|
||||
"Alphabetical (Star)": "按字母顺序(星标优先)",
|
||||
"Updated": "最近使用",
|
||||
"Updated (Star)": "最近更新(星标优先)",
|
||||
"Downloads": "下载",
|
||||
"Likes": "点赞量",
|
||||
"ID": "ID",
|
||||
"Order": "顺序",
|
||||
"Descending": "降序",
|
||||
"Descending (down)": "降序",
|
||||
"Desc": "降序",
|
||||
"Ascending": "升序",
|
||||
"Ascending (Up)": "升序",
|
||||
"Asc": "升序",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Search": "搜索",
|
||||
|
||||
"Options": "选项",
|
||||
"OK": "确定",
|
||||
"Back": "返回",
|
||||
"Select": "选择",
|
||||
"Open": "打开",
|
||||
"Launch": "启动",
|
||||
"Restart": "重启",
|
||||
"Next": "下一项",
|
||||
"Prev": "上一项",
|
||||
"Unstar": "取消星标",
|
||||
"Star": "星标",
|
||||
"Yes": "是",
|
||||
"No": "否",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
|
||||
"Install": "安装",
|
||||
"Install Selected files?": "安装所选文件?",
|
||||
"Installing ": "正在安装 ",
|
||||
"Installed ": "",
|
||||
"Installed!": "安装完成!",
|
||||
"Trying to load ": "尝试加载 ",
|
||||
"Checking MD5": "正在校验 MD5",
|
||||
|
||||
"Delete": "删除",
|
||||
"Delete Selected files?": "删除选中的文件?",
|
||||
"Completely remove ": "彻底删除 ",
|
||||
"Are you sure you want to delete ": "您确定要删除吗 ",
|
||||
"Are you sure you wish to cancel?": "您确定要取消吗?",
|
||||
"Audio disabled due to suspended game": "由于游戏暂停,音频已禁用",
|
||||
"If this message appears repeatedly, please open an issue.": "若此消息反复出现,请提交问题报告。"
|
||||
}
|
||||
"Scanning ": "正在扫描 ",
|
||||
"Deleting ": "正在删除 ",
|
||||
"Deleting": "正在删除",
|
||||
"Remove": "删除",
|
||||
"Completely remove ": "彻底删除 ",
|
||||
"Removing ": "正在移除 ",
|
||||
"Removed ": "已移除 ",
|
||||
"Uninstalling ": "正在卸载 ",
|
||||
|
||||
"Download": "下载",
|
||||
"Downloading ": "正在下载 ",
|
||||
"Downloaded ": "已下载 ",
|
||||
|
||||
"Update": "更新",
|
||||
"Update avaliable: ": "有可用更新!",
|
||||
"Download update: ": "下载更新:",
|
||||
"Updated to ": "更新至 ",
|
||||
"Failed to download update": "更新下载失败",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
|
||||
"Loading...": "加载中...",
|
||||
"Loading": "加载中",
|
||||
"Empty!": "空空如野!",
|
||||
"Not Ready...": "尚未准备好...",
|
||||
"Error loading page!": "页面加载失败!"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
@@ -78,11 +78,13 @@ function(nx_create_npdm target config)
|
||||
dkp_set_target_file(${outtarget} "${NX_NPDM_OUTPUT}")
|
||||
endfunction()
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/exefs)
|
||||
|
||||
nx_create_nso(hbl
|
||||
OUTPUT main
|
||||
OUTPUT exefs/main
|
||||
)
|
||||
|
||||
nx_create_npdm(hbl
|
||||
OUTPUT main.npdm
|
||||
OUTPUT exefs/main.npdm
|
||||
CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/hbl.json
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(sphaira_VERSION 0.6.3)
|
||||
set(sphaira_VERSION 0.11.1)
|
||||
|
||||
project(sphaira
|
||||
VERSION ${sphaira_VERSION}
|
||||
@@ -46,6 +46,11 @@ add_executable(sphaira
|
||||
source/ui/menus/menu_base.cpp
|
||||
source/ui/menus/themezer.cpp
|
||||
source/ui/menus/ghdl.cpp
|
||||
source/ui/menus/usb_menu.cpp
|
||||
source/ui/menus/ftp_menu.cpp
|
||||
source/ui/menus/gc_menu.cpp
|
||||
source/ui/menus/game_menu.cpp
|
||||
source/ui/menus/grid_menu_base.cpp
|
||||
|
||||
source/ui/error_box.cpp
|
||||
source/ui/notification.cpp
|
||||
@@ -57,28 +62,52 @@ add_executable(sphaira
|
||||
source/ui/sidebar.cpp
|
||||
source/ui/widget.cpp
|
||||
source/ui/list.cpp
|
||||
source/ui/bubbles.cpp
|
||||
source/ui/scrolling_text.cpp
|
||||
|
||||
source/app.cpp
|
||||
source/download.cpp
|
||||
source/dumper.cpp
|
||||
source/option.cpp
|
||||
source/evman.cpp
|
||||
source/fs.cpp
|
||||
source/image.cpp
|
||||
source/location.cpp
|
||||
source/log.cpp
|
||||
source/main.cpp
|
||||
source/nro.cpp
|
||||
source/nxlink.cpp
|
||||
source/owo.cpp
|
||||
source/swkbd.cpp
|
||||
source/web.cpp
|
||||
source/hasher.cpp
|
||||
source/i18n.cpp
|
||||
source/ftpsrv_helper.cpp
|
||||
source/threaded_file_transfer.cpp
|
||||
|
||||
source/usb/base.cpp
|
||||
source/usb/usbds.cpp
|
||||
source/usb/usbhs.cpp
|
||||
source/usb/usb_uploader.cpp
|
||||
|
||||
source/yati/yati.cpp
|
||||
source/yati/container/nsp.cpp
|
||||
source/yati/container/xci.cpp
|
||||
source/yati/source/file.cpp
|
||||
source/yati/source/usb.cpp
|
||||
source/yati/source/stream.cpp
|
||||
source/yati/source/stream_file.cpp
|
||||
|
||||
source/yati/nx/es.cpp
|
||||
source/yati/nx/keys.cpp
|
||||
source/yati/nx/nca.cpp
|
||||
source/yati/nx/ncm.cpp
|
||||
source/yati/nx/ns.cpp
|
||||
source/yati/nx/nxdumptool_rsa.c
|
||||
)
|
||||
|
||||
target_compile_definitions(sphaira PRIVATE
|
||||
-DAPP_VERSION="${sphaira_VERSION}"
|
||||
-DAPP_VERSION_HASH="${sphaira_VERSION_HASH}"
|
||||
-DCURL_NO_OLDIES=1
|
||||
)
|
||||
|
||||
target_compile_options(sphaira PRIVATE
|
||||
@@ -118,8 +147,6 @@ target_compile_options(sphaira PRIVATE
|
||||
-Wimplicit-fallthrough=5
|
||||
-Wsuggest-final-types
|
||||
-Wuninitialized
|
||||
-fimplicit-constexpr
|
||||
-Wmissing-requires
|
||||
)
|
||||
|
||||
include(FetchContent)
|
||||
@@ -127,13 +154,14 @@ set(FETCHCONTENT_QUIET FALSE)
|
||||
|
||||
FetchContent_Declare(ftpsrv
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
|
||||
GIT_TAG 1.2.2
|
||||
# GIT_TAG 1.2.2
|
||||
GIT_TAG f8a30fd
|
||||
SOURCE_SUBDIR NONE
|
||||
)
|
||||
|
||||
FetchContent_Declare(libhaze
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
|
||||
GIT_TAG 3244b9e
|
||||
GIT_TAG 04f1526
|
||||
)
|
||||
|
||||
FetchContent_Declare(libpulsar
|
||||
@@ -161,9 +189,32 @@ FetchContent_Declare(minIni
|
||||
GIT_TAG 11cac8b
|
||||
)
|
||||
|
||||
FetchContent_Declare(zstd
|
||||
GIT_REPOSITORY https://github.com/facebook/zstd.git
|
||||
GIT_TAG v1.5.7
|
||||
SOURCE_SUBDIR build/cmake
|
||||
)
|
||||
|
||||
FetchContent_Declare(libusbhsfs
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git
|
||||
GIT_TAG db2bf2a
|
||||
)
|
||||
|
||||
set(USE_NEW_ZSTD ON)
|
||||
|
||||
set(ZSTD_BUILD_STATIC ON)
|
||||
set(ZSTD_BUILD_SHARED OFF)
|
||||
set(ZSTD_BUILD_COMPRESSION OFF)
|
||||
set(ZSTD_BUILD_DECOMPRESSION ON)
|
||||
set(ZSTD_BUILD_DICTBUILDER OFF)
|
||||
set(ZSTD_LEGACY_SUPPORT OFF)
|
||||
set(ZSTD_MULTITHREAD_SUPPORT OFF)
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
set(ZSTD_BUILD_TESTS OFF)
|
||||
|
||||
set(MININI_LIB_NAME minIni)
|
||||
set(MININI_USE_STDIO ON)
|
||||
set(MININI_USE_NX ON)
|
||||
set(MININI_USE_NX OFF)
|
||||
set(MININI_USE_FLOAT OFF)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
@@ -187,6 +238,10 @@ set(YYJSON_DISABLE_NON_STANDARD ON)
|
||||
set(YYJSON_DISABLE_UTF8_VALIDATION ON)
|
||||
set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF)
|
||||
|
||||
# enable this if you want ntfs and ext4 support, at the cost of a huge final binary size.
|
||||
set(USBHSFS_GPL OFF)
|
||||
set(USBHSFS_SXOS_DISABLE ON)
|
||||
|
||||
FetchContent_MakeAvailable(
|
||||
ftpsrv
|
||||
libhaze
|
||||
@@ -195,6 +250,8 @@ FetchContent_MakeAvailable(
|
||||
stb
|
||||
minIni
|
||||
yyjson
|
||||
zstd
|
||||
libusbhsfs
|
||||
)
|
||||
|
||||
set(FTPSRV_LIB_BUILD TRUE)
|
||||
@@ -238,31 +295,6 @@ if (USE_VFS_GC)
|
||||
)
|
||||
endif()
|
||||
|
||||
# todo: upstream cmake
|
||||
add_library(libhaze
|
||||
${libhaze_SOURCE_DIR}/source/async_usb_server.cpp
|
||||
${libhaze_SOURCE_DIR}/source/device_properties.cpp
|
||||
${libhaze_SOURCE_DIR}/source/event_reactor.cpp
|
||||
${libhaze_SOURCE_DIR}/source/haze.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_object_database.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_object_heap.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_responder_android_operations.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_responder_mtp_operations.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_responder_ptp_operations.cpp
|
||||
${libhaze_SOURCE_DIR}/source/ptp_responder.cpp
|
||||
${libhaze_SOURCE_DIR}/source/usb_session.cpp
|
||||
)
|
||||
target_include_directories(libhaze PUBLIC ${libhaze_SOURCE_DIR}/include)
|
||||
set_target_properties(libhaze PROPERTIES
|
||||
C_STANDARD 11
|
||||
C_EXTENSIONS ON
|
||||
CXX_STANDARD 20
|
||||
CXX_EXTENSIONS ON
|
||||
# force optimisations in debug mode as otherwise vapor errors
|
||||
# due to force_inline attribute failing...
|
||||
COMPILE_OPTIONS "$<$<CONFIG:Debug>:-Os>"
|
||||
)
|
||||
|
||||
add_library(stb INTERFACE)
|
||||
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
|
||||
|
||||
@@ -274,10 +306,15 @@ find_package(CURL REQUIRED)
|
||||
find_path(mbedtls_inc mbedtls REQUIRED)
|
||||
find_library(mbedcrypto_lib mbedcrypto REQUIRED)
|
||||
|
||||
if (NOT USE_NEW_ZSTD)
|
||||
find_path(zstd_inc zstd.h REQUIRED)
|
||||
find_library(zstd_lib zstd REQUIRED)
|
||||
endif()
|
||||
|
||||
set_target_properties(sphaira PROPERTIES
|
||||
C_STANDARD 11
|
||||
C_STANDARD 23
|
||||
C_EXTENSIONS ON
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD 26
|
||||
CXX_EXTENSIONS ON
|
||||
)
|
||||
|
||||
@@ -289,6 +326,7 @@ target_link_libraries(sphaira PRIVATE
|
||||
nanovg
|
||||
stb
|
||||
yyjson
|
||||
libusbhsfs
|
||||
|
||||
${minizip_lib}
|
||||
ZLIB::ZLIB
|
||||
@@ -296,6 +334,15 @@ target_link_libraries(sphaira PRIVATE
|
||||
${mbedcrypto_lib}
|
||||
)
|
||||
|
||||
if (USE_NEW_ZSTD)
|
||||
message(STATUS "USING UPSTREAM ZSTD")
|
||||
target_link_libraries(sphaira PRIVATE libzstd_static)
|
||||
else()
|
||||
message(STATUS "USING LOCAL ZSTD")
|
||||
target_link_libraries(sphaira PRIVATE ${zstd_lib})
|
||||
target_include_directories(sphaira PRIVATE ${zstd_inc})
|
||||
endif()
|
||||
|
||||
target_include_directories(sphaira PRIVATE
|
||||
include
|
||||
${minizip_inc}
|
||||
@@ -308,12 +355,13 @@ file(COPY ${CMAKE_SOURCE_DIR}/assets/romfs DESTINATION ${CMAKE_CURRENT_BINARY_DI
|
||||
# create assets target
|
||||
dkp_add_asset_target(sphaira_romfs ${CMAKE_CURRENT_BINARY_DIR}/romfs)
|
||||
|
||||
# add hbl exefs to romfs, used for forwarders
|
||||
dkp_install_assets(sphaira_romfs
|
||||
DESTINATION exefs
|
||||
TARGETS
|
||||
hbl_nso
|
||||
hbl_npdm
|
||||
# wait until hbl is built first as we need the exefs to embed
|
||||
add_dependencies(sphaira hbl_nso hbl_npdm)
|
||||
|
||||
# set the embed path for assets and hbl
|
||||
target_compile_options(sphaira PRIVATE
|
||||
--embed-dir=${CMAKE_SOURCE_DIR}/assets/embed
|
||||
--embed-dir=${CMAKE_BINARY_DIR}/hbl
|
||||
)
|
||||
|
||||
# add nanovg shaders to romfs
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "owo.hpp"
|
||||
#include "option.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
@@ -44,6 +45,8 @@ public:
|
||||
~App();
|
||||
void Loop();
|
||||
|
||||
static App* GetApp();
|
||||
|
||||
static void Exit();
|
||||
static void ExitRestart();
|
||||
static auto GetVg() -> NVGcontext*;
|
||||
@@ -58,11 +61,15 @@ public:
|
||||
static void NotifyClear(ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
|
||||
static void NotifyFlashLed();
|
||||
|
||||
// if R_FAILED(rc), pushes error box. returns rc passed in.
|
||||
static Result PushErrorBox(Result rc, const std::string& message);
|
||||
|
||||
static auto GetThemeMetaList() -> std::span<ThemeMeta>;
|
||||
static void SetTheme(s64 theme_index);
|
||||
static auto GetThemeIndex() -> s64;
|
||||
|
||||
static auto GetDefaultImage(int* w = nullptr, int* h = nullptr) -> int;
|
||||
static auto GetDefaultImage() -> int;
|
||||
static auto GetDefaultImageData() -> std::span<const u8>;
|
||||
|
||||
// returns argv[0]
|
||||
static auto GetExePath() -> fs::FsPath;
|
||||
@@ -72,12 +79,15 @@ public:
|
||||
static auto GetMtpEnable() -> bool;
|
||||
static auto GetFtpEnable() -> bool;
|
||||
static auto GetNxlinkEnable() -> bool;
|
||||
static auto GetHddEnable() -> bool;
|
||||
static auto GetWriteProtect() -> bool;
|
||||
static auto GetLogEnable() -> bool;
|
||||
static auto GetReplaceHbmenuEnable() -> bool;
|
||||
static auto GetInstallEnable() -> bool;
|
||||
static auto GetInstallSysmmcEnable() -> bool;
|
||||
static auto GetInstallEmummcEnable() -> bool;
|
||||
static auto GetInstallSdEnable() -> bool;
|
||||
static auto GetInstallPrompt() -> bool;
|
||||
static auto GetThemeShuffleEnable() -> bool;
|
||||
static auto GetThemeMusicEnable() -> bool;
|
||||
static auto Get12HourTimeEnable() -> bool;
|
||||
static auto GetLanguage() -> long;
|
||||
@@ -86,12 +96,14 @@ public:
|
||||
static void SetMtpEnable(bool enable);
|
||||
static void SetFtpEnable(bool enable);
|
||||
static void SetNxlinkEnable(bool enable);
|
||||
static void SetHddEnable(bool enable);
|
||||
static void SetWriteProtect(bool enable);
|
||||
static void SetLogEnable(bool enable);
|
||||
static void SetReplaceHbmenuEnable(bool enable);
|
||||
static void SetInstallEnable(bool enable);
|
||||
static void SetInstallSysmmcEnable(bool enable);
|
||||
static void SetInstallEmummcEnable(bool enable);
|
||||
static void SetInstallSdEnable(bool enable);
|
||||
static void SetInstallPrompt(bool enable);
|
||||
static void SetThemeShuffleEnable(bool enable);
|
||||
static void SetThemeMusicEnable(bool enable);
|
||||
static void Set12HourTimeEnable(bool enable);
|
||||
static void SetLanguage(long index);
|
||||
@@ -102,6 +114,14 @@ public:
|
||||
|
||||
static void PlaySoundEffect(SoundEffect effect);
|
||||
|
||||
static void DisplayThemeOptions(bool left_side = true);
|
||||
// todo:
|
||||
static void DisplayNetworkOptions(bool left_side = true);
|
||||
static void DisplayMiscOptions(bool left_side = true);
|
||||
static void DisplayAdvancedOptions(bool left_side = true);
|
||||
static void DisplayInstallOptions(bool left_side = true);
|
||||
static void DisplayDumpOptions(bool left_side = true);
|
||||
|
||||
void Draw();
|
||||
void Update();
|
||||
void Poll();
|
||||
@@ -116,6 +136,10 @@ public:
|
||||
void ScanThemes(const std::string& path);
|
||||
void ScanThemeEntries();
|
||||
|
||||
// helper that converts 1.2.3 to a u32 used for comparisons.
|
||||
static auto GetVersionFromString(const char* str) -> u32;
|
||||
static auto IsVersionNewer(const char* current, const char* new_version) -> u32;
|
||||
|
||||
static auto IsApplication() -> bool {
|
||||
const auto type = appletGetAppletType();
|
||||
return type == AppletType_Application || type == AppletType_SystemApplication;
|
||||
@@ -135,16 +159,50 @@ public:
|
||||
return R_SUCCEEDED(pmdmntGetApplicationProcessId(&pid));
|
||||
}
|
||||
|
||||
static auto IsEmunand() -> bool {
|
||||
alignas(0x1000) struct EmummcPaths {
|
||||
char unk[0x80];
|
||||
char nintendo[0x80];
|
||||
} paths{};
|
||||
|
||||
SecmonArgs args{};
|
||||
args.X[0] = 0xF0000404; /* smcAmsGetEmunandConfig */
|
||||
args.X[1] = 0; /* EXO_EMUMMC_MMC_NAND*/
|
||||
args.X[2] = (u64)&paths; /* out path */
|
||||
svcCallSecureMonitor(&args);
|
||||
|
||||
return (paths.unk[0] != '\0') || (paths.nintendo[0] != '\0');
|
||||
}
|
||||
|
||||
static void SetAutoSleepDisabled(bool enable) {
|
||||
static Mutex mutex{};
|
||||
static int ref_count{};
|
||||
|
||||
mutexLock(&mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&mutex));
|
||||
|
||||
if (enable) {
|
||||
appletSetAutoSleepDisabled(true);
|
||||
ref_count++;
|
||||
} else {
|
||||
if (ref_count) {
|
||||
ref_count--;
|
||||
}
|
||||
|
||||
if (!ref_count) {
|
||||
appletSetAutoSleepDisabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private:
|
||||
static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini";
|
||||
static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini";
|
||||
static constexpr inline auto INI_SECTION = "config";
|
||||
static constexpr inline auto DEFAULT_THEME_PATH = "romfs:/themes/abyss_theme.ini";
|
||||
|
||||
fs::FsPath m_app_path;
|
||||
u64 m_start_timestamp{};
|
||||
u64 m_prev_timestamp{};
|
||||
fs::FsPath m_prev_last_launch{};
|
||||
int m_default_image{};
|
||||
|
||||
bool m_is_launched_via_sphaira_forwader{};
|
||||
@@ -169,18 +227,51 @@ public:
|
||||
|
||||
bool m_quit{};
|
||||
|
||||
// network
|
||||
option::OptionBool m_nxlink_enabled{INI_SECTION, "nxlink_enabled", true};
|
||||
option::OptionBool m_mtp_enabled{INI_SECTION, "mtp_enabled", false};
|
||||
option::OptionBool m_ftp_enabled{INI_SECTION, "ftp_enabled", false};
|
||||
option::OptionBool m_hdd_enabled{INI_SECTION, "hdd_enabled", true};
|
||||
option::OptionBool m_hdd_write_protect{INI_SECTION, "hdd_write_protect", false};
|
||||
|
||||
option::OptionBool m_log_enabled{INI_SECTION, "log_enabled", false};
|
||||
option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false};
|
||||
option::OptionBool m_install{INI_SECTION, "install", false};
|
||||
option::OptionBool m_install_sd{INI_SECTION, "install_sd", true};
|
||||
option::OptionLong m_install_prompt{INI_SECTION, "install_prompt", true};
|
||||
option::OptionBool m_theme_shuffle{INI_SECTION, "theme_shuffle", false};
|
||||
option::OptionString m_theme_path{INI_SECTION, "theme", DEFAULT_THEME_PATH};
|
||||
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};
|
||||
option::OptionBool m_12hour_time{INI_SECTION, "12hour_time", false};
|
||||
option::OptionLong m_language{INI_SECTION, "language", 0}; // auto
|
||||
option::OptionString m_left_menu{INI_SECTION, "left_side_menu", "FileBrowser"};
|
||||
option::OptionString m_right_menu{INI_SECTION, "right_side_menu", "Appstore"};
|
||||
option::OptionBool m_progress_boost_mode{INI_SECTION, "progress_boost_mode", true};
|
||||
|
||||
// install options
|
||||
option::OptionBool m_install_sysmmc{INI_SECTION, "install_sysmmc", false};
|
||||
option::OptionBool m_install_emummc{INI_SECTION, "install_emummc", false};
|
||||
option::OptionBool m_install_sd{INI_SECTION, "install_sd", true};
|
||||
option::OptionLong m_install_prompt{INI_SECTION, "install_prompt", true};
|
||||
option::OptionBool m_allow_downgrade{INI_SECTION, "allow_downgrade", false};
|
||||
option::OptionBool m_skip_if_already_installed{INI_SECTION, "skip_if_already_installed", true};
|
||||
option::OptionBool m_ticket_only{INI_SECTION, "ticket_only", false};
|
||||
option::OptionBool m_skip_base{INI_SECTION, "skip_base", false};
|
||||
option::OptionBool m_skip_patch{INI_SECTION, "skip_patch", false};
|
||||
option::OptionBool m_skip_addon{INI_SECTION, "skip_addon", false};
|
||||
option::OptionBool m_skip_data_patch{INI_SECTION, "skip_data_patch", false};
|
||||
option::OptionBool m_skip_ticket{INI_SECTION, "skip_ticket", false};
|
||||
option::OptionBool m_skip_nca_hash_verify{INI_SECTION, "skip_nca_hash_verify", false};
|
||||
option::OptionBool m_skip_rsa_header_fixed_key_verify{INI_SECTION, "skip_rsa_header_fixed_key_verify", false};
|
||||
option::OptionBool m_skip_rsa_npdm_fixed_key_verify{INI_SECTION, "skip_rsa_npdm_fixed_key_verify", false};
|
||||
option::OptionBool m_ignore_distribution_bit{INI_SECTION, "ignore_distribution_bit", false};
|
||||
option::OptionBool m_convert_to_standard_crypto{INI_SECTION, "convert_to_standard_crypto", false};
|
||||
option::OptionBool m_lower_master_key{INI_SECTION, "lower_master_key", false};
|
||||
option::OptionBool m_lower_system_version{INI_SECTION, "lower_system_version", false};
|
||||
|
||||
// dump options
|
||||
option::OptionBool m_dump_app_folder{"dump", "app_folder", true};
|
||||
option::OptionBool m_dump_append_folder_with_xci{"dump", "append_folder_with_xci", true};
|
||||
option::OptionBool m_dump_trim_xci{"dump", "trim_xci", false};
|
||||
option::OptionBool m_dump_label_trim_xci{"dump", "label_trim_xci", false};
|
||||
option::OptionBool m_dump_usb_transfer_stream{"dump", "usb_transfer_stream", true};
|
||||
|
||||
// todo: move this into it's own menu
|
||||
option::OptionLong m_text_scroll_speed{"accessibility", "text_scroll_speed", 1}; // normal
|
||||
|
||||
|
||||
@@ -13,10 +13,14 @@ namespace sphaira::curl {
|
||||
|
||||
enum {
|
||||
Flag_None = 0,
|
||||
|
||||
// requests to download send etag in the header.
|
||||
// the received etag is then saved on success.
|
||||
// this api is only available on downloading to file.
|
||||
Flag_Cache = 1 << 0,
|
||||
|
||||
// sets CURLOPT_NOBODY.
|
||||
Flag_NoBody = 1 << 1,
|
||||
};
|
||||
|
||||
enum class Priority {
|
||||
@@ -29,7 +33,9 @@ struct ApiResult;
|
||||
|
||||
using Path = fs::FsPath;
|
||||
using OnComplete = std::function<void(ApiResult& result)>;
|
||||
using OnProgress = std::function<bool(u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow)>;
|
||||
using OnProgress = std::function<bool(s64 dltotal, s64 dlnow, s64 ultotal, s64 ulnow)>;
|
||||
using OnUploadCallback = std::function<size_t(void *ptr, size_t size)>;
|
||||
using OnUploadSeek = std::function<bool(s64 offset)>;
|
||||
using StopToken = std::stop_token;
|
||||
|
||||
struct Url {
|
||||
@@ -62,6 +68,55 @@ struct Flags {
|
||||
u32 m_flags{Flag_None};
|
||||
};
|
||||
|
||||
struct Port {
|
||||
Port() = default;
|
||||
Port(u16 port) : m_port{port} {}
|
||||
u16 m_port{};
|
||||
};
|
||||
|
||||
struct CustomRequest {
|
||||
CustomRequest() = default;
|
||||
CustomRequest(const std::string& str) : m_str{str} {}
|
||||
std::string m_str;
|
||||
};
|
||||
|
||||
struct UserPass {
|
||||
UserPass() = default;
|
||||
UserPass(const std::string& user) : m_user{user} {}
|
||||
UserPass(const std::string& user, const std::string& pass) : m_user{user}, m_pass{pass} {}
|
||||
std::string m_user;
|
||||
std::string m_pass;
|
||||
};
|
||||
|
||||
struct UploadInfo {
|
||||
UploadInfo() = default;
|
||||
UploadInfo(const std::string& name) : m_name{name} {}
|
||||
UploadInfo(const std::string& name, s64 size, OnUploadCallback cb) : m_name{name}, m_size{size}, m_callback{cb} {}
|
||||
UploadInfo(const std::string& name, const std::vector<u8>& data) : m_name{name}, m_data{data} {}
|
||||
std::string m_name{};
|
||||
std::vector<u8> m_data{};
|
||||
s64 m_size{};
|
||||
OnUploadCallback m_callback{};
|
||||
};
|
||||
|
||||
struct Bearer {
|
||||
Bearer() = default;
|
||||
Bearer(const std::string& str) : m_str{str} {}
|
||||
std::string m_str;
|
||||
};
|
||||
|
||||
struct PubKey {
|
||||
PubKey() = default;
|
||||
PubKey(const std::string& str) : m_str{str} {}
|
||||
std::string m_str;
|
||||
};
|
||||
|
||||
struct PrivKey {
|
||||
PrivKey() = default;
|
||||
PrivKey(const std::string& str) : m_str{str} {}
|
||||
std::string m_str;
|
||||
};
|
||||
|
||||
struct ApiResult {
|
||||
bool success;
|
||||
long code;
|
||||
@@ -76,16 +131,29 @@ struct DownloadEventData {
|
||||
StopToken stoken;
|
||||
};
|
||||
|
||||
// helper that generates the api using an location.
|
||||
#define CURL_LOCATION_TO_API(loc) \
|
||||
curl::Url{loc.url}, \
|
||||
curl::UserPass{loc.user, loc.pass}, \
|
||||
curl::Bearer{loc.bearer}, \
|
||||
curl::PubKey{loc.pub_key}, \
|
||||
curl::PrivKey{loc.priv_key}, \
|
||||
curl::Port(loc.port)
|
||||
|
||||
auto Init() -> bool;
|
||||
void Exit();
|
||||
|
||||
// sync functions
|
||||
auto ToMemory(const Api& e) -> ApiResult;
|
||||
auto ToFile(const Api& e) -> ApiResult;
|
||||
auto FromMemory(const Api& e) -> ApiResult;
|
||||
auto FromFile(const Api& e) -> ApiResult;
|
||||
|
||||
// async functions
|
||||
auto ToMemoryAsync(const Api& e) -> bool;
|
||||
auto ToFileAsync(const Api& e) -> bool;
|
||||
auto FromMemoryAsync(const Api& e) -> bool;
|
||||
auto FromFileAsync(const Api& e) -> bool;
|
||||
|
||||
// uses curl to convert string to their %XX
|
||||
auto EscapeString(const std::string& str) -> std::string;
|
||||
@@ -107,6 +175,15 @@ struct Api {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto From(Ts&&... ts) {
|
||||
if constexpr(std::disjunction_v<std::is_same<Path, Ts>...>) {
|
||||
return FromFile(std::forward<Ts>(ts)...);
|
||||
} else {
|
||||
return FromMemory(std::forward<Ts>(ts)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto ToAsync(Ts&&... ts) {
|
||||
if constexpr(std::disjunction_v<std::is_same<Path, Ts>...>) {
|
||||
@@ -116,6 +193,15 @@ struct Api {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto FromAsync(Ts&&... ts) {
|
||||
if constexpr(std::disjunction_v<std::is_same<Path, Ts>...>) {
|
||||
return FromFileAsync(std::forward<Ts>(ts)...);
|
||||
} else {
|
||||
return FromMemoryAsync(std::forward<Ts>(ts)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto ToMemory(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
@@ -125,6 +211,16 @@ struct Api {
|
||||
return curl::ToMemory(*this);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto FromMemory(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<UploadInfo, Ts>...>, "UploadInfo must be specified");
|
||||
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
|
||||
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
|
||||
Api::set_option(std::forward<Ts>(ts)...);
|
||||
return curl::FromMemory(*this);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto ToFile(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
@@ -134,6 +230,16 @@ struct Api {
|
||||
return curl::ToFile(*this);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto FromFile(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<UploadInfo, Ts>...>, "UploadInfo must be specified");
|
||||
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
|
||||
Api::set_option(std::forward<Ts>(ts)...);
|
||||
return curl::FromFile(*this);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto ToMemoryAsync(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
@@ -144,6 +250,17 @@ struct Api {
|
||||
return curl::ToMemoryAsync(*this);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto FromMemoryAsync(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<UploadInfo, Ts>...>, "UploadInfo must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
|
||||
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
|
||||
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
|
||||
Api::set_option(std::forward<Ts>(ts)...);
|
||||
return curl::FromMemoryAsync(*this);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto ToFileAsync(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
@@ -154,62 +271,55 @@ struct Api {
|
||||
return curl::ToFileAsync(*this);
|
||||
}
|
||||
|
||||
auto& GetUrl() const {
|
||||
return m_url.m_str;
|
||||
}
|
||||
auto& GetFields() const {
|
||||
return m_fields.m_str;
|
||||
}
|
||||
auto& GetHeader() const {
|
||||
return m_header;
|
||||
}
|
||||
auto& GetFlags() const {
|
||||
return m_flags.m_flags;
|
||||
}
|
||||
auto& GetPath() const {
|
||||
return m_path;
|
||||
}
|
||||
auto& GetOnComplete() const {
|
||||
return m_on_complete;
|
||||
}
|
||||
auto& GetOnProgress() const {
|
||||
return m_on_progress;
|
||||
}
|
||||
auto& GetPriority() const {
|
||||
return m_prio;
|
||||
}
|
||||
auto& GetToken() const {
|
||||
return m_stoken;
|
||||
template <typename... Ts>
|
||||
auto FromFileAsync(Ts&&... ts) {
|
||||
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<UploadInfo, Ts>...>, "UploadInfo must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
|
||||
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
|
||||
Api::set_option(std::forward<Ts>(ts)...);
|
||||
return curl::FromFileAsync(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
void SetOption(Url&& v) {
|
||||
m_url = v;
|
||||
}
|
||||
void SetOption(Fields&& v) {
|
||||
m_fields = v;
|
||||
}
|
||||
void SetOption(Header&& v) {
|
||||
m_header = v;
|
||||
}
|
||||
void SetOption(Flags&& v) {
|
||||
m_flags = v;
|
||||
}
|
||||
void SetOption(Path&& v) {
|
||||
m_path = v;
|
||||
}
|
||||
void SetOption(OnComplete&& v) {
|
||||
m_on_complete = v;
|
||||
}
|
||||
void SetOption(OnProgress&& v) {
|
||||
m_on_progress = v;
|
||||
}
|
||||
void SetOption(Priority&& v) {
|
||||
m_prio = v;
|
||||
}
|
||||
void SetOption(StopToken&& v) {
|
||||
m_stoken = v;
|
||||
}
|
||||
void SetUpload(bool enable) { m_is_upload = enable; }
|
||||
|
||||
auto IsUpload() const { return m_is_upload; }
|
||||
auto& GetUrl() const { return m_url.m_str; }
|
||||
auto& GetFields() const { return m_fields.m_str; }
|
||||
auto& GetHeader() const { return m_header; }
|
||||
auto& GetFlags() const { return m_flags.m_flags; }
|
||||
auto& GetPath() const { return m_path; }
|
||||
auto& GetPort() const { return m_port.m_port; }
|
||||
auto& GetCustomRequest() const { return m_custom_request.m_str; }
|
||||
auto& GetUserPass() const { return m_userpass; }
|
||||
auto& GetBearer() const { return m_bearer.m_str; }
|
||||
auto& GetPubKey() const { return m_pub_key.m_str; }
|
||||
auto& GetPrivKey() const { return m_priv_key.m_str; }
|
||||
auto& GetUploadInfo() const { return m_info; }
|
||||
auto& GetOnComplete() const { return m_on_complete; }
|
||||
auto& GetOnProgress() const { return m_on_progress; }
|
||||
auto& GetOnUploadSeek() const { return m_on_upload_seek; }
|
||||
auto& GetPriority() const { return m_prio; }
|
||||
auto& GetToken() const { return m_stoken; }
|
||||
|
||||
void SetOption(Url&& v) { m_url = v; }
|
||||
void SetOption(Fields&& v) { m_fields = v; }
|
||||
void SetOption(Header&& v) { m_header = v; }
|
||||
void SetOption(Flags&& v) { m_flags = v; }
|
||||
void SetOption(Path&& v) { m_path = v; }
|
||||
void SetOption(Port&& v) { m_port = v; }
|
||||
void SetOption(CustomRequest&& v) { m_custom_request = v; }
|
||||
void SetOption(UserPass&& v) { m_userpass = v; }
|
||||
void SetOption(Bearer&& v) { m_bearer = v; }
|
||||
void SetOption(PubKey&& v) { m_pub_key = v; }
|
||||
void SetOption(PrivKey&& v) { m_priv_key = v; }
|
||||
void SetOption(UploadInfo&& v) { m_info = v; }
|
||||
void SetOption(OnComplete&& v) { m_on_complete = v; }
|
||||
void SetOption(OnProgress&& v) { m_on_progress = v; }
|
||||
void SetOption(OnUploadSeek&& v) { m_on_upload_seek = v; }
|
||||
void SetOption(Priority&& v) { m_prio = v; }
|
||||
void SetOption(StopToken&& v) { m_stoken = v; }
|
||||
|
||||
template <typename T>
|
||||
void set_option(T&& t) {
|
||||
@@ -223,16 +333,25 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
Url m_url;
|
||||
Url m_url{};
|
||||
Fields m_fields{};
|
||||
Header m_header{};
|
||||
Flags m_flags{};
|
||||
Path m_path{};
|
||||
OnComplete m_on_complete{nullptr};
|
||||
OnProgress m_on_progress{nullptr};
|
||||
Port m_port{};
|
||||
CustomRequest m_custom_request{};
|
||||
UserPass m_userpass{};
|
||||
Bearer m_bearer{};
|
||||
PubKey m_pub_key{};
|
||||
PrivKey m_priv_key{};
|
||||
UploadInfo m_info{};
|
||||
OnComplete m_on_complete{};
|
||||
OnProgress m_on_progress{};
|
||||
OnUploadSeek m_on_upload_seek{};
|
||||
Priority m_prio{Priority::High};
|
||||
std::stop_source m_stop_source{};
|
||||
StopToken m_stoken{m_stop_source.get_token()};
|
||||
bool m_is_upload{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::curl
|
||||
|
||||
46
sphaira/include/dumper.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace sphaira::dump {
|
||||
|
||||
enum DumpLocationType {
|
||||
// dump using native fs.
|
||||
DumpLocationType_SdCard,
|
||||
// dump to usb using tinfoil protocol.
|
||||
DumpLocationType_UsbS2S,
|
||||
// speed test, only reads the data, doesn't write anything.
|
||||
DumpLocationType_DevNull,
|
||||
// dump to stdio, ideal for custom mount points using devoptab, such as hdd.
|
||||
DumpLocationType_Stdio,
|
||||
// dump to custom locations found in locations.ini.
|
||||
DumpLocationType_Network,
|
||||
};
|
||||
|
||||
enum DumpLocationFlag {
|
||||
DumpLocationFlag_SdCard = 1 << DumpLocationType_SdCard,
|
||||
DumpLocationFlag_UsbS2S = 1 << DumpLocationType_UsbS2S,
|
||||
DumpLocationFlag_DevNull = 1 << DumpLocationType_DevNull,
|
||||
DumpLocationFlag_Stdio = 1 << DumpLocationType_Stdio,
|
||||
DumpLocationFlag_Network = 1 << DumpLocationType_Network,
|
||||
DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio | DumpLocationFlag_Network,
|
||||
};
|
||||
|
||||
struct BaseSource {
|
||||
virtual ~BaseSource() = default;
|
||||
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
virtual auto GetName(const std::string& path) const -> std::string = 0;
|
||||
virtual auto GetSize(const std::string& path) const -> s64 = 0;
|
||||
virtual auto GetIcon(const std::string& path) const -> int { return 0; }
|
||||
};
|
||||
|
||||
// called after dump has finished.
|
||||
using OnExit = std::function<void(Result rc)>;
|
||||
|
||||
void Dump(std::shared_ptr<BaseSource> source, const std::vector<fs::FsPath>& paths, OnExit on_exit = [](Result){}, u32 location_flags = DumpLocationFlag_All);
|
||||
|
||||
} // namespace sphaira::dump
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include <dirent.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
@@ -169,6 +170,40 @@ static_assert(FsPath::TestFrom(std::string_view{"abc"}));
|
||||
static_assert(FsPath::TestFrom(std::string{"abc"}));
|
||||
static_assert(FsPath::TestFrom(FsPath{"abc"}));
|
||||
|
||||
// fwd
|
||||
struct Fs;
|
||||
|
||||
struct File {
|
||||
~File();
|
||||
|
||||
Result Read(s64 off, void* buf, u64 read_size, u32 option, u64* bytes_read);
|
||||
Result Write(s64 off, const void* buf, u64 write_size, u32 option);
|
||||
Result SetSize(s64 sz);
|
||||
Result GetSize(s64* out);
|
||||
void Close();
|
||||
|
||||
fs::Fs* m_fs{};
|
||||
FsFile m_native{};
|
||||
std::FILE* m_stdio{};
|
||||
s64 m_stdio_off{};
|
||||
// sadly, fatfs doesn't support fstat, so we have to manually
|
||||
// stat the file to get it's size.
|
||||
FsPath m_path{};
|
||||
};
|
||||
|
||||
struct Dir {
|
||||
~Dir();
|
||||
|
||||
Result GetEntryCount(s64* out);
|
||||
Result ReadAll(std::vector<FsDirectoryEntry>& buf);
|
||||
void Close();
|
||||
|
||||
fs::Fs* m_fs{};
|
||||
FsDir m_native{};
|
||||
DIR* m_stdio{};
|
||||
u32 m_mode{};
|
||||
};
|
||||
|
||||
FsPath AppendPath(const fs::FsPath& root_path, const fs::FsPath& file_path);
|
||||
|
||||
Result CreateFile(FsFileSystem* fs, const FsPath& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true);
|
||||
@@ -182,6 +217,7 @@ Result RenameFile(FsFileSystem* fs, const FsPath& src, const FsPath& dst, bool i
|
||||
Result RenameDirectory(FsFileSystem* fs, const FsPath& src, const FsPath& dst, bool ignore_read_only = true);
|
||||
Result GetEntryType(FsFileSystem* fs, const FsPath& path, FsDirEntryType* out);
|
||||
Result GetFileTimeStampRaw(FsFileSystem* fs, const FsPath& path, FsTimeStampRaw *out);
|
||||
Result SetTimestamp(FsFileSystem* fs, const FsPath& path, const FsTimeStampRaw* ts);
|
||||
bool FileExists(FsFileSystem* fs, const FsPath& path);
|
||||
bool DirExists(FsFileSystem* fs, const FsPath& path);
|
||||
Result read_entire_file(FsFileSystem* fs, const FsPath& path, std::vector<u8>& out);
|
||||
@@ -199,12 +235,31 @@ Result RenameFile(const FsPath& src, const FsPath& dst, bool ignore_read_only =
|
||||
Result RenameDirectory(const FsPath& src, const FsPath& dst, bool ignore_read_only = true);
|
||||
Result GetEntryType(const FsPath& path, FsDirEntryType* out);
|
||||
Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out);
|
||||
Result SetTimestamp(const FsPath& path, const FsTimeStampRaw* ts);
|
||||
bool FileExists(const FsPath& path);
|
||||
bool DirExists(const FsPath& path);
|
||||
Result read_entire_file(const FsPath& path, std::vector<u8>& out);
|
||||
Result write_entire_file(const FsPath& path, const std::vector<u8>& in, bool ignore_read_only = true);
|
||||
Result copy_entire_file(const FsPath& dst, const FsPath& src, bool ignore_read_only = true);
|
||||
|
||||
Result OpenFile(fs::Fs* fs, const fs::FsPath& path, u32 mode, File* f);
|
||||
Result OpenDirectory(fs::Fs* fs, const fs::FsPath& path, u32 mode, Dir* d);
|
||||
|
||||
// opens dir, fetches count for all entries.
|
||||
// NOTE: this function will be slow on non-native fs, due to multiple
|
||||
// readdir() functions being needed!
|
||||
Result DirGetEntryCount(fs::Fs* fs, const fs::FsPath& path, s64* count, u32 mode);
|
||||
// same as the above, but fetches file and folder count in a single pass
|
||||
// this is faster when using native, and *much* faster for stdio.
|
||||
Result DirGetEntryCount(fs::Fs* fs, const fs::FsPath& path, s64* file_count, s64* dir_count, u32 mode = FsDirOpenMode_ReadDirs|FsDirOpenMode_ReadFiles);
|
||||
|
||||
// optimised for stdio calls as stat returns size and timestamp in a single call.
|
||||
// whereas for native, this is 2 function calls.
|
||||
// however if you need both, you will need 2 calls for native anyway,
|
||||
// but can avoid the second (expensive) stat call.
|
||||
Result FileGetSizeAndTimestamp(fs::Fs* fs, const FsPath& path, FsTimeStampRaw* ts, s64* size);
|
||||
Result IsDirEmpty(fs::Fs* m_fs, const fs::FsPath& path, bool* out);
|
||||
|
||||
struct Fs {
|
||||
static constexpr inline u32 FsModule = 505;
|
||||
static constexpr inline Result ResultTooManyEntries = MAKERESULT(FsModule, 1);
|
||||
@@ -236,12 +291,34 @@ struct Fs {
|
||||
virtual Result RenameDirectory(const FsPath& src, const FsPath& dst) = 0;
|
||||
virtual Result GetEntryType(const FsPath& path, FsDirEntryType* out) = 0;
|
||||
virtual Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out) = 0;
|
||||
virtual Result SetTimestamp(const FsPath& path, const FsTimeStampRaw* ts) = 0;
|
||||
virtual bool FileExists(const FsPath& path) = 0;
|
||||
virtual bool DirExists(const FsPath& path) = 0;
|
||||
virtual bool IsNative() const = 0;
|
||||
virtual FsPath Root() const { return "/"; }
|
||||
virtual Result read_entire_file(const FsPath& path, std::vector<u8>& out) = 0;
|
||||
virtual Result write_entire_file(const FsPath& path, const std::vector<u8>& in) = 0;
|
||||
virtual Result copy_entire_file(const FsPath& dst, const FsPath& src) = 0;
|
||||
|
||||
Result OpenFile(const fs::FsPath& path, u32 mode, File* f) {
|
||||
return fs::OpenFile(this, path, mode, f);
|
||||
}
|
||||
Result OpenDirectory(const fs::FsPath& path, u32 mode, Dir* d) {
|
||||
return fs::OpenDirectory(this, path, mode, d);
|
||||
}
|
||||
Result DirGetEntryCount(const fs::FsPath& path, s64* count, u32 mode) {
|
||||
return fs::DirGetEntryCount(this, path, count, mode);
|
||||
}
|
||||
Result DirGetEntryCount(const fs::FsPath& path, s64* file_count, s64* dir_count, u32 mode = FsDirOpenMode_ReadDirs|FsDirOpenMode_ReadFiles) {
|
||||
return fs::DirGetEntryCount(this, path, file_count, dir_count, mode);
|
||||
}
|
||||
Result FileGetSizeAndTimestamp(const FsPath& path, FsTimeStampRaw* ts, s64* size) {
|
||||
return fs::FileGetSizeAndTimestamp(this, path, ts, size);
|
||||
}
|
||||
Result IsDirEmpty(const fs::FsPath& path, bool* out) {
|
||||
return fs::IsDirEmpty(this, path, out);
|
||||
}
|
||||
|
||||
void SetIgnoreReadOnly(bool enable) {
|
||||
m_ignore_read_only = enable;
|
||||
}
|
||||
@@ -251,7 +328,7 @@ protected:
|
||||
};
|
||||
|
||||
struct FsStdio : Fs {
|
||||
FsStdio(bool ignore_read_only = true) : Fs{ignore_read_only} {}
|
||||
FsStdio(bool ignore_read_only = true, const FsPath& root = "/") : Fs{ignore_read_only}, m_root{root} {}
|
||||
virtual ~FsStdio() = default;
|
||||
|
||||
Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0) override {
|
||||
@@ -287,12 +364,21 @@ struct FsStdio : Fs {
|
||||
Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out) override {
|
||||
return fs::GetFileTimeStampRaw(path, out);
|
||||
}
|
||||
Result SetTimestamp(const FsPath& path, const FsTimeStampRaw *ts) override {
|
||||
return fs::SetTimestamp(path, ts);
|
||||
}
|
||||
bool FileExists(const FsPath& path) override {
|
||||
return fs::FileExists(path);
|
||||
}
|
||||
bool DirExists(const FsPath& path) override {
|
||||
return fs::DirExists(path);
|
||||
}
|
||||
bool IsNative() const override {
|
||||
return false;
|
||||
}
|
||||
FsPath Root() const override {
|
||||
return m_root;
|
||||
}
|
||||
Result read_entire_file(const FsPath& path, std::vector<u8>& out) override {
|
||||
return fs::read_entire_file(path, out);
|
||||
}
|
||||
@@ -302,6 +388,8 @@ struct FsStdio : Fs {
|
||||
Result copy_entire_file(const FsPath& dst, const FsPath& src) override {
|
||||
return fs::copy_entire_file(dst, src, m_ignore_read_only);
|
||||
}
|
||||
|
||||
const FsPath m_root;
|
||||
};
|
||||
|
||||
struct FsNative : Fs {
|
||||
@@ -326,39 +414,35 @@ struct FsNative : Fs {
|
||||
return fsFsGetTotalSpace(&m_fs, path, out);
|
||||
}
|
||||
|
||||
Result OpenFile(const FsPath& path, u32 mode, FsFile *out) {
|
||||
return fsFsOpenFile(&m_fs, path, mode, out);
|
||||
}
|
||||
// Result OpenDirectory(const FsPath& path, u32 mode, FsDir *out) {
|
||||
// return fsFsOpenDirectory(&m_fs, path, mode, out);
|
||||
// }
|
||||
|
||||
Result OpenDirectory(const FsPath& path, u32 mode, FsDir *out) {
|
||||
return fsFsOpenDirectory(&m_fs, path, mode, out);
|
||||
}
|
||||
// void DirClose(FsDir *d) {
|
||||
// fsDirClose(d);
|
||||
// }
|
||||
|
||||
void DirClose(FsDir *d) {
|
||||
fsDirClose(d);
|
||||
}
|
||||
// Result DirGetEntryCount(FsDir *d, s64* out) {
|
||||
// return fsDirGetEntryCount(d, out);
|
||||
// }
|
||||
|
||||
Result DirGetEntryCount(FsDir *d, s64* out) {
|
||||
return fsDirGetEntryCount(d, out);
|
||||
}
|
||||
// Result DirGetEntryCount(const FsPath& path, u32 mode, s64* out) {
|
||||
// FsDir d;
|
||||
// R_TRY(OpenDirectory(path, mode, &d));
|
||||
// ON_SCOPE_EXIT(DirClose(&d));
|
||||
// return DirGetEntryCount(&d, out);
|
||||
// }
|
||||
|
||||
Result DirGetEntryCount(const FsPath& path, u32 mode, s64* out) {
|
||||
FsDir d;
|
||||
R_TRY(OpenDirectory(path, mode, &d));
|
||||
ON_SCOPE_EXIT(DirClose(&d));
|
||||
return DirGetEntryCount(&d, out);
|
||||
}
|
||||
// Result DirRead(FsDir *d, s64 *total_entries, size_t max_entries, FsDirectoryEntry *buf) {
|
||||
// return fsDirRead(d, total_entries, max_entries, buf);
|
||||
// }
|
||||
|
||||
Result DirRead(FsDir *d, s64 *total_entries, size_t max_entries, FsDirectoryEntry *buf) {
|
||||
return fsDirRead(d, total_entries, max_entries, buf);
|
||||
}
|
||||
|
||||
Result DirRead(const FsPath& path, u32 mode, s64 *total_entries, size_t max_entries, FsDirectoryEntry *buf) {
|
||||
FsDir d;
|
||||
R_TRY(OpenDirectory(path, mode, &d));
|
||||
ON_SCOPE_EXIT(DirClose(&d));
|
||||
return DirRead(&d, total_entries, max_entries, buf);
|
||||
}
|
||||
// Result DirRead(const FsPath& path, u32 mode, s64 *total_entries, size_t max_entries, FsDirectoryEntry *buf) {
|
||||
// FsDir d;
|
||||
// R_TRY(OpenDirectory(path, mode, &d));
|
||||
// ON_SCOPE_EXIT(DirClose(&d));
|
||||
// return DirRead(&d, total_entries, max_entries, buf);
|
||||
// }
|
||||
|
||||
virtual bool IsFsActive() {
|
||||
return serviceIsActive(&m_fs.s);
|
||||
@@ -401,12 +485,18 @@ struct FsNative : Fs {
|
||||
Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out) override {
|
||||
return fs::GetFileTimeStampRaw(&m_fs, path, out);
|
||||
}
|
||||
Result SetTimestamp(const FsPath& path, const FsTimeStampRaw *ts) override {
|
||||
return fs::SetTimestamp(&m_fs, path, ts);
|
||||
}
|
||||
bool FileExists(const FsPath& path) override {
|
||||
return fs::FileExists(&m_fs, path);
|
||||
}
|
||||
bool DirExists(const FsPath& path) override {
|
||||
return fs::DirExists(&m_fs, path);
|
||||
}
|
||||
bool IsNative() const override {
|
||||
return true;
|
||||
}
|
||||
Result read_entire_file(const FsPath& path, std::vector<u8>& out) override {
|
||||
return fs::read_entire_file(&m_fs, path, out);
|
||||
}
|
||||
@@ -454,4 +544,10 @@ struct FsNativeContentStorage final : FsNative {
|
||||
}
|
||||
};
|
||||
|
||||
struct FsNativeGameCard final : FsNative {
|
||||
FsNativeGameCard(const FsGameCardHandle* handle, FsGameCardPartition partition, bool ignore_read_only = true) : FsNative{ignore_read_only} {
|
||||
m_open_result = fsOpenGameCardFileSystem(&m_fs, handle, partition);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fs
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace sphaira::ftpsrv {
|
||||
|
||||
bool Init();
|
||||
void Exit();
|
||||
|
||||
using OnInstallStart = std::function<bool(void* user, const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(void* user, const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void(void* user)>;
|
||||
|
||||
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
|
||||
void DisableInstallMode();
|
||||
|
||||
unsigned GetPort();
|
||||
bool IsAnon();
|
||||
const char* GetUser();
|
||||
const char* GetPass();
|
||||
|
||||
} // namespace sphaira::ftpsrv
|
||||
|
||||
30
sphaira/include/hasher.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::hash {
|
||||
|
||||
enum class Type {
|
||||
Crc32,
|
||||
Md5,
|
||||
Sha1,
|
||||
Sha256,
|
||||
};
|
||||
|
||||
struct BaseSource {
|
||||
virtual ~BaseSource() = default;
|
||||
virtual Result Size(s64* out) = 0;
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
};
|
||||
|
||||
auto GetTypeStr(Type type) -> const char*;
|
||||
|
||||
// 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, fs::Fs* fs, const fs::FsPath& path, std::string& out);
|
||||
|
||||
} // namespace sphaira::hash
|
||||
@@ -1,18 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace sphaira::i18n {
|
||||
|
||||
bool init(long index);
|
||||
void exit();
|
||||
|
||||
std::string get(const char* str);
|
||||
std::string get(std::string_view str);
|
||||
|
||||
} // namespace sphaira::i18n
|
||||
|
||||
inline namespace literals {
|
||||
|
||||
std::string operator"" _i18n(const char* str, size_t len);
|
||||
std::string operator""_i18n(const char* str, size_t len);
|
||||
|
||||
} // namespace literals
|
||||
|
||||
41
sphaira/include/location.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::location {
|
||||
|
||||
struct Entry {
|
||||
std::string name{};
|
||||
std::string url{};
|
||||
std::string user{};
|
||||
std::string pass{};
|
||||
std::string bearer{};
|
||||
std::string pub_key{};
|
||||
std::string priv_key{};
|
||||
u16 port{};
|
||||
};
|
||||
using Entries = std::vector<Entry>;
|
||||
|
||||
auto Load() -> Entries;
|
||||
void Add(const Entry& e);
|
||||
|
||||
// helper for hdd devices.
|
||||
// this doesn't really belong in this header, however
|
||||
// locations likely will be renamed to something more generic soon.
|
||||
struct StdioEntry {
|
||||
// mount point (ums0:)
|
||||
std::string mount{};
|
||||
// ums0: (USB Flash Disk)
|
||||
std::string name{};
|
||||
// set if read-only.
|
||||
bool write_protect;
|
||||
};
|
||||
|
||||
using StdioEntries = std::vector<StdioEntry>;
|
||||
|
||||
// set write=true to filter out write protected devices.
|
||||
auto GetStdio(bool write) -> StdioEntries;
|
||||
|
||||
} // namespace sphaira::location
|
||||
@@ -1,24 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define sphaira_USE_LOG 1
|
||||
|
||||
#include <cstdarg>
|
||||
#include <stdarg.h>
|
||||
|
||||
#if sphaira_USE_LOG
|
||||
auto log_file_init() -> bool;
|
||||
auto log_nxlink_init() -> bool;
|
||||
bool log_file_init();
|
||||
bool log_nxlink_init();
|
||||
void log_file_exit();
|
||||
void log_nxlink_exit();
|
||||
void log_write(const char* s, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
void log_write_arg(const char* s, std::va_list& v);
|
||||
void log_write_arg(const char* s, va_list* v);
|
||||
#else
|
||||
inline auto log_file_init() -> bool {
|
||||
inline bool log_file_init() {
|
||||
return true;
|
||||
}
|
||||
inline auto log_nxlink_init() -> bool {
|
||||
inline bool log_nxlink_init() {
|
||||
return true;
|
||||
}
|
||||
#define log_file_exit()
|
||||
#define log_nxlink_exit()
|
||||
#define log_write(...)
|
||||
#define log_write_arg(...)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -11,13 +11,17 @@ namespace sphaira {
|
||||
|
||||
struct Hbini {
|
||||
u64 timestamp{}; // timestamp of last launch
|
||||
u32 launch_count{}; //
|
||||
};
|
||||
|
||||
struct MiniNacp {
|
||||
NacpLanguageEntry lang;
|
||||
char display_version[0x10];
|
||||
};
|
||||
|
||||
struct NroEntry {
|
||||
fs::FsPath path{};
|
||||
s64 size{};
|
||||
NacpStruct nacp{};
|
||||
MiniNacp nacp{};
|
||||
|
||||
u64 icon_size{};
|
||||
u64 icon_offset{};
|
||||
@@ -31,11 +35,11 @@ struct NroEntry {
|
||||
std::optional<bool> has_star{std::nullopt};
|
||||
|
||||
auto GetName() const -> const char* {
|
||||
return nacp.lang[0].name;
|
||||
return nacp.lang.name;
|
||||
}
|
||||
|
||||
auto GetAuthor() const -> const char* {
|
||||
return nacp.lang[0].author;
|
||||
return nacp.lang.author;
|
||||
}
|
||||
|
||||
auto GetDisplayVersion() const -> const char* {
|
||||
|
||||
@@ -14,8 +14,17 @@ struct OptionBase {
|
||||
{}
|
||||
|
||||
auto Get() -> T;
|
||||
auto GetOr(const char* name) -> T;
|
||||
void Set(T value);
|
||||
|
||||
// returns true if loaded.
|
||||
auto LoadFrom(const char* section, const char* name, const char* value) -> bool;
|
||||
// same as above, but only checks the name.
|
||||
auto LoadFrom(const char* name, const char* value) -> bool;
|
||||
|
||||
private:
|
||||
auto GetInternal(const char* name) -> T;
|
||||
|
||||
private:
|
||||
const std::string m_section;
|
||||
const std::string m_name;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "ui/progress_box.hpp"
|
||||
// #include <optional>
|
||||
|
||||
namespace sphaira {
|
||||
|
||||
@@ -15,12 +14,9 @@ struct OwoConfig {
|
||||
std::string author{};
|
||||
NacpStruct nacp;
|
||||
std::vector<u8> icon;
|
||||
std::vector<u8> main;
|
||||
std::vector<u8> npdm;
|
||||
std::vector<u8> logo;
|
||||
std::vector<u8> gif;
|
||||
|
||||
// std::optional<u64> tid;
|
||||
std::vector<u8> program_nca{};
|
||||
};
|
||||
|
||||
|
||||
32
sphaira/include/threaded_file_transfer.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/progress_box.hpp"
|
||||
#include <functional>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::thread {
|
||||
|
||||
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)>;
|
||||
|
||||
// used for pull api
|
||||
using PullCallback = std::function<Result(void* data, s64 size, u64* bytes_read)>;
|
||||
using StartThreadCallback = std::function<Result(void)>;
|
||||
|
||||
// called when threads are started.
|
||||
// call pull() to receive data.
|
||||
using StartCallback = std::function<Result(PullCallback pull)>;
|
||||
|
||||
// same as above, but the callee must call start() in order to start threads.
|
||||
// this is for convenience as there may be race conditions otherwise, such as the read thread
|
||||
// trying to read from the pull callback before it is set.
|
||||
using StartCallback2 = std::function<Result(StartThreadCallback start, PullCallback pull)>;
|
||||
|
||||
// reads data from rfunc into wfunc.
|
||||
Result Transfer(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, WriteCallback wfunc);
|
||||
|
||||
// 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, StartCallback2 sfunc);
|
||||
|
||||
} // namespace sphaira::thread
|
||||
@@ -1,10 +0,0 @@
|
||||
#include "ui/types.hpp"
|
||||
#include "ui/object.hpp"
|
||||
|
||||
namespace sphaira::ui::bubble {
|
||||
|
||||
void Init();
|
||||
void Draw(NVGcontext* vg, Theme* theme);
|
||||
void Exit();
|
||||
|
||||
} // namespace sphaira::ui::bubble
|
||||
@@ -8,15 +8,14 @@ namespace sphaira::ui {
|
||||
class ErrorBox final : public Widget {
|
||||
public:
|
||||
ErrorBox(Result code, const std::string& message);
|
||||
ErrorBox(const std::string& message);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
private:
|
||||
Result m_code{};
|
||||
std::optional<Result> m_code{};
|
||||
std::string m_message{};
|
||||
std::string m_module_str{};
|
||||
std::string m_description_str{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -5,12 +5,17 @@
|
||||
namespace sphaira::ui {
|
||||
|
||||
struct List final : Object {
|
||||
enum class Layout {
|
||||
HOME,
|
||||
GRID,
|
||||
};
|
||||
|
||||
using Callback = std::function<void(NVGcontext* vg, Theme* theme, Vec4 v, s64 index)>;
|
||||
using TouchCallback = std::function<void(s64 index)>;
|
||||
using TouchCallback = std::function<void(bool touch, s64 index)>;
|
||||
|
||||
List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad = {});
|
||||
|
||||
void OnUpdate(Controller* controller, TouchInfo* touch, s64 count, TouchCallback callback);
|
||||
void OnUpdate(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
|
||||
|
||||
void Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
|
||||
|
||||
@@ -35,10 +40,44 @@ struct List final : Object {
|
||||
return m_v.h + m_pad.y;
|
||||
}
|
||||
|
||||
auto GetMaxX() const {
|
||||
return m_v.w + m_pad.x;
|
||||
}
|
||||
|
||||
auto GetLayout() const {
|
||||
return m_layout;
|
||||
}
|
||||
|
||||
void SetLayout(Layout layout) {
|
||||
m_layout = layout;
|
||||
}
|
||||
|
||||
auto GetRow() const {
|
||||
return m_row;
|
||||
}
|
||||
|
||||
auto GetPage() const {
|
||||
return m_page;
|
||||
}
|
||||
|
||||
auto SetPageJump(bool enable) {
|
||||
m_page_jump = enable;
|
||||
}
|
||||
|
||||
auto GetPageJump() const {
|
||||
return m_page_jump;
|
||||
}
|
||||
|
||||
private:
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override {}
|
||||
auto ClampX(float x, s64 count) const -> float;
|
||||
auto ClampY(float y, s64 count) const -> float;
|
||||
|
||||
void OnUpdateHome(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
|
||||
void OnUpdateGrid(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
|
||||
void DrawHome(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
|
||||
void DrawGrid(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
|
||||
|
||||
private:
|
||||
const s64 m_row;
|
||||
const s64 m_page;
|
||||
@@ -52,6 +91,9 @@ private:
|
||||
float m_yoff{};
|
||||
// in progress y offset, used when scrolling.
|
||||
float m_y_prog{};
|
||||
|
||||
Layout m_layout{Layout::GRID};
|
||||
bool m_page_jump{true};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/menus/grid_menu_base.hpp"
|
||||
#include "ui/scrollable_text.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::appstore {
|
||||
@@ -73,6 +74,7 @@ struct EntryMenu final : MenuBase {
|
||||
EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu);
|
||||
~EntryMenu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Entry"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
// void OnFocusGained() override;
|
||||
@@ -134,27 +136,25 @@ enum OrderType {
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(const std::vector<NroEntry>& nro_entries);
|
||||
using LayoutType = grid::LayoutType;
|
||||
|
||||
struct Menu final : grid::Menu {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Store"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(s64 index);
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
|
||||
void SetFilter(Filter filter);
|
||||
void SetSort(SortType sort);
|
||||
void SetOrder(OrderType order);
|
||||
|
||||
void SetSearch(const std::string& term);
|
||||
void SetAuthor();
|
||||
|
||||
auto GetEntry(s64 i) -> Entry& {
|
||||
return m_entries[m_entries_current[i]];
|
||||
}
|
||||
|
||||
auto GetEntry() -> Entry& {
|
||||
return m_entries[m_entries_current[m_index]];
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
auto SetDirty() {
|
||||
@@ -162,16 +162,27 @@ struct Menu final : MenuBase {
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<NroEntry>& m_nro_entries;
|
||||
void SetIndex(s64 index);
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
void SortAndFindLastFile();
|
||||
void SetFilter();
|
||||
void SetSearch(const std::string& term);
|
||||
void OnLayoutChange();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "appstore";
|
||||
|
||||
std::vector<Entry> m_entries{};
|
||||
std::vector<EntryMini> m_entries_index[Filter_MAX]{};
|
||||
std::vector<EntryMini> m_entries_index_author{};
|
||||
std::vector<EntryMini> m_entries_index_search{};
|
||||
std::span<EntryMini> m_entries_current{};
|
||||
|
||||
Filter m_filter{Filter::Filter_All};
|
||||
SortType m_sort{SortType::SortType_Updated};
|
||||
OrderType m_order{OrderType::OrderType_Descending};
|
||||
option::OptionLong m_filter{INI_SECTION, "filter", Filter::Filter_All};
|
||||
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_GridDetail};
|
||||
|
||||
s64 m_index{}; // where i am in the array
|
||||
LazyImage m_default_image{};
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace sphaira::ui::menu::fileview {
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(const fs::FsPath& path);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "File"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
@@ -17,7 +17,7 @@ struct Menu final : MenuBase {
|
||||
private:
|
||||
const fs::FsPath m_path;
|
||||
fs::FsNativeSd m_fs{};
|
||||
FsFile m_file{};
|
||||
fs::File m_file{};
|
||||
s64 m_file_size{};
|
||||
s64 m_file_offset{};
|
||||
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
#include "hasher.hpp"
|
||||
// #include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::filebrowser {
|
||||
|
||||
enum FsEntryFlag {
|
||||
FsEntryFlag_None,
|
||||
// write protected.
|
||||
FsEntryFlag_ReadOnly = 1 << 0,
|
||||
// supports file assoc.
|
||||
FsEntryFlag_Assoc = 1 << 1,
|
||||
};
|
||||
|
||||
enum class FsType {
|
||||
Sd,
|
||||
ImageNand,
|
||||
ImageSd,
|
||||
Stdio,
|
||||
};
|
||||
|
||||
enum class SelectedType {
|
||||
@@ -23,6 +33,11 @@ enum class SelectedType {
|
||||
Delete,
|
||||
};
|
||||
|
||||
enum class ViewSide {
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
|
||||
enum SortType {
|
||||
SortType_Size,
|
||||
SortType_Alphabetical,
|
||||
@@ -33,6 +48,25 @@ enum OrderType {
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
struct FsEntry {
|
||||
fs::FsPath name{};
|
||||
fs::FsPath root{};
|
||||
FsType type{};
|
||||
u32 flags{FsEntryFlag_None};
|
||||
|
||||
auto IsReadOnly() const -> bool {
|
||||
return flags & FsEntryFlag_ReadOnly;
|
||||
}
|
||||
|
||||
auto IsAssoc() const -> bool {
|
||||
return flags & FsEntryFlag_Assoc;
|
||||
}
|
||||
|
||||
auto IsSame(const FsEntry& e) const {
|
||||
return root == e.root && type == e.type;
|
||||
}
|
||||
};
|
||||
|
||||
// roughly 1kib in size per entry
|
||||
struct FileEntry : FsDirectoryEntry {
|
||||
std::string extension{}; // if any
|
||||
@@ -89,6 +123,19 @@ struct FileAssocEntry {
|
||||
std::string name{}; // ini name
|
||||
std::vector<std::string> ext{}; // list of ext
|
||||
std::vector<std::string> database{}; // list of systems
|
||||
bool use_base_name{}; // if set, uses base name (rom.zip) otherwise uses internal name (rom.gba)
|
||||
|
||||
auto IsExtension(std::string_view extension, std::string_view internal_extension) const -> bool {
|
||||
for (const auto& assoc_ext : ext) {
|
||||
if (extension.length() == assoc_ext.length() && !strncasecmp(assoc_ext.data(), extension.data(), assoc_ext.length())) {
|
||||
return true;
|
||||
}
|
||||
if (internal_extension.length() == assoc_ext.length() && !strncasecmp(assoc_ext.data(), internal_extension.data(), assoc_ext.length())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct LastFile {
|
||||
@@ -107,9 +154,14 @@ struct FsDirCollection {
|
||||
|
||||
using FsDirCollections = std::vector<FsDirCollection>;
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(const std::vector<NroEntry>& nro_entries);
|
||||
~Menu();
|
||||
struct Menu;
|
||||
|
||||
struct FsView final : Widget {
|
||||
friend class Menu;
|
||||
|
||||
FsView(Menu* menu, ViewSide side);
|
||||
FsView(Menu* menu, const fs::FsPath& path, const FsEntry& entry, ViewSide side);
|
||||
~FsView();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
@@ -119,14 +171,26 @@ struct Menu final : MenuBase {
|
||||
return fs::AppendPath(root_path, file_path);
|
||||
}
|
||||
|
||||
auto GetFs() {
|
||||
return m_fs.get();
|
||||
}
|
||||
|
||||
auto& GetFsEntry() const {
|
||||
return m_fs_entry;
|
||||
}
|
||||
|
||||
void SetSide(ViewSide side);
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
void InstallForwarder();
|
||||
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
|
||||
|
||||
void LoadAssocEntriesPath(const fs::FsPath& path);
|
||||
void LoadAssocEntries();
|
||||
auto FindFileAssocFor() -> std::vector<FileAssocEntry>;
|
||||
void InstallFiles();
|
||||
void UnzipFiles(fs::FsPath folder);
|
||||
void ZipFiles(fs::FsPath zip_path);
|
||||
void UploadFiles();
|
||||
|
||||
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
|
||||
|
||||
auto GetNewPath(const FileEntry& entry) const -> fs::FsPath {
|
||||
return GetNewPath(m_path, entry.name);
|
||||
@@ -141,61 +205,21 @@ private:
|
||||
}
|
||||
|
||||
auto GetSelectedEntries() const -> std::vector<FileEntry> {
|
||||
if (!m_selected_count) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<FileEntry> out;
|
||||
|
||||
for (auto&e : m_entries) {
|
||||
if (e.IsSelected()) {
|
||||
out.emplace_back(e);
|
||||
if (!m_selected_count) {
|
||||
out.emplace_back(GetEntry());
|
||||
} else {
|
||||
for (auto&e : m_entries) {
|
||||
if (e.IsSelected()) {
|
||||
out.emplace_back(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void AddSelectedEntries(SelectedType type) {
|
||||
auto entries = GetSelectedEntries();
|
||||
if (entries.empty()) {
|
||||
// log_write("%s with no selected files\n", __PRETTY_FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
m_selected_type = type;
|
||||
m_selected_files = entries;
|
||||
m_selected_path = m_path;
|
||||
}
|
||||
|
||||
void AddCurrentFileToSelection(SelectedType type) {
|
||||
m_selected_files.emplace_back(GetEntry());
|
||||
m_selected_count++;
|
||||
m_selected_type = type;
|
||||
m_selected_path = m_path;
|
||||
}
|
||||
|
||||
void ResetSelection() {
|
||||
m_selected_files.clear();
|
||||
m_selected_count = 0;
|
||||
m_selected_type = SelectedType::None;
|
||||
m_selected_path = {};
|
||||
}
|
||||
|
||||
auto HasTypeInSelectedEntries(FsDirEntryType type) const -> bool {
|
||||
if (!m_selected_count) {
|
||||
return GetEntry().type == type;
|
||||
} else {
|
||||
for (auto&p : m_selected_files) {
|
||||
if (p.type == type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) -> FileEntry& {
|
||||
return m_entries[m_entries_current[index]];
|
||||
}
|
||||
@@ -212,27 +236,42 @@ private:
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
auto IsSd() const -> bool {
|
||||
return m_fs_entry.type == FsType::Sd;
|
||||
}
|
||||
|
||||
void Sort();
|
||||
void SortAndFindLastFile();
|
||||
void SetIndexFromLastFile(const LastFile& last_file);
|
||||
void UpdateSubheading();
|
||||
|
||||
void OnDeleteCallback();
|
||||
void OnPasteCallback();
|
||||
void OnRenameCallback();
|
||||
auto CheckIfUpdateFolder() -> 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) -> 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;
|
||||
|
||||
void SetFs(const fs::FsPath& new_path, u32 new_type);
|
||||
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;
|
||||
|
||||
void SetFs(const fs::FsPath& new_path, const FsEntry& new_entry);
|
||||
|
||||
auto GetNative() -> fs::FsNative* {
|
||||
return (fs::FsNative*)m_fs.get();
|
||||
}
|
||||
|
||||
void DisplayHash(hash::Type type);
|
||||
|
||||
void DisplayOptions();
|
||||
void DisplayAdvancedOptions();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "filebrowser";
|
||||
Menu* m_menu{};
|
||||
ViewSide m_side{};
|
||||
|
||||
const std::vector<NroEntry>& m_nro_entries;
|
||||
std::unique_ptr<fs::FsNative> m_fs{};
|
||||
FsType m_fs_type{};
|
||||
std::unique_ptr<fs::Fs> m_fs{};
|
||||
FsEntry m_fs_entry{};
|
||||
fs::FsPath m_path{};
|
||||
std::vector<FileEntry> m_entries{};
|
||||
std::vector<u32> m_entries_index{}; // files not including hidden
|
||||
@@ -243,23 +282,123 @@ private:
|
||||
std::unique_ptr<List> m_list{};
|
||||
std::optional<fs::FsPath> m_daybreak_path{};
|
||||
|
||||
// search options
|
||||
// show files [X]
|
||||
// show folders [X]
|
||||
// recursive (slow) [ ]
|
||||
// this keeps track of the highlighted file before opening a folder
|
||||
// if the user presses B to go back to the previous dir
|
||||
// this vector is popped, then, that entry is checked if it still exists
|
||||
// if it does, the index becomes that file.
|
||||
std::vector<LastFile> m_previous_highlighted_file{};
|
||||
s64 m_index{};
|
||||
s64 m_selected_count{};
|
||||
ScrollingText m_scroll_name{};
|
||||
|
||||
bool m_is_update_folder{};
|
||||
};
|
||||
|
||||
// contains all selected files for a command, such as copy, delete, cut etc.
|
||||
struct SelectedStash {
|
||||
void Add(std::shared_ptr<FsView> view, SelectedType type, const std::vector<FileEntry>& files, const fs::FsPath& path) {
|
||||
if (files.empty()) {
|
||||
Reset();
|
||||
} else {
|
||||
m_view = view;
|
||||
m_type = type;
|
||||
m_files = files;
|
||||
m_path = path;
|
||||
}
|
||||
}
|
||||
|
||||
auto SameFs(FsView* view) -> bool {
|
||||
if (m_view && view->GetFsEntry().IsSame(m_view->GetFsEntry())) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto Type() const -> SelectedType {
|
||||
return m_type;
|
||||
}
|
||||
|
||||
auto Empty() const -> bool {
|
||||
return m_files.empty();
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
m_view = {};
|
||||
m_type = {};
|
||||
m_files = {};
|
||||
m_path = {};
|
||||
}
|
||||
|
||||
// private:
|
||||
std::shared_ptr<FsView> m_view{};
|
||||
std::vector<FileEntry> m_files{};
|
||||
fs::FsPath m_path{};
|
||||
SelectedType m_type{SelectedType::None};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
friend class FsView;
|
||||
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Files"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
static auto GetNewPath(const fs::FsPath& root_path, const fs::FsPath& file_path) -> fs::FsPath {
|
||||
return fs::AppendPath(root_path, file_path);
|
||||
}
|
||||
|
||||
private:
|
||||
auto IsSplitScreen() const {
|
||||
return m_split_screen;
|
||||
}
|
||||
|
||||
void SetSplitScreen(bool enable);
|
||||
|
||||
void RefreshViews();
|
||||
|
||||
void LoadAssocEntriesPath(const fs::FsPath& path);
|
||||
void LoadAssocEntries();
|
||||
auto FindFileAssocFor() -> std::vector<FileAssocEntry>;
|
||||
|
||||
void AddSelectedEntries(SelectedType type) {
|
||||
auto entries = view->GetSelectedEntries();
|
||||
if (entries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_selected.Add(view, type, entries, view->m_path);
|
||||
}
|
||||
|
||||
void ResetSelection() {
|
||||
m_selected.Reset();
|
||||
}
|
||||
|
||||
void UpdateSubheading();
|
||||
|
||||
void PromptIfShouldExit();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "filebrowser";
|
||||
|
||||
std::shared_ptr<FsView> view{};
|
||||
std::shared_ptr<FsView> view_left{};
|
||||
std::shared_ptr<FsView> view_right{};
|
||||
|
||||
std::vector<FileAssocEntry> m_assoc_entries{};
|
||||
std::vector<FileEntry> m_selected_files{};
|
||||
SelectedStash m_selected{};
|
||||
|
||||
// this keeps track of the highlighted file before opening a folder
|
||||
// if the user presses B to go back to the previous dir
|
||||
// this vector is popped, then, that entry is checked if it still exists
|
||||
// if it does, the index becomes that file.
|
||||
std::vector<LastFile> m_previous_highlighted_file{};
|
||||
fs::FsPath m_selected_path{};
|
||||
s64 m_index{};
|
||||
s64 m_selected_count{};
|
||||
SelectedType m_selected_type{SelectedType::None};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||
@@ -267,10 +406,10 @@ private:
|
||||
option::OptionBool m_folders_first{INI_SECTION, "folders_first", true};
|
||||
option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false};
|
||||
option::OptionBool m_ignore_read_only{INI_SECTION, "ignore_read_only", false};
|
||||
option::OptionLong m_mount{INI_SECTION, "mount", 0};
|
||||
|
||||
bool m_loaded_assoc_entries{};
|
||||
bool m_is_update_folder{};
|
||||
bool m_split_screen{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::filebrowser
|
||||
|
||||
61
sphaira/include/ui/menus/ftp_menu.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "yati/source/stream.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::ftp {
|
||||
|
||||
enum class State {
|
||||
// not connected.
|
||||
None,
|
||||
// just connected, starts the transfer.
|
||||
Connected,
|
||||
// set whilst transfer is in progress.
|
||||
Progress,
|
||||
// set when the transfer is finished.
|
||||
Done,
|
||||
// failed to connect.
|
||||
Failed,
|
||||
};
|
||||
|
||||
struct StreamFtp final : yati::source::Stream {
|
||||
StreamFtp(const fs::FsPath& path, std::stop_token token);
|
||||
|
||||
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
|
||||
bool Push(const void* buf, s64 size);
|
||||
void Disable();
|
||||
|
||||
// private:
|
||||
fs::FsPath m_path{};
|
||||
std::stop_token m_token{};
|
||||
std::vector<u8> m_buffer{};
|
||||
Mutex m_mutex{};
|
||||
bool m_active{};
|
||||
// bool m_push_exit{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "FTP"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
// this should be private
|
||||
// private:
|
||||
std::shared_ptr<StreamFtp> m_source{};
|
||||
Thread m_thread{};
|
||||
Mutex m_mutex{};
|
||||
// the below are shared across threads, lock with the above mutex!
|
||||
State m_state{State::None};
|
||||
|
||||
const char* m_user{};
|
||||
const char* m_pass{};
|
||||
unsigned m_port{};
|
||||
bool m_anon{};
|
||||
bool m_was_ftp_enabled{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::ftp
|
||||
153
sphaira/include/ui/menus/game_menu.hpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/grid_menu_base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace sphaira::ui::menu::game {
|
||||
|
||||
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 {
|
||||
u64 app_id{};
|
||||
char display_version[0x10]{};
|
||||
NacpLanguageEntry lang{};
|
||||
int image{};
|
||||
bool selected{};
|
||||
|
||||
std::shared_ptr<NsApplicationControlData> control{};
|
||||
u64 control_size{};
|
||||
NacpLoadStatus status{NacpLoadStatus::None};
|
||||
|
||||
auto GetName() const -> const char* {
|
||||
return lang.name;
|
||||
}
|
||||
|
||||
auto GetAuthor() const -> const char* {
|
||||
return lang.author;
|
||||
}
|
||||
|
||||
auto GetDisplayVersion() const -> const char* {
|
||||
return display_version;
|
||||
}
|
||||
};
|
||||
|
||||
struct ThreadResultData {
|
||||
u64 id{};
|
||||
std::shared_ptr<NsApplicationControlData> control{};
|
||||
u64 control_size{};
|
||||
char display_version[0x10]{};
|
||||
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 "Games"; };
|
||||
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 DeleteGames();
|
||||
void DumpGames(u32 flags);
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "games";
|
||||
static constexpr inline const char* INI_SECTION_DUMP = "dump";
|
||||
|
||||
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{};
|
||||
|
||||
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_GridDetail};
|
||||
option::OptionBool m_hide_forwarders{INI_SECTION, "hide_forwarders", false};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::game
|
||||
245
sphaira/include/ui/menus/gc_menu.hpp
Normal file
@@ -0,0 +1,245 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "yati/container/base.hpp"
|
||||
#include "yati/source/base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include <span>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::menu::gc {
|
||||
|
||||
typedef enum {
|
||||
FsGameCardPartitionRaw_None = -1,
|
||||
FsGameCardPartitionRaw_Normal = 0,
|
||||
FsGameCardPartitionRaw_Secure = 1,
|
||||
} FsGameCardPartitionRaw;
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// The below structs are taken from nxdumptool./
|
||||
////////////////////////////////////////////////
|
||||
|
||||
/// Located at offset 0x7000 in the gamecard image.
|
||||
typedef struct {
|
||||
u8 signature[0x100]; ///< RSA-2048-PKCS#1 v1.5 with SHA-256 signature over the rest of the data.
|
||||
u32 magic; ///< "CERT".
|
||||
u32 version;
|
||||
u8 kek_index;
|
||||
u8 reserved[0x7];
|
||||
u8 t1_card_device_id[0x10];
|
||||
u8 iv[0x10];
|
||||
u8 hw_key[0x10]; ///< Encrypted.
|
||||
u8 data[0xC0]; ///< Encrypted.
|
||||
} FsGameCardCertificate;
|
||||
|
||||
static_assert(sizeof(FsGameCardCertificate) == 0x200);
|
||||
|
||||
typedef struct {
|
||||
u8 maker_code; ///< FsCardId1MakerCode.
|
||||
u8 memory_capacity; ///< Matches GameCardRomSize.
|
||||
u8 reserved; ///< Known values: 0x00, 0x01, 0x02, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0D, 0x0E, 0x80.
|
||||
u8 memory_type; ///< FsCardId1MemoryType.
|
||||
} FsCardId1;
|
||||
|
||||
static_assert(sizeof(FsCardId1) == 0x4);
|
||||
|
||||
typedef struct {
|
||||
u8 card_security_number; ///< FsCardId2CardSecurityNumber.
|
||||
u8 card_type; ///< FsCardId2CardType.
|
||||
u8 reserved[0x2]; ///< Usually filled with zeroes.
|
||||
} FsCardId2;
|
||||
|
||||
static_assert(sizeof(FsCardId2) == 0x4);
|
||||
|
||||
typedef struct {
|
||||
u8 reserved[0x4]; ///< Usually filled with zeroes.
|
||||
} FsCardId3;
|
||||
|
||||
static_assert(sizeof(FsCardId3) == 0x4);
|
||||
|
||||
/// Returned by fsDeviceOperatorGetGameCardIdSet.
|
||||
typedef struct {
|
||||
FsCardId1 id1; ///< Specifies maker code, memory capacity and memory type.
|
||||
FsCardId2 id2; ///< Specifies card security number and card type.
|
||||
FsCardId3 id3; ///< Always zero (so far).
|
||||
} FsGameCardIdSet;
|
||||
|
||||
/// Encrypted using AES-128-ECB with the common titlekek generator key (stored in the .rodata segment from the Lotus firmware).
|
||||
typedef struct {
|
||||
union {
|
||||
u8 value[0x10];
|
||||
struct {
|
||||
u8 package_id[0x8]; ///< Matches package_id from GameCardHeader.
|
||||
u8 reserved[0x8]; ///< Just zeroes.
|
||||
};
|
||||
};
|
||||
} GameCardKeySource;
|
||||
|
||||
static_assert(sizeof(GameCardKeySource) == 0x10);
|
||||
|
||||
/// Plaintext area. Dumped from FS program memory.
|
||||
typedef struct {
|
||||
GameCardKeySource key_source;
|
||||
u8 encrypted_titlekey[0x10]; ///< Encrypted using AES-128-CCM with the decrypted key_source and the nonce from this section.
|
||||
u8 mac[0x10]; ///< Used to verify the validity of the decrypted titlekey.
|
||||
u8 nonce[0xC]; ///< Used as the IV to decrypt encrypted_titlekey using AES-128-CCM.
|
||||
u8 reserved[0x1C4];
|
||||
} GameCardInitialData;
|
||||
|
||||
static_assert(sizeof(GameCardInitialData) == 0x200);
|
||||
|
||||
typedef struct {
|
||||
u8 maker_code; ///< GameCardUidMakerCode.
|
||||
u8 version; ///< TODO: determine whether this matches GameCardVersion or not.
|
||||
u8 card_type; ///< GameCardUidCardType.
|
||||
u8 unique_data[0x9];
|
||||
u32 random;
|
||||
u8 platform_flag;
|
||||
u8 reserved[0xB];
|
||||
FsCardId1 card_id_1_mirror; ///< This field mirrors bit 5 of FsCardId1MemoryType.
|
||||
u8 mac[0x20];
|
||||
} GameCardUid;
|
||||
|
||||
static_assert(sizeof(GameCardUid) == 0x40);
|
||||
|
||||
/// Plaintext area. Dumped from FS program memory.
|
||||
/// Overall structure may change with each new LAFW version.
|
||||
typedef struct {
|
||||
u32 asic_security_mode; ///< Determines how the Lotus ASIC initialised the gamecard security mode. Usually 0xFFFFFFF9.
|
||||
u32 asic_status; ///< Bitmask of the internal gamecard interface status. Usually 0x20000000.
|
||||
FsCardId1 card_id1;
|
||||
FsCardId2 card_id2;
|
||||
GameCardUid card_uid;
|
||||
u8 reserved[0x190];
|
||||
u8 mac[0x20]; ///< Changes with each gamecard (re)insertion.
|
||||
} GameCardSpecificData;
|
||||
|
||||
static_assert(sizeof(GameCardSpecificData) == 0x200);
|
||||
|
||||
/// Plaintext area. Dumped from FS program memory.
|
||||
/// This struct is returned by Lotus command "ChangeToSecureMode" (0xF). This means it is only available *after* the gamecard secure area has been mounted.
|
||||
/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardInfo precedes this struct in FS program memory.
|
||||
typedef struct {
|
||||
GameCardSpecificData specific_data;
|
||||
FsGameCardCertificate certificate;
|
||||
u8 reserved[0x200];
|
||||
GameCardInitialData initial_data;
|
||||
} GameCardSecurityInformation;
|
||||
|
||||
static_assert(sizeof(GameCardSecurityInformation) == 0x800);
|
||||
|
||||
///////////////////
|
||||
// nxdumptool fin./
|
||||
///////////////////
|
||||
|
||||
struct GcCollection : yati::container::CollectionEntry {
|
||||
GcCollection(const char* _name, s64 _size, u8 _type, u8 _id_offset) {
|
||||
name = _name;
|
||||
size = _size;
|
||||
type = _type;
|
||||
id_offset = _id_offset;
|
||||
}
|
||||
|
||||
// NcmContentType
|
||||
u8 type{};
|
||||
u8 id_offset{};
|
||||
};
|
||||
|
||||
using GcCollections = std::vector<GcCollection>;
|
||||
|
||||
struct ApplicationEntry {
|
||||
u64 app_id{};
|
||||
u32 version{};
|
||||
u8 key_gen{};
|
||||
std::unique_ptr<NsApplicationControlData> control{};
|
||||
u64 control_size{};
|
||||
NacpLanguageEntry lang_entry{};
|
||||
|
||||
std::vector<GcCollections> application{};
|
||||
std::vector<GcCollections> patch{};
|
||||
std::vector<GcCollections> add_on{};
|
||||
std::vector<GcCollections> data_patch{};
|
||||
yati::container::Collections tickets{};
|
||||
|
||||
auto GetSize() const -> s64;
|
||||
auto GetSize(const std::vector<GcCollections>& entries) const -> s64;
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "GC"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
Result GcStorageRead(void* buf, s64 off, s64 size);
|
||||
|
||||
private:
|
||||
Result GcPoll(bool* inserted);
|
||||
Result GcOnEvent(bool force = false);
|
||||
|
||||
// GameCard FS api.
|
||||
Result GcMount();
|
||||
void GcUnmount();
|
||||
|
||||
// GameCard Storage api
|
||||
Result GcMountStorage();
|
||||
void GcUmountStorage();
|
||||
Result GcMountPartition(FsGameCardPartitionRaw partition);
|
||||
void GcUnmountPartition();
|
||||
Result GcStorageReadInternal(void* buf, s64 off, s64 size, u64* bytes_read);
|
||||
|
||||
// taken from nxdumptool.
|
||||
Result GcGetSecurityInfo(GameCardSecurityInformation& out);
|
||||
|
||||
Result LoadControlData(ApplicationEntry& e);
|
||||
Result UpdateStorageSize();
|
||||
void FreeImage();
|
||||
void OnChangeIndex(s64 new_index);
|
||||
Result DumpGames(u32 flags);
|
||||
|
||||
private:
|
||||
FsDeviceOperator m_dev_op{};
|
||||
FsGameCardHandle m_handle{};
|
||||
std::unique_ptr<fs::FsNativeGameCard> m_fs{};
|
||||
FsEventNotifier m_event_notifier{};
|
||||
Event m_event{};
|
||||
|
||||
std::vector<ApplicationEntry> m_entries{};
|
||||
std::unique_ptr<List> m_list{};
|
||||
s64 m_entry_index{};
|
||||
s64 m_option_index{};
|
||||
|
||||
s64 m_size_free_sd{};
|
||||
s64 m_size_total_sd{};
|
||||
s64 m_size_free_nand{};
|
||||
s64 m_size_total_nand{};
|
||||
int m_icon{};
|
||||
bool m_mounted{};
|
||||
|
||||
FsStorage m_storage{};
|
||||
// size of normal partition.
|
||||
s64 m_parition_normal_size{};
|
||||
// size of secure partition.
|
||||
s64 m_parition_secure_size{};
|
||||
// used size reported in the xci header.
|
||||
s64 m_storage_trimmed_size{};
|
||||
// total size of m_parition_normal_size + m_parition_secure_size.
|
||||
s64 m_storage_total_size{};
|
||||
// reported size via rom_size in the xci header.
|
||||
s64 m_storage_full_size{};
|
||||
// found in xci header.
|
||||
u64 m_package_id{};
|
||||
// found in xci header.
|
||||
u8 m_initial_data_hash[SHA256_HASH_SIZE]{};
|
||||
// currently mounted storage partiton.
|
||||
FsGameCardPartitionRaw m_partition{FsGameCardPartitionRaw_None};
|
||||
bool m_storage_mounted{};
|
||||
|
||||
// set when the gc should be re-mounted, cleared when handled.
|
||||
bool m_dirty{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::gc
|
||||
@@ -42,9 +42,10 @@ struct GhApiEntry {
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu();
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "GitHub"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
35
sphaira/include/ui/menus/grid_menu_base.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::menu::grid {
|
||||
|
||||
enum LayoutType {
|
||||
LayoutType_List,
|
||||
LayoutType_Grid,
|
||||
LayoutType_GridDetail,
|
||||
};
|
||||
|
||||
struct Menu : MenuBase {
|
||||
using MenuBase::MenuBase;
|
||||
|
||||
protected:
|
||||
void OnLayoutChange(std::unique_ptr<List>& list, int layout);
|
||||
void DrawEntry(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version);
|
||||
// same as above but doesn't draw image and returns image dimension.
|
||||
Vec4 DrawEntryNoImage(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, const char* name, const char* author, const char* version);
|
||||
|
||||
private:
|
||||
Vec4 DrawEntry(NVGcontext* vg, Theme* theme, bool draw_image, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version);
|
||||
|
||||
private:
|
||||
ScrollingText m_scroll_name{};
|
||||
ScrollingText m_scroll_author{};
|
||||
ScrollingText m_scroll_version{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::grid
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/menus/grid_menu_base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "fs.hpp"
|
||||
@@ -22,31 +22,39 @@ enum OrderType {
|
||||
OrderType_Ascending,
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
using LayoutType = grid::LayoutType;
|
||||
|
||||
auto GetNroEntries() -> std::span<const NroEntry>;
|
||||
|
||||
struct Menu final : grid::Menu {
|
||||
Menu();
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Apps"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
void SetIndex(s64 index);
|
||||
void InstallHomebrew();
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
void SortAndFindLastFile();
|
||||
|
||||
auto GetHomebrewList() const -> const std::vector<NroEntry>& {
|
||||
return m_entries;
|
||||
}
|
||||
|
||||
static Result InstallHomebrew(const fs::FsPath& path, const std::vector<u8>& icon);
|
||||
static Result InstallHomebrewFromPath(const fs::FsPath& path);
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
void InstallHomebrew();
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
void SortAndFindLastFile();
|
||||
void FreeEntries();
|
||||
void OnLayoutChange();
|
||||
|
||||
auto IsStarEnabled() -> bool {
|
||||
return m_sort.Get() >= SortType_UpdatedStar;
|
||||
}
|
||||
|
||||
static Result InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon);
|
||||
static Result InstallHomebrewFromPath(const fs::FsPath& path);
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "homebrew";
|
||||
|
||||
@@ -56,6 +64,7 @@ private:
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
|
||||
option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};
|
||||
};
|
||||
|
||||
|
||||
@@ -27,19 +27,22 @@ struct Entry {
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu();
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "IRS"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
private:
|
||||
void PollCameraStatus(bool statup = false);
|
||||
void LoadDefaultConfig();
|
||||
void UpdateConfig(const IrsImageTransferProcessorExConfig* config);
|
||||
void ResetImage();
|
||||
void UpdateImage();
|
||||
void updateColourArray();
|
||||
auto GetEntryName(s64 i) -> std::string;
|
||||
|
||||
private:
|
||||
Result m_init_rc{};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/menus/homebrew.hpp"
|
||||
#include "ui/menus/filebrowser.hpp"
|
||||
#include "ui/menus/appstore.hpp"
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::main {
|
||||
|
||||
@@ -18,6 +17,32 @@ enum class UpdateState {
|
||||
Error,
|
||||
};
|
||||
|
||||
using MiscMenuFunction = std::function<std::shared_ptr<ui::menu::MenuBase>(u32 flags)>;
|
||||
|
||||
enum MiscMenuFlag : u8 {
|
||||
// can be set as the rightside menu.
|
||||
MiscMenuFlag_Shortcut = 1 << 0,
|
||||
// needs install option to be enabled.
|
||||
MiscMenuFlag_Install = 1 << 1,
|
||||
};
|
||||
|
||||
struct MiscMenuEntry {
|
||||
const char* name;
|
||||
const char* title;
|
||||
MiscMenuFunction func;
|
||||
u8 flag;
|
||||
|
||||
auto IsShortcut() const -> bool {
|
||||
return flag & MiscMenuFlag_Shortcut;
|
||||
}
|
||||
|
||||
auto IsInstall() const -> bool {
|
||||
return flag & MiscMenuFlag_Install;
|
||||
}
|
||||
};
|
||||
|
||||
auto GetMiscMenuEntries() -> std::span<const MiscMenuEntry>;
|
||||
|
||||
// this holds 2 menus and allows for switching between them
|
||||
struct MainMenu final : Widget {
|
||||
MainMenu();
|
||||
@@ -37,9 +62,9 @@ private:
|
||||
void AddOnLRPress();
|
||||
|
||||
private:
|
||||
std::shared_ptr<homebrew::Menu> m_homebrew_menu{};
|
||||
std::shared_ptr<filebrowser::Menu> m_filebrowser_menu{};
|
||||
std::shared_ptr<appstore::Menu> m_app_store_menu{};
|
||||
std::shared_ptr<MenuBase> m_centre_menu{};
|
||||
std::shared_ptr<MenuBase> m_left_menu{};
|
||||
std::shared_ptr<MenuBase> m_right_menu{};
|
||||
std::shared_ptr<MenuBase> m_current_menu{};
|
||||
|
||||
std::string m_update_url{};
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "nro.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace sphaira::ui::menu {
|
||||
|
||||
enum MenuFlag {
|
||||
MenuFlag_None = 0,
|
||||
MenuFlag_Tab = 1 << 1,
|
||||
};
|
||||
|
||||
struct PolledData {
|
||||
struct tm tm{};
|
||||
u32 battery_percetange{};
|
||||
PsmChargerType charger_type{};
|
||||
NifmInternetConnectionType type{};
|
||||
NifmInternetConnectionStatus status{};
|
||||
u32 strength{};
|
||||
u32 ip{};
|
||||
};
|
||||
|
||||
struct MenuBase : Widget {
|
||||
MenuBase(std::string title);
|
||||
MenuBase(const std::string& title, u32 flags);
|
||||
virtual ~MenuBase();
|
||||
|
||||
virtual auto GetShortTitle() const -> const char* = 0;
|
||||
virtual void Update(Controller* controller, TouchInfo* touch);
|
||||
virtual void Draw(NVGcontext* vg, Theme* theme);
|
||||
|
||||
@@ -21,22 +37,25 @@ struct MenuBase : Widget {
|
||||
void SetTitleSubHeading(std::string sub_heading);
|
||||
void SetSubHeading(std::string sub_heading);
|
||||
|
||||
private:
|
||||
void UpdateVars();
|
||||
auto GetTitle() const {
|
||||
return m_title;
|
||||
}
|
||||
|
||||
auto IsTab() const -> bool {
|
||||
return m_flags & MenuFlag_Tab;
|
||||
}
|
||||
|
||||
static auto GetPolledData(bool force_refresh = false) -> PolledData;
|
||||
|
||||
private:
|
||||
std::string m_title{};
|
||||
std::string m_title_sub_heading{};
|
||||
std::string m_sub_heading{};
|
||||
|
||||
struct tm m_tm{};
|
||||
TimeStamp m_poll_timestamp{};
|
||||
u32 m_battery_percetange{};
|
||||
PsmChargerType m_charger_type{};
|
||||
NifmInternetConnectionType m_type{};
|
||||
NifmInternetConnectionStatus m_status{};
|
||||
u32 m_strength{};
|
||||
u32 m_ip{};
|
||||
ScrollingText m_scroll_title_sub_heading{};
|
||||
ScrollingText m_scroll_sub_heading{};
|
||||
|
||||
u32 m_flags{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrollable_text.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "option.hpp"
|
||||
#include <span>
|
||||
@@ -129,9 +130,10 @@ struct PageEntry {
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu();
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Themezer"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
@@ -160,6 +162,9 @@ private:
|
||||
s64 m_index{}; // where i am in the array
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
ScrollingText m_scroll_name{};
|
||||
ScrollingText m_scroll_author{};
|
||||
|
||||
// options
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
||||
option::OptionLong m_order{INI_SECTION, "order", 0};
|
||||
|
||||
45
sphaira/include/ui/menus/usb_menu.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "yati/source/usb.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::usb {
|
||||
|
||||
enum class State {
|
||||
// not connected.
|
||||
None,
|
||||
// just connected, waiting for file list.
|
||||
Connected_WaitForFileList,
|
||||
// just connected, starts the transfer.
|
||||
Connected_StartingTransfer,
|
||||
// set whilst transfer is in progress.
|
||||
Progress,
|
||||
// set when the transfer is finished.
|
||||
Done,
|
||||
// failed to connect.
|
||||
Failed,
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "USB"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
// this should be private
|
||||
// private:
|
||||
std::shared_ptr<yati::source::Usb> m_usb_source{};
|
||||
bool m_was_mtp_enabled{};
|
||||
|
||||
Thread m_thread{};
|
||||
Mutex m_mutex{};
|
||||
// the below are shared across threads, lock with the above mutex!
|
||||
State m_state{State::None};
|
||||
std::vector<std::string> m_names{};
|
||||
bool m_usb_has_connection{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::usb
|
||||
@@ -2,24 +2,26 @@
|
||||
|
||||
#include "nanovg.h"
|
||||
#include "ui/types.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
|
||||
namespace sphaira::ui::gfx {
|
||||
|
||||
void drawImage(NVGcontext*, float x, float y, float w, float h, int texture);
|
||||
void drawImage(NVGcontext*, const Vec4& v, int texture);
|
||||
void drawImageRounded(NVGcontext*, float x, float y, float w, float h, int texture);
|
||||
void drawImageRounded(NVGcontext*, const Vec4& v, int texture);
|
||||
void drawImage(NVGcontext*, float x, float y, float w, float h, int texture, float rounded = 0.F);
|
||||
void drawImage(NVGcontext*, const Vec4& v, int texture, float rounded = 0.F);
|
||||
|
||||
void dimBackground(NVGcontext*);
|
||||
|
||||
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor& c, bool rounded = false);
|
||||
void drawRect(NVGcontext*, const Vec4& v, const NVGcolor& c, bool rounded = false);
|
||||
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint& p, bool rounded = false);
|
||||
void drawRect(NVGcontext*, const Vec4& v, const NVGpaint& p, bool rounded = false);
|
||||
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor& c, float rounding = 0.F);
|
||||
void drawRect(NVGcontext*, const Vec4& v, const NVGcolor& c, float rounding = 0.F);
|
||||
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint& p, float rounding = 0.F);
|
||||
void drawRect(NVGcontext*, const Vec4& v, const NVGpaint& p, float rounding = 0.F);
|
||||
|
||||
void drawRectOutline(NVGcontext*, const Theme*, float size, float x, float y, float w, float h);
|
||||
void drawRectOutline(NVGcontext*, const Theme*, float size, const Vec4& v);
|
||||
|
||||
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c);
|
||||
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p);
|
||||
|
||||
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c);
|
||||
void drawText(NVGcontext*, float x, float y, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
void drawText(NVGcontext*, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c);
|
||||
@@ -37,6 +39,8 @@ void drawScrollbar(NVGcontext*, const Theme*, float x, float y, float h, u32 ind
|
||||
void drawScrollbar2(NVGcontext*, const Theme*, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page);
|
||||
void drawScrollbar2(NVGcontext*, const Theme*, s64 index_off, s64 count, s64 row, s64 page);
|
||||
|
||||
void drawAppLable(NVGcontext* vg, const Theme*, ScrollingText& st, float x, float y, float w, const char* name);
|
||||
|
||||
void updateHighlightAnimation();
|
||||
void getHighlightAnimation(float* gradientX, float* gradientY, float* color);
|
||||
|
||||
|
||||
@@ -32,11 +32,9 @@ public:
|
||||
using Options = std::vector<Option>;
|
||||
|
||||
public:
|
||||
OptionBox(const std::string& message, const Option& a, Callback cb = [](auto){}); // confirm
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, Callback cb); // yesno
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb); // yesno
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, Callback cb); // tri
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, s64 index, Callback cb); // tri
|
||||
OptionBox(const std::string& message, const Option& a, Callback cb = [](auto){}, int image = 0); // confirm
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, Callback cb, int image = 0); // yesno
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb, int image = 0); // yesno
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
@@ -50,6 +48,7 @@ private:
|
||||
private:
|
||||
std::string m_message{};
|
||||
Callback m_callback{};
|
||||
int m_image{};
|
||||
|
||||
Vec4 m_spacer_line{};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include <optional>
|
||||
|
||||
@@ -36,8 +37,10 @@ private:
|
||||
Items m_items{};
|
||||
Callback m_callback{};
|
||||
s64 m_index{}; // index in list array
|
||||
s64 m_starting_index{};
|
||||
|
||||
std::unique_ptr<List> m_list{};
|
||||
ScrollingText m_scroll_text{};
|
||||
|
||||
float m_yoff{};
|
||||
float m_line_top{};
|
||||
|
||||
@@ -1,62 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include "widget.hpp"
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <functional>
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
struct ProgressBox;
|
||||
using ProgressBoxCallback = std::function<bool(ProgressBox*)>;
|
||||
using ProgressBoxDoneCallback = std::function<void(bool success)>;
|
||||
using ProgressBoxCallback = std::function<Result(ProgressBox*)>;
|
||||
using ProgressBoxDoneCallback = std::function<void(Result rc)>;
|
||||
|
||||
struct ProgressBox final : Widget {
|
||||
ProgressBox(
|
||||
int image,
|
||||
const std::string& action,
|
||||
const std::string& title,
|
||||
ProgressBoxCallback callback, ProgressBoxDoneCallback done = [](bool success){},
|
||||
int cpuid = 1, int prio = 0x2C, int stack_size = 1024*1024
|
||||
ProgressBoxCallback callback, ProgressBoxDoneCallback done = [](Result rc){},
|
||||
int cpuid = 1, int prio = 0x2C, int stack_size = 1024*128
|
||||
);
|
||||
~ProgressBox();
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
auto SetTitle(const std::string& title) -> ProgressBox&;
|
||||
auto NewTransfer(const std::string& transfer) -> ProgressBox&;
|
||||
auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;
|
||||
// not const in order to avoid copy by using std::swap
|
||||
auto SetImage(int image) -> ProgressBox&;
|
||||
auto SetImageData(std::vector<u8>& data) -> ProgressBox&;
|
||||
auto SetImageDataConst(std::span<const u8> data) -> ProgressBox&;
|
||||
|
||||
void RequestExit();
|
||||
auto ShouldExit() -> bool;
|
||||
auto ShouldExitResult() -> Result;
|
||||
|
||||
// helper functions
|
||||
auto CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||
auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||
void Yield();
|
||||
|
||||
auto GetCpuId() const {
|
||||
return m_cpuid;
|
||||
}
|
||||
|
||||
auto OnDownloadProgressCallback() {
|
||||
return [this](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){
|
||||
return [this](s64 dltotal, s64 dlnow, s64 ultotal, s64 ulnow){
|
||||
if (this->ShouldExit()) {
|
||||
return false;
|
||||
}
|
||||
this->UpdateTransfer(dlnow, dltotal);
|
||||
|
||||
if (dltotal) {
|
||||
this->UpdateTransfer(dlnow, dltotal);
|
||||
} else {
|
||||
this->UpdateTransfer(ulnow, ultotal);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
void FreeImage();
|
||||
|
||||
public:
|
||||
struct ThreadData {
|
||||
ProgressBox* pbox{};
|
||||
ProgressBoxCallback callback{};
|
||||
bool result{};
|
||||
Result result{};
|
||||
};
|
||||
|
||||
private:
|
||||
Mutex m_mutex{};
|
||||
Thread m_thread{};
|
||||
ThreadData m_thread_data{};
|
||||
|
||||
ProgressBoxDoneCallback m_done{};
|
||||
|
||||
// shared data start.
|
||||
std::string m_action{};
|
||||
std::string m_title{};
|
||||
std::string m_transfer{};
|
||||
s64 m_size{};
|
||||
s64 m_offset{};
|
||||
s64 m_last_offset{};
|
||||
s64 m_speed{};
|
||||
TimeStamp m_timestamp{};
|
||||
std::vector<u8> m_image_data{};
|
||||
int m_image_pending{};
|
||||
bool m_is_image_pending{};
|
||||
// shared data end.
|
||||
|
||||
ScrollingText m_scroll_title{};
|
||||
ScrollingText m_scroll_transfer{};
|
||||
|
||||
int m_cpuid{};
|
||||
int m_image{};
|
||||
bool m_own_image{};
|
||||
};
|
||||
|
||||
// this is a helper function that does many things.
|
||||
|
||||
20
sphaira/include/ui/scrolling_text.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
struct ScrollingText final {
|
||||
public:
|
||||
void Draw(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const std::string& text_entry);
|
||||
void DrawArgs(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const char* s, ...) __attribute__ ((format (printf, 10, 11)));
|
||||
void Reset(const std::string& text_entry = "");
|
||||
|
||||
private:
|
||||
std::string m_str;
|
||||
s64 m_tick;
|
||||
float m_text_xoff;
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
@@ -14,7 +14,7 @@ namespace sphaira {
|
||||
#define SCREEN_WIDTH 1280.f
|
||||
#define SCREEN_HEIGHT 720.f
|
||||
|
||||
struct [[nodiscard]] Vec2 {
|
||||
struct Vec2 {
|
||||
constexpr Vec2() = default;
|
||||
constexpr Vec2(float _x, float _y) : x{_x}, y{_y} {}
|
||||
|
||||
@@ -53,7 +53,7 @@ struct [[nodiscard]] Vec2 {
|
||||
float x{}, y{};
|
||||
};
|
||||
|
||||
struct [[nodiscard]] Vec4 {
|
||||
struct Vec4 {
|
||||
constexpr Vec4() = default;
|
||||
constexpr Vec4(float _x, float _y, float _w, float _h) : x{_x}, y{_y}, w{_w}, h{_h} {}
|
||||
constexpr Vec4(Vec2 vec0, Vec2 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.x}, h{vec1.y} {}
|
||||
@@ -226,7 +226,6 @@ struct ThemeMeta {
|
||||
|
||||
struct Theme {
|
||||
ThemeMeta meta;
|
||||
PLSR_BFSTM music;
|
||||
ElementEntry elements[ThemeEntryID_MAX];
|
||||
|
||||
auto GetColour(ThemeEntryID id) const {
|
||||
@@ -300,6 +299,7 @@ enum class Button : u64 {
|
||||
UP = static_cast<u64>(HidNpadButton_AnyUp),
|
||||
DOWN = static_cast<u64>(HidNpadButton_AnyDown),
|
||||
|
||||
NONE = 0,
|
||||
ANY_BUTTON = A | B | X | Y | L | R | L2 | R2 | L3 | R3 | START | SELECT,
|
||||
ANY_HORIZONTAL = LEFT | RIGHT,
|
||||
ANY_VERTICAL = UP | DOWN,
|
||||
|
||||
102
sphaira/include/usb/base.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <new>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb {
|
||||
|
||||
struct Base {
|
||||
enum { USBModule = 523 };
|
||||
|
||||
enum : Result {
|
||||
Result_Cancelled = MAKERESULT(USBModule, 100),
|
||||
};
|
||||
|
||||
Base(u64 transfer_timeout);
|
||||
virtual ~Base();
|
||||
|
||||
// sets up usb.
|
||||
virtual Result Init() = 0;
|
||||
|
||||
// returns 0 if usb is connected to a device.
|
||||
virtual Result IsUsbConnected(u64 timeout) = 0;
|
||||
|
||||
// transfers a chunk of data, check out_size_transferred for how much was transferred.
|
||||
Result TransferPacketImpl(bool read, void *page, u32 remaining, u32 size, u32 *out_size_transferred, u64 timeout);
|
||||
Result TransferPacketImpl(bool read, void *page, u32 remaining, u32 size, u32 *out_size_transferred) {
|
||||
return TransferPacketImpl(read, page, remaining, size, out_size_transferred, m_transfer_timeout);
|
||||
}
|
||||
|
||||
// transfers all data.
|
||||
Result TransferAll(bool read, void *data, u32 size, u64 timeout);
|
||||
Result TransferAll(bool read, void *data, u32 size) {
|
||||
return TransferAll(read, data, size, m_transfer_timeout);
|
||||
}
|
||||
|
||||
// returns the cancel event.
|
||||
auto GetCancelEvent() {
|
||||
return &m_uevent;
|
||||
}
|
||||
|
||||
// cancels an in progress transfer.
|
||||
void Cancel() {
|
||||
ueventSignal(GetCancelEvent());
|
||||
}
|
||||
|
||||
auto& GetTransferBuffer() {
|
||||
return m_aligned;
|
||||
}
|
||||
|
||||
auto GetTransferTimeout() const {
|
||||
return m_transfer_timeout;
|
||||
}
|
||||
|
||||
public:
|
||||
// custom allocator for std::vector that respects alignment.
|
||||
// https://en.cppreference.com/w/cpp/named_req/Allocator
|
||||
template <typename T, std::size_t Align>
|
||||
struct CustomVectorAllocator {
|
||||
public:
|
||||
// https://en.cppreference.com/w/cpp/memory/new/operator_new
|
||||
auto allocate(std::size_t n) -> T* {
|
||||
n = (n + (Align - 1)) &~ (Align - 1);
|
||||
return new(align) T[n];
|
||||
}
|
||||
|
||||
// https://en.cppreference.com/w/cpp/memory/new/operator_delete
|
||||
auto deallocate(T* p, std::size_t n) noexcept -> void {
|
||||
// ::operator delete[] (p, n, align);
|
||||
::operator delete[] (p, align);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr inline std::align_val_t align{Align};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct PageAllocator : CustomVectorAllocator<T, 0x1000> {
|
||||
using value_type = T; // used by std::vector
|
||||
};
|
||||
|
||||
using PageAlignedVector = std::vector<u8, PageAllocator<u8>>;
|
||||
|
||||
protected:
|
||||
enum UsbSessionEndpoint {
|
||||
UsbSessionEndpoint_In = 0,
|
||||
UsbSessionEndpoint_Out = 1,
|
||||
};
|
||||
|
||||
virtual Event *GetCompletionEvent(UsbSessionEndpoint ep) = 0;
|
||||
virtual Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) = 0;
|
||||
virtual Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_xfer_id) = 0;
|
||||
virtual Result GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) = 0;
|
||||
|
||||
private:
|
||||
u64 m_transfer_timeout{};
|
||||
UEvent m_uevent{};
|
||||
PageAlignedVector m_aligned{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb
|
||||
57
sphaira/include/usb/tinfoil.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb::tinfoil {
|
||||
|
||||
enum Magic : u32 {
|
||||
Magic_List0 = 0x304C5554, // TUL0 (Tinfoil Usb List 0)
|
||||
Magic_Command0 = 0x30435554, // TUC0 (Tinfoil USB Command 0)
|
||||
};
|
||||
|
||||
enum USBCmdType : u8 {
|
||||
REQUEST = 0,
|
||||
RESPONSE = 1
|
||||
};
|
||||
|
||||
enum USBCmdId : u32 {
|
||||
EXIT = 0,
|
||||
FILE_RANGE = 1
|
||||
};
|
||||
|
||||
// extension flags for sphaira.
|
||||
enum USBFlag : u8 {
|
||||
USBFlag_NONE = 0,
|
||||
// stream install, does not allow for random access.
|
||||
// allows the upload to be multi threaded., do not modify!
|
||||
// the order of the file list must be kept as-is.
|
||||
USBFlag_STREAM = 1 << 0,
|
||||
};
|
||||
|
||||
struct TUSHeader {
|
||||
u32 magic; // TUL0 (Tinfoil Usb List 0)
|
||||
u32 nspListSize;
|
||||
u8 flags;
|
||||
u8 padding[0x7];
|
||||
};
|
||||
|
||||
struct NX_PACKED USBCmdHeader {
|
||||
u32 magic; // TUC0 (Tinfoil USB Command 0)
|
||||
USBCmdType type;
|
||||
u8 padding[0x3];
|
||||
u32 cmdId;
|
||||
u64 dataSize;
|
||||
u8 reserved[0xC];
|
||||
};
|
||||
|
||||
struct FileRangeCmdHeader {
|
||||
u64 size;
|
||||
u64 offset;
|
||||
u64 nspNameLen;
|
||||
u64 padding;
|
||||
};
|
||||
|
||||
static_assert(sizeof(TUSHeader) == 0x10, "TUSHeader must be 0x10!");
|
||||
static_assert(sizeof(USBCmdHeader) == 0x20, "USBCmdHeader must be 0x20!");
|
||||
|
||||
} // namespace sphaira::usb::tinfoil
|
||||
47
sphaira/include/usb/usb_uploader.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "usb/usbhs.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb::upload {
|
||||
|
||||
struct Usb {
|
||||
enum { USBModule = 523 };
|
||||
|
||||
enum : Result {
|
||||
Result_BadMagic = MAKERESULT(USBModule, 0),
|
||||
Result_Exit = MAKERESULT(USBModule, 1),
|
||||
Result_BadCount = MAKERESULT(USBModule, 2),
|
||||
Result_BadTransferSize = MAKERESULT(USBModule, 3),
|
||||
Result_BadTotalSize = MAKERESULT(USBModule, 4),
|
||||
Result_BadCommand = MAKERESULT(USBModule, 4),
|
||||
};
|
||||
|
||||
Usb(u64 transfer_timeout);
|
||||
virtual ~Usb();
|
||||
|
||||
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
|
||||
Result IsUsbConnected(u64 timeout) {
|
||||
return m_usb->IsUsbConnected(timeout);
|
||||
}
|
||||
|
||||
// waits for connection and then sends file list.
|
||||
Result WaitForConnection(u64 timeout, u8 flags, std::span<const std::string> names);
|
||||
|
||||
// polls for command, executes transfer if possible.
|
||||
// will return Result_Exit if exit command is recieved.
|
||||
Result PollCommands();
|
||||
|
||||
private:
|
||||
Result FileRangeCmd(u64 data_size);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbHs> m_usb;
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb::upload
|
||||
29
sphaira/include/usb/usbds.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.hpp"
|
||||
|
||||
namespace sphaira::usb {
|
||||
|
||||
// Device Host
|
||||
struct UsbDs final : Base {
|
||||
using Base::Base;
|
||||
~UsbDs();
|
||||
|
||||
Result Init() override;
|
||||
Result IsUsbConnected(u64 timeout) override;
|
||||
Result GetSpeed(UsbDeviceSpeed* out, u16* max_packet_size);
|
||||
|
||||
private:
|
||||
Result WaitUntilConfigured(u64 timeout);
|
||||
Event *GetCompletionEvent(UsbSessionEndpoint ep) override;
|
||||
Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) override;
|
||||
Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_urb_id) override;
|
||||
Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) override;
|
||||
|
||||
private:
|
||||
UsbDsInterface* m_interface{};
|
||||
UsbDsEndpoint* m_endpoints[2]{};
|
||||
u16 m_max_packet_size{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb
|
||||
33
sphaira/include/usb/usbhs.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.hpp"
|
||||
|
||||
namespace sphaira::usb {
|
||||
|
||||
struct UsbHs final : Base {
|
||||
UsbHs(u8 index, const UsbHsInterfaceFilter& filter, u64 transfer_timeout);
|
||||
~UsbHs();
|
||||
|
||||
Result Init() override;
|
||||
Result IsUsbConnected(u64 timeout) override;
|
||||
|
||||
private:
|
||||
Event *GetCompletionEvent(UsbSessionEndpoint ep) override;
|
||||
Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) override;
|
||||
Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_xfer_id) override;
|
||||
Result GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) override;
|
||||
|
||||
Result Connect();
|
||||
void Close();
|
||||
|
||||
private:
|
||||
u8 m_index{};
|
||||
UsbHsInterfaceFilter m_filter{};
|
||||
UsbHsInterface m_interface{};
|
||||
UsbHsClientIfSession m_s{};
|
||||
UsbHsClientEpSession m_endpoints[2]{};
|
||||
Event m_event{};
|
||||
bool m_connected{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include <string>
|
||||
|
||||
namespace sphaira {
|
||||
|
||||
// if show_error = true, it will display popup error box on
|
||||
// faliure. set this to false if you want to handle errors
|
||||
// from the caller.
|
||||
auto WebShow(const std::string& url, bool show_error = true) -> Result;
|
||||
|
||||
} // namespace sphaira
|
||||
43
sphaira/include/yati/container/base.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "yati/source/base.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::yati::container {
|
||||
|
||||
enum class CollectionType {
|
||||
CollectionType_NCA,
|
||||
CollectionType_NCZ,
|
||||
CollectionType_TIK,
|
||||
CollectionType_CERT,
|
||||
};
|
||||
|
||||
struct CollectionEntry {
|
||||
// collection name within file.
|
||||
std::string name{};
|
||||
// collection offset within file.
|
||||
s64 offset{};
|
||||
// collection size within file, may be compressed size.
|
||||
s64 size{};
|
||||
};
|
||||
|
||||
using Collections = std::vector<CollectionEntry>;
|
||||
|
||||
struct Base {
|
||||
using Source = source::Base;
|
||||
|
||||
Base(std::shared_ptr<Source> source) : m_source{source} { }
|
||||
virtual ~Base() = default;
|
||||
virtual Result GetCollections(Collections& out) = 0;
|
||||
auto GetSource() const {
|
||||
return m_source;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Source> m_source;
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
17
sphaira/include/yati/container/nsp.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.hpp"
|
||||
#include <switch.h>
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::yati::container {
|
||||
|
||||
struct Nsp final : Base {
|
||||
using Base::Base;
|
||||
Result GetCollections(Collections& out) override;
|
||||
|
||||
// builds nsp meta data and the size of the entier nsp.
|
||||
static auto Build(std::span<CollectionEntry> collections, s64& size) -> std::vector<u8>;
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
15
sphaira/include/yati/container/xci.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.hpp"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::yati::container {
|
||||
|
||||
struct Xci final : Base {
|
||||
using Base::Base;
|
||||
Result GetCollections(Collections& out) override;
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
57
sphaira/include/yati/nx/crypto.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::crypto {
|
||||
|
||||
struct Aes128 {
|
||||
Aes128(const void *key, bool is_encryptor) {
|
||||
m_is_encryptor = is_encryptor;
|
||||
aes128ContextCreate(&m_ctx, key, is_encryptor);
|
||||
}
|
||||
|
||||
void Run(void *dst, const void *src) {
|
||||
if (m_is_encryptor) {
|
||||
aes128EncryptBlock(&m_ctx, dst, src);
|
||||
} else {
|
||||
aes128DecryptBlock(&m_ctx, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Aes128Context m_ctx;
|
||||
bool m_is_encryptor;
|
||||
};
|
||||
|
||||
struct Aes128Xts {
|
||||
Aes128Xts(const u8 *key, bool is_encryptor) : Aes128Xts{key, key + 0x10, is_encryptor} { }
|
||||
Aes128Xts(const void *key0, const void *key1, bool is_encryptor) {
|
||||
m_is_encryptor = is_encryptor;
|
||||
aes128XtsContextCreate(&m_ctx, key0, key1, is_encryptor);
|
||||
}
|
||||
|
||||
void Run(void *dst, const void *src, u64 sector, u64 sector_size, u64 data_size) {
|
||||
for (u64 pos = 0; pos < data_size; pos += sector_size) {
|
||||
aes128XtsContextResetSector(&m_ctx, sector++, true);
|
||||
if (m_is_encryptor) {
|
||||
aes128XtsEncrypt(&m_ctx, static_cast<u8*>(dst) + pos, static_cast<const u8*>(src) + pos, sector_size);
|
||||
} else {
|
||||
aes128XtsDecrypt(&m_ctx, static_cast<u8*>(dst) + pos, static_cast<const u8*>(src) + pos, sector_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Aes128XtsContext m_ctx;
|
||||
bool m_is_encryptor;
|
||||
};
|
||||
|
||||
static inline void cryptoAes128(const void *in, void *out, const void* key, bool is_encryptor) {
|
||||
Aes128(key, is_encryptor).Run(out, in);
|
||||
}
|
||||
|
||||
static inline void cryptoAes128Xts(const void* in, void* out, const u8* key, u64 sector, u64 sector_size, u64 data_size, bool is_encryptor) {
|
||||
Aes128Xts(key, is_encryptor).Run(out, in, sector, sector_size, data_size);
|
||||
}
|
||||
|
||||
} // namespace sphaira::crypto
|
||||
101
sphaira/include/yati/nx/es.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include <span>
|
||||
#include "ncm.hpp"
|
||||
#include "keys.hpp"
|
||||
|
||||
namespace sphaira::es {
|
||||
|
||||
enum { TicketModule = 522 };
|
||||
|
||||
enum : Result {
|
||||
// found ticket has missmatching rights_id from it's name.
|
||||
Result_InvalidTicketBadRightsId = MAKERESULT(TicketModule, 71),
|
||||
Result_InvalidTicketVersion = MAKERESULT(TicketModule, 72),
|
||||
Result_InvalidTicketKeyType = MAKERESULT(TicketModule, 73),
|
||||
Result_InvalidTicketKeyRevision = MAKERESULT(TicketModule, 74),
|
||||
};
|
||||
|
||||
enum TicketSigantureType {
|
||||
TicketSigantureType_RSA_4096_SHA1 = 0x010000,
|
||||
TicketSigantureType_RSA_2048_SHA1 = 0x010001,
|
||||
TicketSigantureType_ECDSA_SHA1 = 0x010002,
|
||||
TicketSigantureType_RSA_4096_SHA256 = 0x010003,
|
||||
TicketSigantureType_RSA_2048_SHA256 = 0x010004,
|
||||
TicketSigantureType_ECDSA_SHA256 = 0x010005,
|
||||
TicketSigantureType_HMAC_SHA1_160 = 0x010006,
|
||||
};
|
||||
|
||||
enum TicketTitleKeyType {
|
||||
TicketTitleKeyType_Common = 0,
|
||||
TicketTitleKeyType_Personalized = 1,
|
||||
};
|
||||
|
||||
enum TicketPropertiesBitfield {
|
||||
TicketPropertiesBitfield_None = 0,
|
||||
// temporary ticket, removed on restart
|
||||
TicketPropertiesBitfield_Temporary = 1 << 4,
|
||||
};
|
||||
|
||||
struct TicketData {
|
||||
u8 issuer[0x40];
|
||||
u8 title_key_block[0x100];
|
||||
u8 ticket_version1;
|
||||
u8 title_key_type;
|
||||
u16 ticket_version2;
|
||||
u8 license_type;
|
||||
u8 master_key_revision;
|
||||
u16 properties_bitfield;
|
||||
u8 _0x148[0x8];
|
||||
u64 ticket_id;
|
||||
u64 device_id;
|
||||
FsRightsId rights_id;
|
||||
u32 account_id;
|
||||
u8 _0x174[0xC];
|
||||
u8 _0x180[0x140];
|
||||
};
|
||||
static_assert(sizeof(TicketData) == 0x2C0);
|
||||
|
||||
struct EticketRsaDeviceKey {
|
||||
u8 ctr[AES_128_KEY_SIZE];
|
||||
u8 private_exponent[0x100];
|
||||
u8 modulus[0x100];
|
||||
u32 public_exponent; ///< Stored using big endian byte order. Must match ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT.
|
||||
u8 padding[0x14];
|
||||
u64 device_id;
|
||||
u8 ghash[0x10];
|
||||
};
|
||||
static_assert(sizeof(EticketRsaDeviceKey) == 0x240);
|
||||
|
||||
// es functions.
|
||||
Result Initialize();
|
||||
void Exit();
|
||||
Service* GetServiceSession();
|
||||
|
||||
// todo: find the ipc that gets personalised tickets.
|
||||
// todo: if ipc doesn't exist, manually parse es personalised save.
|
||||
// todo: add personalised -> common ticket conversion.
|
||||
// todo: make the above an option for both dump and install.
|
||||
|
||||
Result ImportTicket(const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size);
|
||||
Result CountCommonTicket(s32* count);
|
||||
Result CountPersonalizedTicket(s32* count);
|
||||
Result ListCommonTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count);
|
||||
Result ListPersonalizedTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count);
|
||||
Result ListMissingPersonalizedTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count); // untested
|
||||
Result GetCommonTicketSize(u64 *size_out, const FsRightsId* rightsId);
|
||||
Result GetCommonTicketData(u64 *size_out, void *tik_data, u64 tik_size, const FsRightsId* rightsId);
|
||||
Result GetCommonTicketAndCertificateSize(u64 *tik_size_out, u64 *cert_size_out, const FsRightsId* rightsId); // [4.0.0+]
|
||||
Result GetCommonTicketAndCertificateData(u64 *tik_size_out, u64 *cert_size_out, void* tik_buf, u64 tik_size, void* cert_buf, u64 cert_size, const FsRightsId* rightsId); // [4.0.0+]
|
||||
|
||||
// ticket functions.
|
||||
Result GetTicketDataOffset(std::span<const u8> ticket, u64& out);
|
||||
Result GetTicketData(std::span<const u8> ticket, es::TicketData* out);
|
||||
Result SetTicketData(std::span<u8> ticket, const es::TicketData* in);
|
||||
|
||||
Result GetTitleKey(keys::KeyEntry& out, const TicketData& data, const keys::Keys& keys);
|
||||
Result DecryptTitleKey(keys::KeyEntry& out, u8 key_gen, const keys::Keys& keys);
|
||||
Result PatchTicket(std::span<u8> ticket, const keys::Keys& keys);
|
||||
|
||||
} // namespace sphaira::es
|
||||
70
sphaira/include/yati/nx/keys.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace sphaira::keys {
|
||||
|
||||
struct KeyEntry {
|
||||
u8 key[AES_128_KEY_SIZE]{};
|
||||
|
||||
auto IsValid() const -> bool {
|
||||
const KeyEntry empty{};
|
||||
return std::memcmp(key, &empty, sizeof(key));
|
||||
}
|
||||
};
|
||||
|
||||
using KeySection = std::array<KeyEntry, 0x20>;
|
||||
struct Keys {
|
||||
u8 header_key[0x20]{};
|
||||
// the below are only found if read_from_file=true
|
||||
KeySection key_area_key[0x3]{}; // index
|
||||
KeySection titlekek{};
|
||||
KeySection master_key{};
|
||||
KeyEntry eticket_rsa_kek{};
|
||||
SetCalRsa2048DeviceKey eticket_device_key{};
|
||||
|
||||
static auto FixKey(u8 key) -> u8 {
|
||||
if (key) {
|
||||
return key - 1;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
auto HasNcaKeyArea(u8 key, u8 index) const -> bool {
|
||||
return key_area_key[index][FixKey(key)].IsValid();
|
||||
}
|
||||
|
||||
auto HasTitleKek(u8 key) const -> bool {
|
||||
return titlekek[FixKey(key)].IsValid();
|
||||
}
|
||||
|
||||
auto HasMasterKey(u8 key) const -> bool {
|
||||
return master_key[FixKey(key)].IsValid();
|
||||
}
|
||||
|
||||
auto GetNcaKeyArea(KeyEntry* out, u8 key, u8 index) const -> Result {
|
||||
R_UNLESS(HasNcaKeyArea(key, index), 0x1);
|
||||
*out = key_area_key[index][FixKey(key)];
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
auto GetTitleKek(KeyEntry* out, u8 key) const -> Result {
|
||||
R_UNLESS(HasTitleKek(key), 0x1);
|
||||
*out = titlekek[FixKey(key)];
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
auto GetMasterKey(KeyEntry* out, u8 key) const -> Result {
|
||||
R_UNLESS(HasMasterKey(key), 0x1);
|
||||
*out = master_key[FixKey(key)];
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
void parse_hex_key(void* key, const char* hex);
|
||||
Result parse_keys(Keys& out, bool read_from_file);
|
||||
|
||||
} // namespace sphaira::keys
|
||||
228
sphaira/include/yati/nx/nca.hpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "keys.hpp"
|
||||
#include "ncm.hpp"
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
|
||||
namespace sphaira::nca {
|
||||
|
||||
#define NCA0_MAGIC 0x3041434E
|
||||
#define NCA2_MAGIC 0x3241434E
|
||||
#define NCA3_MAGIC 0x3341434E
|
||||
|
||||
#define NCA_SECTOR_SIZE 0x200
|
||||
#define NCA_XTS_SECTION_SIZE 0xC00
|
||||
#define NCA_SECTION_TOTAL 0x4
|
||||
#define NCA_MEDIA_REAL(x)((x * 0x200))
|
||||
|
||||
#define NCA_PROGRAM_LOGO_OFFSET 0x8000
|
||||
#define NCA_META_CNMT_OFFSET 0xC20
|
||||
|
||||
enum KeyGenerationOld {
|
||||
KeyGenerationOld_100 = 0x0,
|
||||
KeyGenerationOld_Unused = 0x1,
|
||||
KeyGenerationOld_300 = 0x2,
|
||||
};
|
||||
|
||||
enum KeyGeneration {
|
||||
KeyGeneration_301 = 0x3,
|
||||
KeyGeneration_400 = 0x4,
|
||||
KeyGeneration_500 = 0x5,
|
||||
KeyGeneration_600 = 0x6,
|
||||
KeyGeneration_620 = 0x7,
|
||||
KeyGeneration_700 = 0x8,
|
||||
KeyGeneration_810 = 0x9,
|
||||
KeyGeneration_900 = 0x0A,
|
||||
KeyGeneration_910 = 0x0B,
|
||||
KeyGeneration_1210 = 0x0C,
|
||||
KeyGeneration_1300 = 0x0D,
|
||||
KeyGeneration_1400 = 0x0E,
|
||||
KeyGeneration_1500 = 0x0F,
|
||||
KeyGeneration_1600 = 0x10,
|
||||
KeyGeneration_1700 = 0x11,
|
||||
KeyGeneration_1800 = 0x12,
|
||||
KeyGeneration_1900 = 0x13,
|
||||
KeyGeneration_2000 = 0x14,
|
||||
KeyGeneration_Invalid = 0xFF,
|
||||
};
|
||||
|
||||
enum KeyAreaEncryptionKeyIndex {
|
||||
KeyAreaEncryptionKeyIndex_Application = 0x0,
|
||||
KeyAreaEncryptionKeyIndex_Ocean = 0x1,
|
||||
KeyAreaEncryptionKeyIndex_System = 0x2
|
||||
};
|
||||
|
||||
enum DistributionType {
|
||||
DistributionType_System = 0x0,
|
||||
DistributionType_GameCard = 0x1
|
||||
};
|
||||
|
||||
enum ContentType {
|
||||
ContentType_Program = 0x0,
|
||||
ContentType_Meta = 0x1,
|
||||
ContentType_Control = 0x2,
|
||||
ContentType_Manual = 0x3,
|
||||
ContentType_Data = 0x4,
|
||||
ContentType_PublicData = 0x5,
|
||||
};
|
||||
|
||||
enum FileSystemType {
|
||||
FileSystemType_RomFS = 0x0,
|
||||
FileSystemType_PFS0 = 0x1
|
||||
};
|
||||
|
||||
enum HashType {
|
||||
HashType_Auto = 0x0,
|
||||
HashType_HierarchicalSha256 = 0x2,
|
||||
HashType_HierarchicalIntegrity = 0x3
|
||||
};
|
||||
|
||||
enum EncryptionType {
|
||||
EncryptionType_Auto = 0x0,
|
||||
EncryptionType_None = 0x1,
|
||||
EncryptionType_AesXts = 0x2,
|
||||
EncryptionType_AesCtr = 0x3,
|
||||
EncryptionType_AesCtrEx = 0x4,
|
||||
EncryptionType_AesCtrSkipLayerHash = 0x5, // [14.0.0+]
|
||||
EncryptionType_AesCtrExSkipLayerHash = 0x6, // [14.0.0+]
|
||||
};
|
||||
|
||||
struct SectionTableEntry {
|
||||
u32 media_start_offset; // divided by 0x200.
|
||||
u32 media_end_offset; // divided by 0x200.
|
||||
u8 _0x8[0x4]; // unknown.
|
||||
u8 _0xC[0x4]; // unknown.
|
||||
};
|
||||
|
||||
struct LayerRegion {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct HierarchicalSha256Data {
|
||||
u8 master_hash[0x20];
|
||||
u32 block_size;
|
||||
u32 layer_count;
|
||||
LayerRegion hash_layer;
|
||||
LayerRegion pfs0_layer;
|
||||
LayerRegion unused_layers[3];
|
||||
u8 _0x78[0x80];
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct HierarchicalIntegrityVerificationLevelInformation {
|
||||
u64 logical_offset;
|
||||
u64 hash_data_size;
|
||||
u32 block_size; // log2
|
||||
u32 _0x14; // reserved
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct InfoLevelHash {
|
||||
u32 max_layers;
|
||||
HierarchicalIntegrityVerificationLevelInformation levels[6];
|
||||
u8 signature_salt[0x20];
|
||||
};
|
||||
|
||||
struct IntegrityMetaInfo {
|
||||
u32 magic; // IVFC
|
||||
u32 version;
|
||||
u32 master_hash_size;
|
||||
InfoLevelHash info_level_hash;
|
||||
u8 master_hash[0x20];
|
||||
u8 _0xE0[0x18];
|
||||
};
|
||||
|
||||
static_assert(sizeof(HierarchicalSha256Data) == 0xF8);
|
||||
static_assert(sizeof(IntegrityMetaInfo) == 0xF8);
|
||||
static_assert(sizeof(HierarchicalSha256Data) == sizeof(IntegrityMetaInfo));
|
||||
|
||||
struct FsHeader {
|
||||
u16 version; // always 2.
|
||||
u8 fs_type; // see FileSystemType.
|
||||
u8 hash_type; // see HashType.
|
||||
u8 encryption_type; // see EncryptionType.
|
||||
u8 metadata_hash_type;
|
||||
u8 _0x6[0x2]; // empty.
|
||||
|
||||
union {
|
||||
HierarchicalSha256Data hierarchical_sha256_data;
|
||||
IntegrityMetaInfo integrity_meta_info; // used for romfs
|
||||
} hash_data;
|
||||
|
||||
u8 patch_info[0x40];
|
||||
u64 section_ctr;
|
||||
u8 spares_info[0x30];
|
||||
u8 compression_info[0x28];
|
||||
u8 meta_data_hash_data_info[0x30];
|
||||
u8 reserved[0x30];
|
||||
};
|
||||
static_assert(sizeof(FsHeader) == 0x200);
|
||||
static_assert(sizeof(FsHeader::hash_data) == 0xF8);
|
||||
|
||||
struct SectionHeaderHash {
|
||||
u8 sha256[0x20];
|
||||
};
|
||||
|
||||
struct KeyArea {
|
||||
u8 area[0x10];
|
||||
};
|
||||
|
||||
struct Header {
|
||||
u8 rsa_fixed_key[0x100];
|
||||
u8 rsa_npdm[0x100]; // key from npdm.
|
||||
u32 magic;
|
||||
u8 distribution_type; // see DistributionType.
|
||||
u8 content_type; // see ContentType.
|
||||
u8 old_key_gen; // see KeyGenerationOld.
|
||||
u8 kaek_index; // see KeyAreaEncryptionKeyIndex.
|
||||
u64 size;
|
||||
u64 program_id;
|
||||
u32 context_id;
|
||||
u32 sdk_version;
|
||||
u8 key_gen; // see KeyGeneration.
|
||||
u8 sig_key_gen;
|
||||
u8 _0x222[0xE]; // empty.
|
||||
FsRightsId rights_id;
|
||||
|
||||
SectionTableEntry fs_table[NCA_SECTION_TOTAL];
|
||||
SectionHeaderHash fs_header_hash[NCA_SECTION_TOTAL];
|
||||
KeyArea key_area[NCA_SECTION_TOTAL];
|
||||
|
||||
u8 _0x340[0xC0]; // empty.
|
||||
|
||||
FsHeader fs_header[NCA_SECTION_TOTAL];
|
||||
|
||||
auto GetKeyGeneration() const -> u8 {
|
||||
if (old_key_gen < key_gen) {
|
||||
return key_gen;
|
||||
} else {
|
||||
return old_key_gen;
|
||||
}
|
||||
}
|
||||
|
||||
void SetKeyGeneration(u8 key_generation) {
|
||||
if (key_generation <= 0x2) {
|
||||
old_key_gen = key_generation;
|
||||
key_gen = 0;
|
||||
} else {
|
||||
old_key_gen = 0x2;
|
||||
key_gen = key_generation;
|
||||
}
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Header) == 0xC00);
|
||||
|
||||
Result DecryptKeak(const keys::Keys& keys, Header& header);
|
||||
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation);
|
||||
Result VerifyFixedKey(const Header& header);
|
||||
|
||||
// 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 ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out = nullptr, s64 nacp_size = 0, std::vector<u8>* icon_out = nullptr);
|
||||
|
||||
auto GetKeyGenStr(u8 key_gen) -> const char*;
|
||||
|
||||
} // namespace sphaira::nca
|
||||
47
sphaira/include/yati/nx/ncm.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::ncm {
|
||||
|
||||
struct PackagedContentMeta {
|
||||
u64 title_id;
|
||||
u32 title_version;
|
||||
u8 meta_type; // NcmContentMetaType
|
||||
u8 content_meta_platform; // [17.0.0+]
|
||||
NcmContentMetaHeader meta_header;
|
||||
u8 install_type; // NcmContentInstallType
|
||||
u8 _0x17;
|
||||
u32 required_sys_version;
|
||||
u8 _0x1C[0x4];
|
||||
};
|
||||
static_assert(sizeof(PackagedContentMeta) == 0x20);
|
||||
|
||||
struct ContentStorageRecord {
|
||||
NcmContentMetaKey key;
|
||||
u8 storage_id; // NcmStorageId
|
||||
u8 padding[0x7];
|
||||
};
|
||||
|
||||
union ExtendedHeader {
|
||||
NcmApplicationMetaExtendedHeader application;
|
||||
NcmPatchMetaExtendedHeader patch;
|
||||
NcmAddOnContentMetaExtendedHeader addon;
|
||||
NcmLegacyAddOnContentMetaExtendedHeader addon_legacy;
|
||||
NcmDataPatchMetaExtendedHeader data_patch;
|
||||
};
|
||||
|
||||
auto GetMetaTypeStr(u8 meta_type) -> const char*;
|
||||
auto GetStorageIdStr(u8 storage_id) -> const char*;
|
||||
auto GetMetaTypeShortStr(u8 meta_type) -> const char*;
|
||||
|
||||
auto GetAppId(u8 meta_type, u64 id) -> u64;
|
||||
auto GetAppId(const NcmContentMetaKey& key) -> u64;
|
||||
auto GetAppId(const PackagedContentMeta& meta) -> u64;
|
||||
|
||||
auto GetContentIdFromStr(const char* str) -> NcmContentId;
|
||||
|
||||
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id);
|
||||
Result Register(NcmContentStorage* cs, const NcmContentId *content_id, const NcmPlaceHolderId *placeholder_id);
|
||||
|
||||
} // namespace sphaira::ncm
|
||||
54
sphaira/include/yati/nx/ncz.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::ncz {
|
||||
|
||||
#define NCZ_SECTION_MAGIC 0x4E544345535A434EUL
|
||||
// todo: byteswap this
|
||||
#define NCZ_BLOCK_MAGIC std::byteswap(0x4E435A424C4F434BUL)
|
||||
|
||||
#define NCZ_SECTION_OFFSET (0x4000 + sizeof(ncz::Header))
|
||||
|
||||
struct Header {
|
||||
u64 magic; // NCZ_SECTION_MAGIC
|
||||
u64 total_sections;
|
||||
};
|
||||
|
||||
struct BlockHeader {
|
||||
u64 magic; // NCZ_BLOCK_MAGIC
|
||||
u8 version;
|
||||
u8 type;
|
||||
u8 padding;
|
||||
u8 block_size_exponent;
|
||||
u32 total_blocks;
|
||||
u64 decompressed_size;
|
||||
};
|
||||
|
||||
struct Block {
|
||||
u32 size;
|
||||
};
|
||||
|
||||
struct BlockInfo {
|
||||
u64 offset; // compressed offset.
|
||||
u64 size; // compressed size.
|
||||
|
||||
auto InRange(u64 off) const -> bool {
|
||||
return off < offset + size && off >= offset;
|
||||
}
|
||||
};
|
||||
|
||||
struct Section {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u64 crypto_type;
|
||||
u64 padding;
|
||||
u8 key[0x10];
|
||||
u8 counter[0x10];
|
||||
|
||||
auto InRange(u64 off) const -> bool {
|
||||
return off < offset + size && off >= offset;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sphaira::ncz
|
||||
62
sphaira/include/yati/nx/npdm.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::npdm {
|
||||
|
||||
struct Meta {
|
||||
u32 magic; // "META"
|
||||
u32 signature_key_generation; // +9.0.0
|
||||
u32 _0x8;
|
||||
u8 flags;
|
||||
u8 _0xD;
|
||||
u8 main_thread_priority;
|
||||
u8 main_thread_core_num;
|
||||
u32 _0x10;
|
||||
u32 sys_resource_size; // +3.0.0
|
||||
u32 version;
|
||||
u32 main_thread_stack_size;
|
||||
char title_name[0x10];
|
||||
char product_code[0x10];
|
||||
u8 _0x40[0x30];
|
||||
u32 aci0_offset;
|
||||
u32 aci0_size;
|
||||
u32 acid_offset;
|
||||
u32 acid_size;
|
||||
};
|
||||
|
||||
struct Acid {
|
||||
u8 rsa_sig[0x100];
|
||||
u8 rsa_pub[0x100];
|
||||
u32 magic; // "ACID"
|
||||
u32 size;
|
||||
u8 version;
|
||||
u8 _0x209[0x1];
|
||||
u8 _0x20A[0x2];
|
||||
u32 flags;
|
||||
u64 program_id_min;
|
||||
u64 program_id_max;
|
||||
u32 fac_offset;
|
||||
u32 fac_size;
|
||||
u32 sac_offset;
|
||||
u32 sac_size;
|
||||
u32 kac_offset;
|
||||
u32 kac_size;
|
||||
u8 _0x238[0x8];
|
||||
};
|
||||
|
||||
struct Aci0 {
|
||||
u32 magic; // "ACI0"
|
||||
u8 _0x4[0xC];
|
||||
u64 program_id;
|
||||
u8 _0x18[0x8];
|
||||
u32 fac_offset;
|
||||
u32 fac_size;
|
||||
u32 sac_offset;
|
||||
u32 sac_size;
|
||||
u32 kac_offset;
|
||||
u32 kac_size;
|
||||
u8 _0x38[0x8];
|
||||
};
|
||||
|
||||
} // namespace sphaira::npdm
|
||||
22
sphaira/include/yati/nx/ns.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "ncm.hpp"
|
||||
|
||||
namespace sphaira::ns {
|
||||
|
||||
enum ApplicationRecordType {
|
||||
// installed
|
||||
ApplicationRecordType_Installed = 0x3,
|
||||
// application is gamecard, but gamecard isn't insterted
|
||||
ApplicationRecordType_GamecardMissing = 0x5,
|
||||
// archived
|
||||
ApplicationRecordType_Archived = 0xB,
|
||||
};
|
||||
|
||||
Result PushApplicationRecord(Service* srv, u64 tid, const ncm::ContentStorageRecord* records, u32 count);
|
||||
Result ListApplicationRecordContentMeta(Service* srv, u64 offset, u64 tid, ncm::ContentStorageRecord* out_records, u32 count, s32* entries_read);
|
||||
Result DeleteApplicationRecord(Service* srv, u64 tid);
|
||||
Result InvalidateApplicationControlCache(Service* srv, u64 tid);
|
||||
|
||||
} // namespace sphaira::ns
|
||||
62
sphaira/include/yati/nx/nxdumptool_rsa.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* rsa.c
|
||||
*
|
||||
* Copyright (c) 2018-2019, SciresM.
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __RSA_H__
|
||||
#define __RSA_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define RSA2048_BYTES 0x100
|
||||
#define RSA2048_BITS (RSA2048_BYTES * 8)
|
||||
|
||||
#define RSA2048_SIG_SIZE RSA2048_BYTES
|
||||
#define RSA2048_PUBKEY_SIZE RSA2048_BYTES
|
||||
|
||||
/// Verifies a RSA-2048-PSS with SHA-256 signature.
|
||||
/// Suitable for NCA and NPDM signatures.
|
||||
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size);
|
||||
|
||||
/// Verifies a RSA-2048-PKCS#1 v1.5 with SHA-256 signature.
|
||||
/// Suitable for ticket and certificate chain signatures.
|
||||
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
bool rsa2048VerifySha256BasedPkcs1v15Signature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size);
|
||||
|
||||
/// Performs RSA-2048-OAEP decryption.
|
||||
/// Suitable to decrypt the titlekey block from personalized tickets.
|
||||
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
/// 'label' and 'label_size' arguments are optional -- if not needed, these may be set to NULL and 0, respectively.
|
||||
bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \
|
||||
size_t private_exponent_size, const void *label, size_t label_size, size_t *out_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __RSA_H__ */
|
||||
56
sphaira/include/yati/nx/service_guard.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
#include <switch/types.h>
|
||||
#include <switch/result.h>
|
||||
#include <switch/kernel/mutex.h>
|
||||
#include <switch/sf/service.h>
|
||||
#include <switch/services/sm.h>
|
||||
|
||||
typedef struct ServiceGuard {
|
||||
Mutex mutex;
|
||||
u32 refCount;
|
||||
} ServiceGuard;
|
||||
|
||||
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
|
||||
{
|
||||
mutexLock(&g->mutex);
|
||||
return (g->refCount++) == 0;
|
||||
}
|
||||
|
||||
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
|
||||
{
|
||||
if (R_FAILED(rc)) {
|
||||
cleanupFunc();
|
||||
--g->refCount;
|
||||
}
|
||||
mutexUnlock(&g->mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
|
||||
{
|
||||
mutexLock(&g->mutex);
|
||||
if (g->refCount && (--g->refCount) == 0)
|
||||
cleanupFunc();
|
||||
mutexUnlock(&g->mutex);
|
||||
}
|
||||
|
||||
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
|
||||
\
|
||||
static ServiceGuard g_##name##Guard; \
|
||||
NX_INLINE Result _##name##Initialize _paramdecl; \
|
||||
static void _##name##Cleanup(void); \
|
||||
\
|
||||
Result name##Initialize _paramdecl \
|
||||
{ \
|
||||
Result rc = 0; \
|
||||
if (serviceGuardBeginInit(&g_##name##Guard)) \
|
||||
rc = _##name##Initialize _parampass; \
|
||||
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
|
||||
} \
|
||||
\
|
||||
void name##Exit(void) \
|
||||
{ \
|
||||
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
|
||||
}
|
||||
|
||||
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())
|
||||
29
sphaira/include/yati/source/base.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
|
||||
struct Base {
|
||||
virtual ~Base() = default;
|
||||
// virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
|
||||
virtual bool IsStream() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void SignalCancel() {
|
||||
|
||||
}
|
||||
|
||||
Result GetOpenResult() const {
|
||||
return m_open_result;
|
||||
}
|
||||
|
||||
protected:
|
||||
Result m_open_result{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::source
|
||||
19
sphaira/include/yati/source/file.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <switch.h>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
|
||||
struct File final : Base {
|
||||
File(fs::Fs* fs, const fs::FsPath& path);
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
|
||||
private:
|
||||
fs::Fs* m_fs{};
|
||||
fs::File m_file{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::source
|
||||
32
sphaira/include/yati/source/stream.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.hpp"
|
||||
#include <vector>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
|
||||
// streams are for data that do not allow for random access,
|
||||
// such as FTP or MTP.
|
||||
struct Stream : Base {
|
||||
virtual ~Stream() = default;
|
||||
virtual Result ReadChunk(void* buf, s64 size, u64* bytes_read) = 0;
|
||||
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
|
||||
bool IsStream() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
m_offset = 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
Result m_open_result{};
|
||||
|
||||
private:
|
||||
s64 m_offset{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::source
|
||||
22
sphaira/include/yati/source/stream_file.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
// this is used for testing that streams work, this code isn't used in normal
|
||||
// release builds as it is slower / less feature complete than normal.
|
||||
#pragma once
|
||||
|
||||
#include "stream.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <switch.h>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
|
||||
struct StreamFile final : Stream {
|
||||
StreamFile(fs::Fs* fs, const fs::FsPath& path);
|
||||
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
|
||||
|
||||
private:
|
||||
fs::Fs* m_fs{};
|
||||
fs::File m_file{};
|
||||
s64 m_offset{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::source
|
||||
52
sphaira/include/yati/source/usb.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "usb/usbds.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
|
||||
struct Usb final : Base {
|
||||
enum { USBModule = 523 };
|
||||
|
||||
enum : Result {
|
||||
Result_BadMagic = MAKERESULT(USBModule, 0),
|
||||
Result_BadVersion = MAKERESULT(USBModule, 1),
|
||||
Result_BadCount = MAKERESULT(USBModule, 2),
|
||||
Result_BadTransferSize = MAKERESULT(USBModule, 3),
|
||||
Result_BadTotalSize = MAKERESULT(USBModule, 4),
|
||||
};
|
||||
|
||||
Usb(u64 transfer_timeout);
|
||||
~Usb();
|
||||
|
||||
bool IsStream() const override;
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
Result Finished(u64 timeout);
|
||||
|
||||
Result IsUsbConnected(u64 timeout) {
|
||||
return m_usb->IsUsbConnected(timeout);
|
||||
}
|
||||
|
||||
Result WaitForConnection(u64 timeout, std::vector<std::string>& out_names);
|
||||
void SetFileNameForTranfser(const std::string& name);
|
||||
|
||||
void SignalCancel() override {
|
||||
m_usb->Cancel();
|
||||
}
|
||||
|
||||
private:
|
||||
Result SendCmdHeader(u32 cmdId, size_t dataSize, u64 timeout);
|
||||
Result SendFileRangeCmd(u64 offset, u64 size, u64 timeout);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbDs> m_usb;
|
||||
std::string m_transfer_file_name{};
|
||||
u8 m_flags{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::source
|
||||
133
sphaira/include/yati/yati.hpp
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Notes:
|
||||
* - nca's that use title key encryption are decrypted using Tegra SE, whereas
|
||||
* standard crypto uses software decryption.
|
||||
* The latter is almost always (slightly) faster, and removed the need for es patch.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "source/base.hpp"
|
||||
#include "container/base.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace sphaira::yati {
|
||||
|
||||
enum { YatiModule = 521 };
|
||||
|
||||
enum : Result {
|
||||
// unkown container for the source provided.
|
||||
Result_ContainerNotFound = MAKERESULT(YatiModule, 10),
|
||||
Result_Cancelled = MAKERESULT(YatiModule, 11),
|
||||
|
||||
// nca required by the cnmt but not found in collection.
|
||||
Result_NcaNotFound = MAKERESULT(YatiModule, 30),
|
||||
Result_InvalidNcaReadSize = MAKERESULT(YatiModule, 31),
|
||||
Result_InvalidNcaSigKeyGen = MAKERESULT(YatiModule, 32),
|
||||
Result_InvalidNcaMagic = MAKERESULT(YatiModule, 33),
|
||||
Result_InvalidNcaSignature0 = MAKERESULT(YatiModule, 34),
|
||||
Result_InvalidNcaSignature1 = MAKERESULT(YatiModule, 35),
|
||||
// invalid sha256 over the entire nca.
|
||||
Result_InvalidNcaSha256 = MAKERESULT(YatiModule, 36),
|
||||
|
||||
// section could not be found.
|
||||
Result_NczSectionNotFound = MAKERESULT(YatiModule, 50),
|
||||
// section count == 0.
|
||||
Result_InvalidNczSectionCount = MAKERESULT(YatiModule, 51),
|
||||
// block could not be found.
|
||||
Result_NczBlockNotFound = MAKERESULT(YatiModule, 52),
|
||||
// block version != 2.
|
||||
Result_InvalidNczBlockVersion = MAKERESULT(YatiModule, 53),
|
||||
// block type != 1.
|
||||
Result_InvalidNczBlockType = MAKERESULT(YatiModule, 54),
|
||||
// block count == 0.
|
||||
Result_InvalidNczBlockTotal = MAKERESULT(YatiModule, 55),
|
||||
// block size exponent < 14 || > 32.
|
||||
Result_InvalidNczBlockSizeExponent = MAKERESULT(YatiModule, 56),
|
||||
// zstd error while decompressing ncz.
|
||||
Result_InvalidNczZstdError = MAKERESULT(YatiModule, 57),
|
||||
|
||||
// nca has rights_id but matching ticket wasn't found.
|
||||
Result_TicketNotFound = MAKERESULT(YatiModule, 70),
|
||||
// found ticket has missmatching rights_id from it's name.
|
||||
Result_InvalidTicketBadRightsId = MAKERESULT(YatiModule, 71),
|
||||
Result_InvalidTicketVersion = MAKERESULT(YatiModule, 72),
|
||||
Result_InvalidTicketKeyType = MAKERESULT(YatiModule, 73),
|
||||
Result_InvalidTicketKeyRevision = MAKERESULT(YatiModule, 74),
|
||||
|
||||
// cert not found for the ticket.
|
||||
Result_CertNotFound = MAKERESULT(YatiModule, 90),
|
||||
|
||||
// unable to fetch header from ncm database.
|
||||
Result_NcmDbCorruptHeader = MAKERESULT(YatiModule, 110),
|
||||
// unable to total infos from ncm database.
|
||||
Result_NcmDbCorruptInfos = MAKERESULT(YatiModule, 111),
|
||||
};
|
||||
|
||||
struct Config {
|
||||
bool sd_card_install{};
|
||||
|
||||
// enables downgrading patch / data patch (dlc) version.
|
||||
bool allow_downgrade{};
|
||||
|
||||
// ignores the install if already installed.
|
||||
// checks that every nca is available.
|
||||
bool skip_if_already_installed{};
|
||||
|
||||
// installs tickets only.
|
||||
bool ticket_only{};
|
||||
|
||||
// flags to enable / disable install of specific types.
|
||||
bool skip_base{};
|
||||
bool skip_patch{};
|
||||
bool skip_addon{};
|
||||
bool skip_data_patch{};
|
||||
bool skip_ticket{};
|
||||
|
||||
// enables the option to skip sha256 verification.
|
||||
bool skip_nca_hash_verify{};
|
||||
|
||||
// enables the option to skip rsa nca fixed key verification.
|
||||
bool skip_rsa_header_fixed_key_verify{};
|
||||
|
||||
// enables the option to skip rsa npdm fixed key verification.
|
||||
bool skip_rsa_npdm_fixed_key_verify{};
|
||||
|
||||
// if set, it will ignore the distribution bit in the nca header.
|
||||
bool ignore_distribution_bit{};
|
||||
|
||||
// converts titlekey to standard crypto, also known as "ticketless".
|
||||
// this will not work with addon (dlc), so, addon tickets will be installed.
|
||||
bool convert_to_standard_crypto{};
|
||||
|
||||
// encrypts the keak with master key 0, this allows the game to be launched on every fw.
|
||||
// implicitly performs standard crypto.
|
||||
bool lower_master_key{};
|
||||
|
||||
// sets the system_firmware field in the cnmt extended header.
|
||||
// if mkey is higher than fw version, the game still won't launch
|
||||
// as the fw won't have the key to decrypt keak.
|
||||
bool lower_system_version{};
|
||||
};
|
||||
|
||||
// overridable options, set to avoid
|
||||
struct ConfigOverride {
|
||||
std::optional<bool> sd_card_install{};
|
||||
std::optional<bool> skip_nca_hash_verify{};
|
||||
std::optional<bool> skip_rsa_header_fixed_key_verify{};
|
||||
std::optional<bool> skip_rsa_npdm_fixed_key_verify{};
|
||||
std::optional<bool> ignore_distribution_bit{};
|
||||
std::optional<bool> convert_to_standard_crypto{};
|
||||
std::optional<bool> lower_master_key{};
|
||||
std::optional<bool> lower_system_version{};
|
||||
};
|
||||
|
||||
Result InstallFromFile(ui::ProgressBox* pbox, fs::Fs* fs, const fs::FsPath& path, const ConfigOverride& override = {});
|
||||
Result InstallFromSource(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const fs::FsPath& path, const ConfigOverride& override = {});
|
||||
Result InstallFromContainer(ui::ProgressBox* pbox, std::shared_ptr<container::Base> container, const ConfigOverride& override = {});
|
||||
Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections, const ConfigOverride& override = {});
|
||||
|
||||
} // namespace sphaira::yati
|
||||
@@ -1,7 +1,11 @@
|
||||
#include "ui/menus/main_menu.hpp"
|
||||
#include "ui/error_box.hpp"
|
||||
#include "ui/option_box.hpp"
|
||||
#include "ui/bubbles.hpp"
|
||||
#include "ui/sidebar.hpp"
|
||||
#include "ui/popup_list.hpp"
|
||||
#include "ui/option_box.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
#include "ui/error_box.hpp"
|
||||
|
||||
#include "ui/menus/main_menu.hpp"
|
||||
|
||||
#include "app.hpp"
|
||||
#include "log.hpp"
|
||||
@@ -21,11 +25,13 @@
|
||||
#include <pulsar.h>
|
||||
#include <haze.h>
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <span>
|
||||
#include <dirent.h>
|
||||
#include <usbhsfs.h>
|
||||
|
||||
extern "C" {
|
||||
u32 __nx_applet_exit_mode = 0;
|
||||
@@ -34,8 +40,39 @@ extern "C" {
|
||||
namespace sphaira {
|
||||
namespace {
|
||||
|
||||
constexpr fs::FsPath DEFAULT_MUSIC_PATH = "/config/sphaira/themes/default_music.bfstm";
|
||||
constexpr const char* DEFAULT_MUSIC_URL = "https://files.catbox.moe/1ovji1.bfstm";
|
||||
// constexpr const char* DEFAULT_MUSIC_URL = "https://raw.githubusercontent.com/ITotalJustice/sphaira/refs/heads/master/assets/default_music.bfstm";
|
||||
|
||||
constexpr const u8 DEFAULT_IMAGE_DATA[]{
|
||||
#embed <icons/default.png>
|
||||
};
|
||||
|
||||
void download_default_music() {
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Downloading "_i18n, "default_music.bfstm", [](auto pbox){
|
||||
const auto result = curl::Api().ToFile(
|
||||
curl::Url{DEFAULT_MUSIC_URL},
|
||||
curl::Path{DEFAULT_MUSIC_PATH},
|
||||
curl::OnProgress{pbox->OnDownloadProgressCallback()}
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
R_THROW(0x1);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}, [](Result rc){
|
||||
App::PushErrorBox(rc, "Failed to, TODO: add message here"_i18n);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
App::Notify("Downloaded "_i18n + "default_music.bfstm");
|
||||
App::SetTheme(App::GetThemeIndex());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
struct ThemeData {
|
||||
fs::FsPath music_path{"/config/sphaira/themes/default_music.bfstm"};
|
||||
fs::FsPath music_path{DEFAULT_MUSIC_PATH};
|
||||
std::string elements[ThemeEntryID_MAX]{};
|
||||
};
|
||||
|
||||
@@ -271,30 +308,33 @@ auto GetNroIcon(const std::vector<u8>& nro_icon) -> std::vector<u8> {
|
||||
auto LoadThemeMeta(const fs::FsPath& path, ThemeMeta& meta) -> bool {
|
||||
meta = {};
|
||||
|
||||
char buf[FS_MAX_PATH]{};
|
||||
int len{};
|
||||
len = ini_gets("meta", "name", "", buf, sizeof(buf) - 1, path);
|
||||
if (len <= 1) {
|
||||
auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||
auto meta = static_cast<ThemeMeta*>(UserData);
|
||||
|
||||
if (!std::strcmp(Section, "meta")) {
|
||||
if (!std::strcmp(Key, "name")) {
|
||||
meta->name = Value;
|
||||
} else if (!std::strcmp(Key, "author")) {
|
||||
meta->author = Value;
|
||||
} else if (!std::strcmp(Key, "version")) {
|
||||
meta->version = Value;
|
||||
} else if (!std::strcmp(Key, "inherit")) {
|
||||
meta->inherit = Value;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
if (!ini_browse(cb, &meta, path)) {
|
||||
return false;
|
||||
}
|
||||
meta.name = buf;
|
||||
|
||||
len = ini_gets("meta", "author", "", buf, sizeof(buf) - 1, path);
|
||||
if (len <= 1) {
|
||||
if (meta.name.empty() || meta.author.empty() || meta.version.empty()) {
|
||||
return false;
|
||||
}
|
||||
meta.author = buf;
|
||||
|
||||
len = ini_gets("meta", "version", "", buf, sizeof(buf) - 1, path);
|
||||
if (len <= 1) {
|
||||
return false;
|
||||
}
|
||||
meta.version = buf;
|
||||
|
||||
len = ini_gets("meta", "inherit", "", buf, sizeof(buf) - 1, path);
|
||||
if (len > 1) {
|
||||
meta.inherit = buf;
|
||||
}
|
||||
|
||||
log_write("loaded meta from: %s\n", path.s);
|
||||
meta.ini_path = path;
|
||||
@@ -327,7 +367,7 @@ void LoadThemeInternal(ThemeMeta meta, ThemeData& theme_data, int inherit_level
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||
auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||
auto theme_data = static_cast<ThemeData*>(UserData);
|
||||
|
||||
if (!std::strcmp(Section, "theme")) {
|
||||
@@ -407,15 +447,9 @@ void App::Loop() {
|
||||
timeGetCurrentTime(TimeType_LocalSystemClock, ×tamp);
|
||||
const auto nro_path = nro_normalise_path(arg.path);
|
||||
|
||||
ini_puts("paths", "last_launch_full", arg.argv.c_str(), App::CONFIG_PATH);
|
||||
ini_puts("paths", "last_launch_path", nro_path.c_str(), App::CONFIG_PATH);
|
||||
|
||||
// update timestamp
|
||||
ini_putl(nro_path.c_str(), "timestamp", timestamp, App::PLAYLOG_PATH);
|
||||
// update launch_count
|
||||
const long old_launch_count = ini_getl(nro_path.c_str(), "launch_count", 0, App::PLAYLOG_PATH);
|
||||
ini_putl(nro_path.c_str(), "launch_count", old_launch_count + 1, App::PLAYLOG_PATH);
|
||||
log_write("updating timestamp and launch count for: %s %lu %ld\n", nro_path.c_str(), timestamp, old_launch_count + 1);
|
||||
log_write("updating timestamp for: %s %lu\n", nro_path.c_str(), timestamp);
|
||||
|
||||
// force disable pop-back to main menu.
|
||||
__nx_applet_exit_mode = 0;
|
||||
@@ -484,11 +518,11 @@ auto App::Push(std::shared_ptr<ui::Widget> widget) -> void {
|
||||
}
|
||||
|
||||
auto App::PopToMenu() -> void {
|
||||
for (auto it = g_app->m_widgets.rbegin(); it != g_app->m_widgets.rend(); it++) {
|
||||
const auto& p = *it;
|
||||
for (auto& p : std::ranges::views::reverse(g_app->m_widgets)) {
|
||||
if (p->IsMenu()) {
|
||||
break;
|
||||
}
|
||||
|
||||
p->SetPop();
|
||||
}
|
||||
}
|
||||
@@ -510,7 +544,7 @@ void App::NotifyClear(ui::NotifEntry::Side side) {
|
||||
}
|
||||
|
||||
void App::NotifyFlashLed() {
|
||||
static const HidsysNotificationLedPattern pattern = {
|
||||
static constexpr HidsysNotificationLedPattern pattern = {
|
||||
.baseMiniCycleDuration = 0x1, // 12.5ms.
|
||||
.totalMiniCycles = 0x1, // 1 mini cycle(s).
|
||||
.totalFullCycles = 0x1, // 1 full run(s).
|
||||
@@ -522,15 +556,30 @@ void App::NotifyFlashLed() {
|
||||
}}
|
||||
};
|
||||
|
||||
Result rc;
|
||||
s32 total;
|
||||
HidsysUniquePadId unique_pad_ids[16] = {0};
|
||||
if (R_SUCCEEDED(hidsysGetUniquePadIds(unique_pad_ids, 16, &total))) {
|
||||
for (int i = 0; i < total; i++) {
|
||||
hidsysSetNotificationLedPattern(&pattern, unique_pad_ids[i]);
|
||||
HidsysUniquePadId unique_pad_id;
|
||||
|
||||
rc = hidsysGetUniquePadsFromNpad(HidNpadIdType_Handheld, &unique_pad_id, 1, &total);
|
||||
if (R_SUCCEEDED(rc) && total) {
|
||||
rc = hidsysSetNotificationLedPattern(&pattern, unique_pad_id);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc) || !total) {
|
||||
rc = hidsysGetUniquePadsFromNpad(HidNpadIdType_No1, &unique_pad_id, 1, &total);
|
||||
if (R_SUCCEEDED(rc) && total) {
|
||||
hidsysSetNotificationLedPattern(&pattern, unique_pad_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result App::PushErrorBox(Result rc, const std::string& message) {
|
||||
if (R_FAILED(rc)) {
|
||||
App::Push(std::make_shared<ui::ErrorBox>(rc, message));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
auto App::GetThemeMetaList() -> std::span<ThemeMeta> {
|
||||
return g_app->m_theme_meta_entries;
|
||||
}
|
||||
@@ -544,10 +593,14 @@ auto App::GetThemeIndex() -> s64 {
|
||||
return g_app->m_theme_index;
|
||||
}
|
||||
|
||||
auto App::GetDefaultImage(int* w, int* h) -> int {
|
||||
auto App::GetDefaultImage() -> int {
|
||||
return g_app->m_default_image;
|
||||
}
|
||||
|
||||
auto App::GetDefaultImageData() -> std::span<const u8> {
|
||||
return DEFAULT_IMAGE_DATA;
|
||||
}
|
||||
|
||||
auto App::GetExePath() -> fs::FsPath {
|
||||
return g_app->m_app_path;
|
||||
}
|
||||
@@ -560,6 +613,14 @@ auto App::GetNxlinkEnable() -> bool {
|
||||
return g_app->m_nxlink_enabled.Get();
|
||||
}
|
||||
|
||||
auto App::GetHddEnable() -> bool {
|
||||
return g_app->m_hdd_enabled.Get();
|
||||
}
|
||||
|
||||
auto App::GetWriteProtect() -> bool {
|
||||
return g_app->m_hdd_write_protect.Get();
|
||||
}
|
||||
|
||||
auto App::GetLogEnable() -> bool {
|
||||
return g_app->m_log_enabled.Get();
|
||||
}
|
||||
@@ -569,7 +630,19 @@ auto App::GetReplaceHbmenuEnable() -> bool {
|
||||
}
|
||||
|
||||
auto App::GetInstallEnable() -> bool {
|
||||
return g_app->m_install.Get();
|
||||
if (IsEmunand()) {
|
||||
return GetInstallEmummcEnable();
|
||||
} else {
|
||||
return GetInstallSysmmcEnable();
|
||||
}
|
||||
}
|
||||
|
||||
auto App::GetInstallSysmmcEnable() -> bool {
|
||||
return g_app->m_install_sysmmc.GetOr("install");
|
||||
}
|
||||
|
||||
auto App::GetInstallEmummcEnable() -> bool {
|
||||
return g_app->m_install_emummc.GetOr("install");
|
||||
}
|
||||
|
||||
auto App::GetInstallSdEnable() -> bool {
|
||||
@@ -580,10 +653,6 @@ auto App::GetInstallPrompt() -> bool {
|
||||
return g_app->m_install_prompt.Get();
|
||||
}
|
||||
|
||||
auto App::GetThemeShuffleEnable() -> bool {
|
||||
return g_app->m_theme_shuffle.Get();
|
||||
}
|
||||
|
||||
auto App::GetThemeMusicEnable() -> bool {
|
||||
return g_app->m_theme_music.Get();
|
||||
}
|
||||
@@ -619,6 +688,32 @@ void App::SetNxlinkEnable(bool enable) {
|
||||
}
|
||||
}
|
||||
|
||||
void App::SetHddEnable(bool enable) {
|
||||
if (App::GetHddEnable() != enable) {
|
||||
g_app->m_hdd_enabled.Set(enable);
|
||||
if (enable) {
|
||||
if (App::GetWriteProtect()) {
|
||||
usbHsFsSetFileSystemMountFlags(UsbHsFsMountFlags_ReadOnly);
|
||||
}
|
||||
usbHsFsInitialize(1);
|
||||
} else {
|
||||
usbHsFsExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App::SetWriteProtect(bool enable) {
|
||||
if (App::GetWriteProtect() != enable) {
|
||||
g_app->m_hdd_write_protect.Set(enable);
|
||||
|
||||
if (enable) {
|
||||
usbHsFsSetFileSystemMountFlags(UsbHsFsMountFlags_ReadOnly);
|
||||
} else {
|
||||
usbHsFsSetFileSystemMountFlags(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App::SetLogEnable(bool enable) {
|
||||
if (App::GetLogEnable() != enable) {
|
||||
g_app->m_log_enabled.Set(enable);
|
||||
@@ -679,7 +774,7 @@ void App::SetReplaceHbmenuEnable(bool enable) {
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
|
||||
if (std::strcmp(sphaira_nacp.display_version, hbmenu_nacp.display_version) < 0) {
|
||||
if (IsVersionNewer(sphaira_nacp.display_version, hbmenu_nacp.display_version)) {
|
||||
if (R_FAILED(rc = fs.copy_entire_file(sphaira_path, "/hbmenu.nro"))) {
|
||||
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path.s, rc, R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
} else {
|
||||
@@ -697,9 +792,10 @@ void App::SetReplaceHbmenuEnable(bool enable) {
|
||||
if (R_FAILED(rc = fs.copy_entire_file("/hbmenu.nro", "/switch/hbmenu.nro"))) {
|
||||
// try and restore sphaira in a last ditch effort.
|
||||
if (R_FAILED(rc = fs.copy_entire_file("/hbmenu.nro", sphaira_path))) {
|
||||
App::Push(std::make_shared<ui::ErrorBox>(rc,
|
||||
App::PushErrorBox(rc, "Failed to, TODO: add message here"_i18n);
|
||||
App::PushErrorBox(rc,
|
||||
"Failed to restore hbmenu, please re-download hbmenu"_i18n
|
||||
));
|
||||
);
|
||||
} else {
|
||||
App::Push(std::make_shared<ui::OptionBox>(
|
||||
"Failed to restore hbmenu, using sphaira instead"_i18n,
|
||||
@@ -729,8 +825,12 @@ void App::SetReplaceHbmenuEnable(bool enable) {
|
||||
}
|
||||
}
|
||||
|
||||
void App::SetInstallEnable(bool enable) {
|
||||
g_app->m_install.Set(enable);
|
||||
void App::SetInstallSysmmcEnable(bool enable) {
|
||||
g_app->m_install_sysmmc.Set(enable);
|
||||
}
|
||||
|
||||
void App::SetInstallEmummcEnable(bool enable) {
|
||||
g_app->m_install_emummc.Set(enable);
|
||||
}
|
||||
|
||||
void App::SetInstallSdEnable(bool enable) {
|
||||
@@ -741,10 +841,6 @@ void App::SetInstallPrompt(bool enable) {
|
||||
g_app->m_install_prompt.Set(enable);
|
||||
}
|
||||
|
||||
void App::SetThemeShuffleEnable(bool enable) {
|
||||
g_app->m_theme_shuffle.Set(enable);
|
||||
}
|
||||
|
||||
void App::SetThemeMusicEnable(bool enable) {
|
||||
g_app->m_theme_music.Set(enable);
|
||||
PlaySoundEffect(SoundEffect::SoundEffect_Music);
|
||||
@@ -758,7 +854,7 @@ void App::SetMtpEnable(bool enable) {
|
||||
if (App::GetMtpEnable() != enable) {
|
||||
g_app->m_mtp_enabled.Set(enable);
|
||||
if (enable) {
|
||||
hazeInitialize(haze_callback);
|
||||
hazeInitialize(haze_callback, 0x2C, 2);
|
||||
} else {
|
||||
hazeExit();
|
||||
}
|
||||
@@ -797,18 +893,7 @@ void App::SetTextScrollSpeed(long index) {
|
||||
}
|
||||
|
||||
auto App::Install(OwoConfig& config) -> Result {
|
||||
R_TRY(romfsInit());
|
||||
ON_SCOPE_EXIT(romfsExit());
|
||||
|
||||
std::vector<u8> main_data, npdm_data, logo_data, gif_data;
|
||||
R_TRY(fs::read_entire_file("romfs:/exefs/main", main_data));
|
||||
R_TRY(fs::read_entire_file("romfs:/exefs/main.npdm", npdm_data));
|
||||
|
||||
config.nro_path = nro_add_arg_file(config.nro_path);
|
||||
config.main = main_data;
|
||||
config.npdm = npdm_data;
|
||||
config.logo = logo_data;
|
||||
config.gif = gif_data;
|
||||
if (!config.icon.empty()) {
|
||||
config.icon = GetNroIcon(config.icon);
|
||||
}
|
||||
@@ -827,18 +912,7 @@ auto App::Install(OwoConfig& config) -> Result {
|
||||
}
|
||||
|
||||
auto App::Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result {
|
||||
R_TRY(romfsInit());
|
||||
ON_SCOPE_EXIT(romfsExit());
|
||||
|
||||
std::vector<u8> main_data, npdm_data, logo_data, gif_data;
|
||||
R_TRY(fs::read_entire_file("romfs:/exefs/main", main_data));
|
||||
R_TRY(fs::read_entire_file("romfs:/exefs/main.npdm", npdm_data));
|
||||
|
||||
config.nro_path = nro_add_arg_file(config.nro_path);
|
||||
config.main = main_data;
|
||||
config.npdm = npdm_data;
|
||||
config.logo = logo_data;
|
||||
config.gif = gif_data;
|
||||
if (!config.icon.empty()) {
|
||||
config.icon = GetNroIcon(config.icon);
|
||||
}
|
||||
@@ -1009,13 +1083,16 @@ void App::Draw() {
|
||||
}
|
||||
|
||||
m_notif_manager.Draw(vg, &m_theme);
|
||||
ui::bubble::Draw(vg, &m_theme);
|
||||
|
||||
nvgResetTransform(vg);
|
||||
nvgEndFrame(this->vg);
|
||||
this->queue.presentImage(this->swapchain, slot);
|
||||
}
|
||||
|
||||
auto App::GetApp() -> App* {
|
||||
return g_app;
|
||||
}
|
||||
|
||||
auto App::GetVg() -> NVGcontext* {
|
||||
return g_app->vg;
|
||||
}
|
||||
@@ -1108,7 +1185,6 @@ void App::CloseTheme() {
|
||||
if (m_sound_ids[SoundEffect_Music]) {
|
||||
plsrPlayerFree(m_sound_ids[SoundEffect_Music]);
|
||||
m_sound_ids[SoundEffect_Music] = nullptr;
|
||||
plsrBFSTMClose(&m_theme.music);
|
||||
}
|
||||
|
||||
for (auto& e : m_theme.elements) {
|
||||
@@ -1138,10 +1214,12 @@ void App::LoadTheme(const ThemeMeta& meta) {
|
||||
|
||||
// load music
|
||||
if (!theme_data.music_path.empty()) {
|
||||
if (R_SUCCEEDED(plsrBFSTMOpen(theme_data.music_path, &m_theme.music))) {
|
||||
if (R_SUCCEEDED(plsrPlayerLoadStream(&m_theme.music, &m_sound_ids[SoundEffect_Music]))) {
|
||||
PLSR_BFSTM music_stream;
|
||||
if (R_SUCCEEDED(plsrBFSTMOpen(theme_data.music_path, &music_stream))) {
|
||||
if (R_SUCCEEDED(plsrPlayerLoadStream(&music_stream, &m_sound_ids[SoundEffect_Music]))) {
|
||||
PlaySoundEffect(SoundEffect_Music);
|
||||
}
|
||||
plsrBFSTMClose(&music_stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1185,11 +1263,17 @@ void App::ScanThemeEntries() {
|
||||
ScanThemes("romfs:/themes/");
|
||||
romfsExit();
|
||||
}
|
||||
|
||||
// then load custom entries
|
||||
ScanThemes("/config/sphaira/themes/");
|
||||
}
|
||||
|
||||
App::App(const char* argv0) {
|
||||
TimeStamp ts;
|
||||
|
||||
appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad);
|
||||
ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal));
|
||||
|
||||
g_app = this;
|
||||
m_start_timestamp = armGetSystemTick();
|
||||
if (!std::strncmp(argv0, "sdmc:/", 6)) {
|
||||
@@ -1205,18 +1289,68 @@ App::App(const char* argv0) {
|
||||
}
|
||||
|
||||
fs::FsNativeSd fs;
|
||||
fs.CreateDirectoryRecursively("/config/sphaira/assoc");
|
||||
fs.CreateDirectoryRecursively("/config/sphaira/themes");
|
||||
fs.CreateDirectoryRecursively("/config/sphaira/github");
|
||||
fs.CreateDirectoryRecursively("/config/sphaira/i18n");
|
||||
fs.CreateDirectoryRecursively("/config/sphaira");
|
||||
fs.CreateDirectory("/config/sphaira/assoc");
|
||||
fs.CreateDirectory("/config/sphaira/themes");
|
||||
fs.CreateDirectory("/config/sphaira/github");
|
||||
fs.CreateDirectory("/config/sphaira/i18n");
|
||||
|
||||
auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||
auto app = static_cast<App*>(UserData);
|
||||
|
||||
if (!std::strcmp(Section, INI_SECTION)) {
|
||||
if (app->m_nxlink_enabled.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_mtp_enabled.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ftp_enabled.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_hdd_enabled.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_hdd_write_protect.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_log_enabled.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_replace_hbmenu.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_theme_path.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_theme_music.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_12hour_time.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_language.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_left_menu.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_right_menu.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_install_sysmmc.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_install_emummc.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_install_sd.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_install_prompt.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_progress_boost_mode.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_allow_downgrade.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_skip_if_already_installed.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ticket_only.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_skip_base.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_skip_patch.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_skip_addon.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_skip_data_patch.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_skip_ticket.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_skip_nca_hash_verify.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_skip_rsa_header_fixed_key_verify.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_skip_rsa_npdm_fixed_key_verify.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_ignore_distribution_bit.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_convert_to_standard_crypto.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_lower_master_key.LoadFrom(Key, Value)) {}
|
||||
else if (app->m_lower_system_version.LoadFrom(Key, Value)) {}
|
||||
} else if (!std::strcmp(Section, "accessibility")) {
|
||||
if (app->m_text_scroll_speed.LoadFrom(Key, Value)) {}
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
// load all configs ahead of time, as this is actually faster than
|
||||
// loading each config one by one as it avoids re-opening the file multiple times.
|
||||
ini_browse(cb, this, CONFIG_PATH);
|
||||
|
||||
if (App::GetLogEnable()) {
|
||||
log_file_init();
|
||||
log_write("hello world\n");
|
||||
App::Notify("Warning! Logs are enabled, Sphaira will run slowly!"_i18n);
|
||||
}
|
||||
|
||||
if (App::GetMtpEnable()) {
|
||||
hazeInitialize(haze_callback);
|
||||
hazeInitialize(haze_callback, 0x2C, 2);
|
||||
}
|
||||
|
||||
if (App::GetFtpEnable()) {
|
||||
@@ -1227,6 +1361,14 @@ App::App(const char* argv0) {
|
||||
nxlinkInitialize(nxlink_callback);
|
||||
}
|
||||
|
||||
if (App::GetWriteProtect()) {
|
||||
usbHsFsSetFileSystemMountFlags(UsbHsFsMountFlags_ReadOnly);
|
||||
}
|
||||
|
||||
if (App::GetHddEnable()) {
|
||||
usbHsFsInitialize(1);
|
||||
}
|
||||
|
||||
curl::Init();
|
||||
|
||||
// get current size of the framebuffer
|
||||
@@ -1304,16 +1446,21 @@ App::App(const char* argv0) {
|
||||
if (R_SUCCEEDED(plsrBFSAROpen("qlaunch:/sound/qlaunch.bfsar", &qlaunch_bfsar))) {
|
||||
ON_SCOPE_EXIT(plsrBFSARClose(&qlaunch_bfsar));
|
||||
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconFocus", &m_sound_ids[SoundEffect_Focus]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconScroll", &m_sound_ids[SoundEffect_Scroll]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconLimit", &m_sound_ids[SoundEffect_Limit]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeStartupMenu_game", &m_sound_ids[SoundEffect_Startup]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconAdd", &m_sound_ids[SoundEffect_Install]);
|
||||
plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeInsertError", &m_sound_ids[SoundEffect_Error]);
|
||||
const auto load_sound = [&](const char* name, u32 id) {
|
||||
if (R_FAILED(plsrPlayerLoadSoundByName(&qlaunch_bfsar, name, &m_sound_ids[id]))) {
|
||||
log_write("[PLSR] failed to load sound effect: %s\n", name);
|
||||
}
|
||||
};
|
||||
|
||||
load_sound("SeGameIconFocus", SoundEffect_Focus);
|
||||
load_sound("SeGameIconScroll", SoundEffect_Scroll);
|
||||
load_sound("SeGameIconLimit", SoundEffect_Limit);
|
||||
load_sound("StartupMenu_Game", SoundEffect_Startup);
|
||||
load_sound("SeGameIconAdd", SoundEffect_Install);
|
||||
load_sound("SeInsertError", SoundEffect_Error);
|
||||
|
||||
plsrPlayerSetVolume(m_sound_ids[SoundEffect_Limit], 2.0f);
|
||||
plsrPlayerSetVolume(m_sound_ids[SoundEffect_Focus], 0.5f);
|
||||
PlaySoundEffect(SoundEffect_Startup);
|
||||
}
|
||||
} else {
|
||||
log_write("failed to mount romfs 0x0100000000001000\n");
|
||||
@@ -1321,21 +1468,14 @@ App::App(const char* argv0) {
|
||||
|
||||
ScanThemeEntries();
|
||||
|
||||
fs::FsPath theme_path{};
|
||||
constexpr fs::FsPath default_theme_path{"romfs:/themes/abyss_theme.ini"};
|
||||
if (App::GetThemeShuffleEnable() && m_theme_meta_entries.size()) {
|
||||
theme_path = m_theme_meta_entries[randomGet64() % m_theme_meta_entries.size()].ini_path;
|
||||
} else {
|
||||
ini_gets("config", "theme", default_theme_path, theme_path, sizeof(theme_path), CONFIG_PATH);
|
||||
}
|
||||
|
||||
// try and load previous theme, default to previous version otherwise.
|
||||
fs::FsPath theme_path = m_theme_path.Get();
|
||||
ThemeMeta theme_meta;
|
||||
if (R_SUCCEEDED(romfsInit())) {
|
||||
ON_SCOPE_EXIT(romfsExit());
|
||||
if (!LoadThemeMeta(theme_path, theme_meta)) {
|
||||
log_write("failed to load meta using default\n");
|
||||
theme_path = default_theme_path;
|
||||
theme_path = DEFAULT_THEME_PATH;
|
||||
LoadThemeMeta(theme_path, theme_meta);
|
||||
}
|
||||
}
|
||||
@@ -1358,13 +1498,6 @@ App::App(const char* argv0) {
|
||||
// padInitializeDefault(&m_pad);
|
||||
padInitializeAny(&m_pad);
|
||||
|
||||
m_prev_timestamp = ini_getl("paths", "timestamp", 0, App::CONFIG_PATH);
|
||||
const auto last_launch_path_size = ini_gets("paths", "last_launch_path", "", m_prev_last_launch, sizeof(m_prev_last_launch), App::CONFIG_PATH);
|
||||
fs::FsPath last_launch_path;
|
||||
if (last_launch_path_size) {
|
||||
ini_gets("paths", "last_launch_path", "", last_launch_path, sizeof(last_launch_path), App::CONFIG_PATH);
|
||||
}
|
||||
ini_puts("paths", "last_launch_path", "", App::CONFIG_PATH);
|
||||
|
||||
const auto loader_info_size = envGetLoaderInfoSize();
|
||||
if (loader_info_size) {
|
||||
@@ -1379,32 +1512,12 @@ App::App(const char* argv0) {
|
||||
}
|
||||
|
||||
ini_putl(GetExePath(), "timestamp", m_start_timestamp, App::PLAYLOG_PATH);
|
||||
const long old_launch_count = ini_getl(GetExePath(), "launch_count", 0, App::PLAYLOG_PATH);
|
||||
ini_putl(GetExePath(), "launch_count", old_launch_count + 1, App::PLAYLOG_PATH);
|
||||
|
||||
s64 sd_free_space;
|
||||
if (R_SUCCEEDED(fs.GetFreeSpace("/", &sd_free_space))) {
|
||||
log_write("sd_free_space: %zd\n", sd_free_space);
|
||||
}
|
||||
s64 sd_total_space;
|
||||
if (R_SUCCEEDED(fs.GetTotalSpace("/", &sd_total_space))) {
|
||||
log_write("sd_total_space: %zd\n", sd_total_space);
|
||||
}
|
||||
|
||||
// load default image
|
||||
if (R_SUCCEEDED(romfsInit())) {
|
||||
ON_SCOPE_EXIT(romfsExit());
|
||||
const auto image = ImageLoadFromFile("romfs:/default.png");
|
||||
if (!image.data.empty()) {
|
||||
m_default_image = nvgCreateImageRGBA(vg, image.w, image.h, 0, image.data.data());
|
||||
}
|
||||
}
|
||||
|
||||
// soon (tm)
|
||||
// ui::bubble::Init();
|
||||
m_default_image = nvgCreateImageMem(vg, 0, DEFAULT_IMAGE_DATA, std::size(DEFAULT_IMAGE_DATA));
|
||||
|
||||
App::Push(std::make_shared<ui::menu::main::MainMenu>());
|
||||
log_write("finished app constructor\n");
|
||||
log_write("\n\tfinished app constructor, time taken: %.2fs %zums\n\n", ts.GetSecondsD(), ts.GetMs());
|
||||
}
|
||||
|
||||
void App::PlaySoundEffect(SoundEffect effect) {
|
||||
@@ -1420,14 +1533,269 @@ void App::PlaySoundEffect(SoundEffect effect) {
|
||||
plsrPlayerPlay(id);
|
||||
}
|
||||
|
||||
void App::DisplayThemeOptions(bool left_side) {
|
||||
ui::SidebarEntryArray::Items theme_items{};
|
||||
const auto theme_meta = App::GetThemeMetaList();
|
||||
for (auto& p : theme_meta) {
|
||||
theme_items.emplace_back(p.name);
|
||||
}
|
||||
|
||||
auto options = std::make_shared<ui::Sidebar>("Theme Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryArray>("Select Theme"_i18n, theme_items, [](s64& index_out){
|
||||
App::SetTheme(index_out);
|
||||
}, App::GetThemeIndex()));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Music"_i18n, App::GetThemeMusicEnable(), [](bool& enable){
|
||||
App::SetThemeMusicEnable(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("12 Hour Time"_i18n, App::Get12HourTimeEnable(), [](bool& enable){
|
||||
App::Set12HourTimeEnable(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Download Default Music"_i18n, [](){
|
||||
// check if we already have music
|
||||
if (fs::FileExists(DEFAULT_MUSIC_PATH)) {
|
||||
App::Push(std::make_shared<ui::OptionBox>(
|
||||
"Overwrite current default music?"_i18n,
|
||||
"No"_i18n, "Yes"_i18n, 0, [](auto op_index){
|
||||
if (op_index && *op_index) {
|
||||
download_default_music();
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
} else {
|
||||
download_default_music();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void App::DisplayNetworkOptions(bool left_side) {
|
||||
|
||||
}
|
||||
|
||||
void App::DisplayMiscOptions(bool left_side) {
|
||||
auto options = std::make_shared<ui::Sidebar>("Misc Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
for (auto& e : ui::menu::main::GetMiscMenuEntries()) {
|
||||
if (e.name == g_app->m_right_menu.Get()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.IsInstall() && !App::GetInstallEnable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>(i18n::get(e.title), [e](){
|
||||
App::Push(e.func(ui::menu::MenuFlag_None));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void App::DisplayAdvancedOptions(bool left_side) {
|
||||
auto options = std::make_shared<ui::Sidebar>("Advanced Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
ui::SidebarEntryArray::Items text_scroll_speed_items;
|
||||
text_scroll_speed_items.push_back("Slow"_i18n);
|
||||
text_scroll_speed_items.push_back("Normal"_i18n);
|
||||
text_scroll_speed_items.push_back("Fast"_i18n);
|
||||
|
||||
std::vector<const char*> menu_names;
|
||||
for (auto& e : ui::menu::main::GetMiscMenuEntries()) {
|
||||
if (!e.IsShortcut()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.IsInstall() && !App::GetInstallEnable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
menu_names.emplace_back(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){
|
||||
App::SetLogEnable(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Replace hbmenu on exit"_i18n, App::GetReplaceHbmenuEnable(), [](bool& enable){
|
||||
App::SetReplaceHbmenuEnable(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Boost CPU during transfer"_i18n, App::GetApp()->m_progress_boost_mode.Get(), [](bool& enable){
|
||||
App::GetApp()->m_progress_boost_mode.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryArray>("Text scroll speed"_i18n, text_scroll_speed_items, [](s64& index_out){
|
||||
App::SetTextScrollSpeed(index_out);
|
||||
}, App::GetTextScrollSpeed()));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryArray>("Set left-side menu"_i18n, side_menu_items, [menu_names](s64& index_out){
|
||||
const auto e = menu_names[index_out];
|
||||
if (g_app->m_left_menu.Get() != e) {
|
||||
g_app->m_left_menu.Set(e);
|
||||
App::Push(std::make_shared<ui::OptionBox>(
|
||||
"Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){
|
||||
App::ExitRestart();
|
||||
}
|
||||
));
|
||||
}
|
||||
}, 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){
|
||||
const auto e = menu_names[index_out];
|
||||
if (g_app->m_right_menu.Get() != e) {
|
||||
g_app->m_right_menu.Set(e);
|
||||
App::Push(std::make_shared<ui::OptionBox>(
|
||||
"Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){
|
||||
App::ExitRestart();
|
||||
}
|
||||
));
|
||||
}
|
||||
}, i18n::get(g_app->m_right_menu.Get())));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Install options"_i18n, [left_side](){
|
||||
App::DisplayInstallOptions(left_side);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Dump options"_i18n, [left_side](){
|
||||
App::DisplayDumpOptions(left_side);
|
||||
}));
|
||||
}
|
||||
|
||||
void App::DisplayInstallOptions(bool left_side) {
|
||||
auto options = std::make_shared<ui::Sidebar>("Install Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
ui::SidebarEntryArray::Items install_items;
|
||||
install_items.push_back("System memory"_i18n);
|
||||
install_items.push_back("microSD card"_i18n);
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Enable sysmmc"_i18n, App::GetInstallSysmmcEnable(), [](bool& enable){
|
||||
App::SetInstallSysmmcEnable(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Enable emummc"_i18n, App::GetInstallEmummcEnable(), [](bool& enable){
|
||||
App::SetInstallEmummcEnable(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Show install warning"_i18n, App::GetApp()->m_install_prompt.Get(), [](bool& enable){
|
||||
App::GetApp()->m_install_prompt.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryArray>("Install location"_i18n, install_items, [](s64& index_out){
|
||||
App::SetInstallSdEnable(index_out);
|
||||
}, (s64)App::GetInstallSdEnable()));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Allow downgrade"_i18n, App::GetApp()->m_allow_downgrade.Get(), [](bool& enable){
|
||||
App::GetApp()->m_allow_downgrade.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip if already installed"_i18n, App::GetApp()->m_skip_if_already_installed.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_if_already_installed.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Ticket only"_i18n, App::GetApp()->m_ticket_only.Get(), [](bool& enable){
|
||||
App::GetApp()->m_ticket_only.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip base"_i18n, App::GetApp()->m_skip_base.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_base.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip patch"_i18n, App::GetApp()->m_skip_patch.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_patch.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip dlc"_i18n, App::GetApp()->m_skip_addon.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_addon.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip data patch"_i18n, App::GetApp()->m_skip_data_patch.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_data_patch.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip ticket"_i18n, App::GetApp()->m_skip_ticket.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_ticket.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip NCA hash verify"_i18n, App::GetApp()->m_skip_nca_hash_verify.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_nca_hash_verify.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip RSA header verify"_i18n, App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_rsa_header_fixed_key_verify.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip RSA NPDM verify"_i18n, App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get(), [](bool& enable){
|
||||
App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Ignore distribution bit"_i18n, App::GetApp()->m_ignore_distribution_bit.Get(), [](bool& enable){
|
||||
App::GetApp()->m_ignore_distribution_bit.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Convert to standard crypto"_i18n, App::GetApp()->m_convert_to_standard_crypto.Get(), [](bool& enable){
|
||||
App::GetApp()->m_convert_to_standard_crypto.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Lower master key"_i18n, App::GetApp()->m_lower_master_key.Get(), [](bool& enable){
|
||||
App::GetApp()->m_lower_master_key.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Lower system version"_i18n, App::GetApp()->m_lower_system_version.Get(), [](bool& enable){
|
||||
App::GetApp()->m_lower_system_version.Set(enable);
|
||||
}));
|
||||
}
|
||||
|
||||
void App::DisplayDumpOptions(bool left_side) {
|
||||
auto options = std::make_shared<ui::Sidebar>("Dump Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||
ON_SCOPE_EXIT(App::Push(options));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Created nested folder"_i18n, App::GetApp()->m_dump_app_folder.Get(), [](bool& enable){
|
||||
App::GetApp()->m_dump_app_folder.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Append folder with .xci"_i18n, App::GetApp()->m_dump_append_folder_with_xci.Get(), [](bool& enable){
|
||||
App::GetApp()->m_dump_append_folder_with_xci.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Trim XCI"_i18n, App::GetApp()->m_dump_trim_xci.Get(), [](bool& enable){
|
||||
App::GetApp()->m_dump_trim_xci.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Label trimmed XCI"_i18n, App::GetApp()->m_dump_label_trim_xci.Get(), [](bool& enable){
|
||||
App::GetApp()->m_dump_label_trim_xci.Set(enable);
|
||||
}));
|
||||
|
||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Multi-threaded USB transfer"_i18n, App::GetApp()->m_dump_usb_transfer_stream.Get(), [](bool& enable){
|
||||
App::GetApp()->m_dump_usb_transfer_stream.Set(enable);
|
||||
}));
|
||||
}
|
||||
|
||||
App::~App() {
|
||||
appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad);
|
||||
ON_SCOPE_EXIT(appletSetCpuBoostMode(ApmCpuBoostMode_Normal));
|
||||
|
||||
log_write("starting to exit\n");
|
||||
|
||||
i18n::exit();
|
||||
curl::Exit();
|
||||
|
||||
ui::bubble::Exit();
|
||||
|
||||
// this has to be called before any cleanup to ensure the lifetime of
|
||||
// nvg is still active as some widgets may need to free images.
|
||||
m_widgets.clear();
|
||||
@@ -1492,7 +1860,7 @@ App::~App() {
|
||||
|
||||
// found sphaira, now lets get compare version
|
||||
if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
|
||||
if (std::strcmp(hbmenu_nacp.display_version, sphaira_nacp.display_version) < 0) {
|
||||
if (IsVersionNewer(hbmenu_nacp.display_version, sphaira_nacp.display_version)) {
|
||||
if (R_FAILED(rc = fs.copy_entire_file(GetExePath(), sphaira_path))) {
|
||||
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path.s, rc, R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
} else {
|
||||
@@ -1520,14 +1888,25 @@ App::~App() {
|
||||
nxlinkExit();
|
||||
}
|
||||
|
||||
if (App::GetHddEnable()) {
|
||||
log_write("closing hdd\n");
|
||||
usbHsFsExit();
|
||||
}
|
||||
|
||||
if (App::GetLogEnable()) {
|
||||
log_write("closing log\n");
|
||||
log_file_exit();
|
||||
}
|
||||
}
|
||||
|
||||
u64 timestamp;
|
||||
timeGetCurrentTime(TimeType_LocalSystemClock, ×tamp);
|
||||
ini_putl("paths", "timestamp", timestamp, App::CONFIG_PATH);
|
||||
auto App::GetVersionFromString(const char* str) -> u32 {
|
||||
u32 major{}, minor{}, macro{};
|
||||
std::sscanf(str, "%u.%u.%u", &major, &minor, ¯o);
|
||||
return MAKEHOSVERSION(major, minor, macro);
|
||||
}
|
||||
|
||||
auto App::IsVersionNewer(const char* current, const char* new_version) -> u32 {
|
||||
return GetVersionFromString(current) < GetVersionFromString(new_version);
|
||||
}
|
||||
|
||||
void App::createFramebufferResources() {
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
#include "defines.hpp"
|
||||
#include "evman.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "app.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <curl/curl.h>
|
||||
#include <yyjson.h>
|
||||
|
||||
@@ -25,7 +29,7 @@ namespace {
|
||||
log_write("curl_share_setopt(%s, %s) msg: %s\n", #opt, #v, curl_share_strerror(r)); \
|
||||
} \
|
||||
|
||||
constexpr auto API_AGENT = "ITotalJustice";
|
||||
constexpr auto API_AGENT = "TotalJustice";
|
||||
constexpr u64 CHUNK_SIZE = 1024*1024;
|
||||
constexpr auto MAX_THREADS = 4;
|
||||
constexpr int THREAD_PRIO = 0x2C;
|
||||
@@ -33,15 +37,35 @@ constexpr int THREAD_CORE = 1;
|
||||
|
||||
std::atomic_bool g_running{};
|
||||
CURLSH* g_curl_share{};
|
||||
// this is used for single threaded blocking installs.
|
||||
// avoids the needed for re-creating the handle each time.
|
||||
CURL* g_curl_single{};
|
||||
Mutex g_mutex_share[CURL_LOCK_DATA_LAST]{};
|
||||
|
||||
struct UploadStruct {
|
||||
std::span<const u8> data;
|
||||
s64 offset{};
|
||||
s64 size{};
|
||||
fs::File f{};
|
||||
};
|
||||
|
||||
struct DataStruct {
|
||||
std::vector<u8> data;
|
||||
s64 offset{};
|
||||
FsFile f{};
|
||||
fs::File f{};
|
||||
s64 file_offset{};
|
||||
};
|
||||
|
||||
struct SeekCustomData {
|
||||
OnUploadSeek cb{};
|
||||
s64 size{};
|
||||
};
|
||||
|
||||
// helper for creating webdav folders as libcurl does not have built-in
|
||||
// support for it.
|
||||
// only creates the folders if they don't exist.
|
||||
auto WebdavCreateFolder(CURL* curl, const Api& e) -> bool;
|
||||
|
||||
auto generate_key_from_path(const fs::FsPath& path) -> std::string {
|
||||
const auto key = crc32Calculate(path.s, path.size());
|
||||
return std::to_string(key);
|
||||
@@ -195,6 +219,8 @@ private:
|
||||
} else {
|
||||
const auto update_entry = [this, &hash_key](const char* tag, const std::string& value) {
|
||||
if (value.empty()) {
|
||||
// workaround for appstore accepting etags but not returning them.
|
||||
yyjson_mut_obj_remove_str(hash_key, tag);
|
||||
return true;
|
||||
} else {
|
||||
auto key = yyjson_mut_obj_get(hash_key, tag);
|
||||
@@ -300,7 +326,7 @@ struct ThreadQueue {
|
||||
threadClose(&m_thread);
|
||||
}
|
||||
|
||||
auto Add(const Api& api) -> bool {
|
||||
auto Add(const Api& api, bool is_upload = false) -> bool {
|
||||
if (api.GetUrl().empty() || api.GetPath().empty() || !api.GetOnComplete()) {
|
||||
return false;
|
||||
}
|
||||
@@ -310,10 +336,10 @@ struct ThreadQueue {
|
||||
|
||||
switch (api.GetPriority()) {
|
||||
case Priority::Normal:
|
||||
m_entries.emplace_back(api);
|
||||
m_entries.emplace_back(api).api.SetUpload(is_upload);
|
||||
break;
|
||||
case Priority::High:
|
||||
m_entries.emplace_front(api);
|
||||
m_entries.emplace_front(api).api.SetUpload(is_upload);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -364,6 +390,94 @@ auto ProgressCallbackFunc2(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto SeekCallback(void *clientp, curl_off_t offset, int origin) -> int {
|
||||
if (!g_running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto data_struct = static_cast<UploadStruct*>(clientp);
|
||||
|
||||
if (origin == SEEK_SET) {
|
||||
offset = offset;
|
||||
} else if (origin == SEEK_CUR) {
|
||||
offset = data_struct->offset + offset;
|
||||
} else if (origin == SEEK_END) {
|
||||
offset = data_struct->size;
|
||||
}
|
||||
|
||||
if (offset < 0 || offset > data_struct->size) {
|
||||
return CURL_SEEKFUNC_CANTSEEK;
|
||||
}
|
||||
|
||||
data_struct->offset = offset;
|
||||
return CURL_SEEKFUNC_OK;
|
||||
}
|
||||
|
||||
auto SeekCustomCallback(void *clientp, curl_off_t offset, int origin) -> int {
|
||||
if (!g_running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto data_struct = static_cast<SeekCustomData*>(clientp);
|
||||
if (origin != SEEK_SET || offset < 0 || offset > data_struct->size) {
|
||||
return CURL_SEEKFUNC_CANTSEEK;
|
||||
}
|
||||
|
||||
if (!data_struct->cb(offset)) {
|
||||
return CURL_SEEKFUNC_CANTSEEK;
|
||||
}
|
||||
|
||||
return CURL_SEEKFUNC_OK;
|
||||
}
|
||||
|
||||
auto ReadFileCallback(char *ptr, size_t size, size_t nmemb, void *userp) -> size_t {
|
||||
if (!g_running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto data_struct = static_cast<UploadStruct*>(userp);
|
||||
const auto realsize = size * nmemb;
|
||||
|
||||
u64 bytes_read;
|
||||
if (R_FAILED(data_struct->f.Read(data_struct->offset, ptr, realsize, FsReadOption_None, &bytes_read))) {
|
||||
log_write("reading file error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
data_struct->offset += bytes_read;
|
||||
svcSleepThread(YieldType_WithoutCoreMigration);
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
auto ReadMemoryCallback(char *ptr, size_t size, size_t nmemb, void *userp) -> size_t {
|
||||
if (!g_running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto data_struct = static_cast<UploadStruct*>(userp);
|
||||
auto realsize = size * nmemb;
|
||||
realsize = std::min(realsize, data_struct->data.size() - data_struct->offset);
|
||||
|
||||
std::memcpy(ptr, data_struct->data.data(), realsize);
|
||||
data_struct->offset += realsize;
|
||||
|
||||
svcSleepThread(YieldType_WithoutCoreMigration);
|
||||
return realsize;
|
||||
}
|
||||
|
||||
auto ReadCustomCallback(char *ptr, size_t size, size_t nmemb, void *userp) -> size_t {
|
||||
if (!g_running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto data_struct = static_cast<UploadInfo*>(userp);
|
||||
auto realsize = size * nmemb;
|
||||
const auto result = data_struct->m_callback(ptr, realsize);
|
||||
|
||||
svcSleepThread(YieldType_WithoutCoreMigration);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto WriteMemoryCallback(void *contents, size_t size, size_t num_files, void *userp) -> size_t {
|
||||
if (!g_running) {
|
||||
return 0;
|
||||
@@ -379,11 +493,9 @@ auto WriteMemoryCallback(void *contents, size_t size, size_t num_files, void *us
|
||||
|
||||
data_struct->data.resize(data_struct->offset + realsize);
|
||||
std::memcpy(data_struct->data.data() + data_struct->offset, contents, realsize);
|
||||
|
||||
data_struct->offset += realsize;
|
||||
|
||||
svcSleepThread(YieldType_WithoutCoreMigration);
|
||||
|
||||
return realsize;
|
||||
}
|
||||
|
||||
@@ -397,7 +509,7 @@ auto WriteFileCallback(void *contents, size_t size, size_t num_files, void *user
|
||||
|
||||
// flush data if incomming data would overflow the buffer
|
||||
if (data_struct->offset && data_struct->data.size() < data_struct->offset + realsize) {
|
||||
if (R_FAILED(fsFileWrite(&data_struct->f, data_struct->file_offset, data_struct->data.data(), data_struct->offset, FsWriteOption_None))) {
|
||||
if (R_FAILED(data_struct->f.Write(data_struct->file_offset, data_struct->data.data(), data_struct->offset, FsWriteOption_None))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -407,7 +519,7 @@ auto WriteFileCallback(void *contents, size_t size, size_t num_files, void *user
|
||||
|
||||
// we have a huge chunk! write it directly to file
|
||||
if (data_struct->data.size() < realsize) {
|
||||
if (R_FAILED(fsFileWrite(&data_struct->f, data_struct->file_offset, contents, realsize, FsWriteOption_None))) {
|
||||
if (R_FAILED(data_struct->f.Write(data_struct->file_offset, contents, realsize, FsWriteOption_None))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -442,7 +554,125 @@ auto header_callback(char* b, size_t size, size_t nitems, void* userdata) -> siz
|
||||
return numbytes;
|
||||
}
|
||||
|
||||
auto EscapeString(CURL* curl, const std::string& str) -> std::string {
|
||||
char* s{};
|
||||
if (!curl) {
|
||||
s = curl_escape(str.data(), str.length());
|
||||
} else {
|
||||
s = curl_easy_escape(curl, str.data(), str.length());
|
||||
}
|
||||
|
||||
if (!s) {
|
||||
return str;
|
||||
}
|
||||
|
||||
const std::string result = s;
|
||||
curl_free(s);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto EncodeUrl(std::string url) -> std::string {
|
||||
log_write("[CURL] encoding url\n");
|
||||
|
||||
if (url.starts_with("webdav://")) {
|
||||
log_write("[CURL] updating host\n");
|
||||
url.replace(0, std::strlen("webdav"), "https");
|
||||
log_write("[CURL] updated host: %s\n", url.c_str());
|
||||
}
|
||||
|
||||
auto clu = curl_url();
|
||||
R_UNLESS(clu, url);
|
||||
ON_SCOPE_EXIT(curl_url_cleanup(clu));
|
||||
|
||||
log_write("[CURL] setting url\n");
|
||||
CURLUcode clu_code;
|
||||
clu_code = curl_url_set(clu, CURLUPART_URL, url.c_str(), CURLU_URLENCODE);
|
||||
R_UNLESS(clu_code == CURLUE_OK, url);
|
||||
log_write("[CURL] set url success\n");
|
||||
|
||||
char* encoded_url;
|
||||
clu_code = curl_url_get(clu, CURLUPART_URL, &encoded_url, 0);
|
||||
R_UNLESS(clu_code == CURLUE_OK, url);
|
||||
|
||||
log_write("[CURL] encoded url: %s [vs]: %s\n", encoded_url, url.c_str());
|
||||
const std::string out = encoded_url;
|
||||
curl_free(encoded_url);
|
||||
return out;
|
||||
}
|
||||
|
||||
void SetCommonCurlOptions(CURL* curl, const Api& e) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_USERAGENT, API_AGENT);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_FAILONERROR, 1L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SHARE, g_curl_share);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_BUFFERSIZE, 1024*512);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_UPLOAD_BUFFERSIZE, 1024*512);
|
||||
|
||||
// enable all forms of compression supported by libcurl.
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_ACCEPT_ENCODING, "");
|
||||
|
||||
// for smb / ftp, try and use ssl if possible.
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_TRY);
|
||||
|
||||
// in most cases, this will use CURLAUTH_BASIC.
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HTTPAUTH, (long)CURLAUTH_ANY);
|
||||
|
||||
// enable TE is server supports it.
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_TRANSFER_ENCODING, 1L);
|
||||
|
||||
// set flags.
|
||||
if (e.GetFlags() & Flag_NoBody) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_NOBODY, 1L);
|
||||
}
|
||||
|
||||
// set custom request.
|
||||
if (!e.GetCustomRequest().empty()) {
|
||||
log_write("[CURL] setting custom request: %s\n", e.GetCustomRequest().c_str());
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_CUSTOMREQUEST, e.GetCustomRequest().c_str());
|
||||
}
|
||||
|
||||
// set oath2 bearer.
|
||||
if (!e.GetBearer().empty()) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XOAUTH2_BEARER, e.GetBearer().c_str());
|
||||
}
|
||||
|
||||
// set ssh pub/priv key file.
|
||||
if (!e.GetPubKey().empty()) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSH_PUBLIC_KEYFILE, e.GetPubKey().c_str());
|
||||
}
|
||||
if (!e.GetPrivKey().empty()) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSH_PRIVATE_KEYFILE, e.GetPrivKey().c_str());
|
||||
}
|
||||
|
||||
// set auth.
|
||||
if (!e.GetUserPass().m_user.empty()) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_USERPWD, e.GetUserPass().m_user.c_str());
|
||||
}
|
||||
if (!e.GetUserPass().m_pass.empty()) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_PASSWORD, e.GetUserPass().m_pass.c_str());
|
||||
}
|
||||
|
||||
// set port, if valid.
|
||||
if (e.GetPort()) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_PORT, (long)e.GetPort());
|
||||
}
|
||||
|
||||
// progress calls.
|
||||
if (e.GetOnProgress()) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &e);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc2);
|
||||
} else {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc1);
|
||||
}
|
||||
|
||||
}
|
||||
auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
App::SetAutoSleepDisabled(true);
|
||||
ON_SCOPE_EXIT(App::SetAutoSleepDisabled(false));
|
||||
|
||||
// check if stop has been requested before starting download
|
||||
if (e.GetToken().stop_requested()) {
|
||||
return {};
|
||||
@@ -451,6 +681,7 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
fs::FsPath tmp_buf;
|
||||
const bool has_file = !e.GetPath().empty() && e.GetPath() != "";
|
||||
const bool has_post = !e.GetFields().empty() && e.GetFields() != "";
|
||||
const auto encoded_url = EncodeUrl(e.GetUrl());
|
||||
|
||||
DataStruct chunk;
|
||||
Header header_in = e.GetHeader();
|
||||
@@ -471,7 +702,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (e.GetFlags() & Flag_Cache) {
|
||||
// only add etag if the dst file still exists.
|
||||
if ((e.GetFlags() & Flag_Cache) && fs::FileExists(&fs.m_fs, e.GetPath())) {
|
||||
g_cache.get(e.GetPath(), header_in);
|
||||
}
|
||||
}
|
||||
@@ -480,14 +712,9 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
chunk.data.reserve(CHUNK_SIZE);
|
||||
|
||||
curl_easy_reset(curl);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, e.GetUrl().c_str());
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_USERAGENT, "TotalJustice");
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_FAILONERROR, 1L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SHARE, g_curl_share);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_BUFFERSIZE, 1024*512);
|
||||
SetCommonCurlOptions(curl, e);
|
||||
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, encoded_url.c_str());
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERFUNCTION, header_callback);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERDATA, &header_out);
|
||||
|
||||
@@ -521,15 +748,6 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HTTPHEADER, list);
|
||||
}
|
||||
|
||||
// progress calls.
|
||||
if (e.GetOnProgress()) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &e);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc2);
|
||||
} else {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc1);
|
||||
}
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
|
||||
// write calls.
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_WRITEFUNCTION, has_file ? WriteFileCallback : WriteMemoryCallback);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_WRITEDATA, &chunk);
|
||||
@@ -544,10 +762,10 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
if (has_file) {
|
||||
ON_SCOPE_EXIT( fs.DeleteFile(tmp_buf) );
|
||||
if (res == CURLE_OK && chunk.offset) {
|
||||
fsFileWrite(&chunk.f, chunk.file_offset, chunk.data.data(), chunk.offset, FsWriteOption_None);
|
||||
chunk.f.Write(chunk.file_offset, chunk.data.data(), chunk.offset, FsWriteOption_None);
|
||||
}
|
||||
|
||||
fsFileClose(&chunk.f);
|
||||
chunk.f.Close();
|
||||
|
||||
if (res == CURLE_OK) {
|
||||
if (http_code == 304) {
|
||||
@@ -558,6 +776,15 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
g_cache.set(e.GetPath(), header_out);
|
||||
}
|
||||
|
||||
// enable to log received headers.
|
||||
#if 0
|
||||
log_write("\n\nLOGGING HEADER\n");
|
||||
for (auto [a, b] : header_out.m_map) {
|
||||
log_write("\t%s: %s\n", a.c_str(), b.c_str());
|
||||
}
|
||||
log_write("\n\n");
|
||||
#endif
|
||||
|
||||
fs.DeleteFile(e.GetPath());
|
||||
fs.CreateDirectoryRecursivelyWithPath(e.GetPath());
|
||||
if (R_FAILED(fs.RenameFile(tmp_buf, e.GetPath()))) {
|
||||
@@ -573,18 +800,210 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
}
|
||||
}
|
||||
|
||||
log_write("Downloaded %s %s\n", e.GetUrl().c_str(), curl_easy_strerror(res));
|
||||
log_write("Downloaded %s code: %ld %s\n", e.GetUrl().c_str(), http_code, curl_easy_strerror(res));
|
||||
return {success, http_code, header_out, chunk.data, e.GetPath()};
|
||||
}
|
||||
|
||||
auto DownloadInternal(const Api& e) -> ApiResult {
|
||||
auto curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
log_write("curl init failed\n");
|
||||
auto UploadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
// check if stop has been requested before starting download
|
||||
if (e.GetToken().stop_requested()) {
|
||||
return {};
|
||||
}
|
||||
ON_SCOPE_EXIT(curl_easy_cleanup(curl));
|
||||
return DownloadInternal(curl, e);
|
||||
|
||||
if (e.GetUrl().starts_with("webdav://")) {
|
||||
if (!WebdavCreateFolder(curl, e)) {
|
||||
log_write("[CURL] failed to create webdav folder, aborting\n");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const auto& info = e.GetUploadInfo();
|
||||
const auto url = e.GetUrl() + "/" + info.m_name;
|
||||
const auto encoded_url = EncodeUrl(url);
|
||||
const bool has_file = !e.GetPath().empty() && e.GetPath() != "";
|
||||
|
||||
UploadStruct chunk{};
|
||||
DataStruct chunk_out{};
|
||||
SeekCustomData seek_data{};
|
||||
Header header_in = e.GetHeader();
|
||||
Header header_out;
|
||||
fs::FsNativeSd fs{};
|
||||
|
||||
if (has_file) {
|
||||
if (R_FAILED(fs.OpenFile(e.GetPath(), FsOpenMode_Read, &chunk.f))) {
|
||||
log_write("failed to open file: %s\n", e.GetPath().s);
|
||||
return {};
|
||||
}
|
||||
|
||||
chunk.f.GetSize(&chunk.size);
|
||||
log_write("got chunk size: %zd\n", chunk.size);
|
||||
} else {
|
||||
if (info.m_callback) {
|
||||
chunk.size = info.m_size;
|
||||
log_write("setting upload size: %zu\n", chunk.size);
|
||||
} else {
|
||||
chunk.size = info.m_data.size();
|
||||
chunk.data = info.m_data;
|
||||
}
|
||||
}
|
||||
|
||||
if (url.starts_with("file://")) {
|
||||
const auto folder_path = fs::AppendPath("/", url.substr(std::strlen("file://")));
|
||||
log_write("creating local folder: %s\n", folder_path.s);
|
||||
// create the folder as libcurl doesn't seem to manually create it.
|
||||
fs.CreateDirectoryRecursivelyWithPath(folder_path);
|
||||
// remove the path so that libcurl can upload over it.
|
||||
fs.DeleteFile(folder_path);
|
||||
}
|
||||
|
||||
// reserve the first chunk
|
||||
chunk_out.data.reserve(CHUNK_SIZE);
|
||||
|
||||
curl_easy_reset(curl);
|
||||
SetCommonCurlOptions(curl, e);
|
||||
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, encoded_url.c_str());
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERFUNCTION, header_callback);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERDATA, &header_out);
|
||||
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_UPLOAD, 1L);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)chunk.size);
|
||||
|
||||
// instruct libcurl to create ftp folders if they don't yet exist.
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR_RETRY);
|
||||
|
||||
struct curl_slist* list = NULL;
|
||||
ON_SCOPE_EXIT(if (list) { curl_slist_free_all(list); } );
|
||||
|
||||
for (const auto& [key, value] : header_in.m_map) {
|
||||
if (value.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// create header key value pair.
|
||||
const auto header_str = key + ": " + value;
|
||||
|
||||
// try to append header chunk.
|
||||
auto temp = curl_slist_append(list, header_str.c_str());
|
||||
if (temp) {
|
||||
log_write("adding header: %s\n", header_str.c_str());
|
||||
list = temp;
|
||||
} else {
|
||||
log_write("failed to append header\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (list) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HTTPHEADER, list);
|
||||
}
|
||||
|
||||
// set callback for reading more data.
|
||||
if (info.m_callback) {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_READFUNCTION, ReadCustomCallback);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_READDATA, &info);
|
||||
|
||||
if (e.GetOnUploadSeek()) {
|
||||
seek_data.cb = e.GetOnUploadSeek();
|
||||
seek_data.size = chunk.size;
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SEEKFUNCTION, SeekCustomCallback);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SEEKDATA, &seek_data);
|
||||
}
|
||||
} else {
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_READFUNCTION, has_file ? ReadFileCallback : ReadMemoryCallback);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_READDATA, &chunk);
|
||||
|
||||
// allow for seeking upon uploads, may be used for ftp and http.
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SEEKFUNCTION, SeekCallback);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SEEKDATA, &chunk);
|
||||
}
|
||||
|
||||
// write calls.
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
|
||||
CURL_EASY_SETOPT_LOG(curl, CURLOPT_WRITEDATA, &chunk_out);
|
||||
|
||||
// perform upload and cleanup after and report the result.
|
||||
const auto res = curl_easy_perform(curl);
|
||||
bool success = res == CURLE_OK;
|
||||
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
if (has_file) {
|
||||
chunk.f.Close();
|
||||
}
|
||||
|
||||
log_write("Uploaded %s code: %ld %s\n", url.c_str(), http_code, curl_easy_strerror(res));
|
||||
return {success, http_code, header_out, chunk_out.data};
|
||||
}
|
||||
|
||||
auto WebdavCreateFolder(CURL* curl, const Api& e) -> bool {
|
||||
// if using webdav, extract the file path and create the directories.
|
||||
// https://github.com/WebDAVDevs/webdav-request-samples/blob/master/webdav_curl.md
|
||||
if (e.GetUrl().starts_with("webdav://")) {
|
||||
log_write("[CURL] found webdav url\n");
|
||||
|
||||
const auto info = e.GetUploadInfo();
|
||||
if (info.m_name.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& file_path = info.m_name;
|
||||
log_write("got file path: %s\n", file_path.c_str());
|
||||
|
||||
const auto file_loc = file_path.find_last_of('/');
|
||||
if (file_loc == file_path.npos) {
|
||||
log_write("failed to find last slash\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto path_view = file_path.substr(0, file_loc);
|
||||
log_write("got folder path: %s\n", path_view.c_str());
|
||||
|
||||
auto e2 = e;
|
||||
e2.SetOption(Path{});
|
||||
e2.SetOption(Url{e.GetUrl() + "/" + path_view});
|
||||
e2.SetOption(Flags{e.GetFlags() | Flag_NoBody});
|
||||
e2.SetOption(CustomRequest{"PROPFIND"});
|
||||
e2.SetOption(Header{
|
||||
{ "Depth", "0" },
|
||||
});
|
||||
|
||||
// test to see if the directory exists first.
|
||||
const auto exist_result = DownloadInternal(curl, e2);
|
||||
if (exist_result.success) {
|
||||
log_write("[CURL] folder already exist: %s\n", path_view.c_str());
|
||||
return true;
|
||||
} else {
|
||||
log_write("[CURL] folder does NOT exist, manually creating: %s\n", path_view.c_str());
|
||||
}
|
||||
|
||||
// make the request to create the folder.
|
||||
std::string folder;
|
||||
for (const auto dir : std::views::split(path_view, '/')) {
|
||||
if (dir.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
folder += "/" + std::string{dir.data(), dir.size()};
|
||||
e2.SetOption(Url{e.GetUrl() + folder});
|
||||
e2.SetOption(Header{});
|
||||
e2.SetOption(CustomRequest{"MKCOL"});
|
||||
|
||||
const auto result = DownloadInternal(curl, e2);
|
||||
if (result.code == 201) {
|
||||
log_write("[CURL] created webdav directory\n");
|
||||
} else if (result.code == 405) {
|
||||
log_write("[CURL] webdav directory already exists: %ld\n", result.code);
|
||||
} else {
|
||||
log_write("[CURL] failed to create webdav directory: %ld\n", result.code);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_write("[CURL] not a webdav url: %s\n", e.GetUrl().c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void my_lock(CURL *handle, curl_lock_data data, curl_lock_access laccess, void *useptr) {
|
||||
@@ -608,10 +1027,12 @@ void ThreadEntry::ThreadFunc(void* p) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto result = DownloadInternal(data->m_curl, data->m_api);
|
||||
const auto result = data->m_api.IsUpload() ? UploadInternal(data->m_curl, data->m_api) : DownloadInternal(data->m_curl, data->m_api);
|
||||
if (g_running && data->m_api.GetOnComplete() && !data->m_api.GetToken().stop_requested()) {
|
||||
const DownloadEventData event_data{data->m_api.GetOnComplete(), result, data->m_api.GetToken()};
|
||||
evman::push(std::move(event_data), false);
|
||||
evman::push(
|
||||
DownloadEventData{data->m_api.GetOnComplete(), result, data->m_api.GetToken()},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
data->m_in_progress = false;
|
||||
@@ -708,6 +1129,11 @@ auto Init() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
g_curl_single = curl_easy_init();
|
||||
if (!g_curl_single) {
|
||||
log_write("failed to create g_curl_single\n");
|
||||
}
|
||||
|
||||
log_write("finished creating threads\n");
|
||||
|
||||
if (!g_cache.init()) {
|
||||
@@ -722,6 +1148,11 @@ void Exit() {
|
||||
|
||||
g_thread_queue.Close();
|
||||
|
||||
if (g_curl_single) {
|
||||
curl_easy_cleanup(g_curl_single);
|
||||
g_curl_single = nullptr;
|
||||
}
|
||||
|
||||
for (auto& entry : g_threads) {
|
||||
entry.Close();
|
||||
}
|
||||
@@ -739,14 +1170,28 @@ auto ToMemory(const Api& e) -> ApiResult {
|
||||
if (!e.GetPath().empty()) {
|
||||
return {};
|
||||
}
|
||||
return DownloadInternal(e);
|
||||
return DownloadInternal(g_curl_single, e);
|
||||
}
|
||||
|
||||
auto ToFile(const Api& e) -> ApiResult {
|
||||
if (e.GetPath().empty()) {
|
||||
return {};
|
||||
}
|
||||
return DownloadInternal(e);
|
||||
return DownloadInternal(g_curl_single, e);
|
||||
}
|
||||
|
||||
auto FromMemory(const Api& e) -> ApiResult {
|
||||
if (!e.GetPath().empty()) {
|
||||
return {};
|
||||
}
|
||||
return UploadInternal(g_curl_single, e);
|
||||
}
|
||||
|
||||
auto FromFile(const Api& e) -> ApiResult {
|
||||
if (e.GetPath().empty()) {
|
||||
return {};
|
||||
}
|
||||
return UploadInternal(g_curl_single, e);
|
||||
}
|
||||
|
||||
auto ToMemoryAsync(const Api& api) -> bool {
|
||||
@@ -757,14 +1202,16 @@ auto ToFileAsync(const Api& e) -> bool {
|
||||
return g_thread_queue.Add(e);
|
||||
}
|
||||
|
||||
auto FromMemoryAsync(const Api& api) -> bool {
|
||||
return g_thread_queue.Add(api, true);
|
||||
}
|
||||
|
||||
auto FromFileAsync(const Api& e) -> bool {
|
||||
return g_thread_queue.Add(e, true);
|
||||
}
|
||||
|
||||
auto EscapeString(const std::string& str) -> std::string {
|
||||
std::string result;
|
||||
const auto s = curl_escape(str.data(), str.length());
|
||||
if (s) {
|
||||
result = s;
|
||||
curl_free(s);
|
||||
}
|
||||
return result;
|
||||
return EscapeString(nullptr, str);
|
||||
}
|
||||
|
||||
} // namespace sphaira::curl
|
||||
|
||||
381
sphaira/source/dumper.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
#include "dumper.hpp"
|
||||
#include "app.hpp"
|
||||
#include "log.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "download.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "i18n.hpp"
|
||||
#include "location.hpp"
|
||||
#include "threaded_file_transfer.hpp"
|
||||
|
||||
#include "ui/sidebar.hpp"
|
||||
#include "ui/error_box.hpp"
|
||||
#include "ui/option_box.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
#include "ui/popup_list.hpp"
|
||||
#include "ui/nvg_util.hpp"
|
||||
|
||||
#include "yati/source/stream.hpp"
|
||||
|
||||
#include "usb/usb_uploader.hpp"
|
||||
#include "usb/tinfoil.hpp"
|
||||
|
||||
namespace sphaira::dump {
|
||||
namespace {
|
||||
|
||||
struct DumpEntry {
|
||||
DumpLocationType type;
|
||||
s32 index;
|
||||
};
|
||||
|
||||
struct DumpLocation {
|
||||
const DumpLocationType type;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
constexpr DumpLocation DUMP_LOCATIONS[]{
|
||||
{ DumpLocationType_SdCard, "microSD card (/dumps/)" },
|
||||
{ DumpLocationType_UsbS2S, "USB transfer (Switch 2 Switch)" },
|
||||
{ DumpLocationType_DevNull, "/dev/null (Speed Test)" },
|
||||
};
|
||||
|
||||
struct UsbTest final : usb::upload::Usb, yati::source::Stream {
|
||||
UsbTest(ui::ProgressBox* pbox, BaseSource* source) : Usb{UINT64_MAX} {
|
||||
m_pbox = pbox;
|
||||
m_source = source;
|
||||
}
|
||||
|
||||
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override {
|
||||
R_TRY(m_pull(buf, size, bytes_read));
|
||||
m_pull_offset += *bytes_read;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||
if (m_pull) {
|
||||
return Stream::Read(buf, off, size, bytes_read);
|
||||
} else {
|
||||
return ReadInternal(path, buf, off, size, bytes_read);
|
||||
}
|
||||
}
|
||||
|
||||
Result ReadInternal(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) {
|
||||
if (m_path != path) {
|
||||
m_path = path;
|
||||
m_progress = 0;
|
||||
m_pull_offset = 0;
|
||||
Stream::Reset();
|
||||
m_size = m_source->GetSize(path);
|
||||
m_pbox->SetImage(m_source->GetIcon(path));
|
||||
m_pbox->SetTitle(m_source->GetName(path));
|
||||
m_pbox->NewTransfer(m_path);
|
||||
}
|
||||
|
||||
R_TRY(m_source->Read(path, buf, off, size, bytes_read));
|
||||
|
||||
m_offset += *bytes_read;
|
||||
m_progress += *bytes_read;
|
||||
m_pbox->UpdateTransfer(m_progress, m_size);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void SetPullCallback(thread::PullCallback pull) {
|
||||
m_pull = pull;
|
||||
}
|
||||
|
||||
auto* GetSource() {
|
||||
return m_source;
|
||||
}
|
||||
|
||||
auto GetPullOffset() const {
|
||||
return m_pull_offset;
|
||||
}
|
||||
|
||||
private:
|
||||
ui::ProgressBox* m_pbox{};
|
||||
BaseSource* m_source{};
|
||||
std::string m_path{};
|
||||
thread::PullCallback m_pull{};
|
||||
s64 m_offset{};
|
||||
s64 m_size{};
|
||||
s64 m_progress{};
|
||||
s64 m_pull_offset{};
|
||||
};
|
||||
|
||||
Result DumpToFile(ui::ProgressBox* pbox, fs::Fs* fs, const fs::FsPath& root, BaseSource* source, std::span<const fs::FsPath> paths) {
|
||||
constexpr s64 BIG_FILE_SIZE = 1024ULL*1024ULL*1024ULL*4ULL;
|
||||
|
||||
for (const auto& path : paths) {
|
||||
const auto base_path = fs::AppendPath(root, path);
|
||||
const auto file_size = source->GetSize(path);
|
||||
pbox->SetImage(source->GetIcon(path));
|
||||
pbox->SetTitle(source->GetName(path));
|
||||
pbox->NewTransfer(base_path);
|
||||
|
||||
const auto temp_path = base_path + ".temp";
|
||||
fs->CreateDirectoryRecursivelyWithPath(temp_path);
|
||||
fs->DeleteFile(temp_path);
|
||||
|
||||
const auto flags = file_size >= BIG_FILE_SIZE ? FsCreateOption_BigFile : 0;
|
||||
R_TRY(fs->CreateFile(temp_path, file_size, flags));
|
||||
ON_SCOPE_EXIT(fs->DeleteFile(temp_path));
|
||||
|
||||
{
|
||||
fs::File file;
|
||||
R_TRY(fs->OpenFile(temp_path, FsOpenMode_Write, &file));
|
||||
|
||||
R_TRY(thread::Transfer(pbox, file_size,
|
||||
[&](void* data, s64 off, s64 size, u64* bytes_read) -> Result {
|
||||
return source->Read(path, data, off, size, bytes_read);
|
||||
},
|
||||
[&](const void* data, s64 off, s64 size) -> Result {
|
||||
return file.Write(off, data, size, FsWriteOption_None);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
fs->DeleteFile(base_path);
|
||||
R_TRY(fs->RenameFile(temp_path, base_path));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DumpToFileNative(ui::ProgressBox* pbox, BaseSource* source, std::span<const fs::FsPath> paths) {
|
||||
fs::FsNativeSd fs{};
|
||||
return DumpToFile(pbox, &fs, "/", source, paths);
|
||||
}
|
||||
|
||||
Result DumpToStdio(ui::ProgressBox* pbox, const location::StdioEntry& loc, BaseSource* source, std::span<const fs::FsPath> paths) {
|
||||
fs::FsStdio fs{};
|
||||
return DumpToFile(pbox, &fs, loc.mount, source, paths);
|
||||
}
|
||||
|
||||
Result DumpToUsbS2SStream(ui::ProgressBox* pbox, UsbTest* usb, std::span<const fs::FsPath> paths) {
|
||||
auto source = usb->GetSource();
|
||||
|
||||
for (auto& path : paths) {
|
||||
const auto file_size = source->GetSize(path);
|
||||
|
||||
R_TRY(thread::TransferPull(pbox, file_size,
|
||||
[&](void* data, s64 off, s64 size, u64* bytes_read) -> Result {
|
||||
return usb->ReadInternal(path, data, off, size, bytes_read);
|
||||
},
|
||||
[&](thread::StartThreadCallback start, thread::PullCallback pull) -> Result {
|
||||
usb->SetPullCallback(pull);
|
||||
R_TRY(start());
|
||||
|
||||
while (!pbox->ShouldExit()) {
|
||||
R_TRY(usb->PollCommands());
|
||||
|
||||
if (usb->GetPullOffset() >= file_size) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
R_THROW(0xFFFF);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DumpToUsbS2SRandom(ui::ProgressBox* pbox, UsbTest* usb) {
|
||||
while (!pbox->ShouldExit()) {
|
||||
R_TRY(usb->PollCommands());
|
||||
}
|
||||
|
||||
R_THROW(0xFFFF);
|
||||
}
|
||||
|
||||
Result DumpToUsbS2S(ui::ProgressBox* pbox, BaseSource* source, std::span<const fs::FsPath> paths) {
|
||||
std::vector<std::string> file_list;
|
||||
for (const auto& path : paths) {
|
||||
file_list.emplace_back(path);
|
||||
}
|
||||
|
||||
// auto usb = std::make_unique<UsbTest>(pbox, entries);
|
||||
auto usb = std::make_unique<UsbTest>(pbox, source);
|
||||
constexpr u64 timeout = 1e+9;
|
||||
|
||||
while (!pbox->ShouldExit()) {
|
||||
if (R_SUCCEEDED(usb->IsUsbConnected(timeout))) {
|
||||
pbox->NewTransfer("USB connected, sending file list");
|
||||
u8 flags = usb::tinfoil::USBFlag_NONE;
|
||||
if (App::GetApp()->m_dump_usb_transfer_stream.Get()) {
|
||||
flags |= usb::tinfoil::USBFlag_STREAM;
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(usb->WaitForConnection(timeout, flags, file_list))) {
|
||||
pbox->NewTransfer("Sent file list, waiting for command...");
|
||||
|
||||
Result rc;
|
||||
if (flags & usb::tinfoil::USBFlag_STREAM) {
|
||||
rc = DumpToUsbS2SStream(pbox, usb.get(), paths);
|
||||
} else {
|
||||
rc = DumpToUsbS2SRandom(pbox, usb.get());
|
||||
}
|
||||
|
||||
// wait for exit command.
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
log_write("waiting for exit command\n");
|
||||
rc = usb->PollCommands();
|
||||
log_write("finished polling for exit command\n");
|
||||
} else {
|
||||
log_write("skipped polling for exit command\n");
|
||||
}
|
||||
|
||||
if (rc == usb->Result_Exit) {
|
||||
log_write("got exit command\n");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
} else {
|
||||
pbox->NewTransfer("waiting for usb connection...");
|
||||
}
|
||||
}
|
||||
|
||||
R_THROW(0xFFFF);
|
||||
}
|
||||
|
||||
Result DumpToDevNull(ui::ProgressBox* pbox, BaseSource* source, std::span<const fs::FsPath> paths) {
|
||||
for (auto path : paths) {
|
||||
R_TRY(pbox->ShouldExitResult());
|
||||
|
||||
const auto file_size = source->GetSize(path);
|
||||
pbox->SetImage(source->GetIcon(path));
|
||||
pbox->SetTitle(source->GetName(path));
|
||||
pbox->NewTransfer(path);
|
||||
|
||||
R_TRY(thread::Transfer(pbox, file_size,
|
||||
[&](void* data, s64 off, s64 size, u64* bytes_read) -> Result {
|
||||
return source->Read(path, data, off, size, bytes_read);
|
||||
},
|
||||
[&](const void* data, s64 off, s64 size) -> Result {
|
||||
R_SUCCEED();
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DumpToNetwork(ui::ProgressBox* pbox, const location::Entry& loc, BaseSource* source, std::span<const fs::FsPath> paths) {
|
||||
for (auto path : paths) {
|
||||
R_TRY(pbox->ShouldExitResult());
|
||||
|
||||
const auto file_size = source->GetSize(path);
|
||||
pbox->SetImage(source->GetIcon(path));
|
||||
pbox->SetTitle(source->GetName(path));
|
||||
pbox->NewTransfer(path);
|
||||
|
||||
R_TRY(thread::TransferPull(pbox, file_size,
|
||||
[&](void* data, s64 off, s64 size, u64* bytes_read) -> Result {
|
||||
return source->Read(path, data, off, size, bytes_read);
|
||||
},
|
||||
[&](thread::PullCallback pull) -> Result {
|
||||
s64 offset{};
|
||||
const auto result = curl::Api().FromMemory(
|
||||
CURL_LOCATION_TO_API(loc),
|
||||
curl::OnProgress{pbox->OnDownloadProgressCallback()},
|
||||
curl::UploadInfo{
|
||||
path, file_size,
|
||||
[&](void *ptr, size_t size) -> size_t {
|
||||
// curl will request past the size of the file, causing an error.
|
||||
if (offset >= file_size) {
|
||||
log_write("finished file upload\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 bytes_read{};
|
||||
if (R_FAILED(pull(ptr, size, &bytes_read))) {
|
||||
log_write("failed to read in custom callback: %zd size: %zd\n", offset, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
offset += bytes_read;
|
||||
return bytes_read;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
R_UNLESS(result.success, 0x1);
|
||||
R_SUCCEED();
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dump(std::shared_ptr<BaseSource> source, const std::vector<fs::FsPath>& paths, OnExit on_exit, u32 location_flags) {
|
||||
ui::PopupList::Items items;
|
||||
std::vector<DumpEntry> dump_entries;
|
||||
|
||||
const auto network_locations = location::Load();
|
||||
if (location_flags & (1 << DumpLocationType_Network)) {
|
||||
for (s32 i = 0; i < std::size(network_locations); i++) {
|
||||
dump_entries.emplace_back(DumpLocationType_Network, i);
|
||||
items.emplace_back(network_locations[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
const auto stdio_locations = location::GetStdio(true);
|
||||
if (location_flags & (1 << DumpLocationType_Stdio)) {
|
||||
for (s32 i = 0; i < std::size(stdio_locations); i++) {
|
||||
dump_entries.emplace_back(DumpLocationType_Stdio, i);
|
||||
items.emplace_back(stdio_locations[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
for (s32 i = 0; i < std::size(DUMP_LOCATIONS); i++) {
|
||||
if (location_flags & (1 << DUMP_LOCATIONS[i].type)) {
|
||||
dump_entries.emplace_back(DUMP_LOCATIONS[i].type, i);
|
||||
items.emplace_back(i18n::get(DUMP_LOCATIONS[i].name));
|
||||
}
|
||||
}
|
||||
|
||||
App::Push(std::make_shared<ui::PopupList>(
|
||||
"Select dump location"_i18n, items, [source, paths, on_exit, network_locations, stdio_locations, dump_entries](auto op_index){
|
||||
if (!op_index) {
|
||||
on_exit(0xFFFF);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto dump_entry = dump_entries[*op_index];
|
||||
|
||||
App::Push(std::make_shared<ui::ProgressBox>(0, "Dumping"_i18n, "", [source, paths, network_locations, stdio_locations, dump_entry](auto pbox) -> Result {
|
||||
if (dump_entry.type == DumpLocationType_Network) {
|
||||
R_TRY(DumpToNetwork(pbox, network_locations[dump_entry.index], source.get(), paths));
|
||||
} else if (dump_entry.type == DumpLocationType_Stdio) {
|
||||
R_TRY(DumpToStdio(pbox, stdio_locations[dump_entry.index], source.get(), paths));
|
||||
} else if (dump_entry.type == DumpLocationType_SdCard) {
|
||||
R_TRY(DumpToFileNative(pbox, source.get(), paths));
|
||||
} else if (dump_entry.type == DumpLocationType_UsbS2S) {
|
||||
R_TRY(DumpToUsbS2S(pbox, source.get(), paths));
|
||||
} else if (dump_entry.type == DumpLocationType_DevNull) {
|
||||
R_TRY(DumpToDevNull(pbox, source.get(), paths));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}, [on_exit](Result rc){
|
||||
App::PushErrorBox(rc, "Dump failed!"_i18n);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
App::Notify("Dump successfull!"_i18n);
|
||||
log_write("dump successfull!!!\n");
|
||||
}
|
||||
|
||||
on_exit(rc);
|
||||
}));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
} // namespace sphaira::dump
|
||||
@@ -119,13 +119,20 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig
|
||||
R_UNLESS(ignore_read_only || !is_read_only_root(_path), Fs::ResultReadOnly);
|
||||
|
||||
auto path_view = std::string_view{_path};
|
||||
// todo: fix this for sdmc: and ums0:
|
||||
FsPath path{"/"};
|
||||
if (auto s = std::strchr(_path.s, ':')) {
|
||||
const int len = (s - _path.s) + 1;
|
||||
std::snprintf(path, sizeof(path), "%.*s/", len, _path.s);
|
||||
path_view = path_view.substr(len);
|
||||
}
|
||||
|
||||
for (const auto dir : std::views::split(path_view, '/')) {
|
||||
if (dir.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::strncat(path, dir.data(), dir.size());
|
||||
log_write("[FS] dir creation path is now: %s\n", path.s);
|
||||
|
||||
Result rc;
|
||||
if (fs) {
|
||||
@@ -214,6 +221,11 @@ Result GetFileTimeStampRaw(FsFileSystem* fs, const FsPath& path, FsTimeStampRaw
|
||||
return fsFsGetFileTimeStampRaw(fs, path, out);
|
||||
}
|
||||
|
||||
Result SetTimestamp(FsFileSystem* fs, const FsPath& path, const FsTimeStampRaw* ts) {
|
||||
// unsuported.
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
bool FileExists(FsFileSystem* fs, const FsPath& path) {
|
||||
FsDirEntryType type;
|
||||
R_TRY_RESULT(GetEntryType(fs, path, &type), false);
|
||||
@@ -230,16 +242,16 @@ Result read_entire_file(FsFileSystem* _fs, const FsPath& path, std::vector<u8>&
|
||||
FsNative fs{_fs, false};
|
||||
R_TRY(fs.GetFsOpenResult());
|
||||
|
||||
FsFile f;
|
||||
File f;
|
||||
R_TRY(fs.OpenFile(path, FsOpenMode_Read, &f));
|
||||
ON_SCOPE_EXIT(fsFileClose(&f));
|
||||
ON_SCOPE_EXIT(f.Close());
|
||||
|
||||
s64 size;
|
||||
R_TRY(fsFileGetSize(&f, &size));
|
||||
R_TRY(f.GetSize(&size));
|
||||
out.resize(size);
|
||||
|
||||
u64 bytes_read;
|
||||
R_TRY(fsFileRead(&f, 0, out.data(), out.size(), FsReadOption_None, &bytes_read));
|
||||
R_TRY(f.Read(0, out.data(), out.size(), FsReadOption_None, &bytes_read));
|
||||
R_UNLESS(bytes_read == out.size(), 1);
|
||||
|
||||
R_SUCCEED();
|
||||
@@ -255,12 +267,12 @@ Result write_entire_file(FsFileSystem* _fs, const FsPath& path, const std::vecto
|
||||
return rc;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
File f;
|
||||
R_TRY(fs.OpenFile(path, FsOpenMode_Write, &f));
|
||||
ON_SCOPE_EXIT(fsFileClose(&f));
|
||||
ON_SCOPE_EXIT(f.Close());
|
||||
|
||||
R_TRY(fsFileSetSize(&f, in.size()));
|
||||
R_TRY(fsFileWrite(&f, 0, in.data(), in.size(), FsWriteOption_None));
|
||||
R_TRY(f.SetSize(in.size()));
|
||||
R_TRY(f.Write(0, in.data(), in.size(), FsWriteOption_None));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
@@ -276,19 +288,32 @@ Result copy_entire_file(FsFileSystem* fs, const FsPath& dst, const FsPath& src,
|
||||
Result CreateFile(const FsPath& path, u64 size, u32 option, bool ignore_read_only) {
|
||||
R_UNLESS(ignore_read_only || !is_read_only_root(path), Fs::ResultReadOnly);
|
||||
|
||||
auto fd = open(path, O_CREAT | S_IRUSR | S_IWUSR);
|
||||
auto fd = open(path, O_WRONLY | O_CREAT, DEFFILEMODE);
|
||||
if (fd == -1) {
|
||||
if (errno == EEXIST) {
|
||||
return FsError_PathAlreadyExists;
|
||||
}
|
||||
|
||||
R_TRY(fsdevGetLastResult());
|
||||
return Fs::ResultUnknownStdioError;
|
||||
}
|
||||
close(fd);
|
||||
ON_SCOPE_EXIT(close(fd));
|
||||
|
||||
if (size) {
|
||||
R_UNLESS(!ftruncate(fd, size), Fs::ResultUnknownStdioError);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CreateDirectory(const FsPath& path, bool ignore_read_only) {
|
||||
R_UNLESS(ignore_read_only || !is_read_only_root(path), Fs::ResultReadOnly);
|
||||
|
||||
if (mkdir(path, 0777)) {
|
||||
if (mkdir(path, ACCESSPERMS)) {
|
||||
if (errno == EEXIST) {
|
||||
return FsError_PathAlreadyExists;
|
||||
}
|
||||
|
||||
R_TRY(fsdevGetLastResult());
|
||||
return Fs::ResultUnknownStdioError;
|
||||
}
|
||||
@@ -310,7 +335,7 @@ Result CreateDirectoryRecursivelyWithPath(const FsPath& path, bool ignore_read_o
|
||||
Result DeleteFile(const FsPath& path, bool ignore_read_only) {
|
||||
R_UNLESS(ignore_read_only || !is_read_only(path), Fs::ResultReadOnly);
|
||||
|
||||
if (remove(path)) {
|
||||
if (unlink(path)) {
|
||||
R_TRY(fsdevGetLastResult());
|
||||
return Fs::ResultUnknownStdioError;
|
||||
}
|
||||
@@ -320,7 +345,11 @@ Result DeleteFile(const FsPath& path, bool ignore_read_only) {
|
||||
Result DeleteDirectory(const FsPath& path, bool ignore_read_only) {
|
||||
R_UNLESS(ignore_read_only || !is_read_only(path), Fs::ResultReadOnly);
|
||||
|
||||
return DeleteFile(path, ignore_read_only);
|
||||
if (rmdir(path)) {
|
||||
R_TRY(fsdevGetLastResult());
|
||||
return Fs::ResultUnknownStdioError;
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
// ftw / ntfw isn't found by linker...
|
||||
@@ -386,6 +415,20 @@ Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result SetTimestamp(const FsPath& path, const FsTimeStampRaw* ts) {
|
||||
if (ts->is_valid) {
|
||||
timeval val[2]{};
|
||||
val[0].tv_sec = ts->accessed;
|
||||
val[1].tv_sec = ts->modified;
|
||||
|
||||
if (utimes(path, val)) {
|
||||
log_write("utimes() failed: %d %s\n", errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
bool FileExists(const FsPath& path) {
|
||||
FsDirEntryType type;
|
||||
R_TRY_RESULT(GetEntryType(path, &type), false);
|
||||
@@ -438,4 +481,321 @@ Result copy_entire_file(const FsPath& dst, const FsPath& src, bool ignore_read_o
|
||||
return write_entire_file(dst, data, ignore_read_only);
|
||||
}
|
||||
|
||||
Result OpenFile(fs::Fs* fs, const fs::FsPath& path, u32 mode, File* f) {
|
||||
f->m_fs = fs;
|
||||
|
||||
if (f->m_fs->IsNative()) {
|
||||
auto fs = (fs::FsNative*)f->m_fs;
|
||||
R_TRY(fsFsOpenFile(&fs->m_fs, path, mode, &f->m_native));
|
||||
} else {
|
||||
if ((mode & FsOpenMode_Read) && (mode & FsOpenMode_Write)) {
|
||||
f->m_stdio = std::fopen(path, "rb+");
|
||||
} else if (mode & FsOpenMode_Read) {
|
||||
f->m_stdio = std::fopen(path, "rb");
|
||||
} else if (mode & FsOpenMode_Write) {
|
||||
// not possible to open file with just write and not append
|
||||
// or create or truncate. So rw it is!
|
||||
f->m_stdio = std::fopen(path, "rb+");
|
||||
}
|
||||
|
||||
R_UNLESS(f->m_stdio, Fs::ResultUnknownStdioError);
|
||||
std::strcpy(f->m_path, path);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
Close();
|
||||
}
|
||||
|
||||
Result File::Read( s64 off, void* buf, u64 read_size, u32 option, u64* bytes_read) {
|
||||
if (m_fs->IsNative()) {
|
||||
R_TRY(fsFileRead(&m_native, off, buf, read_size, option, bytes_read));
|
||||
} else {
|
||||
if (m_stdio_off != off) {
|
||||
m_stdio_off = off;
|
||||
std::fseek(m_stdio, off, SEEK_SET);
|
||||
}
|
||||
|
||||
*bytes_read = std::fread(buf, 1, read_size, m_stdio);
|
||||
|
||||
// if we read less bytes than expected, check if there was an error (ignoring eof).
|
||||
if (*bytes_read < read_size) {
|
||||
if (!std::feof(m_stdio) && std::ferror(m_stdio)) {
|
||||
R_THROW(Fs::ResultUnknownStdioError);
|
||||
}
|
||||
}
|
||||
|
||||
m_stdio_off += *bytes_read;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result File::Write(s64 off, const void* buf, u64 write_size, u32 option) {
|
||||
if (m_fs->IsNative()) {
|
||||
R_TRY(fsFileWrite(&m_native, off, buf, write_size, option));
|
||||
} else {
|
||||
if (m_stdio_off != off) {
|
||||
log_write("[FS] diff seek\n");
|
||||
m_stdio_off = off;
|
||||
std::fseek(m_stdio, off, SEEK_SET);
|
||||
}
|
||||
|
||||
const auto result = std::fwrite(buf, 1, write_size, m_stdio);
|
||||
// log_write("[FS] fwrite res: %zu vs %zu\n", result, write_size);
|
||||
R_UNLESS(result == write_size, Fs::ResultUnknownStdioError);
|
||||
|
||||
m_stdio_off += write_size;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result File::SetSize(s64 sz) {
|
||||
if (m_fs->IsNative()) {
|
||||
R_TRY(fsFileSetSize(&m_native, sz));
|
||||
} else {
|
||||
const auto fd = fileno(m_stdio);
|
||||
R_UNLESS(fd > 0, Fs::ResultUnknownStdioError);
|
||||
R_UNLESS(!ftruncate(fd, sz), Fs::ResultUnknownStdioError);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result File::GetSize(s64* out) {
|
||||
if (m_fs->IsNative()) {
|
||||
R_TRY(fsFileGetSize(&m_native, out));
|
||||
} else {
|
||||
struct stat st;
|
||||
const auto fd = fileno(m_stdio);
|
||||
bool did_stat{};
|
||||
|
||||
if (fd && !fstat(fd, &st)) {
|
||||
did_stat = true;
|
||||
}
|
||||
|
||||
if (!did_stat) {
|
||||
R_UNLESS(!lstat(m_path, &st), Fs::ResultUnknownStdioError);
|
||||
}
|
||||
|
||||
*out = st.st_size;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void File::Close() {
|
||||
if (m_fs->IsNative()) {
|
||||
if (serviceIsActive(&m_native.s)) {
|
||||
fsFileClose(&m_native);
|
||||
m_native = {};
|
||||
}
|
||||
} else {
|
||||
if (m_stdio) {
|
||||
std::fclose(m_stdio);
|
||||
m_stdio = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result OpenDirectory(fs::Fs* fs, const fs::FsPath& path, u32 mode, Dir* d) {
|
||||
d->m_fs = fs;
|
||||
d->m_mode = mode;
|
||||
|
||||
if (d->m_fs->IsNative()) {
|
||||
auto fs = (fs::FsNative*)d->m_fs;
|
||||
R_TRY(fsFsOpenDirectory(&fs->m_fs, path, mode, &d->m_native));
|
||||
} else {
|
||||
d->m_stdio = opendir(path);
|
||||
R_UNLESS(d->m_stdio, Fs::ResultUnknownStdioError);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DirGetEntryCount(fs::Fs* m_fs, const fs::FsPath& path, s64* count, u32 mode) {
|
||||
s64 file_count, dir_count;
|
||||
R_TRY(DirGetEntryCount(m_fs, path, &file_count, &dir_count, mode));
|
||||
*count = file_count + dir_count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result DirGetEntryCount(fs::Fs* m_fs, const fs::FsPath& path, s64* file_count, s64* dir_count, u32 mode) {
|
||||
*file_count = *dir_count = 0;
|
||||
|
||||
if (m_fs->IsNative()) {
|
||||
if (mode & FsDirOpenMode_ReadDirs){
|
||||
fs::Dir dir;
|
||||
R_TRY(m_fs->OpenDirectory(path, FsDirOpenMode_ReadDirs|FsDirOpenMode_NoFileSize, &dir));
|
||||
R_TRY(dir.GetEntryCount(file_count));
|
||||
}
|
||||
if (mode & FsDirOpenMode_ReadFiles){
|
||||
fs::Dir dir;
|
||||
R_TRY(m_fs->OpenDirectory(path, FsDirOpenMode_ReadFiles|FsDirOpenMode_NoFileSize, &dir));
|
||||
R_TRY(dir.GetEntryCount(file_count));
|
||||
}
|
||||
} else {
|
||||
fs::Dir dir;
|
||||
R_TRY(m_fs->OpenDirectory(path, mode, &dir));
|
||||
|
||||
while (auto d = readdir(dir.m_stdio)) {
|
||||
if (!std::strcmp(d->d_name, ".") || !std::strcmp(d->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (d->d_type == DT_DIR) {
|
||||
if (!(mode & FsDirOpenMode_ReadDirs)) {
|
||||
continue;
|
||||
}
|
||||
(*dir_count)++;
|
||||
} else if (d->d_type == DT_REG) {
|
||||
if (!(mode & FsDirOpenMode_ReadFiles)) {
|
||||
continue;
|
||||
}
|
||||
(*file_count)++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Dir::~Dir() {
|
||||
Close();
|
||||
}
|
||||
|
||||
Result Dir::GetEntryCount(s64* out) {
|
||||
*out = 0;
|
||||
|
||||
if (m_fs->IsNative()) {
|
||||
R_TRY(fsDirGetEntryCount(&m_native, out));
|
||||
} else {
|
||||
while (auto d = readdir(m_stdio)) {
|
||||
if (!std::strcmp(d->d_name, ".") || !std::strcmp(d->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
(*out)++;
|
||||
}
|
||||
|
||||
// NOTE: this will *not* work for native mounted folders!!!
|
||||
rewinddir(m_stdio);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result Dir::ReadAll(std::vector<FsDirectoryEntry>& buf) {
|
||||
buf.clear();
|
||||
|
||||
if (m_fs->IsNative()) {
|
||||
s64 count;
|
||||
R_TRY(GetEntryCount(&count));
|
||||
|
||||
buf.resize(count);
|
||||
R_TRY(fsDirRead(&m_native, &count, buf.size(), buf.data()));
|
||||
buf.resize(count);
|
||||
} else {
|
||||
buf.reserve(1000);
|
||||
|
||||
while (auto d = readdir(m_stdio)) {
|
||||
if (!std::strcmp(d->d_name, ".") || !std::strcmp(d->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FsDirectoryEntry entry{};
|
||||
|
||||
if (d->d_type == DT_DIR) {
|
||||
if (!(m_mode & FsDirOpenMode_ReadDirs)) {
|
||||
continue;
|
||||
}
|
||||
entry.type = FsDirEntryType_Dir;
|
||||
} else if (d->d_type == DT_REG) {
|
||||
if (!(m_mode & FsDirOpenMode_ReadFiles)) {
|
||||
continue;
|
||||
}
|
||||
entry.type = FsDirEntryType_File;
|
||||
} else {
|
||||
log_write("[FS] WARNING: unknown type when reading dir: %u\n", d->d_type);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::strcpy(entry.name, d->d_name);
|
||||
buf.emplace_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Dir::Close() {
|
||||
if (m_fs->IsNative()) {
|
||||
if (serviceIsActive(&m_native.s)) {
|
||||
fsDirClose(&m_native);
|
||||
m_native = {};
|
||||
}
|
||||
} else {
|
||||
if (m_stdio) {
|
||||
closedir(m_stdio);
|
||||
m_stdio = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result FileGetSizeAndTimestamp(fs::Fs* m_fs, const FsPath& path, FsTimeStampRaw* ts, s64* size) {
|
||||
*ts = {};
|
||||
*size = {};
|
||||
|
||||
if (m_fs->IsNative()) {
|
||||
auto fs = (fs::FsNative*)m_fs;
|
||||
R_TRY(fs->GetFileTimeStampRaw(path, ts));
|
||||
|
||||
File f;
|
||||
R_TRY(m_fs->OpenFile(path, FsOpenMode_Read, &f));
|
||||
ON_SCOPE_EXIT(f.Close());
|
||||
|
||||
R_TRY(f.GetSize(size));
|
||||
} else {
|
||||
struct stat st;
|
||||
R_UNLESS(!lstat(path, &st), 0x1);
|
||||
|
||||
ts->is_valid = true;
|
||||
ts->created = st.st_ctim.tv_sec;
|
||||
ts->modified = st.st_mtim.tv_sec;
|
||||
ts->accessed = st.st_atim.tv_sec;
|
||||
*size = st.st_size;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IsDirEmpty(fs::Fs* m_fs, const fs::FsPath& path, bool* out) {
|
||||
*out = true;
|
||||
|
||||
if (m_fs->IsNative()) {
|
||||
auto fs = (fs::FsNative*)m_fs;
|
||||
|
||||
s64 count;
|
||||
R_TRY(fs->DirGetEntryCount(path, &count, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles));
|
||||
*out = !count;
|
||||
} else {
|
||||
auto dir = opendir(path);
|
||||
R_UNLESS(dir, 0x1);
|
||||
ON_SCOPE_EXIT(closedir(dir));
|
||||
|
||||
while (auto d = readdir(dir)) {
|
||||
if (!std::strcmp(d->d_name, ".") || !std::strcmp(d->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*out = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace fs
|
||||
|
||||
@@ -12,14 +12,30 @@
|
||||
#include <nx/vfs_nx.h>
|
||||
#include <nx/utils.h>
|
||||
|
||||
namespace sphaira::ftpsrv {
|
||||
namespace {
|
||||
|
||||
struct InstallSharedData {
|
||||
std::mutex mutex;
|
||||
|
||||
std::deque<std::string> queued_files;
|
||||
|
||||
void* user;
|
||||
OnInstallStart on_start;
|
||||
OnInstallWrite on_write;
|
||||
OnInstallClose on_close;
|
||||
|
||||
bool in_progress;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
const char* INI_PATH = "/config/ftpsrv/config.ini";
|
||||
FtpSrvConfig g_ftpsrv_config = {0};
|
||||
volatile bool g_should_exit = false;
|
||||
bool g_is_running{false};
|
||||
Thread g_thread;
|
||||
std::mutex g_mutex{};
|
||||
InstallSharedData g_shared_data{};
|
||||
|
||||
void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) {
|
||||
sphaira::App::NotifyFlashLed();
|
||||
@@ -29,6 +45,235 @@ void ftp_progress_callback(void) {
|
||||
sphaira::App::NotifyFlashLed();
|
||||
}
|
||||
|
||||
const char* SUPPORTED_EXT[] = {
|
||||
".nsp", ".xci", ".nsz", ".xcz",
|
||||
};
|
||||
|
||||
struct VfsUserData {
|
||||
char* path;
|
||||
int valid;
|
||||
};
|
||||
|
||||
// ive given up with good names.
|
||||
void on_thing() {
|
||||
log_write("[FTP] doing on_thing\n");
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
log_write("[FTP] locked on_thing\n");
|
||||
|
||||
if (!g_shared_data.in_progress) {
|
||||
if (!g_shared_data.queued_files.empty()) {
|
||||
log_write("[FTP] pushing new file data\n");
|
||||
if (!g_shared_data.on_start || !g_shared_data.on_start(g_shared_data.user, g_shared_data.queued_files[0].c_str())) {
|
||||
g_shared_data.queued_files.clear();
|
||||
} else {
|
||||
log_write("[FTP] success on new file push\n");
|
||||
g_shared_data.in_progress = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int vfs_install_open(void* user, const char* path, enum FtpVfsOpenMode mode) {
|
||||
{
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
auto data = static_cast<VfsUserData*>(user);
|
||||
data->valid = 0;
|
||||
|
||||
if (mode != FtpVfsOpenMode_WRITE) {
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_shared_data.enabled) {
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* ext = strrchr(path, '.');
|
||||
if (!ext) {
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < std::size(SUPPORTED_EXT); i++) {
|
||||
if (!strcasecmp(ext, SUPPORTED_EXT[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// check if we already have this file queued.
|
||||
auto it = std::find(g_shared_data.queued_files.cbegin(), g_shared_data.queued_files.cend(), path);
|
||||
if (it != g_shared_data.queued_files.cend()) {
|
||||
errno = EEXIST;
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_shared_data.queued_files.push_back(path);
|
||||
data->path = strdup(path);
|
||||
data->valid = true;
|
||||
}
|
||||
|
||||
on_thing();
|
||||
log_write("[FTP] got file: %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfs_install_read(void* user, void* buf, size_t size) {
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int vfs_install_write(void* user, const void* buf, size_t size) {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
if (!g_shared_data.enabled) {
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto data = static_cast<VfsUserData*>(user);
|
||||
if (!data->valid) {
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_shared_data.on_write || !g_shared_data.on_write(g_shared_data.user, buf, size)) {
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int vfs_install_seek(void* user, const void* buf, size_t size, size_t off) {
|
||||
errno = ESPIPE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int vfs_install_isfile_open(void* user) {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
auto data = static_cast<VfsUserData*>(user);
|
||||
return data->valid;
|
||||
}
|
||||
|
||||
int vfs_install_isfile_ready(void* user) {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
auto data = static_cast<VfsUserData*>(user);
|
||||
const auto ready = !g_shared_data.queued_files.empty() && data->path == g_shared_data.queued_files[0];
|
||||
return ready;
|
||||
}
|
||||
|
||||
int vfs_install_close(void* user) {
|
||||
{
|
||||
log_write("[FTP] closing file\n");
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
auto data = static_cast<VfsUserData*>(user);
|
||||
if (data->valid) {
|
||||
log_write("[FTP] closing valid file\n");
|
||||
|
||||
auto it = std::find(g_shared_data.queued_files.cbegin(), g_shared_data.queued_files.cend(), data->path);
|
||||
if (it != g_shared_data.queued_files.cend()) {
|
||||
if (it == g_shared_data.queued_files.cbegin()) {
|
||||
log_write("[FTP] closing current file\n");
|
||||
if (g_shared_data.on_close) {
|
||||
g_shared_data.on_close(g_shared_data.user);
|
||||
}
|
||||
|
||||
g_shared_data.in_progress = false;
|
||||
} else {
|
||||
log_write("[FTP] closing other file...\n");
|
||||
}
|
||||
|
||||
g_shared_data.queued_files.erase(it);
|
||||
} else {
|
||||
log_write("[FTP] could not find file in queue...\n");
|
||||
}
|
||||
|
||||
if (data->path) {
|
||||
free(data->path);
|
||||
}
|
||||
|
||||
data->valid = 0;
|
||||
}
|
||||
|
||||
memset(data, 0, sizeof(*data));
|
||||
}
|
||||
|
||||
on_thing();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfs_install_opendir(void* user, const char* path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* vfs_install_readdir(void* user, void* user_entry) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int vfs_install_dirlstat(void* user, const void* user_entry, const char* path, struct stat* st) {
|
||||
st->st_nlink = 1;
|
||||
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfs_install_isdir_open(void* user) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int vfs_install_closedir(void* user) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfs_install_stat(const char* path, struct stat* st) {
|
||||
st->st_nlink = 1;
|
||||
st->st_mode = S_IFDIR | S_IWUSR | S_IWGRP | S_IWOTH;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfs_install_mkdir(const char* path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int vfs_install_unlink(const char* path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int vfs_install_rmdir(const char* path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int vfs_install_rename(const char* src, const char* dst) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
FtpVfs g_vfs_install = {
|
||||
.open = vfs_install_open,
|
||||
.read = vfs_install_read,
|
||||
.write = vfs_install_write,
|
||||
.seek = vfs_install_seek,
|
||||
.close = vfs_install_close,
|
||||
.isfile_open = vfs_install_isfile_open,
|
||||
.isfile_ready = vfs_install_isfile_ready,
|
||||
.opendir = vfs_install_opendir,
|
||||
.readdir = vfs_install_readdir,
|
||||
.dirlstat = vfs_install_dirlstat,
|
||||
.closedir = vfs_install_closedir,
|
||||
.isdir_open = vfs_install_isdir_open,
|
||||
.stat = vfs_install_stat,
|
||||
.lstat = vfs_install_stat,
|
||||
.mkdir = vfs_install_mkdir,
|
||||
.unlink = vfs_install_unlink,
|
||||
.rmdir = vfs_install_rmdir,
|
||||
.rename = vfs_install_rename,
|
||||
};
|
||||
|
||||
void loop(void* arg) {
|
||||
while (!g_should_exit) {
|
||||
ftpsrv_init(&g_ftpsrv_config);
|
||||
@@ -44,8 +289,6 @@ void loop(void* arg) {
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace sphaira::ftpsrv {
|
||||
|
||||
bool Init() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
if (g_is_running) {
|
||||
@@ -84,6 +327,9 @@ bool Init() {
|
||||
mount_bis = ini_getbool("Nx-App", "mount_bis", mount_bis, INI_PATH);
|
||||
save_writable = ini_getbool("Nx-App", "save_writable", save_writable, INI_PATH);
|
||||
|
||||
mount_devices = true;
|
||||
g_ftpsrv_config.timeout = 0;
|
||||
|
||||
if (!g_ftpsrv_config.port) {
|
||||
return false;
|
||||
}
|
||||
@@ -93,7 +339,13 @@ bool Init() {
|
||||
g_ftpsrv_config.anon = true;
|
||||
}
|
||||
|
||||
vfs_nx_init(mount_devices, save_writable, mount_bis);
|
||||
const VfsNxCustomPath custom = {
|
||||
.name = "install",
|
||||
.user = NULL,
|
||||
.func = &g_vfs_install,
|
||||
};
|
||||
|
||||
vfs_nx_init(&custom, mount_devices, save_writable, mount_bis);
|
||||
|
||||
Result rc;
|
||||
if (R_FAILED(rc = threadCreate(&g_thread, loop, nullptr, nullptr, 1024*16, 0x2C, 2))) {
|
||||
@@ -123,6 +375,40 @@ void Exit() {
|
||||
fsdev_wrapUnmountAll();
|
||||
}
|
||||
|
||||
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close) {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
g_shared_data.user = user;
|
||||
g_shared_data.on_start = on_start;
|
||||
g_shared_data.on_write = on_write;
|
||||
g_shared_data.on_close = on_close;
|
||||
g_shared_data.enabled = true;
|
||||
}
|
||||
|
||||
void DisableInstallMode() {
|
||||
std::scoped_lock lock{g_shared_data.mutex};
|
||||
g_shared_data.enabled = false;
|
||||
}
|
||||
|
||||
unsigned GetPort() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
return g_ftpsrv_config.port;
|
||||
}
|
||||
|
||||
bool IsAnon() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
return g_ftpsrv_config.anon;
|
||||
}
|
||||
|
||||
const char* GetUser() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
return g_ftpsrv_config.user;
|
||||
}
|
||||
|
||||
const char* GetPass() {
|
||||
std::scoped_lock lock{g_mutex};
|
||||
return g_ftpsrv_config.pass;
|
||||
}
|
||||
|
||||
} // namespace sphaira::ftpsrv
|
||||
|
||||
extern "C" {
|
||||
@@ -132,9 +418,9 @@ void log_file_write(const char* msg) {
|
||||
}
|
||||
|
||||
void log_file_fwrite(const char* fmt, ...) {
|
||||
std::va_list v{};
|
||||
va_list v{};
|
||||
va_start(v, fmt);
|
||||
log_write_arg(fmt, v);
|
||||
log_write_arg(fmt, &v);
|
||||
va_end(v);
|
||||
}
|
||||
|
||||
|
||||