186 Commits

Author SHA1 Message Date
ITotalJustice
7f2d0e72f2 fix workflow 2025-11-18 18:29:38 +00:00
ITotalJustice
c9552f9785 bump version for new release 0.13.3 -> 1.0.0. 2025-11-18 18:11:59 +00:00
ITotalJustice
444ff3e2d1 swkdb: add support for setting the header. save: add support for setting the name for the save file. 2025-10-09 14:45:08 +01:00
ITotalJustice
7d56c8a381 increase list scroll speed. add list jump start/end. add L2 + scroll to select multiple enrties. 2025-10-07 07:05:38 +01:00
ITotalJustice
da051f8d8f add support for replacing the homebrew menu tab with another menu. 2025-10-03 09:58:48 +01:00
ITotalJustice
81e6bc5833 disable sftp as it was unused and very slow compared to other clients. 2025-10-03 09:07:12 +01:00
ITotalJustice
ca5ea827b2 devoptab: fix nginx listing, fix modifying entry overriding the scheme, fix smb failing to parse url if path isn't set. ftpsrv: workaround clients sending PASS for anon. 2025-10-03 07:31:10 +01:00
ITotalJustice
b700fff9ac devoptab: fix create new mount entries failing if the mount folder didn't already exist. 2025-09-29 02:19:25 +01:00
ITotalJustice
81741364a7 homebrew: fix crashing due to using the wrong array size when scrolling. 2025-09-28 23:04:16 +01:00
ITotalJustice
faebc42f0d fs: fix stdio dir count not filtering types. game/bfsar: fix dir listing loop exiting early due to post increment in the loop. 2025-09-27 03:37:29 +01:00
ITotalJustice
63e11ca377 remove unused 12h clock option. add option to hide ip address. 2025-09-21 22:13:53 +01:00
ITotalJustice
54a2215e04 support for filtering mtp/ftp mount options. use builtin config for ftp port,user,pass. 2025-09-21 21:56:36 +01:00
ITotalJustice
5edc3869cd display storage sizes, properly colour info text, and more (see below)
- display internal and sd card storage sizes.
- removed battery info.
- removed current time info.
- fix dumping save to sd card due to not opening the file with append.
- change all sizes to display GB instead of GiB.
- change progress bar units to 1000 rather than 1024.
- all info text, such as sizes and timestamps now use the info text colouring.
- shorten the ncm content type names.
2025-09-21 18:54:08 +01:00
ITotalJustice
a772d660f3 use spinner instead of default icon for homebrew + games menus.
more menus will use it soon.
need to have a way to show the spinner when loading, then revert to the default icon if
failed to load, or an icon doesn't exist.

otherwise, the user may think that the icon is still loading and wait for it.
2025-09-21 04:08:36 +01:00
ITotalJustice
3c504cc85d devoptab: add mounts (wrapper around all mounts, exposed via MTP/FTP). lots of fixes (see below).
- updated libhaze to 81154c1.
- increase ftpsrv stack size as it would crash when modifying custom mounts.
- fix warning for unused log data in haze.
- fix eof read for nsp/xci source by instead returning 0 for bytes read, rather than error.
- add support for lstat the root of a mount.
- handle zero size reads when reading games via devoptab.
2025-09-21 03:51:13 +01:00
ITotalJustice
0a2c16db0c mtp: bump to 6e24502, fixes freezing if write blocks for too long, simplify stream install for mtp and ftp.
see: https://github.com/ITotalJustice/libhaze/issues/1
2025-09-20 20:27:02 +01:00
ITotalJustice
2bd84c8d5a add version overrides for builds. 2025-09-19 19:43:38 +01:00
ITotalJustice
7cd668efb7 keyboard: swap Z/X 2025-09-18 17:14:37 +01:00
ITotalJustice
a6265c3089 add keyboard navigation support. 2025-09-18 17:06:22 +01:00
ITotalJustice
a2300c1a96 fix crashes when signalling a event thats not created yet.
this caused a crash after installing a game if the games menu wasn't init.
the same would happen if the games menu signalled the save menu.
2025-09-18 15:35:24 +01:00
ITotalJustice
3dae3f9173 devoptab/curl: fix rare deadlock casued by sleeping/blocking in curl callback functions.
it seems that curl does not like long blocking in the r/w callbacks.
blocking for too seems to cause a deadlock as the server stops send/recv anymore data.

to fix this, i now use curls pause api.
this api is not thread safe, so it's a little more involved than it needs to be.

however this fixes the deadlock as curls pause actually reduces the download/upload speed
to the minimum. it also reduces exit latency as now exiting is handled in the progress callback
as well, which is called far more often than r/w.
2025-09-16 04:15:56 +01:00
ITotalJustice
63c420d5d8 devoptab: set default url scheme and port in creator. make form sidebar slightly wider and always show on the left side. 2025-09-15 21:51:06 +01:00
ITotalJustice
a94c6bb581 devoptab: add games. add MTP and FTP game exporting. update ftpsrv (see below). fix "fix_path()" parsing.
ftpsrv was updated to support MLST and MLSD, as well as fixing SIZE (was fixed to 32bit).
2025-09-15 21:18:53 +01:00
ITotalJustice
9fe0044a65 devoptab: only push popuplist if the items array is non-empty. curl: guess the url scheme rather than force https. 2025-09-14 15:14:35 +01:00
ITotalJustice
c05ce5eff4 yati: signal change to games menu when a new game is installed. 2025-09-14 14:52:21 +01:00
ITotalJustice
a019103ed5 mui: create menus info text from the menus array, rather than hardcoding them. 2025-09-14 14:37:00 +01:00
ITotalJustice
50e55f4fca mtp: support overriding vid/pid. 2025-09-14 14:30:46 +01:00
ITotalJustice
0706683690 mui: rename misc to menus, change menu options order so that menus is at the top, improve some info boxes text. 2025-09-14 14:16:50 +01:00
ITotalJustice
9cdb77bafa devoptab: add mount creator. 2025-09-14 14:04:20 +01:00
ITotalJustice
b476c54825 devoptab: add workaround for dkp nullptr bug.
manually set the array at startup to avoid nullptr access.
2025-09-13 13:28:55 +01:00
ITotalJustice
8b2e541b1d lots of changes, see description.
- enable sftp by default.
- add more descriptive stdio errors.
- disable devoptab timeout by default.
- handle errors for devoptab seek.
- add r/d/w progress events for threaded_file_transfer
- remove system album from file browser, only show sd card.
- do not clear game selection in games menu. useful for selecting games to backup, then delete after.
- change smb2 r/w to only send max amount, matches nfs behaviour. not sure if its needed as smb probably handles it for us.
2025-09-13 13:16:18 +01:00
ITotalJustice
931531e799 devoptab: add SFTP. fs: disable stdio buffering. cmake: add options to disable components of sphaira, add new "lite" build with minimal features. 2025-09-09 18:39:03 +01:00
ITotalJustice
1695d69aa3 audio: enable flac, make thread safe, fix crash on exit if audio wasn't init. 2025-09-09 10:39:52 +01:00
ITotalJustice
217bd3bed3 mui: add list index to sidebar and popup_list, and better center the index text. 2025-09-08 01:47:41 +01:00
ITotalJustice
384e8794bf devoptab: refactor all custom mounts to inherit from helper struct. 2025-09-08 01:34:20 +01:00
ITotalJustice
61b398a89a fatfs: use devoptab mounting. devoptab: add config for hidding from fs and dump, fix http being writeable. 2025-09-07 17:35:37 +01:00
ITotalJustice
ba78fd0dc5 devoptab: add vfs, change mount.ini path location. 2025-09-07 15:43:01 +01:00
ITotalJustice
43969a773e fs: fix CreateDirectoryRecursivelyWithPath() for root files. save: fix restore detection. devoptab: return proper errno codes. 2025-09-07 14:40:45 +01:00
ITotalJustice
6e1eabbe0f devoptab: deprecate locations.ini in favour of hdd/network mounts, better handle folder creation errors. 2025-09-07 13:30:53 +01:00
ITotalJustice
b99d1e5dea devoptab: add webdav, refactor network devices, multi thread r/w to improve perf and support webdav uploads. 2025-09-07 12:40:45 +01:00
ITotalJustice
6ce566aea5 http: optimise the dir_list parsing, only parse tables. filebrowser: option to disable stat per fs.
this improves network fs speed by disabling stat for http entriely, and only enabling file stat for everything else.
this can be overriden in the config.
2025-09-05 14:10:06 +01:00
ITotalJustice
a4209961e2 devoptab: add ftp mount with random read and streaming write support.
read still needs some work. opening a file should open a thread where data is read/written async.
this avoids the huge number of roundtrips per r/w.

eg, a read currently has to REST, open the data socket, send the file data, send ABOR to close and then close the data socket.

using the thread approach would just send the file over the already open data socket.
writes will also benefit from the above, just instead of REST it would be APPE.

seeks would need to do the above ABORT, close and re-open. however most reads in sphaira are not random access. so this isn't an issue.
2025-09-04 22:29:35 +01:00
ITotalJustice
181ff3f2bf devoptab: fix http config parsing, add more options to network mounts (timeout,uid,port). fs: update path size.
fs path was changed to 255 in the past because that is the real path size that fs can handle.
however, this restriction does not apply to nfs, samba, http - and it is very easy to exceed this 255 path length.

to fix this, i have increased the path len to 1024. this allows fs to continue to work as the buffer is
big enough, but also gives network mounts enough space to work with.
2025-09-04 12:18:34 +01:00
ITotalJustice
b85b522643 app: remove ams erpt disable as it is possible to cause ams to fatal if a crash report fails to write. 2025-09-04 09:40:39 +01:00
ITotalJustice
5158e264c0 devoptab: add http, nfs and smb mount. nca: zero init ncz structs. fs: fix FsPath compare. 2025-09-03 18:56:54 +01:00
ITotalJustice
fd67da0527 webusb: add support for exporting. usb: block requests with no timeout, using pbox to cancel if the user presses B. 2025-09-02 04:24:45 +01:00
ITotalJustice
7bdec8457f tests: move location of usb tests and update workflows for the new paths. 2025-08-31 07:37:54 +01:00
ITotalJustice
bc75c9a89f workflow: temp disable main workflow on dev until libnx pushes a new release. 2025-08-31 07:30:13 +01:00
ITotalJustice
c2e8734e85 webusb: add webUSB page and workflow to auto build it. 2025-08-31 07:15:53 +01:00
ITotalJustice
22e965521a usb: change api so that every packet sent is crc32c, update python usb api, add automated tests for usb. 2025-08-31 06:12:02 +01:00
ITotalJustice
b6b1af5959 Revert "Clean up python (#212)"
This reverts commit 0a8bc01870.
2025-08-31 04:27:53 +01:00
ITotalJustice
876be3b7b6 download: add ref count to cache init/exit and have all threads call init.
this ensures that the etag cache is ready to use before a download request is made, avoiding a rare race condition.
2025-08-31 03:50:44 +01:00
ITotalJustice
62f48353ba download: use new etag cache file and delete the old one, this removes all the old themezer entries. 2025-08-31 03:50:44 +01:00
ITotalJustice
9c65e5a12d app: remove old themezer cache 2025-08-31 03:50:44 +01:00
ITotalJustice
235e947186 app: move config to async init 2025-08-31 03:50:44 +01:00
AndrewSpangler
0a8bc01870 Clean up python (#212)
* Python cleanup
2025-08-31 03:48:37 +01:00
ITotalJustice
f0bdc01156 huge changes to everything (see below).
Changelog:
- re-enable use in release build.
- remove ftpsrv and untitled from builtin ghdl options, as both packages are available in the appstore.
- add image viewer (png, jpg, bmp)
- add music player (bfstm, bfwav, mp3, wav, ogg)
- add idv3 tag parsing support for mp3.
- add "decyption" of GTA Vice City mp3.
- add usbdvd support for music playback and file browsing.
- add nsz export support (solid, block, ldm).
- add xcz export support (same as above).
- add nro fs proper mount support (romfs, nacp, icon).
- add program nca fs support.
- add bfsar fs support.
- re-write the usb protocol, still wip. replaces tinfoil protocol.
- all threads are now create with pre-emptive support with the proper affinity mask set.
- fix oob crash in libpulsar when a bfwav was opened that had more than 2 channels.
- bump yyjson version.
- bump usbhsfs version.
- disable nvjpg.
- add support for theme music of any supported playback type (bfstm, bfwav, mp3, wav, ogg).
- add support for setting background music.
- add async exit to blocking threads (download, nxlink, ftpsrv) to reduce exit time.
- add support for dumping to pc via usb.
- add null, deflate, zstd hash options, mainly used for benchmarking.
- add sidebar slider (currently unused).
- file_viwer can now be used with any filesystem.
- filebrowser will only ever stat file once. previously it would keep stat'ing until it succeeded.
- disabled themezer due to the api breaking and i am not willing to keep maintaining it.
- disable zlt handling in usbds as it's not needed for my api's because the size is always known.
- remove usbds enums and GetSpeed() as i pr'd it to libnx.
- added support for mounting nca's from any source, including files, memory, nsps, xcis etc.
- split the lru cache into it's own header as it's now used in multiple places (nsz, all mounted options).
- add support for fetching and decrypting es personalised tickets.
- fix es common ticket converting where i forgot to also convert the cert chain as well.
- remove the download default music option.
- improve performance of libpulsar when opening a bfsar by remove the large setvbuf option. instead, use the default 1k buffer and handle large buffers manually in sphaira by using a lru cache (todo: just write my own bfsar parser).
- during app init and exit, load times have been halved as i now load/exit async. timestamps have also been added to measure how long everything takes.
- download now async loads / exits the etag json file to improve init times.
- add custom zip io to dumper to support writing a zip to any dest (such as usb).
- dumper now returns a proper error if the transfer was cancelled by the user.
- fatfs mount now sets the timestamp for files.
- fatfs mount handles folders with the archive bit by reporting them as a file.
- ftpsrv config is async loaded to speed up load times.
- nxlink now tries attempt to connect/accept by handling blocking rather than just bailing out.
- added support for minini floats.
- thread_file_transfer now spawns 3 threads rather than 2, to have the middle thread be a optional processor (mainly used for compressing/decompressing).
- added spinner to progress box, taken from nvg demo.
- progress box disables sleep mode on init.
- add gamecard detection to game menu to detect a refresh.
- handle xci that have the key area prepended.
- change gamecard mount fs to use the xci mount code instead of native fs, that way we can see all the partitions rather than just secure.
- reformat the ghdl entries to show the timestamp first.
- support for exporting saves to pc via usb.
- zip fs now uses lru cache.
2025-08-28 23:12:34 +01:00
ITotalJustice
cd6fed6aae devoptab: use fixed size array of entries rather than vector as vector can change/break pointers when it reallocs. fs: disable loading assoc when mounting custom fs. 2025-08-12 08:04:24 +01:00
ITotalJustice
7835ebc346 perf: async signal exit ftpsrv and nxlink thread in order to not block. add perf logging for exit. 2025-08-11 22:26:28 +01:00
ITotalJustice
3c33581a08 devoptab: cache all reads to max read throughput, now as fast as normal sd card reads, including compressed zips (80MiB/s). 2025-08-11 21:35:43 +01:00
ITotalJustice
3e9a8c9249 devoptab: only add new entry if mounting is successful. 2025-08-11 20:59:56 +01:00
ITotalJustice
d6c8f120c6 fs: add zip mount support. hash: fix not checking open result for file. fs: fix stdio not checking nullptr access. 2025-08-11 20:01:53 +01:00
ITotalJustice
cb2fa1abfc fs: add support for mounting nsp an xci files in the filebrowser. 2025-08-11 07:01:52 +01:00
ITotalJustice
25f2cfbff2 save: support for mounting save fs, add LRU cache for fatfs. add mounting nro romfs. 2025-08-10 17:31:22 +01:00
ITotalJustice
3404d4cece app: destroy widgets in reverse order on exit as the widgets are a stack. 2025-08-09 11:45:13 +01:00
ITotalJustice
44e1584461 fs: add support for mounting nca, save and gamecard fs. file picker inherits from browser. fix bugs (see below).
- fixed fs real path length not actually being 0x301, but instead 255. fixes #204
- file picker inherits from file browser now so there's a lot less duplicated code.
- file browser now saves the last highlighted file.
- fix bug in file browser where the new file path could be empty (ie not containing a /).
- added support for viewing qlaunch romfs.
- moved fs mount options to the top of the list (may revert).
2025-08-09 11:34:35 +01:00
ITotalJustice
8a16188996 fat: add support for mounting fat bis paritions (prodinfof, safe, user, system), inital work needed for fetching personalised es tickets. 2025-08-08 05:12:51 +01:00
ITotalJustice
70518762ae game_menu: remove stubbed option to enable/disable title cache.
the option was never actually implemented and there's no benifit to disabling it anyway.
2025-08-06 14:11:59 +01:00
ITotalJustice
a0370912da Add content/nca viewer for games menu, fix manual nca title fetch for chinese lang icon, rename "dump" to "export". 2025-08-06 14:11:05 +01:00
ITotalJustice
3fee702ee2 ftp_menu: fix passphrase ascii convert which could cause a stack overflow.
from my notes:
this converts the passphrase array into an ascii string
well, turns out std::transform does not actually increase the std::string size when pushing the new ascii characters
basically, std::string (normally) has small string optimisation. For this, they have a small array which it uses instead of allocating memory.

std::transform casts the std::string itr to a void*. sooo it was basically manually writing to the class, which just so happened to be the SSO buffer. however after 32chars, it runs out...and starts overwriting the stack. so this was a stack overflow lmao
only reason i found this is because i changed some optimisation flags, so the compiler ended up inlining std::transform, which detected the buffer overflow. and then i realised how fucked it was lol.
2025-08-04 20:17:14 +01:00
ITotalJustice
54d73a6d3b optimise: pass all large objects (std::function, std::string, structs) by const ref rather than value.
really, these functions should be passed by && and using std::forward on assignment.
however, this means writing a lot of extra code for every single class, as well as explicitly calling move
in a lot of cases.

In the case of std::string, passing by value and calling std::move is the correct approach, especially if the
string is created as an rvalue, then it is only created once and moved into the dest.
Whereas with a const ref, the string is created and then copied into the dst, basically creating 2 copies.
The same thing happens std::function, and well any object.

However, accepting everything by value sucks if you call a constructor from within a constructor, as now you need to create 2 impls
that accept by value and the other by rvalue. All of this extra code to have a more efficent impl just isn't worth it when, going by
the benchmarks, makes no measurable difference (i count anything within >= 1ms as measurable).
2025-08-04 18:58:20 +01:00
ITotalJustice
9fe9c9d491 themezer: only show latest release for nxtheme when prompting user to download nro. 2025-08-03 04:00:21 +01:00
ITotalJustice
4300c9ee1b filebrowser/picker: backport changes in totalsms (optimise zip peek, remove unused vars and code, optimise folder count, fix missed extension parse). 2025-08-03 03:26:24 +01:00
ITotalJustice
a3780bdcea filebrowser: add forwarder creator. 2025-08-03 00:58:03 +01:00
ITotalJustice
6554b68efa menu: add filepicker, sidebar: add file picker entry.
the filepicker is a stripped down version of the file browser. only file picking is supported atm, no "select folder" yet.
2025-08-02 22:19:48 +01:00
ITotalJustice
1a00db9d55 sidebar: add text input entry 2025-08-02 19:18:42 +01:00
ITotalJustice
fb3ad260da install: add enable prompt to filebrowser and gc menu when an install option is clicked whilst disabled. 2025-08-02 18:41:52 +01:00
ITotalJustice
ed02b0f260 sidebar: add callback when a disabled option is clicked. install: option to enable when a disabled option is clicked. 2025-08-02 18:30:56 +01:00
ITotalJustice
620334439c themezer: show option to launch nro. 2025-08-02 17:51:36 +01:00
ITotalJustice
ab5c54b47a themeze: prompt user to install theme after installing. 2025-08-02 17:47:09 +01:00
ITotalJustice
40e4616520 themezer: prompt user to download ThemeInjector if not installed on launch. 2025-08-02 17:21:07 +01:00
ITotalJustice
3ebb3bd055 option to hide homebrew. 2025-07-31 18:47:41 +01:00
ITotalJustice
25e19b22f7 fix broken link in readme for switch-010editor-templates 2025-07-31 03:05:47 +01:00
ITotalJustice
c67266fe1a display install options by default, but warn the user that installing is disabled an instruct them on how to enable. 2025-07-31 02:48:19 +01:00
ITotalJustice
92d747a0f5 remove seconds from the clock, fix long file name in info box for theme music. 2025-07-31 01:41:07 +01:00
ITotalJustice
79b52ed13e fix the spacing on some of the info boxes 2025-07-31 01:32:21 +01:00
ITotalJustice
bd3ad8782a bump ftpsrv and haze versions (ftpsrv 85b3cf0, haze 0be1523). haze now has 4gb+ support. 2025-07-31 01:23:50 +01:00
ITotalJustice
6b57619871 change default option when installing for nca verify.
the default options where to always verify the nca content, this was for brick protection.
however, users seem to not care about this protection and get frustrated with the default values,
seemingly unable to find the install menu to disable said options.

this change skips verify by default. less issues for me, less unhappy users, win win.
2025-07-30 23:28:25 +01:00
ITotalJustice
c8644c80cd add more info boxes to options, merge option.cpp changes from totalsms. 2025-07-30 23:26:15 +01:00
ITotalJustice
430ee2280a update scrolling using delta_time to allow for smooth scrolling at different refresh rates (ie, using fpslocker). 2025-07-30 18:03:50 +01:00
vanTheodore
f71c10619c Update es.json (#194) 2025-07-30 18:03:28 +01:00
ITotalJustice
b6497c03f6 backport widget changes from totalsms 2025-07-21 12:40:54 +01:00
ITotalJustice
ecb470b938 ghdl: display timestamp for each release and asset 2025-07-21 12:24:28 +01:00
ITotalJustice
a9d3734117 ghdl: list all releases 2025-07-21 11:23:43 +01:00
redraz
faf77d8212 Update ru.json (#192) 2025-07-21 10:34:10 +01:00
Ny'hrarr
3e6d7af720 Update pt.json (#189) 2025-07-21 10:33:57 +01:00
BIGBIGSUI
f69cf8130d Update zh.json (#187)
an updated zh.json.  hope it helps.
2025-07-21 10:33:42 +01:00
ITotalJustice
796208722e workflow: only push new workflow release on push, set specific cmake version, use nproc to use all cores when building. 2025-07-21 10:30:11 +01:00
ITotalJustice
85dbc54641 remove unused usb script 2025-07-21 10:20:58 +01:00
ITotalJustice
8381446a69 disable usb and ftp installs 2025-07-21 10:17:27 +01:00
ITotalJustice
97dc39620c use condivar in install stream to wait until the buffer is empty 2025-07-20 20:25:49 +01:00
ITotalJustice
7fb973c28d handle more usb errors, set max for notifications, usb transfer uses unique ptr over vector, show usb speed in usb menu. 2025-07-19 20:29:53 +01:00
ITotalJustice
4421ac1ceb use uevent in transfer loops to reduce latency between exiting and updating progress time. 2025-07-19 18:33:47 +01:00
ITotalJustice
159abfa246 bump libhaze version, multithread mtp transfers to maximise sd throughput (77MiB/s read, 40MiB/s write) 2025-07-19 17:45:39 +01:00
ITotalJustice
4b06700187 add info boxes, fix case sensitive path compares, many other changes (see comments)
- nca::ParseControl now gets the nacp base on the language.
- xci returns a more descriptive error and logs info during install.
- replace volatile with atmoic, label atmoic methods with volatile as per the standard.
- s2s usb install skips verifying the content as its being installed from another switch.
- fix game / save menu bug where it would only load the nacp if it has started yet, now it will force load even if its in progress.
  this fixes save file zips having empty names.
- game menu saves the application type, to be later used for displaying if its a gamecard (inserted or not), launched etc.
2025-07-19 17:33:28 +01:00
Chronoss
ee68ca54b8 Update fr.json (#182) 2025-06-25 19:31:22 +01:00
ITotalJustice
0bfd336796 always set FsCreateOption_BigFile in fs.cpp when creating 4GiB+ file. 2025-06-25 19:28:23 +01:00
ITotalJustice
9b967a9af0 set FsSaveDataMetaInfo in save creation to match hos. 2025-06-25 19:25:00 +01:00
ITotalJustice
299aaa5359 fix save menu crash caused by commit 2ff2923. 2025-06-25 19:17:35 +01:00
ITotalJustice
4e3927bbd0 reduce explicit calls to make_unique by having app::push and options::add call it for us.
this commit doesn't change the codegen. it just cleans up the code slightly.
2025-06-25 19:17:01 +01:00
ITotalJustice
38f19ec778 add option to disable erpt_reports 2025-06-25 17:49:45 +01:00
ITotalJustice
168f4b0303 slightly optimise hbl by reading nro in a single pass and remove dynamic memory. 2025-06-25 17:37:28 +01:00
ITotalJustice
6f8300fb32 filter out some apps (hbmenu, retroarch core) if replaced via another app, or false positive. remove uneeded file.close() and commit calls. 2025-06-25 01:39:46 +01:00
ITotalJustice
2ff2923d39 replace almost all uses of shared_ptr with unique_ptr. 2025-06-23 20:02:04 +01:00
ITotalJustice
aa724e12ba fix invalid character being set in file path for themes (via theme name or author). log module in error box.
fixes #184
2025-06-23 17:08:42 +01:00
ITotalJustice
e039309a77 clear title result array when nxtc cache is deleted. rename sys-tweek -> sys-tweak. 2025-06-22 21:34:36 +01:00
ITotalJustice
a4f0a97088 use app_id for save backups for when the app is corrupted. 2025-06-21 23:25:21 +01:00
ITotalJustice
9d4c431eef add save creation. add loading sys-tweek entries. add title cache delete. 2025-06-21 22:25:51 +01:00
ITotalJustice
1f7179e941 simplify title cache loading. 2025-06-21 15:32:55 +01:00
ITotalJustice
4d27bf5492 bump version for new release 0.13.2 -> 0.13.3 2025-06-19 11:40:42 +01:00
ITotalJustice
6b85d2cef1 fix account save listing where the profile uid differs from the account uid.
i am unsure how this happens, as i thought the profile uid was the same as the account uid, but apparently this can differ.
on the same switch in sysmmc, the uid's match, so everything works.
again on the same switch, created in emummc, same account, the uid differs...

i performed the same test but on another 2 switches, and the uid's all match, so i am not sure what causes them to change.

in any case, using the uid from the account is the intended behaviour anyway, so this commit fixes that.
2025-06-19 11:37:08 +01:00
xHR
aae9930f5e Updated UK translation (#180)
* fixed long strings, translate untranslated

* Updated UK translation
2025-06-19 11:36:42 +01:00
ITotalJustice
eca19aa4bf fix hdd createDirWithPath, improve flashcart detection in gc menu, bump version for new release 0.13.1 -> 0.13.2 2025-06-18 23:17:58 +01:00
ITotalJustice
8e02538405 fix nvjpg crashing when trying to load a non-jpg image, fix building with nvjpg disable, optimise invalid nro asset loading, bump version for new release 0.13.0 -> 0.13.1 2025-06-18 21:10:15 +01:00
ITotalJustice
928da0cbda bump version for new release 0.12.0 -> 0.13.0 2025-06-18 15:42:50 +01:00
ITotalJustice
267693c6ab Revert "disable mtp install"
This reverts commit 8e67e5f0fc.

While MTP install may not work for most people, i guess it's better to have it as an option still.
Who knows, someone may figure out why it randomly freezes on windows when installing heavily compressed nsz files.
2025-06-18 15:22:53 +01:00
ITotalJustice
3f99afaa38 add personalised -> common convert. patch bad common tickets. fix yati installing nca's if already installed.
- dumped nsp now have the tik/cert at the end of the file table, rather than the beginning.
- dumped nsp patches the ticket if needed (no personalised dumping yet).
- installing titles will now patch the ticket, performing personalised -> common convert if needed, as well as fixing bad common tickets.
- yati no longer tries to install ncas if they already exist.
- ticket only option now actually works.
- fixed some translations.
- removed unused error codes.
2025-06-18 15:07:07 +01:00
ITotalJustice
8e67e5f0fc disable mtp install
see #132
2025-06-17 22:16:54 +01:00
Ny'hrarr
cb1508e6d5 Update pt.json (#178)
* Remove duplicate keys

* Add translation for MTP install

* Resolve conflicts with latest upstream commit

* Update pt.json
2025-06-17 17:32:53 +01:00
ITotalJustice
070be1ff94 fix filebrowser touch (related to f2462cff81).
the fix in commit f2462cff81 broke split screen.
this commit properly fixes the touch and split screen buttons.
2025-06-17 11:46:34 +01:00
ITotalJustice
7730eacea8 fix mtp install (again) due to the next transfer trying to start before the previous one had finished. 2025-06-17 11:23:45 +01:00
ITotalJustice
c5e3373fe1 fix filebrowser mount name not being displayed as the current option if translations are enabled 2025-06-17 11:00:56 +01:00
ITotalJustice
d7ec620173 signal homebrew refresh when downloading an app via ghdl 2025-06-17 10:57:24 +01:00
ITotalJustice
1c72350d4a add mtp install, fix es ticket being the wrong size, fix yati not returning the read fail result, updated haze, updated translations
see #132
2025-06-17 10:48:07 +01:00
sandmaennchen5
4ef15f8b81 German File Update (#177) 2025-06-17 01:59:13 +01:00
Aistra
8fc7b614a0 update Chinese translation and fix typo (#172) 2025-06-17 01:58:39 +01:00
ITotalJustice
0789a69975 delete save file before restoring. always commit fs after every write + close, delete, rename, create. 2025-06-17 01:57:42 +01:00
ITotalJustice
b405a816c9 use actual save timestamp when creating a zip backup 2025-06-17 01:03:18 +01:00
ITotalJustice
99c1db3655 disable hdd->hdd threading. only open log write on write to allow for reads. log fw/ams version.
hdd->hdd threading is disabled due to a bug in libusbhsfs which causes a deadlock if 2 fs calls happen at the same time.
2025-06-17 00:57:46 +01:00
ITotalJustice
6b099de63c fix passing raw string to vsprintf.
fixes #173
2025-06-12 21:50:06 +01:00
ITotalJustice
275707fe27 add mtp custom mount support (image sd, image nand, install, speed test). 2025-06-12 14:47:33 +01:00
ITotalJustice
c535b96b12 bump oss-nvjpg version fdcaba8 -> 16c10a3 (fixes homebrew icon decoding). 2025-06-10 20:23:37 +01:00
ITotalJustice
6b77cbb0c0 enable boost mode as early as possible during init, and exit boost as late as possible during exit. 2025-06-09 12:32:43 +01:00
ITotalJustice
a33d8e1061 fix gc menu button::A not being enabled when a gamecard is not inserted. 2025-06-09 12:20:27 +01:00
ITotalJustice
aaf11211dc add basic error messages for internal sphaira errors. 2025-06-09 12:11:05 +01:00
ITotalJustice
83b2aca942 bump ftpsrv version from 8c18431 -> 8782f6b 2025-06-09 10:26:31 +01:00
impeeza
fbae286dff Spanish file Update (#170)
* Spanish file Update

Making Spanish file update to latest version changes.

Short some strings.
2025-06-08 22:08:46 +01:00
游家小少
ba9b6b54bf Update zh.json (#171)
* Update zh.json

Add relevant strings to the Chinese language file and translate it based on the latest “pt.json” language file.

* Update zh.json
2025-06-08 22:07:43 +01:00
Ny'hrarr
1677514355 Update pt.json (#169)
* Update pt.json

* Update BCAT fields
2025-06-07 20:37:18 +01:00
ITotalJustice
ec1042efa3 rename Bcat -> BCAT, rename bcat folder to BCAT, better impl for stripping leading '/' for zip_add. 2025-06-07 20:27:36 +01:00
ITotalJustice
b03ad4ade3 support for all save types (system, bcat, cache, device). 2025-06-07 17:55:04 +01:00
ITotalJustice
04f6e5d2a8 update homebrew menu when app is installed from the appstore, add same effect to a few other menus (unused). 2025-06-07 13:03:44 +01:00
ITotalJustice
16c074db1a strip leading '/' in fs::AppendPath(). 2025-06-07 11:54:56 +01:00
ITotalJustice
8d958a2d1d in save menu close account service as soon as account data is loaded. 2025-06-07 11:49:50 +01:00
ITotalJustice
74c1cd3be0 add support for backup/restore save to usb 2025-06-07 11:45:06 +01:00
shadow2560
0fd5f348e2 Update french translation and fix a small typing error. (#168)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2025-06-07 08:13:34 +01:00
redraz
0c9433d0d3 Update ru.json (#166) 2025-06-05 08:59:00 +01:00
ITotalJustice
8fb34d42dc fix very rare crash when closing sphaira from the appstore when saves/games menu has also been opened.
i've been trying to track down this bug for a while. i still don't understand why it happens, however i have managed to
reproduce it an narrow down the crash, and thus fix it.

the bug was caused calling nvgDeleteImage() inside ~LazyImage() on image 43. this would only trigger once games/saves menu
had been opened at least once also.

i can only assume that the image fd was still refrenced in deko3d when drawing, as it would only ever crash on the visible images.
destroying fb resources before the calls to nvg delete seems to fix the issue.

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

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

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

* Add new strings and update Korean and Japanese translations.

* fix ja.json.

---------

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

* Tweak colors and add default

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

* Update pt.json

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

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

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

View File

@@ -1,6 +1,12 @@
name: build
on: [push, pull_request]
on:
push:
branches-ignore:
- dev
pull_request:
branches-ignore:
- dev
jobs:
build:
@@ -8,7 +14,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
preset: [MinSizeRel]
preset: [Release]
runs-on: ${{ matrix.os }}
container: devkitpro/devkita64:latest
@@ -16,16 +22,17 @@ jobs:
- uses: actions/checkout@v3
# fetch latest cmake
- uses: lukka/get-cmake@latest
- uses: lukka/get-cmake@v3.31.6
- name: Configure CMake
run: |
cmake --preset ${{ matrix.preset }} -DUSE_VFS_GC=0
run: cmake --preset ${{ matrix.preset }}
- name: Build
run: cmake --build --preset ${{ matrix.preset }} --parallel 4
run: cmake --build --preset ${{ matrix.preset }} --parallel $(nproc)
- uses: actions/upload-artifact@master
- name: Deploy
if: ${{ github.event_name != 'pull_request' && github.event.action != 'unassigned' }}
uses: actions/upload-artifact@master
with:
name: sphaira-${{ matrix.preset }}
path: build/${{ matrix.preset }}/sphaira.nro

33
.github/workflows/python-usb-export.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: USB Export Python Tests
on:
push:
paths: &python_usb_export_paths
- 'tools/tests/test_usb_export.py'
- 'tools/usb_export.py'
- 'tools/usb_common.py'
- 'tools/requirements.txt'
- '.github/workflows/python-usb-export.yml'
pull_request:
paths: *python_usb_export_paths
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r tools/requirements.txt
- name: Run tests
run: |
python3 tools/tests/test_usb_export.py

View File

@@ -0,0 +1,33 @@
name: USB Install Python Tests
on:
push:
paths: &python_usb_install_paths
- 'tools/tests/test_usb_install.py'
- 'tools/usb_install.py'
- 'tools/usb_common.py'
- 'tools/requirements.txt'
- '.github/workflows/python-usb-install.yml'
pull_request:
paths: *python_usb_install_paths
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r tools/requirements.txt
- name: Run tests
run: |
python3 tools/tests/test_usb_install.py

55
.github/workflows/webusb-build.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Build and Deploy WebUSB Site
on:
push:
paths:
- 'tools/webusb/**'
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install minifiers
run: |
npm install -g html-minifier-terser terser csso-cli
- name: Minify HTML
run: |
html-minifier-terser --collapse-whitespace --remove-comments --minify-css true --minify-js true -o tools/webusb/index.html.min tools/webusb/index.html
- name: Minify JS
run: |
terser tools/webusb/index.js -c -m -o tools/webusb/index.js.min
- name: Minify CSS
run: |
csso tools/webusb/index.css --output tools/webusb/index.css.min
- name: Prepare deploy branch
run: |
rm -rf webusb
mkdir webusb
cp tools/webusb/index.html.min webusb/index.html
cp tools/webusb/index.js.min webusb/index.js
cp tools/webusb/index.css.min webusb/index.css
cp -r tools/webusb/assets webusb/assets
- name: Commit and force-push to webusb branch
run: |
cd webusb
git init
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "Deploy minified webusb build"
git branch -M webusb
git remote add origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
git push --force origin webusb

5
.gitignore vendored
View File

@@ -25,3 +25,8 @@ compile_commands.json
out
usb_test/
__pycache__
usb_*.spec
CMakeUserPresets.json
build_patreon.sh

View File

@@ -21,19 +21,21 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# enable LTO (only in release builds)
if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
if (ipo_supported)
message(STATUS "IPO / LTO enabled for ALL targets")
cmake_policy(SET CMP0069 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
if (LTO)
if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
if (ipo_supported)
message(STATUS "IPO / LTO enabled for ALL targets")
cmake_policy(SET CMP0069 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
else()
message(STATUS "IPO / LTO not supported: <${ipo_error}>")
endif()
else()
message(STATUS "IPO / LTO not supported: <${ipo_error}>")
message(STATUS "IPO / LTO not enabled in debug build")
endif()
else()
message(STATUS "IPO / LTO not enabled in debug build")
endif()
function(dkp_fatal_if_not_found var package)

View File

@@ -16,25 +16,49 @@
"name": "Release",
"displayName": "Release",
"inherits":["core"],
"cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
"cacheVariables": {
"CMAKE_BUILD_TYPE": "MinSizeRel",
"LTO": true
}
},
{
"name": "RelWithDebInfo",
"displayName": "RelWithDebInfo",
"name": "Lite",
"displayName": "Lite",
"inherits":["core"],
"cacheVariables": { "CMAKE_BUILD_TYPE":"RelWithDebInfo" }
"cacheVariables": {
"CMAKE_BUILD_TYPE": "MinSizeRel",
"LTO": true,
"ENABLE_NVJPG": false,
"ENABLE_NSZ": false,
"ENABLE_LIBUSBHSFS": false,
"ENABLE_LIBUSBDVD": false,
"ENABLE_FTPSRV": false,
"ENABLE_LIBHAZE": false,
"ENABLE_AUDIO_MP3": false,
"ENABLE_AUDIO_OGG": false,
"ENABLE_AUDIO_WAV": false,
"ENABLE_AUDIO_FLAC": false,
"ENABLE_DEVOPTAB_HTTP": false,
"ENABLE_DEVOPTAB_NFS": false,
"ENABLE_DEVOPTAB_SMB2": false,
"ENABLE_DEVOPTAB_FTP": false,
"ENABLE_DEVOPTAB_SFTP": false,
"ENABLE_DEVOPTAB_WEBDAV": false
}
},
{
"name": "MinSizeRel",
"displayName": "MinSizeRel",
"name": "Dev",
"displayName": "Dev",
"inherits":["core"],
"cacheVariables": { "CMAKE_BUILD_TYPE":"MinSizeRel" }
},
{
"name": "Debug",
"displayName": "Debug",
"inherits":["core"],
"cacheVariables": { "CMAKE_BUILD_TYPE":"Debug" }
"cacheVariables": {
"CMAKE_BUILD_TYPE": "MinSizeRel",
"LTO": false,
"DEV_BUILD": true
}
}
],
"buildPresets": [
@@ -44,18 +68,13 @@
"jobs": 16
},
{
"name": "RelWithDebInfo",
"configurePreset": "RelWithDebInfo",
"name": "Lite",
"configurePreset": "Lite",
"jobs": 16
},
{
"name": "MinSizeRel",
"configurePreset": "MinSizeRel",
"jobs": 16
},
{
"name": "Debug",
"configurePreset": "Debug",
"name": "Dev",
"configurePreset": "Dev",
"jobs": 16
}
]

View File

@@ -4,16 +4,16 @@ A homebrew menu for the Nintendo Switch.
[See the GBATemp thread for more details / discussion](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/).
[We have now have a Discord server!](https://discord.gg/8vZBsrprEc). Please use the issues tab to report bugs, as it is much easier for me to track.
[We have now have a Discord server!](https://discord.gg/8vZBsrprEc) Please use the issues tab to report bugs, as it is much easier for me to track.
## Showcase
| | |
:-------------------------:|:-------------------------:
![Img](assets/screenshots/2024121522512100-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121522514300-879193CD6A8B96CD00931A628B1187CB.jpg)
![Img](assets/screenshots/2024121522513300-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121523084100-879193CD6A8B96CD00931A628B1187CB.jpg)
![Img](assets/screenshots/2024121522505300-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121522502300-879193CD6A8B96CD00931A628B1187CB.jpg)
![Img](assets/screenshots/2024121523033200-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121523070300-879193CD6A8B96CD00931A628B1187CB.jpg)
![Img](assets/screenshots/homebrew.jpg) | ![Img](assets/screenshots/games.jpg)
![Img](assets/screenshots/appstore.jpg) | ![Img](assets/screenshots/appstore_page.jpg)
![Img](assets/screenshots/file_browser.jpg) | ![Img](assets/screenshots/launch_options.jpg)
![Img](assets/screenshots/themezer.jpg) | ![Img](assets/screenshots/web.jpg)
## Bug reports
@@ -86,16 +86,22 @@ The output will be found in `build/MinSizeRel/sphaira.nro`
## Credits
- borealis
- stb
- yyjson
- nx-hbmenu
- nx-hbloader
- deko3d-nanovg
- libpulsar
- minIni
- GBATemp
- hb-appstore
- haze
- nxdumptool (for gamecard bin dumping and rsa verify code)
- [borealis](https://github.com/natinusala/borealis)
- [stb](https://github.com/nothings/stb)
- [yyjson](https://github.com/ibireme/yyjson)
- [nx-hbmenu](https://github.com/switchbrew/nx-hbmenu)
- [nx-hbloader](https://github.com/switchbrew/nx-hbloader)
- [deko3d-nanovg](https://github.com/Adubbz/nanovg-deko3d)
- [libpulsar](https://github.com/p-sam/switch-libpulsar)
- [minIni](https://github.com/compuphase/minIni)
- [GBATemp](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/)
- [hb-appstore](https://github.com/fortheusers/hb-appstore)
- [haze](https://github.com/Atmosphere-NX/Atmosphere/tree/master/troposphere/haze)
- [nxdumptool](https://github.com/DarkMatterCore/nxdumptool) (for gamecard bin dumping and rsa verify code)
- [Liam0](https://github.com/ThatNerdyPikachu/switch-010editor-templates) (for ticket / cert structs)
- [libusbhsfs](https://github.com/DarkMatterCore/libusbhsfs)
- [libnxtc](https://github.com/DarkMatterCore/libnxtc)
- [oss-nvjpg](https://github.com/averne/oss-nvjpg)
- [nsz](https://github.com/nicoboss/nsz)
- [themezer](https://themezer.net/)
- Everyone who has contributed to this project!

View File

@@ -1,8 +0,0 @@
{
"url": "https://github.com/ITotalJustice/ftpsrv",
"assets": [
{
"name": "switch"
}
]
}

View File

@@ -1,3 +0,0 @@
{
"url": "https://github.com/ITotalJustice/untitled"
}

View File

@@ -1,23 +1,24 @@
{
"[Applet Mode]": " | Applet Modus |",
"[Applet Mode]": "[Applet Modus]",
"No Internet": "Kein Internet",
"Switch-Handheld!": "Handheld!",
"Switch-Docked!": "Angedockt!",
"Switch-Handheld!": "Handheld-Modus!",
"Switch-Docked!": "TV-Modus!",
"Warning! Logs are enabled, Sphaira will run slowly!": "Warnung! Protokollierung ist aktiviert, Sphaira läuft langsam!",
"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": "",
"An error occurred": "Ein Fehler ist aufgetreten.",
"If this message appears repeatedly, please open an issue.": "Bei wiederholtem Auftreten bitte Issue erstellen.",
"Menu Options": " Menü | Optionen",
"Menu Options": "Menü | Optionen",
"Menu": "Menü",
"Theme": "Themes",
"Theme Options": " Themes | Optionen",
"Select Theme": "Theme wählen",
"Theme": "Designs",
"Theme Options": "Designs | Optionen",
"Select Theme": "Design wählen",
"Music": "Musik",
"12 Hour Time": "12-Std Zeitformat",
"Download Default Music": "",
"Failed to download default_music.bfstm, please try again": "",
"Overwrite current default music?": "",
"Download Default Music": "Standardmusik herunterladen",
"Failed to download default_music.bfstm, please try again": "Fehler beim Herunterladen von default_music.bfstm. Bitte versuchen Sie es erneut.",
"Overwrite current default music?": "Aktuelle Standardmusik überschreiben?",
"Network": "Konnektivität",
"Network Options": "Konnektivität | Optionen",
@@ -27,6 +28,8 @@
"Nxlink Connected": "NXLink | Verbunden",
"Nxlink Upload": "NXLink | wird hochgeladen...",
"Nxlink Finished": "NXLink | Hochladen beendet",
"Hdd": "HDD",
"Hdd write protect": "HDD Schreibgeschützt",
"Language": "Sprache",
"Auto": "Systemsprache",
@@ -46,33 +49,46 @@
"Ukrainian": "Українська",
"Misc": "Extras",
"Misc Options": " Extras | Optionen",
"Misc Options": "Extras | Optionen",
"Games": "Spiele",
"Game Options": "",
"Hide forwarders": "",
"Launch random game": "",
"Game Options": "Spiel | Optionen",
"Hide forwarders": "Forwarders ausblenden",
"Launch random game": "Zufälliges Spiel starten",
"List meta records": "",
"Entries": "",
"Failed to list application meta entries": "",
"No meta entries found...\n": "",
"Updating application record list": "",
"Dump": "",
"Dump options": "",
"Dump Options": "",
"Select content to dump": "",
"Dump All": "",
"Dump Application": "",
"Dump Patch": "",
"Dump AddOnContent": "",
"Dump DataPatch": "",
"Created nested folder": "",
"Append folder with .xci": "",
"Trim XCI": "",
"Label trimmed XCI": "",
"Multi-threaded USB transfer": "",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "",
"microSD card (/dumps/NSP/)": "",
"microSD card (/dumps/)": "",
"USB transfer (Switch 2 Switch)": "",
"/dev/null (Speed Test)": "",
"Dumping": "",
"Dump successfull!": "",
"Dump failed!": "",
"Success": "",
"Delete successfull!": "",
"Delete failed!": "",
"Success": "",
"Themezer": "Themezer | NX Themes",
"Themezer Options": " Themezer | Optionen",
@@ -86,9 +102,10 @@
"GitHub": "GitHub",
"Downloading json": "Lade JSON-File",
"Select asset to download for ": "Wähle Asset für den Download von ",
"Failed to download json": "Download von JSON-Datei fehlgeschlagen",
"Failed to download app!": "Download der App fehlgeschlagen",
"FTP Install": "",
"FTP Install (EXPERIMENTAL)": "",
"Connection Type: WiFi | Strength: ": "",
"Connection Type: Ethernet": "",
"Connection Type: None": "",
@@ -98,19 +115,19 @@
"Password:": "",
"SSID:": "",
"Passphrase:": "",
"Failed to install via FTP, press B to exit...": "",
"Ftp install success!": "",
"Ftp install failed!": "",
"Failed to install, press B to exit...": "",
"Install success!": "",
"Install failed!": "",
"USB Install": "",
"USB": "",
"Connected, waiting for file list...": "",
"Connected, starting transfer...": "",
"Connected, waiting for file list...": "Verbunden, warte auf die Dateiliste...",
"Connected, starting transfer...": "Verbunden, Übertragung startet...",
"Failed to init usb, press B to exit...": "",
"Waiting for connection...": "",
"Transferring data...": "",
"Waiting for connection...": "Warten auf Verbindung...",
"Transferring data...": "Datenübertragung...",
"USB connected, sending file list": "",
"Sent file list, waiting for command...": "",
"waiting for usb connection...": "",
"waiting for usb connection...": "Warten auf USB Verbindung...",
"Disable MTP for usb install": "",
"Re-enabled MTP": "",
"Installed via usb": "",
@@ -122,9 +139,12 @@
"GC": "",
"System memory %.1f GB": "",
"microSD card %.1f GB": "",
"Nand Install": "",
"SD Card Install": "",
"Exit": "",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "Keine Spielkarte eingelegt",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "",
"Gc install failed!": "",
@@ -160,15 +180,15 @@
"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",
"External Light Filter": "Externes Lichtfilter",
"Load Default": "Standard laden",
"Advanced": "Erweitert...",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Erweitert",
"Advanced Options": " Erweitert | Optionen",
"Logging": "Protokollieren",
"Replace hbmenu on exit": "hbmenu durch sphaira ersetzen",
@@ -181,10 +201,12 @@
"Restored hbmenu": "hbmenu wurde wiederhergestellt",
"Restart Sphaira?": "sphaira erneut starten?",
"Press OK to restart Sphaira": "Drücke OK um sphaira erneut zustarten",
"Boost CPU during transfer": "",
"Text scroll speed": "Laufschrift Tempo",
"Slow": "Niedrig",
"Normal": "Mittel",
"Fast": "Hoch",
"Set left-side menu": "",
"Set right-side menu": "",
"Install options": "",
"Install Options": "",
@@ -194,7 +216,6 @@
"Install location": "Einhängepunkt",
"System memory": "NAND Systemspeicher",
"microSD card": "SD-Karte",
"Boost CPU clock": "",
"Allow downgrade": "",
"Skip if already installed": "",
"Ticket only": "",
@@ -222,15 +243,17 @@
"Creating Control": "Erstelle Control",
"Creating Meta": "Erstelle Meta",
"Writing Nca": "Schreibe NCA",
"Updating ncm databse": "Aktualisiere NCM-Datenbank",
"Updating ncm database": "Aktualisiere NCM-Datenbank",
"Pushing application record": "Übertrage Anwendungsdaten",
"Failed to install forwarder": "Fehler beim installieren des Forwarders",
"Unstar": "Kein Favorit",
"Star": "Favorit",
"Unstarred ": "Favorit entfernt ",
"Starred ": "Favorit ",
"Failed to remove old forwarder, please manually remove it!": "",
"AppStore": "hb-AppStore",
"Appstore": "",
"Appstore": "hb-AppStore",
"Store": "hb-Store",
"Filter: %s | Sort: %s | Order: %s": "Rubrik: %s | Sort.nach.: %s | Ordnung: %s",
"AppStore Options": " hb-AppStore | Optionen",
@@ -238,7 +261,7 @@
"Changelog": "Neuerungen",
"Details": "Details",
"version: %s": "Version: %s",
"updated: %s": "Letztes Update am: %s",
"updated: %s": "Letztes Update: %s",
"category: %s": "Rubrik: %s",
"extracted: %.2f MiB": "Größe: %.2f MiB",
"app_dls: %s": "Anzahl Downloads: %s",
@@ -257,12 +280,13 @@
"Copy": "Kopieren",
"Copying ": "Kopiert wird: ",
"Paste": "Einfügen",
"Paste ": "Einfügen von: ",
" file(s)?": " Datei/en?",
"Paste file(s)?": "",
"Pasting ": "Eingefügt wird: ",
"Pasting": "Eingefügt wurde:",
"Rename": "Umbenennen",
"Set New File Name": "Neuen Dateinamen festlegen",
"Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "",
"Extract Options": "",
"Extract here": "",
@@ -285,13 +309,17 @@
"Create Folder": "Neuer Ordner",
"Set Folder Name": "Ordner umbenennen",
"Creating ": "Erstellt wird: ",
"View as text (unfinished)": "Als Text anzeigen",
"Upload": "",
"Select upload location": "",
"No upload locations set!": "",
"Uploading": "",
"Upload successfull!": "",
"Upload failed!": "",
"View as text (unfinished)": "Als Text anzeigen",
"Hash": "",
"Hash Options": "",
"Hashing": "",
"Failed to hash file...": "",
"Ignore read only": "Schreibschutz umgehen?",
"Mount": "Einhängen",
"Sd": "SD-Karte | Root-Verzeichnis",
@@ -302,9 +330,10 @@
"Launch ": "Starte ",
"Launch option for: ": "Start Option für: ",
"Select launcher for: ": "Wähle Launcher für: ",
"Close FileBrowser?": "Datei-Manager schließen?",
"Sort By": "Sortierung",
"Sort Options": " Sortierung | Optionen",
"Sort Options": "Sortierung | Optionen",
"Filter": "Rubrik",
"All": "Alles anzeigen",
"Emulators": "Emulatoren",
@@ -335,25 +364,25 @@
"Search": "Suchen",
"Options": "Optionen",
"Split": "",
"OK": "OK",
"Back": "Zurück",
"Select": "Auswählen",
"Open": "Öffne",
"Close": "Schließe",
"Launch": "Starte",
"Restart": "Neustart",
"Next": "",
"Prev": "",
"Unstar": "Kein Favorit",
"Star": "Favorit",
"Yes": "Ja",
"No": "Nein",
"On": "",
"Off": "",
"On": "An",
"Off": "Aus",
"Install": "Installieren",
"Install Selected files?": "",
"Installing ": "Installiert wird: ",
"Installed ": "",
"Installed ": "Installiert ",
"Installed!": "Installiert!",
"Trying to load ": "Versucht zu laden wird: ",
"Checking MD5": "Checke MD5 Prüfsumme",
@@ -367,12 +396,13 @@
"Remove": "Entfernen",
"Completely remove ": "Komplett gelöscht wird: ",
"Removing ": "Entfernt wird: ",
"Removed ": "Entfernt wurde: ",
"Uninstalling ": "Deinstalliert wird: ",
"Removed ": "Entfernt wurde: ",
"Download": "Download",
"Downloading ": "Heruntergeladen wird: ",
"Downloaded ": "Heruntergeladen wurde: ",
"Download via the Network options!": "",
"Update": "Update",
"Update avaliable: ": "Update verfügbar: ",
@@ -380,13 +410,13 @@
"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": "",
"%zu hours %zu minutes remaining": "%zu Stunden und %zu Minuten verbleibend",
"%zu minutes %zu seconds remaining": "%zu Minuten und %zu Sekunde verbleibend",
"%zu seconds remaining": "%zu Sekunde verbleibend",
"Loading...": "Wird geladen...",
"Loading": "Wird geladen",
"Empty!": "Keine Daten!",
"Not Ready...": "Nicht bereit...",
"Error loading page!": "Ladefehler!"
}
}

View File

@@ -3,6 +3,7 @@
"No Internet": "No Internet",
"Switch-Handheld!": "Switch-Handheld!",
"Switch-Docked!": "Switch-Docked!",
"Warning! Logs are enabled, Sphaira will run slowly!": "Warning! Logs are enabled, Sphaira will run slowly!",
"Audio disabled due to suspended game": "Audio disabled due to suspended game",
"Are you sure you wish to cancel?": "Are you sure you wish to cancel?",
"An error occurred": "An error occurred",
@@ -27,6 +28,8 @@
"Nxlink Connected": "Nxlink Connected",
"Nxlink Upload": "Nxlink Upload",
"Nxlink Finished": "Nxlink Finished",
"Hdd": "Hdd",
"Hdd write protect": "Hdd write protect",
"Language": "Language",
"Auto": "Auto",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "No meta entries found...\n",
"Updating application record list": "Updating application record list",
"Dump": "Dump",
"Dump options": "Dump options",
"Dump Options": "Dump Options",
"Select content to dump": "Select content to dump",
"Dump All": "Dump All",
"Dump Application": "Dump Application",
"Dump Patch": "Dump Patch",
"Dump AddOnContent": "Dump AddOnContent",
"Dump DataPatch": "Dump DataPatch",
"Created nested folder": "Created nested folder",
"Append folder with .xci": "Append folder with .xci",
"Trim XCI": "Trim XCI",
"Label trimmed XCI": "Label trimmed XCI",
"Multi-threaded USB transfer": "Multi-threaded USB transfer",
"Dump All Bins": "Dump All Bins",
"Dump XCI": "Dump XCI",
"Dump Card ID Set": "Dump Card ID Set",
"Dump Card UID": "Dump Card UID",
"Dump Certificate": "Dump Certificate",
"Dump Initial Data": "Dump Initial Data",
"Select dump location": "Select dump location",
"microSD card (/dumps/NSP/)": "microSD card (/dumps/NSP/)",
"microSD card (/dumps/)": "microSD card (/dumps/)",
"USB transfer (Switch 2 Switch)": "USB transfer (Switch 2 Switch)",
"/dev/null (Speed Test)": "/dev/null (Speed Test)",
"Dumping": "Dumping",
"Dump successfull!": "Dump successfull!",
"Dump failed!": "Dump failed!",
"Success": "Success",
"Delete successfull!": "Delete successfull!",
"Delete failed!": "Delete failed!",
"Success": "Success",
"Themezer": "Themezer",
"Themezer Options": "Themezer Options",
@@ -86,9 +102,10 @@
"GitHub": "GitHub",
"Downloading json": "Downloading json",
"Select asset to download for ": "Select asset to download for ",
"Failed to download json": "Failed to download json",
"Failed to download app!": "Failed to download app!",
"FTP Install": "FTP Install",
"FTP Install (EXPERIMENTAL)": "FTP Install (EXPERIMENTAL)",
"Connection Type: WiFi | Strength: ": "Connection Type: WiFi | Strength: ",
"Connection Type: Ethernet": "Connection Type: Ethernet",
"Connection Type: None": "Connection Type: None",
@@ -98,9 +115,9 @@
"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!",
"Failed to install, press B to exit...": "Failed to install, press  to exit...",
"Install success!": "Install success!",
"Install failed!": "Install failed!",
"USB Install": "USB Install",
"USB": "USB",
"Connected, waiting for file list...": "Connected, waiting for file list...",
@@ -122,9 +139,12 @@
"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",
"Install disabled...\nPlease enable installing via the install options.": "Install disabled...\nPlease enable installing via the install options.",
"No GameCard inserted": "No GameCard inserted",
"GameCard is already trimmed!": "GameCard is already trimmed!",
"WARNING: GameCard is already trimmed!": "WARNING: GameCard is already trimmed!",
"Continue": "Continue",
"Gc install success!": "Gc install success!",
"Gc install failed!": "Gc install failed!",
@@ -160,14 +180,14 @@
"Negative image": "Negative image",
"Format": "Format",
"Trimming Format": "Trimming Format",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"External Light Filter": "External Light Filter",
"Load Default": "Load Default",
"Web": "Web",
"Select URL": "Select URL",
"Enter custom URL": "Enter custom URL",
"Enter URL": "Enter URL",
"Advanced": "Advanced",
"Advanced Options": "Advanced Options",
"Logging": "Logging",
@@ -181,10 +201,12 @@
"Restored hbmenu": "Restored hbmenu",
"Restart Sphaira?": "Restart Sphaira?",
"Press OK to restart Sphaira": "Press OK to restart Sphaira",
"Boost CPU during transfer": "Boost CPU during transfer",
"Text scroll speed": "Text scroll speed",
"Slow": "Slow",
"Normal": "Normal",
"Fast": "Fast",
"Set left-side menu": "Set left-side menu",
"Set right-side menu": "Set right-side menu",
"Install options": "Install options",
"Install Options": "Install Options",
@@ -194,7 +216,6 @@
"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",
@@ -222,9 +243,11 @@
"Creating Control": "Creating Control",
"Creating Meta": "Creating Meta",
"Writing Nca": "Writing Nca",
"Updating ncm databse": "Updating ncm databse",
"Updating ncm database": "Updating ncm database",
"Pushing application record": "Pushing application record",
"Failed to install forwarder": "Failed to install forwarder",
"Unstar": "Unstar",
"Star": "Star",
"Unstarred ": "Unstarred ",
"Starred ": "Starred ",
"Failed to remove old forwarder, please manually remove it!": "Failed to remove old forwarder, please manually remove it!",
@@ -257,12 +280,13 @@
"Copy": "Copy",
"Copying ": "Copying ",
"Paste": "Paste",
"Paste ": "Paste ",
" file(s)?": " file(s)?",
"Paste file(s)?": "Paste file(s)?",
"Pasting ": "Pasting ",
"Pasting": "Pasting",
"Rename": "Rename",
"Set New File Name": "Set New File Name",
"Failed to delete directory": "Failed to delete directory",
"Failed to delete file": "Failed to delete file",
"Extract zip": "Extract zip",
"Extract Options": "Extract Options",
"Extract here": "Extract here",
@@ -285,13 +309,17 @@
"Create Folder": "Create Folder",
"Set Folder Name": "Set Folder Name",
"Creating ": "Creating ",
"View as text (unfinished)": "View as text (unfinished)",
"Upload": "Upload",
"Select upload location": "Select upload location",
"No upload locations set!": "No upload locations set!",
"Uploading": "Uploading",
"Upload successfull!": "Upload successfull!",
"Upload failed!": "Upload failed!",
"View as text (unfinished)": "View as text (unfinished)",
"Hash": "Hash",
"Hash Options": "Hash Options",
"Hashing": "Hashing",
"Failed to hash file...": "Failed to hash file...",
"Ignore read only": "Ignore read only",
"Mount": "Mount",
"Sd": "Sd",
@@ -302,6 +330,7 @@
"Launch ": "Launch ",
"Launch option for: ": "Launch option for: ",
"Select launcher for: ": "Select launcher for: ",
"Close FileBrowser?": "Close FileBrowser?",
"Sort By": "Sort By",
"Sort Options": "Sort Options",
@@ -335,16 +364,16 @@
"Search": "Search",
"Options": "Options",
"Split": "Split",
"OK": "OK",
"Back": "Back",
"Select": "Select",
"Open": "Open",
"Close": "Close",
"Launch": "Launch",
"Restart": "Restart",
"Next": "Next",
"Prev": "Prev",
"Unstar": "Unstar",
"Star": "Star",
"Yes": "Yes",
"No": "No",
"On": "On",
@@ -367,12 +396,13 @@
"Remove": "Remove",
"Completely remove ": "Completely remove ",
"Removing ": "Removing ",
"Removed ": "Removed ",
"Uninstalling ": "Uninstalling ",
"Removed ": "Removed ",
"Download": "Download",
"Downloading ": "Downloading ",
"Downloaded ": "Downloaded ",
"Download via the Network options!": "Download via the Network options!",
"Update": "Update",
"Update avaliable: ": "Update avaliable: ",
@@ -389,4 +419,4 @@
"Empty!": "Empty!",
"Not Ready...": "Not Ready...",
"Error loading page!": "Error loading page!"
}
}

View File

@@ -3,10 +3,11 @@
"No Internet": "Sin Internet",
"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'.",
"Warning! Logs are enabled, Sphaira will run slowly!": "ADVERTENCIA: ¡el registro de eventos está activado, Sphaira correrá más lento!",
"Audio disabled due to suspended game": "Audio deshabilitado debido al juego suspendido",
"Are you sure you wish to cancel?": "¿Realmente deseas cancelar?",
"An error occurred": "Ha ocurrido un error",
"If this message appears repeatedly, please open an issue.": "Si este mensaje aparece repetidamente, por favor abra un 'issue'.",
"Menu Options": "Opciones de menú",
"Menu": "Menú",
@@ -14,10 +15,10 @@
"Theme Options": "Opciones de tema",
"Select Theme": "Seleccionar tema",
"Music": "Música",
"12 Hour Time": "",
"Download Default Music": "",
"Failed to download default_music.bfstm, please try again": "",
"Overwrite current default music?": "",
"12 Hour Time": "Reloj de 12 horas",
"Download Default Music": "Descargar música por defecto",
"Failed to download default_music.bfstm, please try again": "Falló la descarga de default_music.bfstm. Inténtelo de nuevo.",
"Overwrite current default music?": "¿Sobreescribir la música por defecto actual?",
"Network": "Red",
"Network Options": "Opciones de red",
@@ -27,6 +28,8 @@
"Nxlink Connected": "NXlink conectado",
"Nxlink Upload": "NXlink subida",
"Nxlink Finished": "NXlink finalizado",
"Hdd": "Disco Duro",
"Hdd write protect": "Disco duro de solo lectura",
"Language": "Idioma",
"Auto": "Automático",
@@ -45,104 +48,121 @@
"Vietnamese": "Vietnamese",
"Ukrainian": "Українська",
"Misc": "Varios",
"Misc Options": "Opciones varias",
"Misc": "Misceláneas",
"Misc Options": "Opciones misceláneas",
"Games": "Juegos",
"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!": "",
"Game Options": "Opciones de juegos",
"Hide forwarders": "Ocultar forwarders",
"Launch random game": "Ejecutar un juego aleatorio",
"List meta records": "Listar registros de metadatos",
"Entries": "Entradas",
"Failed to list application meta entries": "Error al listar las entradas de metadatos de las aplicaciones",
"No meta entries found...\n": "No se encontraron entradas de metadatos…\n",
"Updating application record list": "Actualizando la lista de registros de aplicaciones",
"Dump": "Volcar",
"Dump options": "Opciones de volcado",
"Dump Options": "Opciones de volcado",
"Select content to dump": "Seleccione el contenido a volcar",
"Dump All": "Volcar todo",
"Dump Application": "Volcar aplicación",
"Dump Patch": "Volcar parche",
"Dump AddOnContent": "Volcar contenido adicional",
"Dump DataPatch": "Volcar datos de parche",
"Created nested folder": "Se creó una carpeta anidadada",
"Append folder with .xci": "Adjuntar carpeta con archivo .XCI",
"Trim XCI": "Archivo XCI recortado",
"Label trimmed XCI": "Archivo XCI con etiqueta removida",
"Multi-threaded USB transfer": "Transferencia USB multi-hilos",
"Dump All Bins": "Volcar todos los binarios",
"Dump XCI": "Volcar archivo XCI",
"Dump Card ID Set": "Volcar conjunto de ID de tarjeta",
"Dump Card UID": "Volcar UID de tarjeta",
"Dump Certificate": "Volcar Certificado",
"Dump Initial Data": "Volcar datos iniciales",
"Select dump location": "Escoja la ubicación para el volcado",
"microSD card (/dumps/)": "Tarjeta microSD (/dumps/)",
"USB transfer (Switch 2 Switch)": "Transferencia USB (de Switch a Switch)",
"/dev/null (Speed Test)": "Medir la velocidad usando dispositivo /dev/null",
"Dumping": "Volcando",
"Dump successfull!": "¡Volcado exitoso!",
"Dump failed!": "¡Error en el volcado!",
"Delete successfull!": "¡Borrado exitoso!",
"Delete failed!": "¡Error al borrar!",
"Success": "Proceso exitoso",
"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?",
"Enter Page Number": "Ingrese número de página",
"Bad Page": "Página errónea",
"Download theme?": "¿Descargar tema?",
"GitHub": "GitHub",
"Downloading json": "Descargando json",
"Downloading json": "Descargando JSON",
"Select asset to download for ": "Seleccionar recurso a descargar para ",
"Failed to download json": "Error al descargar JSON",
"Failed to download app!": "¡Error al descargar aplicación!",
"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!": "",
"FTP Install": "Instalación vía FTP",
"Connection Type: WiFi | Strength: ": "Tipo de conexión: WiFi | Fuerza de la señal",
"Connection Type: Ethernet": "Tipo de conexión: Ethernet",
"Connection Type: None": "Tipo de conexión: ninguna",
"Host:": "Host",
"Port:": "Puerto",
"Username:": "Nombre de usuario",
"Password:": "Contraseña",
"SSID:": "SSID",
"Passphrase:": "Parafrase",
"Failed to install, press B to exit...": "Error al instalar vía FTP. Presione B para salir…",
"Install success!": "¡Instalación vía FTP satisfactoria!",
"Install failed!": "¡Error en la instalación vía FTP!",
"USB Install": "Instalación vía USB",
"USB": "USB",
"Connected, waiting for file list...": "Conectado, esperando lista de archivos…",
"Connected, starting transfer...": "Conectado, iniciando la transferencia…",
"Failed to init usb, press B to exit...": "Error al iniciar el USB. Presione B para salir…",
"Waiting for connection...": "Esperando conexión…",
"Transferring data...": "Tranfiriendo datos…",
"USB connected, sending file list": "USB conectado, enviando listado de archivos",
"Sent file list, waiting for command...": "Lista de archivos enviada, esperando comando…",
"waiting for usb connection...": "Esperando por conexión USB…",
"Disable MTP for usb install": "Instalación vía MTP deshabilitada",
"Re-enabled MTP": "Instalación vía MTP rehabilitada",
"Installed via usb": "Instalado vía USB",
"Usb install success!": "Instalación USB satisfactoria",
"Usb install failed!": "Error en instalación USB",
"Press B to exit...": "Presione B para salir…",
"GameCard Install": "Instalación de tarjeta de juego",
"GameCard": "Tarjeta de juego",
"GC": "TJ",
"System memory %.1f GB": "Memoria del sistema %.1f GB",
"microSD card %.1f GB": "Tarjeta microSD %.1f GB",
"Exit": "Salir",
"Install disabled...\nPlease enable installing via the install options.": "Instalación deshabilitada…\nPor favor habilite instalar aplicaciones en las opciones de instalación",
"No GameCard inserted": "No hay una tarjeta de juego insertada",
"GameCard is already trimmed!": "¡La tarjeta de juego ya se encuentra recortada!",
"WARNING: GameCard is already trimmed!": "ADVERTENCIA: ¡la tarjeta de juego ya se encuentra recortada!",
"Continue": "Continuar",
"Gc install success!": "Instalación de TJ satisfactoria",
"Gc install failed!": "Fallo al instalar TJ",
"IRS (Infrared Joycon Camera)": "",
"IRS": "",
"Irs": "IRS",
"Ambient Noise Level: ": "Nivel de Ruido Ambiente",
"IRS (Infrared Joycon Camera)": "IRS (Cámara infraroja del JoyCon)",
"IRS": "IRS",
"Irs": "Irs",
"Ambient Noise Level: ": "Nivel de ruido ambiente",
"Controller": "Control",
"Pad ": "GamePad ",
"Pad ": "Pad ",
"HandHeld": "Portátil",
" (Available)": " (Disponible)",
" (Unsupported)": "(No Compatible)",
" (Unsupported)": "(No compatible)",
" (Unconnected)": " (Desconectado)",
"Rotation": "Rotación",
"0 (Sideways)": "0° (De lado)",
"90 (Flat)": "90° (Plano)",
"180 (-Sideways)": "180° (De lado)",
"270 (Upside down)": "270° (Al revés)",
"0 (Sideways)": "0° (de lado)",
"90 (Flat)": "90° (plano)",
"180 (-Sideways)": "180° (-de lado)",
"270 (Upside down)": "270° (al revés)",
"Colour": "Color",
"Grey": "Gris",
"Ironbow": "Paleta térmica",
@@ -160,90 +180,93 @@
"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",
"External Light Filter": "Filtro de luz externa",
"Load Default": "Cargar predeterminado",
"Web": "Web",
"Select URL": "Seleccione la URL",
"Enter custom URL": "Ingrese una URL personalizada",
"Enter URL": "Escriba la URL",
"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 find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Fallo al encontrar /switch/hbmenu.nro\nUse la tienda para reinstalar hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Fallo al restaurar hbmenu. Por favor vuelva 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",
"Boost CPU during transfer": "Tranferir con overclock",
"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",
"Set left-side menu": "Menú izquierdo",
"Set right-side menu": "Menú derecho",
"Install options": "Opciones de instalación",
"Install Options": "Opciones de instalación",
"Enable sysmmc": "Habilitar SysMMC",
"Enable emummc": "Habilitar EmuMMC",
"Show install warning": "Mostrar precaución al instalar",
"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": "",
"Allow downgrade": "Permitir instalar versiones anteriores",
"Skip if already installed": "Saltar si ya está instalado",
"Ticket only": "Únicamente tickets",
"Skip base": "Saltar base",
"Skip patch": "Saltar parche",
"Skip dlc": "Saltar DLC",
"Skip data patch": "Saltar datos de parche",
"Skip ticket": "Saltar ticket",
"Skip NCA hash verify": "Saltar verificación de hash NCA",
"Skip RSA header verify": "Saltar verificación de encabezado RSA",
"Skip RSA NPDM verify": "Saltar verificación de NPDM RSA",
"Ignore distribution bit": "Ignorar bit de distribución",
"Convert to standard crypto": "Convertir a crypto estándar",
"Lower master key": "Bajar versión de llave maestra",
"Lower system version": "Bajar versión de sistema",
"Homebrew": "Homebrew",
"Apps": "Apps",
"Homebrew Options": "Opciones de 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",
"Install Forwarder": "Instalar forwarder",
"WARNING: Installing forwarders will lead to a ban!": "ADVERTENCIA: ¡la instalación de fordwarders podría terminar en un baneo!",
"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",
"Updating ncm database": "Actualizando base de datos ncm",
"Pushing application record": "Registro de aplicación",
"Failed to install forwarder": "Fallo al instalar forwarder",
"Unstarred ": "Quitar Favorito",
"Unstar": "Quitar de favoritos",
"Star": "Añadir a favoritos",
"Unstarred ": "No favorito",
"Starred ": "Favorito",
"Failed to remove old forwarder, please manually remove it!": "",
"Failed to remove old forwarder, please manually remove it!": "Error al remover forwarder anterior, por favor remuévalo manualmente",
"AppStore": "Tienda",
"Appstore": "",
"Appstore": "Tienda",
"Store": "Tienda",
"Filter: %s | Sort: %s | Order: %s": "Filtrar: %s | Clasificar: %s | Orden: %s",
"AppStore Options": "Opciones de la Tienda",
"AppStore Options": "Opciones de la tienda",
"Info": "Información",
"Changelog": "Log de cambios",
"Changelog": "Registro 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",
"More by Author": "Mostrar mas del autor",
"Leave Feedback": "Dejar mensaje",
"FileBrowser": "Explorador de archivos",
"Files": "Archivos",
@@ -257,69 +280,75 @@
"Copy": "Copiar",
"Copying ": "Copiando ",
"Paste": "Pegar",
"Paste ": "Pegar ",
" file(s)?": " ¿archivo(s)?",
"Paste file(s)?": "Pegar 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!": "",
"Failed to delete directory": "Error al borrar carpeta",
"Failed to delete file": "Error al borrar archivo",
"Extract zip": "Extraer carpeta comprimida ZIP",
"Extract Options": "Opciones de extracción",
"Extract here": "Extraer aquí",
"Extract to root": "Extraer en la raíz",
"Are you sure you want to extract to root?": "¿Realmente desea extraerlo en la carpeta raíz?",
"Extract to...": "Extraer en…",
"Enter the path to the folder to extract into": "Ingrese la ruta en la cual extraer",
"Extracting ": "Extrayendo",
"Extract success!": "¡Extracción satisfactoria!",
"Extract failed!": "¡Extracción fallida!",
"Compress to zip": "Comprimir en archivo ZIP",
"Compress Options": "Opciones de compresión",
"Compress": "Comprimir",
"Compress to...": "Comprimir en…",
"Compressing ": "Comprimiendo",
"Compress success!": "¡Compresión satisfactoria!",
"Compress failed!": "¡Error en la descompresión!",
"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)",
"Upload": "Subir",
"Select upload location": "Seleccione la ubicación a la cual subir",
"No upload locations set!": "No se ha definido la ubicación para subir",
"Uploading": "Subiendo",
"Upload successfull!": "¡Subida satisfactoria!",
"Upload failed!": "¡Error en la subida!",
"Hash": "Hash",
"Hash Options": "Opciones de hash",
"Hashing": "Creando hash",
"Failed to hash file...": "Error al crear hash del archivo…",
"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...",
"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: ",
"Close FileBrowser?": "¿Cerrar explorador de archivos?",
"Sort By": "Ordenar por",
"Sort Options": "Opciones de clasificación",
"Filter": "Filtrar",
"Filter": "Filtro",
"All": "Todo",
"Emulators": "Emuladores",
"Tools": "Herramientas",
"Themes": "Temas",
"Legacy": "Legado",
"Sort": "Clasificar",
"Sort": "Ordenar",
"Size": "Tamaño",
"Size (Star)": "Tamaño (favorito)",
"Size (Star)": "Tamaño (favoritos)",
"Alphabetical": "Alfabético",
"Alphabetical (Star)": "Alfabético (favorito)",
"Alphabetical (Star)": "Alfabético (favoritos)",
"Updated": "Actualizado",
"Updated (Star)": "Actualizado (favorito)",
"Updated (Star)": "Actualizado (favoritos)",
"Downloads": "Descargas",
"Likes": "Me Gusta",
"Likes": "Me gusta",
"ID": "ID",
"Order": "Orden",
"Descending": "Descendente",
@@ -328,51 +357,52 @@
"Ascending": "Ascendente",
"Ascending (Up)": "Ascendente (arriba)",
"Asc": "Ascendente",
"Layout": "",
"List": "",
"Icon": "",
"Grid": "",
"Layout": "Diseño",
"List": "Lista",
"Icon": "Íconos",
"Grid": "Malla",
"Search": "Buscar",
"Options": "Opciones",
"Split": "Dividir",
"OK": "OK",
"Back": "Atrás",
"Select": "Seleccionar",
"Open": "Abrir",
"Close": "Cerrar",
"Launch": "Ejecutar",
"Restart": "Reiniciar",
"Next": "",
"Prev": "",
"Unstar": "Quitar favorito",
"Star": "Favorito",
"Next": "Siguiente",
"Prev": "Anterior",
"Yes": "Sí",
"No": "No",
"On": "",
"Off": "",
"On": "Activo",
"Off": "Apagado",
"Install": "Instalar",
"Install Selected files?": "",
"Install Selected files?": "¿Instalar los archivos seleccionados?",
"Installing ": "Instalando ",
"Installed ": "",
"Installed ": "Instalado",
"Installed!": "¡Instalado!",
"Trying to load ": "Intentando cargar ",
"Checking MD5": "Chequeando MD5",
"Checking MD5": "Revisando MD5",
"Delete": "Borrar",
"Delete Selected files?": "¿Eliminar archivos seleccionados?",
"Are you sure you want to delete ": "¿Estás seguro que quieres eliminar? ",
"Are you sure you want to delete ": "¿Realmente desea eliminar? ",
"Scanning ": "Escaneando ",
"Deleting ": "Borrando ",
"Deleting": "Borrando",
"Remove": "Borrar",
"Completely remove ": "Eliminar completamente",
"Removing ": "Removiendo ",
"Removed ": "Removido ",
"Uninstalling ": "Desinstalando ",
"Removed ": "Removido ",
"Download": "Descargar",
"Downloading ": "Descargando ",
"Downloaded ": "Descargado ",
"Download via the Network options!": "¡Descargar vía las opciones de red!",
"Update": "Actualizar",
"Update avaliable: ": "Actualización disponible: ",
@@ -380,13 +410,13 @@
"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": "",
"%zu hours %zu minutes remaining": "Faltan %zu horas %zu minutos",
"%zu minutes %zu seconds remaining": "Faltan %zu minutos %zu segundos",
"%zu seconds remaining": "Faltan %zu segundos",
"Loading...": "Cargando...",
"Loading...": "Cargando",
"Loading": "Cargando",
"Empty!": "¡Vacío!",
"Not Ready...": "No listo aún...",
"Not Ready...": "Todavía no está listo…",
"Error loading page!": "¡Error cargando la página!"
}
}

View File

@@ -1,32 +1,35 @@
{
"[Applet Mode]": "[Mode Applet]",
"No Internet": "Pas d'Internet",
"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?",
"Switch-Handheld!": "Mode Portable",
"Switch-Docked!": "Mode Dockée",
"Warning! Logs are enabled, Sphaira will run slowly!": "Attention ! Les journaux sont activés, Sphaira fonctionnera lentement !",
"Audio disabled due to suspended game": "Audio désactivé à cause d'un jeu en pause",
"Are you sure you wish to cancel?": "Souhaitez-vous vraiment annuler ?",
"An error occurred": "Une erreur s'est produite",
"If this message appears repeatedly, please open an issue.": "Si ce message apparait en boucle veuillez ouvrir une issue.",
"If this message appears repeatedly, please open an issue.": "Si ce message apparaît en boucle, veuillez ouvrir une issue sur :",
"Menu Options": "Options des Menus",
"Menu": "Menu",
"Theme": "Thème",
"Theme Options": "Options de Thème",
"Select Theme": "Choisir un Thème",
"Menu Options": "Paramètres du menu",
"Menu": "Paramètres",
"Theme": "Thèmes",
"Theme Options": "Paramètres des Thèmes",
"Select Theme": "Choisir un thème",
"Music": "Musique",
"12 Hour Time": "Temps sur 12 heures",
"12 Hour Time": "Format horloge 12 heures",
"Download Default Music": "Télécharger la musique par défaut",
"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?",
"Failed to download default_music.bfstm, please try again": "Échec du téléchargement de default_music.bfstm, veuillez réessayer",
"Overwrite current default music?": "Remplacer la musique actuelle par défaut ?",
"Network": "Réseau",
"Network Options": "Options Réseau",
"Network Options": "Paramètres Réseau",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink Connecté",
"Nxlink Upload": "Nxlink téléversement",
"Nxlink Upload": "Téléversement Nxlink",
"Nxlink Finished": "Nxlink terminé",
"Hdd": "Disque Dur",
"Hdd write protect": "Protection en écriture du disque dur",
"Language": "Langue",
"Auto": "Auto",
@@ -47,65 +50,79 @@
"Misc": "Divers",
"Misc Options": "Options Diverses",
"Games": "Jeux",
"Game Options": "Options de jeu",
"Games": "Jeux installés",
"Game Options": "Options des jeux",
"Hide forwarders": "Masquer les forwarders",
"Launch random game": "Lancer un jeu au hasard",
"List meta records": "Lister les enregistrements meta",
"Entries": "Entrées",
"List meta records": "Lister les méta-enregistrements",
"Entries": "Meta disponible",
"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 options": "Option du Dumper",
"Dump options": "Paramètres",
"Select content to dump": "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/)",
"Dump Application": "Dumper uniquement l'application (jeu)",
"Dump Patch": "Dumper uniquement la mise à jour",
"Dump AddOnContent": "Dumper uniquement le(s) DLCs",
"Dump DataPatch": "Dumper le patch de données",
"Created nested folder": "Créer un dossier imbriqué",
"Append folder with .xci": "Ajouter un dossier avec .xci",
"Trim XCI": "Trimmer le XCI",
"Label trimmed XCI": "Étiquette sur le XCI trimmer",
"Multi-threaded USB transfer": "Transfert USB multithread",
"Dump All Bins": "Dumper tous les éléments",
"Dump XCI": "Dumper le XCI",
"Dump Card ID Set": "Dumper l'ID de la cartouche",
"Dump Card UID": "Dumper l'UID de la cartouche",
"Dump Certificate": "Dumper le certificat",
"Dump Initial Data": "Dumper l'Initial Data",
"Select dump location": "Sélectionner l'emplacement du dump :",
"microSD card (/dumps/)": "carte microSD (/dumps/)",
"USB transfer (Switch 2 Switch)": "Transfert USB (Switch à Switch",
"/dev/null (Speed Test)": "/dev/null (test de vitesse)",
"Dumping": "Dump en cours",
"Dump successfull!": "Dump réussi!",
"Dump failed!": "Dump échoué!",
"Dumping": "Dump en cours...",
"Dump successfull!": "Dump réussi !",
"Dump failed!": "Échec du dump !",
"Delete successful!": "Fichier supprimé !",
"Delete failed!": "Échec de la suppression !",
"Success": "Succès",
"Delete successfull!": "Suprression réussie!",
"Delete failed!": "Suprression échouée!",
"Themezer": "Themezer",
"Themezer Options": "Options Themezer",
"Nsfw": "Nsfw",
"Themezer": "Themezer (thèmes Switch)",
"Themezer Options": "Paramètres",
"Nsfw": "Contenu NSFW",
"Page": "Page",
"Page %zu / %zu": "Page %zu / %zu",
"Enter Page Number": "Entrez Un Numéro De Page",
"Enter Page Number": "Numéro de la page :",
"Bad Page": "Page inexistante",
"Download theme?": "Télécharger le thème?",
"Download theme?": "Voulez-vous télécharger ce thème ?",
"GitHub": "GitHub",
"GitHub": "Github",
"Downloading json": "Téléchargement du json",
"Select asset to download for ": "Sélectionner l'asset à télécharger pour ",
"Select asset to download for ": "Sélectionnez l'actif à télécharger pour :",
"Failed to download json": "Échec du téléchargement du json",
"Failed to download app!": "Échec du téléchargement de l'application",
"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",
"FTP Install": "Installer contenu via FTP",
"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, press B to exit...": "Installation échouée, appuyer sur B pour quitter...",
"Install success!": "Installation réussie !",
"Install failed!": "Installation échouée !",
"USB Install": "Installer contenu via USB",
"USB": "Installation via 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...",
"Failed to init usb, press B to exit...": "Échec de l'initialisation de l'USB, appuyer sur B pour quitter...",
"Waiting for connection...": "En attente de la connexion...",
"Transferring data...": "Transfère de données...",
"USB connected, sending file list": "USB connecté, envoi de la liste des fichiers",
@@ -114,29 +131,32 @@
"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!",
"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",
"GameCard Install": "Installation de la cartouche du jeu",
"GameCard": "Cartouche du jeu",
"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!",
"Install disabled...\nPlease enable installing via the install options.": "Installation désactivée...\nVeuillez activer l'installation via les options d'installation.",
"No GameCard inserted": "Aucune cartouche de jeu insérée !",
"GameCard is already trimmed!": "La carte du jeu est déjà trimmer !",
"WARNING: GameCard is already trimmed!": "AVERTISSEMENT : la carte du jeu est déjà trimmer ! ",
"Continue": "Continuer",
"Gc install success!": "Installation de la cartouche réussie !",
"Gc install failed!": "Installation de la cartouche échouée !",
"IRS (Infrared Joycon Camera)": "IRS (infrarouge de la caméra du Joycon",
"IRS": "IRS",
"IRS (Infrared Joycon Camera)": "IRS (infrarouge de la cam du Joycon",
"IRS": "Caméra Infrarouge",
"Irs": "Irs",
"Ambient Noise Level: ": "Niveau De Bruit Ambiant: ",
"Ambient Noise Level: ": "Niveau de bruit ambiant: ",
"Controller": "Contrôleur",
"Pad ": "Manette ",
"HandHeld": "Portable",
" (Available)": " (Disponible)",
" (Unsupported)": "Non supporté",
"Pad ": "Manette",
"HandHeld": "Joycon",
" (Available)": " (Connectée)",
" (Unsupported)": "Non supportée",
" (Unconnected)": " (Non connectée)",
"Rotation": "Rotation",
"0 (Sideways)": "0 (Paysage)",
@@ -155,59 +175,61 @@
"Dim group": "Groupe sombre",
"None": "Aucun",
"Gain": "Gain",
"Negative Image": "Image Négative",
"Normal image": "Image normale",
"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",
"External Light Filter": "Filtre de Lumière Externe",
"Load Default": "Charger par Défaut",
"Negative Image": "Format d'image",
"Normal image": "Normale",
"Negative image": "Négative",
"Format": "Format d'image",
"Trimming Format": "Format du découpage",
"External Light Filter": "Filtre de lumière externe",
"Load Default": "Rétablir par Défaut",
"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?",
"Web": "Web",
"Select URL": "Sélectionnez une adresse",
"Enter custom URL": "Entrez une adresse personnalisée",
"Enter URL": "Entrer l'adresse :",
"Advanced": "Menu avancé",
"Advanced Options": "Menu avancé",
"Logging": "Fichier journal",
"Replace hbmenu on exit": "Remplacer HBmenu par Sphaira",
"Restore hbmenu?": "Restaurer HBmenu ?",
"Restore": "Restaurer !",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmenu.nro non trouvé\nRéinstaller le HBmenu via Appstore",
"Failed to restore hbmenu, please re-download hbmenu": "Échec de la restauration du HBmenu, veuillez le réinstaller",
"Failed to restore hbmenu, using sphaira instead": "Échec de la restauration de HBmenu, Sphaira sera utilisé à la place",
"Restored hbmenu, closing sphaira": "Hbmenu restauré, fermeture de Sphaira",
"Restored hbmenu": "HBmenu restauré !",
"Restart Sphaira?": "Redémarrer Sphaira ?",
"Press OK to restart Sphaira": "Appuyez sur OK pour redémarrer Sphaira",
"Boost CPU during transfer": "Augmenter le CPU pendant le transfert",
"Text scroll speed": "Vitesse de défilement du texte",
"Slow": "Lent",
"Normal": "Normal",
"Fast": "Rapide",
"Set right-side menu": "Configurer le menu de droite",
"Install options": "Options d'installation",
"Set left-side menu": "Menu de gauche par défaut",
"Set right-side menu": "Menu de droite par défaut",
"Install options": "Options d'installation des jeux",
"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",
"Enable sysmmc": "Afficher la SysNAND",
"Enable emummc": "Afficher l'emuNAND",
"Show install warning": "Avertissement lors 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 base": "Ignorer la base du jeu",
"Skip patch": "Ignorer les mises à jour",
"Skip dlc": "Ignorer le(s) DLC",
"Skip data patch": "Ignorer le 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",
"Skip NCA hash verify": "Vérification du hash NCA",
"Skip RSA header verify": "Vérification de l'entête RSA",
"Skip RSA NPDM verify": "Vérification RSA NPDM",
"Ignore distribution bit": "Ignorer le bit de distribution",
"Convert to standard crypto": "Convertir vers la crypto standard",
"Convert to common ticket": "Convertir en ticket commun",
"Convert to standard crypto": "Convertir en crypto standard",
"Lower master key": "Abaisser la master key",
"Lower system version": "Abaisser la version du système",
@@ -215,34 +237,36 @@
"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",
"Install Forwarder": "Créer un Forwarder (icône au menu)",
"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",
"Writing Nca": "Écriture NCA",
"Updating ncm database": "Mise à jour de la base de données ncm",
"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!",
"Failed to install forwarder": "Échec de l'installation du forwarder",
"Unstar": "Retirer des favories",
"Star": "Ajouter aux favories",
"Unstarred ": "Retiré des favories : ",
"Starred ": "Ajouté aux favories : ",
"Failed to remove old forwarder, please manually remove it!": "Suppression de l'ancien forwarder échouée, supprimez-le manuellement !",
"AppStore": "AppStore",
"Appstore": "Magasin d'applications",
"Store": "Magasin",
"Filter: %s | Sort: %s | Order: %s": "Filtre: %s | Tri: %s | Ordre: %s",
"Appstore": "Boutique d'applications",
"Store": "AppStore",
"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",
"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",
"More by Author": "Plus de cet auteur",
"Leave Feedback": "Laisser un avis",
"FileBrowser": "Explorateur de Fichiers",
@@ -250,76 +274,82 @@
"%zd files": "%zd fichiers",
"%zd dirs": "%zd dossiers",
"File Options": "Options de Fichier",
"Show Hidden": "Afficher Masqués",
"Show Hidden": "Afficher fichier masqué",
"Folders First": "Dossiers en Premier",
"Hidden Last": "Masqués en Dernier",
"Cut": "Couper",
"Copy": "Copier",
"Copying ": "Copie en cours ",
"Copying ": "Copie en cours...",
"Paste": "Coller",
"Paste ": "Coller ",
" file(s)?": " fichier(s)?",
"Pasting ": "Collage en cours ",
"Pasting": "Collage en cours",
"Paste file(s)?": "Copier le(s) fichier(s) ?",
"Pasting": "Collage en cours...",
"Pasting": "Collage en cours...",
"Rename": "Renommer",
"Set New File Name": "Nouveau Nom Du Fichier",
"Set New File Name": "Nouveau nom du fichier",
"Failed to delete directory": "Échec de la suppression du répertoire",
"Failed to delete file": "Échec de la suppression du 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?",
"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!",
"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",
"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é)",
"Upload": "Téléverser",
"Select upload location": "Emplacement du téléversement :",
"No upload locations set!": "Aucun emplacement de téléversement configuré !",
"Uploading": "Téléversement en cours...",
"Upload successfull!": "Téléversement réussi !",
"Upload failed!": "Téléversement échoué !",
"Hash": "Hash",
"Hash Options": "Options du Hash",
"Hashing": "Hachage",
"Failed to hash file...": "Échec du hachage du fichier...",
"Ignore read only": "Ignorer lecture seule",
"Mount": "Monter",
"Sd": "Sd",
"Image System memory": "Image de la mémoire System",
"Sd": "SD",
"Image System memory": "Image de la mémoire Système",
"Image microSD card": "Image de la Carte microSD",
"Empty...": "Vide...",
"Open with DayBreak?": "Ouvrir avec DayBreak?",
"Open with DayBreak?": "Ouvrir avec DayBreak ?",
"Launch ": "Lancer ",
"Launch option for: ": "Option de lancement pour: ",
"Select launcher for: ": "Sélectionner le lanceur pour: ",
"Launch option for: ": "Option de lancement pour : ",
"Select launcher for: ": "Sélectionner le lanceur pour : ",
"Close FileBrowser?": "Fermer le navigateur de fichiers ?",
"Sort By": "Tri Par",
"Sort Options": "Options de Tri",
"Sort By": "Trier par",
"Sort Options": "Options de tri",
"Filter": "Filtre",
"All": "Tous",
"Emulators": "Émulateurs",
"Tools": "Outils",
"Themes": "Thèmes",
"Legacy": "Legacy",
"Sort": "Tri",
"Sort": "Trier",
"Size": "Taille",
"Size (Star)": "Taille (Favories)",
"Size (Star)": "Taille (Favoris)",
"Alphabetical": "Alphabétique",
"Alphabetical (Star)": "Alphabétique (Favories)",
"Alphabetical (Star)": "Alphabétique (Favoris)",
"Updated": "Mis à jour",
"Updated (Star)": "Mis à jour (Favories)",
"Updated (Star)": "Mis à jour (Favoris)",
"Downloads": "Téléchargements",
"Likes": "Likes",
"Likes": "Aimer",
"ID": "ID",
"Order": "Ordre",
"Descending": "Décroissant",
@@ -332,61 +362,62 @@
"List": "Liste",
"Icon": "Icône",
"Grid": "Grille",
"Search": "Recherche",
"Search": "Lancer la recherche",
"Options": "Options",
"Split": "Découper",
"OK": "OK",
"Back": "Retour",
"Select": "Sélectionner",
"Open": "Ouvrir",
"Close": "Fermer",
"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 ",
"Install Selected files?": "Installer les fichiers sélectionnés ?",
"Installing ": "Installation en cours...",
"Installed ": "Installé ",
"Installed!": "Installé!",
"Trying to load ": "Tente de charger ",
"Installed!": "Installé !",
"Trying to load ": "Essayer de charger ",
"Checking MD5": "Vérification MD5",
"Delete": "Supprimer",
"Delete Selected files?": "Supprimer les fichiers sélectionnés?",
"Delete Selected files?": "Supprimer les fichiers sélectionnés ?",
"Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ",
"Scanning ": "Scan en cours ",
"Deleting ": "Suppression en cours ",
"Deleting": "Suppression en cours",
"Scanning ": "Scan en cours...",
"Deleting ": "Suppression en cours...",
"Deleting": "Suppression en cours...",
"Remove": "Supprimer",
"Completely remove ": "Supprimer totalement ",
"Removing ": "Suppression en cours ",
"Removing ": "Suppression en cours...",
"Uninstalling ": "Désinstallation en cours...",
"Removed ": "Supprimé ",
"Uninstalling ": "Désinstallation en cours ",
"Download": "Télécharger",
"Downloading ": "Téléchargement en cours ",
"Downloaded ": "Téléchargé",
"Downloading ": "Téléchargement en cours...",
"Downloaded ": "Téléchargé ",
"Download via the Network options!": "Téléchargez via les options réseau !",
"Update": "Mise à jour",
"Update avaliable: ": "Mise à jour disponible: ",
"Download update: ": "Télécharger la 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",
"Failed to download update": "Échec 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",
"Loading": "Chargement en cours...",
"Empty!": "Vide !",
"Not Ready...": "Pas prêt !",
"Error loading page!": "Erreur du chargement de la page!"
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,15 +3,16 @@
"No Internet": "Sem internet",
"Switch-Handheld!": "Switch-Portátil",
"Switch-Docked!": "Switch-Docado",
"Warning! Logs are enabled, Sphaira will run slowly!": "AVISO: O registro de depuração está habilitado, o sphaira ficará lento!",
"Audio disabled due to suspended game": "Áudio desativado devido ao software suspenso.",
"Are you sure you wish to cancel?": "Tem certeza de que quer cancelar?",
"An error occurred": "Ocorreu um erro.",
"If this message appears repeatedly, please open an issue.": "Se esta mensagem aparecer repetidamente, abra um issue.",
"Menu Options": "Opções do menu",
"Menu Options": "Menu",
"Menu": "Menu",
"Theme": "Tema",
"Theme Options": "Opções de tema",
"Theme Options": "Tema",
"Select Theme": "Estilo",
"Music": "Música",
"12 Hour Time": "Relógio de 12 horas",
@@ -20,7 +21,7 @@
"Overwrite current default music?": "Substituir a música padrão atual?",
"Network": "Rede",
"Network Options": "Opções de rede",
"Network Options": "Rede",
"Ftp": "Servidor FTP",
"Mtp": "Escuta MTP",
"Nxlink": "Nxlink",
@@ -48,23 +49,37 @@
"Ukrainian": "Українська",
"Misc": "Diversos",
"Misc Options": "Opções diversas",
"Misc Options": "Diversos",
"Games": "Softwares",
"Game Options": "Opções de software",
"Game Options": "Opções do software",
"Hide forwarders": "Ocultar atalhos forwarder",
"Launch random game": "Iniciar um software aleatório",
"List meta records": "Registro de dados",
"List meta records": "Lista de registros",
"Entries": "Entradas",
"Failed to list application meta entries": "Falha ao listar as meta entradas do aplicativo.",
"Failed to list application meta entries": "Falha ao listar as meta entradas do software.",
"No meta entries found...\n": "Nenhuma entrada de registros encontrada...\n",
"Updating application record list": "Atualizando a lista de registros do software...",
"Dump": "Exportar dados",
"Select content to dump": "Exportação de dados",
"Dump": "Exportar software",
"Dump options": "Opções de exportação",
"Dump Options": "Opções de exportação",
"Select content to dump": "Exportação de software",
"Dump All": "Exportar tudo",
"Dump Application": "Exportar software base",
"Dump Patch": "Exportar atualização",
"Dump AddOnContent": "Exportar DLCs",
"Dump DataPatch": "Exportar atualização de DLCs",
"Created nested folder": "Criar pastas aninhadas",
"Append folder with .xci": "Acrescentar .xci à pasta",
"Trim XCI": "Aparar arquivos .xci",
"Label trimmed XCI": "Rotular .xci aparados",
"Multi-threaded USB transfer": "Transferências em multithread",
"Dump All Bins": "Exportar todos os binários",
"Dump XCI": "Exportar .xci",
"Dump Card ID Set": "Exportar conjunto de IDs",
"Dump Card UID": "Exportar UID",
"Dump Certificate": "Exportar certificado",
"Dump Initial Data": "Exportar dados iniciais",
"Select dump location": "Selecione o local de exportação",
"microSD card (/dumps/)": "Cartão microSD (/dumps/)",
"USB transfer (Switch 2 Switch)": "Transferência via USB (Switch 2 Switch)",
@@ -72,27 +87,59 @@
"Dumping": "Exportando...",
"Dump successfull!": "Exportação concluída.",
"Dump failed!": "Exportação falhou.",
"Success": "Concluído.",
"Delete successfull!": "Remoção concluída.",
"Delete failed!": "Remoção falhou.",
"Success": "Concluído.",
"Refresh": "Recarregar",
"Create contents folder": "Criar pasta de conteúdo",
"Create save": "Criar dados salvo",
"Title cache": "Usar cache de títulos",
"Delete title cache": "Apagar cache de títulos",
"Saves": "Dados salvos",
"Save Options": "Opções de dados salvos",
"Account": "Usuário",
"Data Type": "Tipo de dados",
"System": "Sistema",
"BCAT": "BCAT",
"Device": "Dispositivo",
"Temporary": "Temporário",
"Cache": "Cache",
"System BCAT": "BCAT (Sistema)",
"Backup": "Backup",
"Auto backup": "Backup automático",
"Auto backup on restore": "Fazer backup ao restaurar",
"Compress backup": "Comprimir backups",
"Are you sure you want to backup save(s)?": "Tem certeza de que quer fazer backup dos dados salvo?",
"No saves found in ": "Nenhum dado salvo encontrado em ",
"Backed up to ": "Backup salvo em ",
"Backup successfull!": "Backup concluído.",
"Backup failed!": "Backup falhou.",
"Select backup location": "Selecione o local de backup",
"Select restore location": "Selecione o local de restauração",
"Restore save for: ": "Restaurar backup de dados salvo para: ",
"Are you sure you want to restore ": "Tem certeza de que quer restaurar os dados salvo de ",
"Restore successfull!": "Restauração concluída.",
"Restore failed!": "Restauração falhou.",
"Themezer": "Themezer",
"Themezer Options": "Opções do Themezer",
"Nsfw": "Temas 18+ (NSFW)",
"Page": "Ir para uma página específica",
"Page": "Página",
"Page %zu / %zu": "Página %zu / %zu",
"Enter Page Number": "Número da página",
"Enter Page Number": "Digite o № da página.",
"Bad Page": "Página inválida.",
"Download theme?": "Baixar tema?",
"GitHub": "GitHub",
"Downloading json": "Baixando JSON",
"Select asset to download for ": "Selecione o recurso para baixar de ",
"Failed to download json": "Falha ao baixar json.",
"Failed to download app!": "falha ao baixar aplicativo.",
"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: Ethernet": "Conexão por cabo Ethernet",
"Connection Type: None": "Sem conexão",
"Host:": "Host:",
"Port:": "Porta:",
@@ -100,33 +147,53 @@
"Password:": "Senha:",
"SSID:": "SSID:",
"Passphrase:": "Senha:",
"Failed to install via FTP, press B to exit...": "Falha ao instalar via FTP, aperte B para sair.",
"Ftp install success!": "Instalação via FTP concluída.",
"Ftp install failed!": "Instalação via FTP falhou.",
"Failed to install, press B to exit...": "Falha ao instalar, aperte para sair.",
"Install success!": "Instalação concluída.",
"Install failed!": "Instalação falhou.",
"MTP Install": "Instalação via MTP",
"State: %s | Speed: %s": "Estado: %s | Velocidade: %s",
"Detached": "Desconectado",
"Attached": "Conectado",
"Powered": "Energia",
"Default": "Padrão",
"Address": "Endereço",
"Configured": "Configurado",
"Suspended": "Suspenso",
"USB 1.0 Low Speed": "USB 1.0",
"USB 1.1 Full Speed": "USB 1.1 (Full Speed)",
"USB 2.0 High Speed": "USB 2.0 (High Speed)",
"USB 3.0 Super Speed": "USB 3.0 (SuperSpeed)",
"Drag'n'Drop (NSP, XCI, NSZ, XCZ) to the install folder": "Arraste o(s) arquivo(s) .nsp/.xci/.nsz/.xcz para a pasta \"Install\".",
"Failed to install via MTP, press B to exit...": "Falha ao instalar via MTP, aperte  para sair.",
"MTP install success!": "Instalação via MTP concluída.",
"MTP install failed!": "Instalação via MTP falhou.",
"USB Install": "Instalação via USB",
"USB": "USB",
"Connected, waiting for file list...": "Conectado, aguardando lista de arquivos...",
"Connected, starting transfer...": "Conectado, iniciando transferência...",
"Failed to init usb, press B to exit...": "Falha ao instalar via USB,\naperte B para sair.",
"Failed to init usb, press B to exit...": "Falha ao inicializar USB,\naperte para sair.",
"Waiting for connection...": "Aguardando conexão...",
"Transferring data...": "Transferindo dados...",
"USB connected, sending file list": "USB conectado, enviando lista de arquivos",
"Sent file list, waiting for command...": "Lista de arquivos enviada, aguardando comando...",
"waiting for usb connection...": "Aguardando conexão USB...",
"Disable MTP for usb install": "Escuta MTP desabilitada temporáriamente.",
"Disable MTP for usb install": "Escuta MTP desabilitada temporariamente.",
"Re-enabled MTP": "Escuta MTP reabilitada.",
"Installed via usb": "Instalado via USB",
"Usb install success!": "Instalação via USB concluída.",
"Usb install failed!": "Instalação via USB falhou.",
"Press B to exit...": "Aperte B para sair.",
"Press B to exit...": "Aperte para sair.",
"GameCard Install": "Instalação de cartão de jogo",
"GameCard": "Cartão de jogo",
"GC": "Cartão de jogo",
"System memory %.1f GB": "Memória do console %.1f GB",
"microSD card %.1f GB": "Cartão microSD %.1f GB",
"Nand Install": "Instalar na memória do console",
"SD Card Install": "Instalar no cartão microSD",
"Exit": "Sair",
"Install disabled...\nPlease enable installing via the install options.": "Instalação desabilitada.\nHabilite a função através das opções de instalação.",
"No GameCard inserted": "Nenhum cartão de jogo inserido.",
"GameCard is already trimmed!": "O cartão de jogo já está aparado.",
"WARNING: GameCard is already trimmed!": "AVISO: O cartão de jogo já está aparado.",
"Continue": "Continuar",
"Gc install success!": "Instalação de cartão de jogo concluída.",
"Gc install failed!": "Instalação de cartão de jogo falhou.",
@@ -162,22 +229,23 @@
"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",
"External Light Filter": "Filtro de luz externa",
"Load Default": "Restaurar padrão",
"Advanced": "Avançados",
"Web": "Navegador de internet",
"Select URL": "Selecione uma URL",
"Enter custom URL": "Digitar URL",
"Enter URL": "Digitar URL",
"Advanced": "Avançado",
"Advanced Options": "Opções avançadas",
"Logging": "Registro de depuração",
"Advanced options": "Opções avançadas",
"Logging": "Registros 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, tente baixa-lo novamente.",
"Failed to restore hbmenu, please re-download hbmenu": "Falha ao restaurar hbmenu, tente baixa-lo novamente.",
"Failed to restore hbmenu, using sphaira instead": "Falha ao restaurar hbmenu, usando sphaira no seu lugar.",
"Restored hbmenu, closing sphaira": "hbmenu restaurado, fechando sphaira.",
"Restored hbmenu": "hbmenu restaurado.",
@@ -192,13 +260,12 @@
"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",
"Enable sysmmc": "Habilitar em sysMMC",
"Enable emummc": "Habilitar em 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": "Impulsionar CPU",
"Allow downgrade": "Permitir downgrade",
"Skip if already installed": "Pular se já instalado",
"Ticket only": "Instalar apenas tickets",
@@ -211,31 +278,27 @@
"Skip RSA header verify": "Pular checagens de header RSA",
"Skip RSA NPDM verify": "Pular checagens de NPDM RSA",
"Ignore distribution bit": "Ignorar bit de distribuição",
"Convert to common ticket": "Converter para ticket comum",
"Convert to standard crypto": "Converter para crypto padrão",
"Lower master key": "Reduzir master keys",
"Lower system version": "Reduzir versão do sistema",
"Dump options": "Opções de exportação",
"Dump Options": "Opções de exportação",
"Created nested folder": "Criar pastas aninhadas",
"Append folder with .xci": "Acrescentar .xci à pasta",
"Trim XCI": "Aparar arquivos .xci",
"Label trimmed XCI": "Rotular .xci aparados",
"Multi-threaded USB transfer": "Trasfêrencia em multithread",
"Homebrew": "Aplicativos",
"Apps": "Aplicativos",
"Homebrew Options": "Opções de aplicativo",
"Hide Sphaira": "Esconder sphaira",
"Homebrew": "Homebrews",
"Apps": "Homebrews",
"Homebrew Options": "Opções do homebrew",
"Hide Sphaira": "Esconder sphaira da lista",
"Install Forwarder": "Instalar atalho forwarder",
"WARNING: Installing forwarders will lead to a ban!": "AVISO: Instalar atalhos forwarder pode resultar em um banimento!",
"Installing Forwarder": "Instalando atalho forwarder...",
"Creating Program": "Criando 'Program'",
"Creating Control": "Criando 'Control'",
"Creating Meta": "Criando 'Meta'",
"Creating Program": "Criando \"Program\"",
"Creating Control": "Criando \"Control\"",
"Creating Meta": "Criando \"Meta\"",
"Writing Nca": "Gravando NCA",
"Updating ncm databse": "Atualizando base de dados NCM",
"Updating ncm database": "Atualizando base de dados NCM",
"Pushing application record": "Aplicando registro do software",
"Failed to install forwarder": "Falha ao instalar atalho forwarder.",
"Unstar": "Desfavoritar",
"Star": "Favoritar",
"Unstarred ": "Desfavoritado ",
"Starred ": "Favoritado ",
"Failed to remove old forwarder, please manually remove it!": "Falha ao desinstalar atalho forwarder, tente remove-lo manualmente.",
@@ -248,11 +311,11 @@
"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",
"version: %s": "Versão: %s",
"updated: %s": "Atualizado: %s",
"category: %s": "Categoria: %s",
"extracted: %.2f MiB": "Tamanho: %.2f MiB",
"app_dls: %s": "№ de downloads: %s",
"More by Author": "Mais deste autor",
"Leave Feedback": "Deixar um feedback",
@@ -260,23 +323,23 @@
"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",
"File Options": "Opções do arquivo",
"Show Hidden": "Mostrar arquivos ocultos",
"Folders First": "Ordenar pastas primeiro",
"Hidden Last": "Ordenar ocultos por último",
"Split": "Dividir",
"Close": "Fechar",
"Cut": "Recortar",
"Copy": "Copiar",
"Copying ": "Copiando ",
"Paste": "Colar",
"Paste ": "Colar ",
" file(s)?": " arquivo(s)?",
"Paste file(s)?": "Colar arquivo(s)?",
"Pasting ": "Colando ",
"Pasting": "Colando ",
"Rename": "Renomear",
"Set New File Name": "Defina o nome do novo arquivo",
"Extract zip": "Extrair zip",
"Set New File Name": "Defina o nome do novo arquivo.",
"Failed to delete directory": "Falha ao apagar diretório.",
"Failed to delete file": "Falha ao apagar arquivo.",
"Extract zip": "Extrair .zip",
"Extract Options": "Opções de extração",
"Extract here": "Extrair aqui",
"Extract to root": "Extrair para a raiz",
@@ -286,41 +349,43 @@
"Extracting ": "Extraindo ",
"Extract success!": "Extração concluída.",
"Extract failed!": "Extração falhou.",
"Compress to zip": "Comprimir em zip",
"Compress Options": "Opções de compressão",
"Compress to zip": "Comprimir em .zip",
"Compress Options": "Comprimir em .zip",
"Compress": "Comprimir",
"Compress to...": "Comprimir para...",
"Compressing ": "Comprimindo ",
"Compress success!": "Compressão concluída.",
"Compress failed!": "Compressão falhou.",
"Create File": "Criar arquivo",
"Set File Name": "Defina o nome do arquivo",
"Set File Name": "Defina o nome do arquivo.",
"Create Folder": "Criar pasta",
"Set Folder Name": "Defina o nome da pasta",
"Set Folder Name": "Defina o nome da pasta.",
"Creating ": "Criando ",
"View as text (unfinished)": "Ver como texto (inacabado)",
"Upload": "Enviar",
"Select upload location": "Selecione o local de envio",
"No upload locations set!": "Nenhum local de envio definido.",
"Uploading": "Enviando",
"Upload successfull!": "Envio concluído.",
"Upload failed!": "Envio falhou.",
"Hash": "Hash",
"Hash Options": "Opções de hash",
"View as text (unfinished)": "Ver como texto (inacabado)",
"Ignore read only": "Ignorar modo somente leitura",
"Hash": "Calcular hash",
"Hash Options": "Calcular hash",
"Hashing": "Calculando hash...",
"Failed to hash file...": "Falha ao calcular hash do arquivo.",
"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",
"Empty...": "Vazio.",
"Open with DayBreak?": "Abrir com DayBreak?",
"Launch ": "Iniciar ",
"Launch option for: ": "Opções de inicialização para: ",
"Select launcher for: ": "Selecionar inicializador para: ",
"Close FileBrowser?": "Fechar o gerenciador de arquivos?",
"Close FileBrowser?": "Fechar gerenciador de arquivos?",
"Sort By": "Ordernar",
"Sort Options": "Opções de ordenação",
"Sort By": "Ordenar",
"Sort Options": "Ordenar",
"Filter": "Filtro",
"All": "Todos",
"Emulators": "Emuladores",
@@ -334,8 +399,8 @@
"Alphabetical (Star)": "Alfabética (favoritos)",
"Updated": "Mais recentes",
"Updated (Star)": "Mais recentes (favoritos)",
"Downloads": " de downloads",
"Likes": " de curtidas",
"Downloads": " de downloads",
"Likes": " de curtidas",
"ID": "ID",
"Order": "Ordem",
"Descending": "Descendente",
@@ -351,16 +416,16 @@
"Search": "Buscar",
"Options": "Opções",
"Split": "Dividir",
"OK": "OK",
"Back": "Voltar",
"Select": "Selecionar",
"Open": "Abrir",
"Close": "Fechar",
"Launch": "Iniciar",
"Restart": "Reiniciar",
"Next": "Prómixo",
"Prev": "Anterior",
"Unstar": "Desfavoritar",
"Star": "Favoritar",
"Yes": "Sim",
"No": "Não",
"On": "Sim",
@@ -383,12 +448,13 @@
"Remove": "Remover",
"Completely remove ": "Remover completamente ",
"Removing ": "Removendo ",
"Removed ": "Removido ",
"Uninstalling ": "Desinstalando ",
"Removed ": "Removido ",
"Download": "Baixar",
"Downloading ": "Baixando ",
"Downloaded ": "Baixado ",
"Download via the Network options!": "Baixe através das opções de rede.",
"Update": "Atualizar",
"Update avaliable: ": "Atualização disponível: ",

View File

@@ -3,6 +3,7 @@
"No Internet": "Нет интернета",
"Switch-Handheld!": "Режим Портатива",
"Switch-Docked!": "Режим Дока",
"Warning! Logs are enabled, Sphaira will run slowly!": "Внимание! Включены логи, Sphaira будет работать медленно!",
"Audio disabled due to suspended game": "Звук отключён из-за приостановки игры",
"Are you sure you wish to cancel?": "Вы уверены, что хотите отменить?",
"An error occurred": "Произошла ошибка",
@@ -59,22 +60,62 @@
"No meta entries found...\n": "Мета-записей не найдено...\n",
"Updating application record list": "Обновление списка записей приложений",
"Dump": "Дамп",
"Dump options": "Опции дампа",
"Dump Options": "Опции дампа",
"Select content to dump": "Выберите содержимое для дампа",
"Dump All": "Дамп всего",
"Dump Application": "Дамп приложения",
"Dump Patch": "Дамп патча",
"Dump AddOnContent": "Дамп дополнительного контента",
"Dump DataPatch": "Дамп патча данных",
"Created nested folder": "Создать вложенную папку",
"Append folder with .xci": "Добавить папку с .xci",
"Trim XCI": "Обрезать .xci",
"Label trimmed XCI": "Пометить обрезанный .xci",
"Multi-threaded USB transfer": "Многоядерная USB передача",
"Dump All Bins": "Дамп всех BIN-файлов",
"Dump XCI": "Дамп XCI",
"Dump Card ID Set": "Дамп набора ID карты",
"Dump Card UID": "Дамп UID карты",
"Dump Certificate": "Дамп сертификата",
"Dump Initial Data": "Дамп начальных данных",
"Select dump location": "Выберите место для дампа",
"microSD card (/dumps/NSP/)": "microSD карта (/dumps/NSP/)",
"microSD card (/dumps/)": "microSD (/dumps/)",
"USB transfer (Switch 2 Switch)": "Передача по USB (Switch 2 Switch)",
"/dev/null (Speed Test)": "/dev/null (тест скорости)",
"Dumping": "Снятие дампа",
"Dump successfull!": "Дамп выполнен успешно!",
"Dump failed!": "Сбой дампа!",
"Success": "Успех",
"Delete successfull!": "Удаление успешно!",
"Delete failed!": "Ошибка удаления!",
"Success": "Успех",
"Title cache": "Кэш заголовков",
"Saves": "Сохранения",
"Save Options": "Опции сохранений",
"Account": "Пользователь",
"Data Type": "Тип данных",
"System": "Система",
"BCAT": "BCAT",
"Device": "Устройство",
"Temporary": "Временные",
"Cache": "Кэш",
"System BCAT": "BCAT (Система)",
"Backup": "Сделать бэкап",
"Auto backup": "Автоматический бэкап",
"Auto backup on restore": "Бэкап при восстановлении",
"Compress backup": "Сжимать бэкапы",
"Are you sure you want to backup save(s)?": "Вы уверены, что хотите сделать бэкап(ы)?",
"No saves found in ": "Сохранения не найдены в ",
"Backed up to ": "Бэкап сохранен в ",
"Backup successfull!": "Бэкап успешно создан!",
"Backup failed!": "Ошибка бэкапа!",
"Select backup location": "Выберите место для бэкапа",
"Select restore location": "Выберите место для восстановления",
"Restore save for: ": "Восстановить сохранение для: ",
"Are you sure you want to restore ": "Вы уверены, что хотите восстановить ",
"Restore successfull!": "Восстановление успешно!",
"Restore failed!": "Ошибка восстановления!",
"Themezer": "Themezer",
"Themezer Options": "Опции Themezer",
@@ -88,9 +129,10 @@
"GitHub": "GitHub",
"Downloading json": "Загрузка json",
"Select asset to download for ": "Выберите ресурс для загрузки: ",
"Failed to download json": "Не удалось загрузить json",
"Failed to download app!": "Не удалось загрузить приложение!",
"FTP Install": "Установка по FTP",
"FTP Install (EXPERIMENTAL)": "Установка по FTP (ЭКСПЕРИМЕНТАЛЬНО)",
"Connection Type: WiFi | Strength: ": "Тип подключения: WiFi | Сигнал: ",
"Connection Type: Ethernet": "Тип подключения: Ethernet",
"Connection Type: None": "Нет подключения",
@@ -100,9 +142,26 @@
"Password:": "Пароль:",
"SSID:": "SSID:",
"Passphrase:": "Пароль:",
"Failed to install via FTP, press B to exit...": "Не удалось установить по FTP, нажмите B для выхода...",
"Ftp install success!": "Установка по FTP прошла успешно!",
"Ftp install failed!": "Сбой установки по FTP!",
"Failed to install, press B to exit...": "Не удалось установить по FTP, нажмите B для выхода...",
"Install success!": "Установка по FTP прошла успешно!",
"Install failed!": "Сбой установки по FTP!",
"MTP Install": "Установка по MTP",
"State: %s | Speed: %s": "Состояние: %s | Скорость: %s",
"Detached": "Отключено",
"Attached": "Подключено",
"Powered": "Заряжается",
"Default": "По умолчанию",
"Address": "Адрес",
"Configured": "Настроено",
"Suspended": "Приостановлено",
"USB 1.0 Low Speed": "USB 1.0",
"USB 1.1 Full Speed": "USB 1.1",
"USB 2.0 High Speed": "USB 2.0",
"USB 3.0 Super Speed": "USB 3.0",
"Drag'n'Drop (NSP, XCI, NSZ, XCZ) to the install folder": "Перетащите (NSP, XCI, NSZ, XCZ) в папку установки",
"Failed to install via MTP, press B to exit...": "Не удалось установить по MTP, нажмите B для выхода...",
"MTP install success!": "Установка по MTP успешна!",
"MTP install failed!": "Сбой установки по MTP!",
"USB Install": "Установка по USB",
"USB": "USB",
"Connected, waiting for file list...": "Подключено, ожидание списка файлов...",
@@ -122,11 +181,14 @@
"GameCard Install": "Установка с картриджа",
"GameCard": "Картридж",
"GC": "GC",
"System memory %.1f GB": "Память системы %.1f ГБ",
"microSD card %.1f GB": "Карта microSD %.1f ГБ",
"Nand Install": "Установка в NAND",
"SD Card Install": "Установка на SD карту",
"System memory %.1f GB": "NAND %.1f ГБ",
"microSD card %.1f GB": "microSD %.1f ГБ",
"Exit": "Выход",
"Install disabled...\nPlease enable installing via the install options.": "Установка отключена...\nПожалуйста, включите установку в опциях установки.",
"No GameCard inserted": "Картридж не вставлен",
"GameCard is already trimmed!": "Картридж уже обрезан!",
"WARNING: GameCard is already trimmed!": "ВНИМАНИЕ: Картридж уже обрезан!",
"Continue": "Продолжить",
"Gc install success!": "Установка с картриджа успешна!",
"Gc install failed!": "Сбой установки с картриджа!",
@@ -162,17 +224,17 @@
"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": "По умолчанию",
"Web": "Браузер",
"Select URL": "Выберите URL",
"Enter custom URL": "Введите свой URL",
"Enter URL": "Введите URL",
"Advanced": "Продвинутые",
"Advanced Options": "Расширенные опции",
"Logging": "Журналирование",
"Logging": "Логи",
"Replace hbmenu on exit": "Замена hbmenu при выходе",
"Restore hbmenu?": "Восстановить hbmenu?",
"Restore": "Восстановить",
@@ -198,7 +260,6 @@
"Install location": "Место установки",
"System memory": "NAND",
"microSD card": "microSD",
"Boost CPU clock": "Разгон CPU",
"Allow downgrade": "Разрешить даунгрейд",
"Skip if already installed": "Пропуск установленного",
"Ticket only": "Только тикет",
@@ -210,17 +271,11 @@
"Skip NCA hash verify": "Не проверять NCA hash",
"Skip RSA header verify": "Не проверять RSA header",
"Skip RSA NPDM verify": "Не проверять RSA NPDM",
"Ignore distribution bit": "Игнор. бит распределения",
"Ignore distribution bit": "Игнор. битa распределения",
"Convert to common ticket": "Конверт. в общий тикет",
"Convert to standard crypto": "Конверт. в стандарт. крипт.",
"Lower master key": "Снизить мастер-ключ",
"Lower system version": "Снизить версию системы",
"Dump options": "Опции дампа",
"Dump Options": "Опции дампа",
"Created nested folder": "Создать вложенную папку",
"Append folder with .xci": "Добавить папку с .xci",
"Trim XCI": "Обрезать .xci",
"Label trimmed XCI": "Пометить обрезанный .xci",
"Multi-threaded USB transfer": "Многоядерная USB передача",
"Homebrew": "Homebrew",
"Apps": "Приложения",
@@ -228,14 +283,16 @@
"Hide Sphaira": "Скрыть Sphaira",
"Install Forwarder": "Установить форвардер",
"WARNING: Installing forwarders will lead to a ban!": "ВНИМАНИЕ: \nУстановка в сиснанд приведет к бану!",
"Installing Forwarder": "Установить форвардер",
"Installing Forwarder": "Установка форвардера",
"Creating Program": "Создание программы",
"Creating Control": "Создание элемента управления",
"Creating Meta": "Создание меты",
"Writing Nca": "Запись NCA",
"Updating ncm databse": "Обновление базы данных NCM",
"Updating ncm database": "Обновление базы данных NCM",
"Pushing application record": "Добавление записи приложения",
"Failed to install forwarder": "Не удалось установить форвардер",
"Unstar": "Убрать из избранного",
"Star": "Добавить в избранное",
"Unstarred ": "Удалено из избранного: ",
"Starred ": "Добавлено в избранное: ",
"Failed to remove old forwarder, please manually remove it!": "Не удалось удалить старый форвардер, удалите его вручную!",
@@ -253,8 +310,9 @@
"category: %s": "категория: %s",
"extracted: %.2f MiB": "извлечено: %.2f MiB",
"app_dls: %s": "app_dls: %s",
"More by Author": "Другие приложения автора",
"More by Author": "Другое от автора",
"Leave Feedback": "Оставить отзыв",
"Visit Website": "Посетить сайт",
"FileBrowser": "Файлы",
"Files": "Файлы",
@@ -265,17 +323,17 @@
"Folders First": "Папки в начале",
"Hidden Last": "Скрытые в конце",
"Split": "Разделить",
"Close": "Закрыть",
"Cut": "Вырезать",
"Copy": "Копировать",
"Copying ": "Копирование ",
"Paste": "Вставить",
"Paste ": "Вставить ",
" file(s)?": " файл(ов)?",
"Paste file(s)?": "Вставить файл(ы)?",
"Pasting ": "Вставка ",
"Pasting": "Вставка",
"Rename": "Переименовать",
"Set New File Name": "Задайте новое имя файла",
"Failed to delete directory": "Не удалось удалить папку",
"Failed to delete file": "Не удалось удалить файл",
"Extract zip": "Распаковать .zip",
"Extract Options": "Опции распаковки",
"Extract here": "Распаковать сюда",
@@ -298,6 +356,7 @@
"Create Folder": "Создать папку",
"Set Folder Name": "Укажите имя папки",
"Creating ": "Создание ",
"View as text (unfinished)": "Открыть как текст (не доделано)",
"Upload": "Отправить",
"Select upload location": "Выберите место загрузки",
"No upload locations set!": "Места загрузки не заданы!",
@@ -306,9 +365,10 @@
"Upload failed!": "Сбой Отправки!",
"Hash": "Хэш",
"Hash Options": "Опции хэша",
"View as text (unfinished)": "Открыть как текст (не доделано)",
"Hashing": "Вычисление хэша",
"Failed to hash file...": "Не удалось вычислить хэш файла...",
"Ignore read only": "Игнор. только для чтения",
"Mount": "Монтирован",
"Mount": "Монтировать",
"Sd": "Micro SD",
"Image System memory": "Альбом Сиснанда",
"Image microSD card": "Альбом Эмунанда",
@@ -326,7 +386,7 @@
"Emulators": "Эмуляторы",
"Tools": "Инструменты",
"Themes": "Темы",
"Legacy": "Старые",
"Legacy": "Легаси",
"Sort": "Сортировать",
"Size": "По размеру",
"Size (Star)": "По размеру (избр.)",
@@ -351,16 +411,16 @@
"Search": "Поиск",
"Options": "Опции",
"Split": "Разделить",
"OK": "ОК",
"Back": "Назад",
"Select": "Выбрать",
"Open": "Открыть",
"Close": "Закрыть",
"Launch": "Запуск",
"Restart": "Перезапустить",
"Next": "Далее",
"Prev": "Назад",
"Unstar": "Убрать из избранного",
"Star": "Добавить в избранное",
"Yes": "Да",
"No": "Нет",
"On": "Вкл",
@@ -383,12 +443,13 @@
"Remove": "Удалить",
"Completely remove ": "Полностью удалить ",
"Removing ": "Удаляется ",
"Removed ": "Удалено ",
"Uninstalling ": "Деинсталляция ",
"Removed ": "Удалено ",
"Download": "Скачать",
"Downloading ": "Загрузка ",
"Downloaded ": "Загружено ",
"Download via the Network options!": "Скачайте через: Меню - Сеть",
"Update": "Обновить",
"Update avaliable: ": "Доступно обновление: ",

View File

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

View File

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

View File

@@ -3,6 +3,7 @@
"No Internet": "Không có Internet",
"Switch-Handheld!": "Switch-Handheld!",
"Switch-Docked!": "Switch-Docked!",
"Warning! Logs are enabled, Sphaira will run slowly!": "",
"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": "",
@@ -27,6 +28,8 @@
"Nxlink Connected": "Nxlink Kết Nối",
"Nxlink Upload": "Nxlink Đăng Tải",
"Nxlink Finished": "Nxlink Hoàn Thành",
"Hdd": "",
"Hdd write protect": "",
"Language": "Ngôn ngữ",
"Auto": "Tự động",
@@ -57,22 +60,35 @@
"No meta entries found...\n": "",
"Updating application record list": "",
"Dump": "",
"Dump options": "",
"Dump Options": "",
"Select content to dump": "",
"Dump All": "",
"Dump Application": "",
"Dump Patch": "",
"Dump AddOnContent": "",
"Dump DataPatch": "",
"Created nested folder": "",
"Append folder with .xci": "",
"Trim XCI": "",
"Label trimmed XCI": "",
"Multi-threaded USB transfer": "",
"Dump All Bins": "",
"Dump XCI": "",
"Dump Card ID Set": "",
"Dump Card UID": "",
"Dump Certificate": "",
"Dump Initial Data": "",
"Select dump location": "",
"microSD card (/dumps/NSP/)": "",
"microSD card (/dumps/)": "",
"USB transfer (Switch 2 Switch)": "",
"/dev/null (Speed Test)": "",
"Dumping": "",
"Dump successfull!": "",
"Dump failed!": "",
"Success": "",
"Delete successfull!": "",
"Delete failed!": "",
"Success": "",
"Themezer": "Themezer",
"Themezer Options": "Tuỳ chọn Themezer",
@@ -86,9 +102,10 @@
"GitHub": "GitHub",
"Downloading json": "Đang tải json",
"Select asset to download for ": "Chọn nội dung để tải xuống cho ",
"Failed to download json": "",
"Failed to download app!": "",
"FTP Install": "",
"FTP Install (EXPERIMENTAL)": "",
"Connection Type: WiFi | Strength: ": "",
"Connection Type: Ethernet": "",
"Connection Type: None": "",
@@ -98,9 +115,9 @@
"Password:": "",
"SSID:": "",
"Passphrase:": "",
"Failed to install via FTP, press B to exit...": "",
"Ftp install success!": "",
"Ftp install failed!": "",
"Failed to install, press B to exit...": "",
"Install success!": "",
"Install failed!": "",
"USB Install": "",
"USB": "",
"Connected, waiting for file list...": "",
@@ -122,9 +139,12 @@
"GC": "",
"System memory %.1f GB": "",
"microSD card %.1f GB": "",
"Nand Install": "",
"SD Card Install": "",
"Exit": "",
"Install disabled...\nPlease enable installing via the install options.": "",
"No GameCard inserted": "",
"GameCard is already trimmed!": "",
"WARNING: GameCard is already trimmed!": "",
"Continue": "",
"Gc install success!": "",
"Gc install failed!": "",
@@ -160,14 +180,14 @@
"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",
"External Light Filter": "Bộ lộc ánh sáng bên ngoài",
"Load Default": "Tải mặc định",
"Web": "",
"Select URL": "",
"Enter custom URL": "",
"Enter URL": "",
"Advanced": "Mở rộng",
"Advanced Options": "Tuỳ chọn mở rộng",
"Logging": "Logging",
@@ -181,10 +201,12 @@
"Restored hbmenu": "Đã khôi phục hbmenu",
"Restart Sphaira?": "Khởi động lại Sphaira?",
"Press OK to restart Sphaira": "",
"Boost CPU during transfer": "",
"Text scroll speed": "",
"Slow": "",
"Normal": "",
"Fast": "",
"Set left-side menu": "",
"Set right-side menu": "",
"Install options": "",
"Install Options": "",
@@ -194,7 +216,6 @@
"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": "",
@@ -222,9 +243,11 @@
"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",
"Updating ncm database": "Cập nhật ncm database",
"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",
"Unstar": "Xoá yêu thích",
"Star": "Yêu thích",
"Unstarred ": "Bỏ yêu thích ",
"Starred ": "Đã yêu thích ",
"Failed to remove old forwarder, please manually remove it!": "",
@@ -257,12 +280,13 @@
"Copy": "Sao chép",
"Copying ": "Đang sao chép ",
"Paste": "Dán",
"Paste ": "Paste ",
" file(s)?": " tập tin(nhiều)?",
"Paste file(s)?": "",
"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",
"Failed to delete directory": "",
"Failed to delete file": "",
"Extract zip": "",
"Extract Options": "",
"Extract here": "",
@@ -285,13 +309,17 @@
"Create Folder": "Tạo thư mục",
"Set Folder Name": "Đặt tên thư mục",
"Creating ": "Đang tạo ",
"View as text (unfinished)": "Xem dạng văn bản (chưa xong)",
"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)",
"Hash": "",
"Hash Options": "",
"Hashing": "",
"Failed to hash file...": "",
"Ignore read only": "Bỏ qua chỉ đọc",
"Mount": "Gắn",
"Sd": "Sd",
@@ -302,6 +330,7 @@
"Launch ": "Chạy ",
"Launch option for: ": "Chạy với tuỳ chọn cho: ",
"Select launcher for: ": "Chọn trình chạy cho: ",
"Close FileBrowser?": "",
"Sort By": "Sắp xếp bởi",
"Sort Options": "Tuỳ chọn sắp xếp",
@@ -335,16 +364,16 @@
"Search": "Tìm kiếm",
"Options": "Tuỳ chọn",
"Split": "",
"OK": "OK",
"Back": "Trở về",
"Select": "Chọn",
"Open": "Mở",
"Close": "",
"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": "",
@@ -367,12 +396,13 @@
"Remove": "Gỡ",
"Completely remove ": "Đã gỡ thành công ",
"Removing ": "Đang gỡ ",
"Removed ": "Đã gỡ ",
"Uninstalling ": "Đang gỡ cài đặt ",
"Removed ": "Đã gỡ ",
"Download": "Tải về",
"Downloading ": "Đang tải xuống ",
"Downloaded ": "Đã tải xong ",
"Download via the Network options!": "",
"Update": "Cập nhật",
"Update avaliable: ": "Cập nhậc có sẵn: ",
@@ -389,4 +419,4 @@
"Empty!": "Trống!",
"Not Ready...": "Chưa sẵn sàng...",
"Error loading page!": "Lỗi tải trang!"
}
}

View File

@@ -3,9 +3,10 @@
"No Internet": "网络未连接",
"Switch-Handheld!": "切换至掌机模式!",
"Switch-Docked!": "切换至底座模式!",
"Warning! Logs are enabled, Sphaira will run slowly!": "警告日志已启用将导致Sphaira运行缓慢",
"Audio disabled due to suspended game": "由于游戏暂停,音频已禁用",
"Are you sure you wish to cancel?": "您确定要取消吗?",
"An error occurred": "",
"An error occurred": "发生错误",
"If this message appears repeatedly, please open an issue.": "若此消息反复出现,请提交问题报告。",
"Menu Options": "菜单选项",
@@ -16,17 +17,20 @@
"Music": "音乐",
"12 Hour Time": "12小时制时间",
"Download Default Music": "下载默认音乐",
"Failed to download default_music.bfstm, please try again": "",
"Overwrite current default music?": "",
"Failed to download default_music.bfstm, please try again": "下载default_music.bfstm失败请重试",
"Overwrite current default music?": "覆盖当前默认音乐?",
"Network": "网络",
"Network Options": "网络选项",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink插件提交",
"Nxlink Connected": "Nxlink 已连接",
"Nxlink Upload": "Nxlink 上传中",
"Nxlink Finished": "Nxlink 已结束",
"MTP Install": "MTP安装",
"Nxlink": "Nxlink上传",
"Nxlink Connected": "Nxlink已连接",
"Nxlink Upload": "Nxlink上传中",
"Nxlink Finished": "Nxlink已结束",
"Hdd": "HDD",
"Hdd write protect": "HDD写保护",
"Language": "语言",
"Auto": "自动",
@@ -46,33 +50,66 @@
"Ukrainian": "Українська",
"Misc": "拓展",
"Misc Options": "拓展设置",
"Misc Options": "拓展选项",
"Games": "游戏",
"Game Options": "游戏选项",
"Hide forwarders": "隐藏前端启动",
"Launch random game": "启随机游戏",
"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!": "",
"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": "转储更新补丁(UPD)",
"Dump AddOnContent": "转储追加内容(DLC)",
"Dump DataPatch": "转储数据补丁",
"Dump All Bins": "转储全部文件",
"Dump XCI": "转储.xci",
"Dump Card ID Set": "转储游戏卡ID",
"Dump Card UID": "转储游戏卡UID",
"Dump Certificate": "转储游戏证书",
"Dump Initial Data": "转储初始数据",
"Select dump location": "选择转储位置",
"microSD card (/dumps/NSP/)": "microSD卡/dumps/NSP/",
"USB transfer (Switch 2 Switch)": "USB传输Switch对Switch",
"/dev/null (Speed Test)": "/dev/null速度测试",
"Dumping": "正在转储",
"Dump successfull!": "转储成功!",
"Dump failed!": "转储失败!",
"Success": "成功",
"Delete successfull!": "删除成功!",
"Delete failed!": "删除失败!",
"Title cache": "标题缓存",
"Saves": "存档",
"Save Options": "存档选项",
"Account": "账户",
"Backup": "备份",
"Auto backup": "自动备份",
"Auto backup on restore": "还原时自动备份",
"Compress backup": "压缩备份",
"Are you sure you want to backup save(s)?": "您确定要备份存档吗?",
"No saves found in ": "未找到存档 ",
"Backed up to ": "已备份到 ",
"Backup successfull!": "已备份成功!",
"Backup failed!": "备份失败!",
"Restore save for: ": "还原备份文件: ",
"Are you sure you want to restore ": "您确定要还原已备份文件吗 ",
"Restore successfull!": "已还原成功!",
"Restore failed!": "还原失败!",
"Data Type": "数据类型",
"System": "系统",
"BCAT": "BCAT数据",
"Device": "设备",
"Temporary": "临时",
"Cache": "缓存",
"System BCAT": "系统备份",
"Select backup location": "选择备份位置",
"Select restore location": "选择还原位置",
"Themezer": "在线主题",
"Themezer Options": "在线主题选项",
@@ -86,10 +123,11 @@
"GitHub": "GitHub",
"Downloading json": "正在下载 json",
"Select asset to download for ": "选择要下载的资源用于 ",
"Failed to download json": "下载json失败",
"Failed to download app!": "下载应用程序失败!",
"FTP Install": "通过 FTP 安装",
"FTP Install (EXPERIMENTAL)": "通过 FTP 安装(实验性)",
"Connection Type: WiFi | Strength: ": "",
"Connection Type: WiFi | Strength: ": "连接类型WiFi |强度:",
"Connection Type: Ethernet": "连接类型:以太网",
"Connection Type: None": "连接类型:无",
"Host:": "主机:",
@@ -98,45 +136,64 @@
"Password:": "密码:",
"SSID:": "网络名称:",
"Passphrase:": "密码:",
"Failed to install via FTP, press B to exit...": "通过 FTP 安装失败,按 B 键退出...",
"Ftp install success!": "通过 FTP 安装成功。",
"Ftp install failed!": "通过 FTP 安装失败。",
"Failed to install, press B to exit...": "通过 FTP 安装失败,按 B 键退出...",
"Install success!": "通过 FTP 安装成功。",
"Install failed!": "通过 FTP 安装失败。",
"USB Install": "通过 USB 安装",
"USB": "USB",
"Connected, waiting for file list...": "",
"Connected, starting transfer...": "",
"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...": "",
"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 安装失败",
"Usb install success!": "USB安装成功",
"Usb install failed!": "USB安装失败",
"State: %s | Speed: %s": "状态: %s | 速度: %s",
"Detached": "已断开",
"Attached": "已连接",
"Powered": "已供电",
"Default": "默认",
"Address": "地址",
"Configured": "已配置",
"Suspended": "已挂起",
"USB 1.0 Low Speed": "USB 1.0 低速",
"USB 1.1 Full Speed": "USB 1.1 全速",
"USB 2.0 High Speed": "USB 2.0 高速",
"USB 3.0 Super Speed": "USB 3.0 超高速",
"Drag'n'Drop (NSP, XCI, NSZ, XCZ) to the install folder": "拖放文件(NSP, XCI, NSZ, XCZ)至安装文件夹",
"Failed to install via MTP, press B to exit...": "MTP安装失败请按B键退出...",
"MTP install success!": "MTP安装成功",
"MTP install failed!": "MTP安装失败",
"Press B to exit...": "按 B 键退出...",
"GameCard Install": "卡安装",
"GameCard": "卡",
"GC": "",
"System memory %.1f GB": "",
"microSD card %.1f GB": "",
"Nand Install": "",
"SD Card Install": "",
"Exit": "",
"GameCard Install": "游戏卡安装",
"GameCard": "游戏卡",
"GC": "GC",
"System memory %.1f GB": "主机内存 %.1f GB",
"microSD card %.1f GB": "microSD卡 %.1f GB",
"Exit": "退出",
"Install disabled...\nPlease enable installing via the install options.": "安装已禁用...\n请通过安装选项启用安装。",
"No GameCard inserted": "未插入游戏卡",
"GameCard is already trimmed!": "游戏卡已被修剪!",
"WARNING: GameCard is already trimmed!": "警告:游戏卡已被修剪!",
"Continue": "继续",
"Gc install success!": "游戏安装成功。",
"Gc install failed!": "游戏安装失败。",
"IRS (Infrared Joycon Camera)": "",
"IRS": "",
"IRS (Infrared Joycon Camera)": "IRS(Joycon红外摄像头)",
"IRS": "IRS",
"Irs": "红外成像",
"Ambient Noise Level: ": "环境噪声等级:",
"Controller": "控制器",
"Pad ": "手柄 ",
"HandHeld": "掌机模式",
" (Available)": " (可用",
" (Unsupported)": " (不支持",
" (Available)": " (可用)",
" (Unsupported)": " (不支持)",
" (Unconnected)": " (未连接)",
"Rotation": "旋转",
"0 (Sideways)": "0度",
@@ -159,7 +216,7 @@
"Normal image": "正常图像",
"Negative image": "负片图像",
"Format": "格式",
"Trimming Format": "剪格式",
"Trimming Format": "剪格式",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
@@ -168,6 +225,11 @@
"External Light Filter": "外部光滤镜",
"Load Default": "加载默认值",
"Web": "网络",
"Select URL": "选择链接",
"Enter custom URL": "输入自定义链接",
"Enter URL": "输入网址",
"Advanced": "高级",
"Advanced Options": "高级选项",
"Logging": "日志",
@@ -181,15 +243,17 @@
"Restored hbmenu": "已恢复 hbmenu",
"Restart Sphaira?": "重启 Sphaira",
"Press OK to restart Sphaira": "按OK键以重启shphaira菜单",
"Boost CPU during transfer": "传输时提升CPU频率",
"Text scroll speed": "文本滚动速度",
"Slow": "慢",
"Normal": "正常",
"Fast": "快",
"Set right-side menu": "",
"Set left-side menu": "设置左侧菜单",
"Set right-side menu": "设置右侧菜单",
"Install options": "安装选项",
"Install Options": "安装选项",
"Enable sysmmc": "",
"Enable emummc": "",
"Enable sysmmc": "启用sysmmc",
"Enable emummc": "启用emummc",
"Show install warning": "显示安装警告",
"Install location": "安装位置",
"System memory": "主机内存",
@@ -199,21 +263,28 @@
"Skip if already installed": "若已安装则跳过",
"Ticket only": "仅安装票据",
"Skip base": "跳过基础部分",
"Skip patch": "跳过补丁",
"Skip dlc": "跳过 DLC可下载内容",
"Skip patch": "跳过更新补丁(UPD)",
"Skip dlc": "跳过追加内容(DLC)",
"Skip data patch": "跳过数据补丁",
"Skip ticket": "跳过票据",
"Skip NCA hash verify": "",
"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": "降低系统版本",
"Dump options": "转储选项",
"Dump Options": "转储选项",
"Created nested folder": "已创建嵌套文件夹",
"Append folder with .xci": "用.xci附加文件夹",
"Trim XCI": "裁剪 XCI",
"Label trimmed XCI": "标记已裁剪的XCI",
"Multi-threaded USB transfer": "多线程USB传输",
"Homebrew": "应用列表",
"Homebrew": "自制软件",
"Apps": "应用",
"Homebrew Options": "应用选项",
"Homebrew Options": "自制软件选项",
"Hide Sphaira": "在应用列表中隐藏Sphaira",
"Install Forwarder": "安装前端应用",
"WARNING: Installing forwarders will lead to a ban!": "警告安装前端应用可能导致ban机",
@@ -222,15 +293,17 @@
"Creating Control": "正在创建控制器",
"Creating Meta": "正在创建元数据",
"Writing Nca": "正在写入Nca",
"Updating ncm databse": "正在更新ncm数据库",
"Updating ncm database": "正在更新ncm数据库",
"Pushing application record": "正在推送应用记录",
"Failed to install forwarder": "前端应用安装失败",
"Unstar": "取消星标",
"Star": "星标",
"Unstarred ": "取消星标 ",
"Starred ": "已星标 ",
"Failed to remove old forwarder, please manually remove it!": "",
"Failed to remove old forwarder, please manually remove it!": "删除旧前端应用失败,请手动删除!",
"AppStore": "应用商店",
"Appstore": "",
"Appstore": "应用商店",
"Store": "商店",
"Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s",
"AppStore Options": "应用商店选项",
@@ -245,7 +318,7 @@
"More by Author": "作者更多作品",
"Leave Feedback": "留言反馈",
"FileBrowser": "文件浏览",
"FileBrowser": "文件浏览",
"Files": "文件",
"%zd files": "%zd 个文件",
"%zd dirs": "%zd 个文件夹",
@@ -257,41 +330,46 @@
"Copy": "复制",
"Copying ": "正在复制 ",
"Paste": "粘贴",
"Paste ": "粘贴 ",
" file(s)?": "个文件(夹)",
"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!": "",
"Failed to delete directory": "删除目录失败",
"Failed to delete file": "删除文件失败",
"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": "",
"Compress to...": "",
"Compressing ": "",
"Compress success!": "",
"Compress failed!": "",
"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)": "以文本形式查看(未完善)",
"Upload": "上传",
"Select upload location": "选择上传位置",
"No upload locations set!": "未设置上传位置",
"Uploading": "正在上传",
"Upload successfull!": "上传成功!",
"Upload failed!": "上传失败!",
"Hash": "哈希",
"Hash Options": "哈希选项",
"Hashing": "正在计算文件哈希",
"Failed to hash file...": "计算文件哈希失败...",
"Ignore read only": "忽略只读",
"Mount": "挂载",
"Sd": "SD卡",
@@ -302,6 +380,7 @@
"Launch ": "启动 ",
"Launch option for: ": "启动选项:",
"Select launcher for: ": "选择启动器用于:",
"Close FileBrowser?": "是否关闭文件浏览器?",
"Sort By": "排序方式",
"Sort Options": "排序选项",
@@ -310,7 +389,7 @@
"Emulators": "模拟器",
"Tools": "工具",
"Themes": "主题",
"Legacy": "可更新",
"Legacy": "旧版",
"Sort": "排序",
"Size": "按大小",
"Size (Star)": "按大小(星标优先)",
@@ -328,32 +407,32 @@
"Ascending": "升序",
"Ascending (Up)": "升序",
"Asc": "升序",
"Layout": "",
"List": "",
"Icon": "",
"Grid": "",
"Layout": "布局",
"List": "列表",
"Icon": "图标",
"Grid": "网格",
"Search": "搜索",
"Options": "选项",
"Split": "拆分",
"OK": "确定",
"Back": "返回",
"Select": "选择",
"Open": "打开",
"Close": "关闭",
"Launch": "启动",
"Restart": "重启",
"Next": "下一项",
"Prev": "上一项",
"Unstar": "取消星标",
"Star": "星标",
"Yes": "是",
"No": "否",
"On": "",
"Off": "",
"On": "开启",
"Off": "关闭",
"Install": "安装",
"Install Selected files?": "安装所选文件?",
"Installing ": "正在安装 ",
"Installed ": "",
"Installed ": "已安装",
"Installed!": "安装完成!",
"Trying to load ": "尝试加载 ",
"Checking MD5": "正在校验 MD5",
@@ -367,12 +446,13 @@
"Remove": "删除",
"Completely remove ": "彻底删除 ",
"Removing ": "正在移除 ",
"Removed ": "已移除 ",
"Uninstalling ": "正在卸载 ",
"Removed ": "已移除 ",
"Download": "下载",
"Downloading ": "正在下载 ",
"Downloaded ": "已下载 ",
"Download via the Network options!": "通过网络选项下载!",
"Update": "更新",
"Update avaliable: ": "有可用更新!",
@@ -380,13 +460,35 @@
"Updated to ": "更新至 ",
"Failed to download update": "更新下载失败",
"%zu hours %zu minutes remaining": "",
"%zu minutes %zu seconds remaining": "",
"%zu seconds remaining": "",
"%zu hours %zu minutes remaining": "剩余 %zu 小时 %zu 分钟",
"%zu minutes %zu seconds remaining": "剩余 %zu 分钟 %zu 秒",
"%zu seconds remaining": "剩余 %zu 秒",
"Loading...": "加载中...",
"Loading": "加载中",
"Empty!": "空空如",
"Empty!": "空空如",
"Not Ready...": "尚未准备好...",
"Error loading page!": "页面加载失败!"
}
"Error loading page!": "页面加载失败!",
"USB Speed Status": "USB速度状态",
"Data Type": "数据类型",
"Save Location Selection": "存档位置选择",
"MTP Transfer": "MTP传输",
"USB Installation": "USB安装",
"Archive Data": "存档数据",
"Game Card Data": "游戏卡数据",
"Storage Path": "存储路径",
"MTP Connection": "MTP连接",
"USB Device": "USB设备",
"Data Category": "数据类别",
"Save Management": "存档管理",
"Transfer Mode": "传输模式",
"Installation Method": "安装方式",
"File Type": "文件类型",
"Storage Device": "存储设备",
"Data Transfer": "数据传输",
"Save Data Type": "存档数据类型",
"USB Connection": "USB连接",
"MTP Settings": "MTP设置",
"USB Configuration": "USB配置",
"Data Storage": "数据存储"
}

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

BIN
assets/screenshots/web.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -8,13 +8,13 @@ build_preset() {
cmake --build --preset $1
}
build_preset MinSizeRel
build_preset Release
rm -rf out
# --- SWITCH --- #
mkdir -p out/switch/sphaira/
cp -r build/MinSizeRel/*.nro out/switch/sphaira/sphaira.nro
cp -r build/Release/*.nro out/switch/sphaira/sphaira.nro
pushd out
zip -r9 sphaira.zip switch
popd

View File

@@ -1,10 +1,9 @@
#include <switch.h>
#include <string.h>
#include <stdlib.h>
#define EXIT_DETECTION_STR "if this isn't replaced i will exit :)"
// this uses 3-4KiB more than nx-hbloader.
// this uses 16KiB less than nx-hbloader.
static char g_argv[2048] = {0};
// this can and will be modified by libnx if launched nro calls envSetNextLoad().
static char g_nextArgv[2048] = {0};
@@ -47,7 +46,7 @@ static void fix_nro_path(char* path) {
// Credit to behemoth
// SOURCE: https://github.com/HookedBehemoth/nx-hbloader/commit/7f8000a41bc5e8a6ad96a097ef56634cfd2fabcb
static NX_NORETURN void selfExit(void) {
static void NX_NORETURN selfExit(void) {
Result rc = smInitialize();
if (R_FAILED(rc))
goto fail0;
@@ -162,7 +161,8 @@ static void getOwnProcessHandle(void) {
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 12));
Thread t;
rc = threadCreate(&t, &procHandleReceiveThread, (void*)(uintptr_t)server_handle, NULL, 0x1000, 0x20, 0);
u8* stack = g_heapAddr;
rc = threadCreate(&t, &procHandleReceiveThread, (void*)(uintptr_t)server_handle, stack, 0x1000, 0x20, 0);
if (R_FAILED(rc))
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 10));
@@ -290,10 +290,10 @@ void NX_NORETURN loadNro(void) {
diagAbortWithResult(rc);
}
u8* romfs_dirs = malloc(romfs_header.dirTableSize); // should be 1 entry ("/")
u8* romfs_files = malloc(romfs_header.fileTableSize); // should be 2 entries (argv and nro)
u8 romfs_dirs[1024 * 2]; // should be 1 entry ("/")
u8 romfs_files[1024 * 4]; // should be 2 entries (argv and nro)
if (!romfs_dirs || !romfs_files) {
if (romfs_header.dirTableSize > sizeof(romfs_dirs) || romfs_header.fileTableSize > sizeof(romfs_files)) {
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, LibnxError_OutOfMemory));
}
@@ -317,8 +317,6 @@ void NX_NORETURN loadNro(void) {
diagAbortWithResult(rc);
}
free(romfs_dirs);
free(romfs_files);
fsStorageClose(&s);
strcpy(g_defaultNroPath, g_nextNroPath);
@@ -337,40 +335,24 @@ void NX_NORETURN loadNro(void) {
}
uint8_t *nrobuf = (uint8_t*) g_heapAddr;
NroStart* start = (NroStart*) (nrobuf + 0);
header = (NroHeader*) (nrobuf + sizeof(NroStart));
uint8_t* rest = (uint8_t*) (nrobuf + sizeof(NroStart) + sizeof(NroHeader));
FsFileSystem fs;
FsFile f;
u64 bytes_read = 0;
s64 offset = 0;
if (R_FAILED(rc = fsOpenSdCardFileSystem(&fs))) {
diagAbortWithResult(rc);
}
// don't fatal if we don't find the nro, exit to menu
FsFile f;
if (R_FAILED(rc = fsFsOpenFile(&fs, fixedNextNroPath, FsOpenMode_Read, &f))) {
diagAbortWithResult(rc);
}
if (R_FAILED(rc = fsFileRead(&f, offset, start, sizeof(*start), FsReadOption_None, &bytes_read)) ||
bytes_read != sizeof(*start)) {
diagAbortWithResult(rc);
}
offset += sizeof(*start);
if (R_FAILED(rc = fsFileRead(&f, offset, header, sizeof(*header), FsReadOption_None, &bytes_read)) ||
bytes_read != sizeof(*header) || header->magic != NROHEADER_MAGIC) {
diagAbortWithResult(rc);
}
offset += sizeof(*header);
const size_t rest_size = header->size - (sizeof(NroStart) + sizeof(NroHeader));
if (R_FAILED(rc = fsFileRead(&f, offset, rest, rest_size, FsReadOption_None, &bytes_read)) ||
bytes_read != rest_size) {
u64 bytes_read;
if (R_FAILED(rc = fsFileRead(&f, 0, start, g_heapSize, FsReadOption_None, &bytes_read)) ||
header->magic != NROHEADER_MAGIC ||
bytes_read < sizeof(*start) + sizeof(*header) + header->size) {
diagAbortWithResult(rc);
}
@@ -513,13 +495,11 @@ bool __nx_fsdev_support_cwd = false;
// u32 __nx_applet_exit_mode = 0;
void __libnx_initheap(void) {
static char g_innerheap[0x4000];
extern char* fake_heap_start;
extern char* fake_heap_end;
fake_heap_start = &g_innerheap[0];
fake_heap_end = &g_innerheap[sizeof(g_innerheap)];
fake_heap_start = NULL;
fake_heap_end = NULL;
}
void __appInit(void) {
@@ -557,7 +537,20 @@ void __appExit(void) {
}
// exit() effectively never gets called, so let's stub it out.
void __wrap_exit(void) {
// exit() effectively never gets called, so let's stub it out.
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 39));
}
// stub alloc calls as they're not used (saves 4KiB).
void* __libnx_alloc(size_t size) {
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 40));
}
void* __libnx_aligned_alloc(size_t alignment, size_t size) {
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 41));
}
void __libnx_free(void* p) {
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 43));
}

View File

@@ -1,6 +1,34 @@
cmake_minimum_required(VERSION 3.13)
set(sphaira_VERSION 0.11.3)
# generic options.
option(ENABLE_NVJPG "" OFF)
option(ENABLE_NSZ "enables exporting to nsz" ON)
# lib options.
option(ENABLE_LIBUSBHSFS "enables FAT/exFAT hdd mounting" ON)
option(ENABLE_LIBUSBDVD "enables cd/dvd/iso/cue mounting" ON)
option(ENABLE_FTPSRV "enables MTP server support" ON)
option(ENABLE_LIBHAZE "enables MTP server support" ON)
# audio options.
option(ENABLE_AUDIO_MP3 "" ON)
option(ENABLE_AUDIO_OGG "" ON)
option(ENABLE_AUDIO_WAV "" ON)
option(ENABLE_AUDIO_FLAC "" ON)
# devoptab options.
option(ENABLE_DEVOPTAB_HTTP "" ON)
option(ENABLE_DEVOPTAB_NFS "" ON)
option(ENABLE_DEVOPTAB_SMB2 "" ON)
option(ENABLE_DEVOPTAB_FTP "" ON)
option(ENABLE_DEVOPTAB_WEBDAV "" ON)
# disable by default because we are CPU bound for upload/download.
# max speed is 8MiB/s, which is fine for wifi, but awful for ethernet.
# other clients get 36-40MiB/s.
# it also adds 230k to binary size, and i don't think anyone will use it.
option(ENABLE_DEVOPTAB_SFTP "" OFF)
set(sphaira_VERSION 1.0.0)
project(sphaira
VERSION ${sphaira_VERSION}
@@ -30,23 +58,32 @@ execute_process(
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(sphaira_VERSION_HASH "${sphaira_VERSION} [${GIT_COMMIT}]")
if (DEFINED sphaira_VERSION_OVERRIDE)
set(sphaira_DISPLAY_VERSION "${sphaira_VERSION_OVERRIDE} [${GIT_COMMIT}]")
else()
set(sphaira_DISPLAY_VERSION "${sphaira_VERSION} [${GIT_COMMIT}]")
endif()
add_executable(sphaira
source/ui/menus/appstore.cpp
source/ui/menus/file_viewer.cpp
source/ui/menus/image_viewer.cpp
source/ui/menus/filebrowser.cpp
source/ui/menus/file_picker.cpp
source/ui/menus/homebrew.cpp
source/ui/menus/irs_menu.cpp
source/ui/menus/main_menu.cpp
source/ui/menus/menu_base.cpp
source/ui/menus/save_menu.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/game_meta_menu.cpp
source/ui/menus/game_nca_menu.cpp
source/ui/menus/grid_menu_base.cpp
source/ui/menus/install_stream_menu_base.cpp
source/ui/error_box.cpp
source/ui/notification.cpp
@@ -59,6 +96,7 @@ add_executable(sphaira
source/ui/widget.cpp
source/ui/list.cpp
source/ui/scrolling_text.cpp
source/ui/music_player.cpp
source/app.cpp
source/download.cpp
@@ -77,34 +115,58 @@ add_executable(sphaira
source/web.cpp
source/hasher.cpp
source/i18n.cpp
source/ftpsrv_helper.cpp
source/threaded_file_transfer.cpp
source/title_info.cpp
source/minizip_helper.cpp
source/utils/utils.cpp
source/utils/audio.cpp
source/utils/devoptab_common.cpp
source/utils/devoptab_romfs.cpp
source/utils/devoptab_save.cpp
source/utils/devoptab_nro.cpp
source/utils/devoptab_nca.cpp
source/utils/devoptab_nsp.cpp
source/utils/devoptab_xci.cpp
source/utils/devoptab_zip.cpp
source/utils/devoptab_bfsar.cpp
source/utils/devoptab_vfs.cpp
source/utils/devoptab_fatfs.cpp
source/utils/devoptab_game.cpp
source/utils/devoptab_mounts.cpp
source/utils/devoptab.cpp
source/usb/base.cpp
source/usb/usbds.cpp
source/usb/usbhs.cpp
source/usb/usb_uploader.cpp
source/usb/usb_installer.cpp
source/usb/usb_dumper.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/ncz.cpp
source/yati/nx/ncm.cpp
source/yati/nx/ns.cpp
source/yati/nx/nxdumptool_rsa.c
source/yati/nx/nxdumptool/save.c
)
target_compile_definitions(sphaira PRIVATE
-DAPP_VERSION="${sphaira_VERSION}"
-DAPP_VERSION_HASH="${sphaira_VERSION_HASH}"
-DAPP_DISPLAY_VERSION="${sphaira_DISPLAY_VERSION}"
-DCURL_NO_OLDIES=1
-DDEV_BUILD=$<BOOL:${DEV_BUILD}>
-DZSTD_STATIC_LINKING_ONLY=1
)
target_compile_options(sphaira PRIVATE
@@ -126,6 +188,8 @@ target_compile_options(sphaira PRIVATE
# disabled as it warns for strcat 2 paths together, but it will never
# overflow due to fs enforcing a max path len anyway.
-Wno-format-truncation
# many false positives when LTO is not enabled.
-Wno-suggest-final-types
# the below are taken from my gba emulator, they've served me well ;)
-Wformat-overflow=2
@@ -142,28 +206,15 @@ target_compile_options(sphaira PRIVATE
-Wcast-qual
-Wcast-align
-Wimplicit-fallthrough=5
-Wsuggest-final-types
-Wuninitialized
)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(ftpsrv
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
# GIT_TAG 1.2.2
GIT_TAG 8c18431
SOURCE_SUBDIR NONE
)
FetchContent_Declare(libhaze
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
GIT_TAG 8e16df2
)
FetchContent_Declare(libpulsar
GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git
GIT_TAG de656e4
GIT_TAG ac7bc97
)
FetchContent_Declare(nanovg
@@ -178,12 +229,12 @@ FetchContent_Declare(stb
FetchContent_Declare(yyjson
GIT_REPOSITORY https://github.com/ibireme/yyjson.git
GIT_TAG 0.10.0
GIT_TAG 0.12.0
)
FetchContent_Declare(minIni
GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git
GIT_TAG 11cac8b
GIT_TAG 6e952b6
)
FetchContent_Declare(zstd
@@ -192,34 +243,301 @@ FetchContent_Declare(zstd
SOURCE_SUBDIR build/cmake
)
FetchContent_Declare(libusbhsfs
GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git
GIT_TAG db2bf2a
FetchContent_Declare(libnxtc
GIT_REPOSITORY https://github.com/ITotalJustice/libnxtc.git
GIT_TAG 88ce3d8
)
set(USE_NEW_ZSTD ON)
FetchContent_Declare(nvjpg
GIT_REPOSITORY https://github.com/ITotalJustice/oss-nvjpg.git
GIT_TAG 45680e7
)
FetchContent_Declare(dr_libs
GIT_REPOSITORY https://github.com/mackron/dr_libs.git
GIT_TAG b962384
SOURCE_SUBDIR NONE
)
if (ENABLE_NVJPG)
FetchContent_Declare(nvjpg
GIT_REPOSITORY https://github.com/ITotalJustice/oss-nvjpg.git
GIT_TAG 45680e7
)
FetchContent_MakeAvailable(nvjpg)
add_library(nvjpg
${nvjpg_SOURCE_DIR}/lib/decoder.cpp
${nvjpg_SOURCE_DIR}/lib/image.cpp
${nvjpg_SOURCE_DIR}/lib/surface.cpp
)
target_include_directories(nvjpg PUBLIC ${nvjpg_SOURCE_DIR}/include)
set_target_properties(nvjpg PROPERTIES CXX_STANDARD 26)
target_link_libraries(nvjpg PRIVATE nvjpg)
target_compile_definitions(sphaira PRIVATE ENABLE_NVJPG)
endif()
if (ENABLE_NSZ)
target_sources(sphaira PRIVATE source/utils/nsz_dumper.cpp)
target_compile_definitions(sphaira PRIVATE ENABLE_NSZ)
endif()
if (ENABLE_LIBUSBHSFS)
# 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_Declare(libusbhsfs
GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git
GIT_TAG 625269b
)
FetchContent_MakeAvailable(libusbhsfs)
target_compile_definitions(sphaira PRIVATE ENABLE_LIBUSBHSFS)
target_link_libraries(sphaira PRIVATE libusbhsfs)
else()
target_sources(sphaira PRIVATE source/ff16/ffunicode.c)
endif()
if (ENABLE_LIBUSBDVD)
FetchContent_Declare(libusbdvd
GIT_REPOSITORY https://github.com/proconsule/libusbdvd.git
GIT_TAG 3cb0613
)
FetchContent_MakeAvailable(libusbdvd)
add_library(libusbdvd
${libusbdvd_SOURCE_DIR}/source/usbdvd.cpp
${libusbdvd_SOURCE_DIR}/source/usbdvd_scsi.cpp
${libusbdvd_SOURCE_DIR}/source/usbdvd_utils.cpp
${libusbdvd_SOURCE_DIR}/source/fs/usbdvd_datadisc.cpp
${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs/audiocdfs.cpp
${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs/cdaudio_devoptab.cpp
${libusbdvd_SOURCE_DIR}/source/fs/iso9660/usbdvd_iso9660.cpp
${libusbdvd_SOURCE_DIR}/source/fs/iso9660/iso9660_devoptab.cpp
${libusbdvd_SOURCE_DIR}/source/fs/udf/usbdvd_udf.cpp
${libusbdvd_SOURCE_DIR}/source/fs/udf/udf_devoptab.cpp
${libusbdvd_SOURCE_DIR}/source/os/switch/switch_usb.cpp
)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/os/switch)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/iso9660)
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/udf)
target_include_directories(libusbdvd PUBLIC ${libusbdvd_SOURCE_DIR}/include)
target_compile_definitions(sphaira PRIVATE ENABLE_LIBUSBDVD)
target_link_libraries(sphaira PRIVATE libusbdvd)
target_sources(sphaira PRIVATE source/usbdvd.cpp)
endif()
if (ENABLE_FTPSRV)
FetchContent_Declare(ftpsrv
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
GIT_TAG 7c82402
SOURCE_SUBDIR NONE
)
FetchContent_MakeAvailable(ftpsrv)
set(FTPSRV_LIB_BUILD TRUE)
set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
set(FTPSRV_LIB_PATH_SIZE 0x301)
set(FTPSRV_LIB_SESSIONS 16)
set(FTPSRV_LIB_BUF_SIZE 1024*64)
set(FTPSRV_LIB_CUSTOM_DEFINES
USE_VFS_SAVE=$<BOOL:FALSE>
USE_VFS_STORAGE=$<BOOL:TRUE>
# disabled as it may conflict with the gamecard menu.
USE_VFS_GC=$<BOOL:FALSE>
USE_VFS_USBHSFS=$<BOOL:FALSE>
VFS_NX_BUFFER_IO=$<BOOL:TRUE>
# let sphaira handle init / closing of the hdd.
USE_VFS_USBHSFS_INIT=$<BOOL:FALSE>
# disable romfs mounting as otherwise we cannot write / modify sphaira.nro
USE_VFS_ROMFS=$<BOOL:FALSE>
FTP_SOCKET_HEADER="${ftpsrv_SOURCE_DIR}/src/platform/nx/socket_nx.h"
)
add_subdirectory(${ftpsrv_SOURCE_DIR} binary_dir)
add_library(ftpsrv_helper
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/utils.c
)
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv)
target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platform)
target_compile_definitions(sphaira PRIVATE ENABLE_FTPSRV)
target_link_libraries(sphaira PRIVATE ftpsrv_helper)
target_sources(sphaira PRIVATE
source/ftpsrv_helper.cpp
source/ui/menus/ftp_menu.cpp
)
endif()
if (ENABLE_LIBHAZE)
FetchContent_Declare(libhaze
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
GIT_TAG 81154c1
)
FetchContent_MakeAvailable(libhaze)
target_compile_definitions(sphaira PRIVATE ENABLE_LIBHAZE)
target_link_libraries(sphaira PRIVATE libhaze)
target_sources(sphaira PRIVATE
source/haze_helper.cpp
source/ui/menus/mtp_menu.cpp
)
endif()
if (ENABLE_DEVOPTAB_HTTP)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_HTTP)
target_sources(sphaira PRIVATE source/utils/devoptab_http.cpp)
endif()
if (ENABLE_DEVOPTAB_NFS)
FetchContent_Declare(libnfs
GIT_REPOSITORY https://github.com/ITotalJustice/libnfs.git
GIT_TAG 65f3e11
)
FetchContent_MakeAvailable(libnfs)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_NFS)
target_link_libraries(sphaira PRIVATE nfs)
target_sources(sphaira PRIVATE source/utils/devoptab_nfs.cpp)
# todo: fix this upstream as nfs should export these folders.
target_include_directories(sphaira PRIVATE
${libnfs_SOURCE_DIR}/include
${libnfs_SOURCE_DIR}/include/nfsc
${libnfs_SOURCE_DIR}/nfs
)
endif()
if (ENABLE_DEVOPTAB_SMB2)
FetchContent_Declare(libsmb2
GIT_REPOSITORY https://github.com/ITotalJustice/libsmb2.git
GIT_TAG 867beea
)
FetchContent_MakeAvailable(libsmb2)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_SMB2)
target_link_libraries(sphaira PRIVATE smb2)
target_sources(sphaira PRIVATE source/utils/devoptab_smb2.cpp)
endif()
if (ENABLE_DEVOPTAB_FTP)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_FTP)
target_sources(sphaira PRIVATE source/utils/devoptab_ftp.cpp)
endif()
if (ENABLE_DEVOPTAB_SFTP)
# set to build from source, otherwise it will link against the older dkp libssh2.
if (1)
set(CRYPTO_BACKEND mbedTLS)
set(ENABLE_ZLIB_COMPRESSION ON)
set(ENABLE_DEBUG_LOGGING OFF)
set(BUILD_EXAMPLES OFF)
set(BUILD_TESTING OFF)
set(LINT OFF)
FetchContent_Declare(libssh2
GIT_REPOSITORY https://github.com/libssh2/libssh2.git
# GIT_TAG a0dafb3 # latest commit, works fine, but i'll stick to main release.
GIT_TAG libssh2-1.11.1
)
FetchContent_MakeAvailable(libssh2)
target_link_libraries(sphaira PRIVATE libssh2::libssh2)
else()
include(FindPkgConfig)
pkg_check_modules(LIBSSH2 libssh2 REQUIRED)
target_include_directories(sphaira PRIVATE ${LIBSSH2_INCLUDE_DIRS})
target_link_libraries(sphaira PRIVATE ${LIBSSH2_LIBRARIES})
endif()
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_SFTP)
target_sources(sphaira PRIVATE source/utils/devoptab_sftp.cpp)
endif()
if (ENABLE_DEVOPTAB_WEBDAV)
set(PUGIXML_NO_EXCEPTIONS ON)
set(PUGIXML_WCHAR_MODE OFF)
FetchContent_Declare(pugixml
GIT_REPOSITORY https://github.com/zeux/pugixml.git
GIT_TAG v1.15
)
FetchContent_MakeAvailable(pugixml)
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_WEBDAV)
target_link_libraries(sphaira PRIVATE pugixml)
target_sources(sphaira PRIVATE source/utils/devoptab_webdav.cpp)
endif()
if (ENABLE_AUDIO_MP3)
FetchContent_Declare(id3v2lib
GIT_REPOSITORY https://github.com/larsbs/id3v2lib.git
GIT_TAG 141ffb8
)
FetchContent_MakeAvailable(id3v2lib)
target_link_libraries(sphaira PRIVATE id3v2lib)
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_MP3)
endif()
if (ENABLE_AUDIO_OGG)
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_OGG)
endif()
if (ENABLE_AUDIO_WAV)
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_WAV)
endif()
if (ENABLE_AUDIO_FLAC)
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_FLAC)
endif()
# ztsd
set(ZSTD_BUILD_STATIC ON)
set(ZSTD_BUILD_SHARED OFF)
set(ZSTD_BUILD_COMPRESSION OFF)
set(ZSTD_BUILD_COMPRESSION ${ENABLE_NSZ})
set(ZSTD_MULTITHREAD_SUPPORT ${ENABLE_NSZ})
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)
# minini
set(MININI_LIB_NAME minIni)
set(MININI_USE_STDIO ON)
set(MININI_USE_NX OFF)
set(MININI_USE_FLOAT OFF)
set(MININI_USE_FLOAT ON)
# nanovg
if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
set(NANOVG_DEBUG ON)
endif()
set(NANOVG_NO_JPEG OFF)
set(NANOVG_NO_PNG OFF)
set(NANOVG_NO_BMP ON)
set(NANOVG_NO_BMP OFF)
set(NANOVG_NO_PSD ON)
set(NANOVG_NO_TGA ON)
set(NANOVG_NO_GIF ON)
@@ -227,6 +545,8 @@ set(NANOVG_NO_HDR ON)
set(NANOVG_NO_PIC ON)
set(NANOVG_NO_PNM ON)
# yyjson
set(YYJSON_INSTALL OFF)
set(YYJSON_DISABLE_READER OFF)
set(YYJSON_DISABLE_WRITER OFF)
set(YYJSON_DISABLE_UTILS ON)
@@ -235,62 +555,30 @@ 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
libpulsar
nanovg
stb
minIni
yyjson
zstd
libusbhsfs
libnxtc
dr_libs
)
set(FTPSRV_LIB_BUILD TRUE)
set(FTPSRV_LIB_SOCK_UNISTD TRUE)
set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
set(FTPSRV_LIB_PATH_SIZE 0x301)
set(FTPSRV_LIB_SESSIONS 16)
set(FTPSRV_LIB_BUF_SIZE 1024*64)
set(FTPSRV_LIB_CUSTOM_DEFINES
USE_VFS_SAVE=$<BOOL:TRUE>
USE_VFS_STORAGE=$<BOOL:TRUE>
# disabled as it may conflict with the gamecard menu.
USE_VFS_GC=$<BOOL:FALSE>
USE_VFS_USBHSFS=$<BOOL:TRUE>
VFS_NX_BUFFER_IO=$<BOOL:TRUE>
# let sphaira handle init / closing of the hdd.
USE_VFS_USBHSFS_INIT=$<BOOL:FALSE>
# disable romfs mounting as otherwise we cannot write / modify sphaira.nro
USE_VFS_ROMFS=$<BOOL:FALSE>
)
add_subdirectory(${ftpsrv_SOURCE_DIR} binary_dir)
add_library(ftpsrv_helper
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_save.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_stdio.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_hdd.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/utils.c
)
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv libusbhsfs)
target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platform)
add_library(stb INTERFACE)
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
add_library(dr_libs INTERFACE)
target_include_directories(dr_libs INTERFACE ${dr_libs_SOURCE_DIR})
add_library(libnxtc
${libnxtc_SOURCE_DIR}/source/nxtc.c
${libnxtc_SOURCE_DIR}/source/nxtc_log.c
${libnxtc_SOURCE_DIR}/source/nxtc_utils.c
)
target_include_directories(libnxtc PUBLIC ${libnxtc_SOURCE_DIR}/include)
find_package(ZLIB REQUIRED)
find_library(minizip_lib minizip REQUIRED)
find_path(minizip_inc minizip REQUIRED)
@@ -299,10 +587,12 @@ 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()
add_library(fatfs
source/ff16/diskio.c
source/ff16/ff.c
)
target_include_directories(fatfs PUBLIC source/ff16)
set_target_properties(sphaira PROPERTIES
C_STANDARD 23
@@ -312,14 +602,15 @@ set_target_properties(sphaira PROPERTIES
)
target_link_libraries(sphaira PRIVATE
ftpsrv_helper
libhaze
libpulsar
minIni
nanovg
stb
yyjson
# libusbhsfs
libnxtc
fatfs
dr_libs
libzstd_static
${minizip_lib}
ZLIB::ZLIB
@@ -327,19 +618,11 @@ 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}
${mbedtls_inc}
include/yati/nx/nxdumptool
)
# copy the romfs

View File

@@ -2,32 +2,27 @@
#include "nanovg.h"
#include "nanovg/dk_renderer.hpp"
#include "pulsar.h"
#include "ui/widget.hpp"
#include "ui/notification.hpp"
#include "owo.hpp"
#include "option.hpp"
#include "fs.hpp"
#include "log.hpp"
#include "utils/audio.hpp"
#ifdef USE_NVJPG
#include <nvjpg.hpp>
#endif
#include <switch.h>
#include <vector>
#include <string>
#include <span>
#include <optional>
#include <utility>
namespace sphaira {
enum SoundEffect {
SoundEffect_Music,
SoundEffect_Focus,
SoundEffect_Scroll,
SoundEffect_Limit,
SoundEffect_Startup,
SoundEffect_Install,
SoundEffect_Error,
SoundEffect_MAX,
};
using SoundEffect = audio::SoundEffect;
enum class LaunchType {
Normal,
@@ -55,12 +50,19 @@ public:
static void Exit();
static void ExitRestart();
static auto GetVg() -> NVGcontext*;
static void Push(std::shared_ptr<ui::Widget>);
static void Push(std::unique_ptr<ui::Widget>&&);
template<ui::DerivedFromWidget T, typename... Args>
static void Push(Args&&... args) {
Push(std::make_unique<T>(std::forward<Args>(args)...));
}
// pops all widgets above a menu
static void PopToMenu();
// this is thread safe
static void Notify(std::string text, ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
static void Notify(const std::string& text, ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
static void Notify(ui::NotifEntry entry);
static void NotifyPop(ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
static void NotifyClear(ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
@@ -92,12 +94,14 @@ public:
static auto GetInstallSysmmcEnable() -> bool;
static auto GetInstallEmummcEnable() -> bool;
static auto GetInstallSdEnable() -> bool;
static auto GetInstallPrompt() -> bool;
static auto GetThemeMusicEnable() -> bool;
static auto Get12HourTimeEnable() -> bool;
static auto GetLanguage() -> long;
static auto GetTextScrollSpeed() -> long;
static auto GetNszCompressLevel() -> u8;
static auto GetNszThreadCount() -> u8;
static auto GetNszBlockExponent() -> u8;
static void SetMtpEnable(bool enable);
static void SetFtpEnable(bool enable);
static void SetNxlinkEnable(bool enable);
@@ -122,10 +126,18 @@ public:
static void DisplayThemeOptions(bool left_side = true);
// todo:
static void DisplayNetworkOptions(bool left_side = true);
static void DisplayMiscOptions(bool left_side = true);
static void DisplayMenuOptions(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);
static void DisplayFtpOptions(bool left_side = true);
static void DisplayMtpOptions(bool left_side = true);
static void DisplayHddOptions(bool left_side = true);
// helper for sidebar options to toggle install on/off
static void ShowEnableInstallPromptOption(option::OptionBool& option, bool& enable);
// displays an option box to enable installing, shows warning.
static void ShowEnableInstallPrompt();
void Draw();
void Update();
@@ -138,8 +150,15 @@ public:
void LoadTheme(const ThemeMeta& meta);
void CloseTheme();
void CloseThemeBackgroundMusic();
void ScanThemes(const std::string& path);
void ScanThemeEntries();
void LoadAndPlayThemeMusic();
static Result SetDefaultBackgroundMusic(fs::Fs* fs, const fs::FsPath& path);
static void SetBackgroundMusicPause(bool pause);
static Result GetSdSize(s64* free, s64* total);
static Result GetEmmcSize(s64* free, s64* total);
// helper that converts 1.2.3 to a u32 used for comparisons.
static auto GetVersionFromString(const char* str) -> u32;
@@ -189,11 +208,59 @@ public:
}
}
static void SetBoostMode(bool enable, bool force = false) {
static Mutex mutex{};
static int ref_count{};
mutexLock(&mutex);
ON_SCOPE_EXIT(mutexUnlock(&mutex));
if (enable) {
ref_count++;
appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad);
} else {
if (ref_count) {
ref_count--;
}
}
if (!ref_count || force) {
ref_count = 0;
appletSetCpuBoostMode(ApmCpuBoostMode_Normal);
}
}
static auto GetAccountList() -> std::vector<AccountProfileBase> {
std::vector<AccountProfileBase> out;
AccountUid uids[ACC_USER_LIST_SIZE];
s32 account_count;
if (R_SUCCEEDED(accountListAllUsers(uids, std::size(uids), &account_count))) {
for (s32 i = 0; i < account_count; i++) {
AccountProfile profile;
if (R_SUCCEEDED(accountGetProfile(&profile, uids[i]))) {
ON_SCOPE_EXIT(accountProfileClose(&profile));
AccountProfileBase base;
if (R_SUCCEEDED(accountProfileGet(&profile, nullptr, &base))) {
// sometimes the uid for the acc can differ to the base.
base.uid = uids[i];
log_write("[ACC] found uid: 0x%016lX%016lX\n", uids[i].uid[0], uids[i].uid[1]);
log_write("[ACC] base uid: 0x%016lX%016lX\n", base.uid.uid[0], base.uid.uid[1]);
out.emplace_back(base);
}
}
}
}
return out;
}
// 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";
static constexpr inline auto DEFAULT_THEME_PATH = "romfs:/themes/default_theme.ini";
fs::FsPath m_app_path;
u64 m_start_timestamp{};
@@ -205,11 +272,12 @@ public:
PadState m_pad{};
TouchInfo m_touch_info{};
Controller m_controller{};
KeyboardState m_keyboard{};
std::vector<ThemeMeta> m_theme_meta_entries;
Vec2 m_scale{1, 1};
std::vector<std::shared_ptr<ui::Widget>> m_widgets;
std::vector<std::unique_ptr<ui::Widget>> m_widgets;
u32 m_pop_count{};
ui::NotifMananger m_notif_manager{};
@@ -231,10 +299,12 @@ public:
option::OptionBool m_log_enabled{INI_SECTION, "log_enabled", false};
option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false};
option::OptionString m_default_music{INI_SECTION, "default_music", "/config/sphaira/themes/default_music.bfstm"};
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::OptionBool m_show_ip_addr{INI_SECTION, "show_ip_addr", true};
option::OptionLong m_language{INI_SECTION, "language", 0}; // auto
option::OptionString m_center_menu{INI_SECTION, "center_side_menu", "Homebrew"};
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};
@@ -243,7 +313,6 @@ public:
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};
@@ -252,25 +321,75 @@ public:
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_skip_nca_hash_verify{INI_SECTION, "skip_nca_hash_verify", true};
option::OptionBool m_skip_rsa_header_fixed_key_verify{INI_SECTION, "skip_rsa_header_fixed_key_verify", true};
option::OptionBool m_skip_rsa_npdm_fixed_key_verify{INI_SECTION, "skip_rsa_npdm_fixed_key_verify", true};
option::OptionBool m_ignore_distribution_bit{INI_SECTION, "ignore_distribution_bit", false};
option::OptionBool m_convert_to_common_ticket{INI_SECTION, "convert_to_common_ticket", true};
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};
option::OptionBool m_lower_system_version{INI_SECTION, "lower_system_version", true};
// 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};
option::OptionBool m_dump_convert_to_common_ticket{"dump", "convert_to_common_ticket", true};
option::OptionLong m_nsz_compress_level{"dump", "nsz_compress_level", 3};
option::OptionLong m_nsz_compress_threads{"dump", "nsz_compress_threads", 3};
option::OptionBool m_nsz_compress_ldm{"dump", "nsz_compress_ldm", true};
option::OptionBool m_nsz_compress_block{"dump", "nsz_compress_block", false};
option::OptionLong m_nsz_compress_block_exponent{"dump", "nsz_compress_block_exponent", 6};
// todo: move this into it's own menu
option::OptionLong m_text_scroll_speed{"accessibility", "text_scroll_speed", 1}; // normal
PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
// ftp options.
option::OptionLong m_ftp_port{"ftp", "port", 5000};
option::OptionBool m_ftp_anon{"ftp", "anon", true};
option::OptionString m_ftp_user{"ftp", "user", ""};
option::OptionString m_ftp_pass{"ftp", "pass", ""};
option::OptionBool m_ftp_show_album{"ftp", "show_album", true};
option::OptionBool m_ftp_show_ams_contents{"ftp", "show_ams_contents", false};
option::OptionBool m_ftp_show_bis_storage{"ftp", "show_bis_storage", false};
option::OptionBool m_ftp_show_bis_fs{"ftp", "show_bis_fs", false};
option::OptionBool m_ftp_show_content_system{"ftp", "show_content_system", false};
option::OptionBool m_ftp_show_content_user{"ftp", "show_content_user", false};
option::OptionBool m_ftp_show_content_sd{"ftp", "show_content_sd", false};
// option::OptionBool m_ftp_show_content_sd0{"ftp", "show_content_sd0", false};
// option::OptionBool m_ftp_show_custom_system{"ftp", "show_custom_system", false};
// option::OptionBool m_ftp_show_custom_sd{"ftp", "show_custom_sd", false};
option::OptionBool m_ftp_show_games{"ftp", "show_games", true};
option::OptionBool m_ftp_show_install{"ftp", "show_install", true};
option::OptionBool m_ftp_show_mounts{"ftp", "show_mounts", false};
option::OptionBool m_ftp_show_switch{"ftp", "show_switch", false};
// mtp options.
option::OptionLong m_mtp_vid{"mtp", "vid", 0x057e}; // nintendo (hidden from ui)
option::OptionLong m_mtp_pid{"mtp", "pid", 0x201d}; // switch (hidden from ui)
option::OptionBool m_mtp_allocate_file{"mtp", "allocate_file", true};
option::OptionBool m_mtp_show_album{"mtp", "show_album", true};
option::OptionBool m_mtp_show_content_sd{"mtp", "show_content_sd", false};
option::OptionBool m_mtp_show_content_system{"mtp", "show_content_system", false};
option::OptionBool m_mtp_show_content_user{"mtp", "show_content_user", false};
option::OptionBool m_mtp_show_games{"mtp", "show_games", true};
option::OptionBool m_mtp_show_install{"mtp", "show_install", true};
option::OptionBool m_mtp_show_mounts{"mtp", "show_mounts", false};
option::OptionBool m_mtp_show_speedtest{"mtp", "show_speedtest", false};
std::shared_ptr<fs::FsNativeSd> m_fs{};
audio::SongID m_background_music{};
#ifdef USE_NVJPG
nj::Decoder m_decoder;
#endif
double m_delta_time{};
static constexpr const char* INSTALL_DEPENDS_STR =
"Installing is disabled.\n\n"
"Enable in the options by selecting Menu (Y) -> Advanced -> Install options -> Enable.";
private: // from nanovg decko3d example by adubbz
static constexpr unsigned NumFramebuffers = 2;

View File

@@ -174,6 +174,7 @@ enum {
Module_Npln = 321,
Module_Tspm = 499,
Module_Devmenu = 500,
Module_Sphaira = 505,
};
enum SvcError {
@@ -494,7 +495,337 @@ enum NcmError {
NcmError_WriteToReadOnlyContentStorage = 0x17C05,
};
#define R_SUCCEED() return 0
enum class SphairaResult : Result {
TransferCancelled,
StreamBadSeek,
FsTooManyEntries,
FsNewPathTooLarge,
FsInvalidType,
FsEmpty,
FsAlreadyRoot,
FsNoCurrentPath,
FsBrokenCurrentPath,
FsIndexOutOfBounds,
FsFsNotActive,
FsNewPathEmpty,
FsLoadingCancelled,
FsBrokenRoot,
FsUnknownStdioError,
FsStdioFailedToSeek,
FsStdioFailedToRead,
FsStdioFailedToWrite,
FsStdioFailedToOpenFile,
FsStdioFailedToCreate,
FsStdioFailedToTruncate,
FsStdioFailedToFlush,
FsStdioFailedToCreateDirectory,
FsStdioFailedToDeleteFile,
FsStdioFailedToDeleteDirectory,
FsStdioFailedToOpenDirectory,
FsStdioFailedToRename,
FsStdioFailedToStat,
FsReadOnly,
FsNotActive,
FsFailedStdioStat,
FsFailedStdioOpendir,
NroBadMagic,
NroBadSize,
AppFailedMusicDownload,
CurlFailedEasyInit,
DumpFailedNetworkUpload,
UnzOpen2_64,
UnzGetGlobalInfo64,
UnzLocateFile,
UnzGoToFirstFile,
UnzGoToNextFile,
UnzOpenCurrentFile,
UnzGetCurrentFileInfo64,
UnzReadCurrentFile,
ZipOpen2_64,
ZipOpenNewFileInZip,
ZipWriteInFileInZip,
MmzBadLocalHeaderSig,
MmzBadLocalHeaderRead,
FileBrowserFailedUpload,
FileBrowserDirNotDaybreak,
AppstoreFailedZipDownload,
AppstoreFailedMd5,
AppstoreFailedParseManifest,
GameBadReadForDump,
GameEmptyMetaEntries,
GameMultipleKeysFound,
GameNoNspEntriesBuilt,
KeyMissingNcaKeyArea,
KeyMissingTitleKek,
KeyMissingMasterKey,
KeyFailedDecyptETicketDeviceKey,
NcaFailedNcaHeaderHashVerify,
NcaBadSigKeyGen,
GcBadReadForDump,
GcEmptyGamecard,
GcBadXciMagic,
GcBadXciRomSize,
GcFailedToGetSecurityInfo,
GhdlEmptyAsset,
GhdlFailedToDownloadAsset,
GhdlFailedToDownloadAssetJson,
ThemezerFailedToDownloadThemeMeta,
ThemezerFailedToDownloadTheme,
MainFailedToDownloadUpdate,
UsbDsBadDeviceSpeed,
NcaBadMagic,
NspBadMagic,
XciBadMagic,
XciSecurePartitionNotFound,
EsBadTitleKeyType,
EsPersonalisedTicketDeviceIdMissmatch,
EsFailedDecryptPersonalisedTicket,
EsBadDecryptedPersonalisedTicketSize,
EsBadTicketSize,
// found ticket has missmatching rights_id from it's name.
EsInvalidTicketBadRightsId,
EsInvalidTicketFromatVersion,
EsInvalidTicketKeyType,
EsInvalidTicketKeyRevision,
OwoBadArgs,
UsbCancelled,
UsbBadMagic,
UsbBadVersion,
UsbBadCount,
UsbBadBufferAlign,
UsbBadTransferSize,
UsbEmptyTransferSize,
UsbOverflowTransferSize,
UsbBadTotalSize,
UsbUploadBadMagic,
UsbUploadExit,
UsbUploadBadCount,
UsbUploadBadTransferSize,
UsbUploadBadTotalSize,
UsbUploadBadCommand,
// unkown container for the source provided.
YatiContainerNotFound,
// nca required by the cnmt but not found in collection.
YatiNcaNotFound,
YatiInvalidNcaReadSize,
YatiInvalidNcaSigKeyGen,
YatiInvalidNcaMagic,
YatiInvalidNcaSignature0,
YatiInvalidNcaSignature1,
// invalid sha256 over the entire nca.
YatiInvalidNcaSha256,
// section could not be found.
YatiNczSectionNotFound,
// section count == 0.
YatiInvalidNczSectionCount,
// block could not be found.
YatiNczBlockNotFound,
// block version != 2.
YatiInvalidNczBlockVersion,
// block type != 1.
YatiInvalidNczBlockType,
// block count == 0.
YatiInvalidNczBlockTotal,
// block size exponent < 14 || > 32.
YatiInvalidNczBlockSizeExponent,
// zstd error while decompressing ncz.
YatiInvalidNczZstdError,
// nca has rights_id but matching ticket wasn't found.
YatiTicketNotFound,
// found ticket has missmatching rights_id from it's name.
YatiInvalidTicketBadRightsId,
// cert not found for the ticket.
YatiCertNotFound,
// unable to fetch header from ncm database.
YatiNcmDbCorruptHeader,
// unable to total infos from ncm database.
YatiNcmDbCorruptInfos,
NszFailedCreateCctx,
NszFailedSetCompressionLevel,
NszFailedSetThreadCount,
NszFailedSetLongDistanceMode,
NszFailedResetCctx,
NszFailedCompress2,
NszFailedCompressStream2,
NszTooManyBlocks,
// set when nca finished but not all blocks were handled.
NszMissingBlocks,
};
#define MAKE_SPHAIRA_RESULT_ENUM(x) Result_##x = MAKERESULT(Module_Sphaira, (Result)SphairaResult::x)
enum : Result {
MAKE_SPHAIRA_RESULT_ENUM(TransferCancelled),
MAKE_SPHAIRA_RESULT_ENUM(StreamBadSeek),
MAKE_SPHAIRA_RESULT_ENUM(FsTooManyEntries),
MAKE_SPHAIRA_RESULT_ENUM(FsNewPathTooLarge),
MAKE_SPHAIRA_RESULT_ENUM(FsInvalidType),
MAKE_SPHAIRA_RESULT_ENUM(FsEmpty),
MAKE_SPHAIRA_RESULT_ENUM(FsAlreadyRoot),
MAKE_SPHAIRA_RESULT_ENUM(FsNoCurrentPath),
MAKE_SPHAIRA_RESULT_ENUM(FsBrokenCurrentPath),
MAKE_SPHAIRA_RESULT_ENUM(FsIndexOutOfBounds),
MAKE_SPHAIRA_RESULT_ENUM(FsFsNotActive),
MAKE_SPHAIRA_RESULT_ENUM(FsNewPathEmpty),
MAKE_SPHAIRA_RESULT_ENUM(FsLoadingCancelled),
MAKE_SPHAIRA_RESULT_ENUM(FsBrokenRoot),
MAKE_SPHAIRA_RESULT_ENUM(FsUnknownStdioError),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToSeek),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToRead),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToWrite),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToOpenFile),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToCreate),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToTruncate),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToFlush),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToCreateDirectory),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToDeleteFile),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToDeleteDirectory),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToOpenDirectory),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToRename),
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToStat),
MAKE_SPHAIRA_RESULT_ENUM(FsReadOnly),
MAKE_SPHAIRA_RESULT_ENUM(FsNotActive),
MAKE_SPHAIRA_RESULT_ENUM(FsFailedStdioStat),
MAKE_SPHAIRA_RESULT_ENUM(FsFailedStdioOpendir),
MAKE_SPHAIRA_RESULT_ENUM(NroBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(NroBadSize),
MAKE_SPHAIRA_RESULT_ENUM(AppFailedMusicDownload),
MAKE_SPHAIRA_RESULT_ENUM(CurlFailedEasyInit),
MAKE_SPHAIRA_RESULT_ENUM(DumpFailedNetworkUpload),
MAKE_SPHAIRA_RESULT_ENUM(UnzOpen2_64),
MAKE_SPHAIRA_RESULT_ENUM(UnzGetGlobalInfo64),
MAKE_SPHAIRA_RESULT_ENUM(UnzLocateFile),
MAKE_SPHAIRA_RESULT_ENUM(UnzGoToFirstFile),
MAKE_SPHAIRA_RESULT_ENUM(UnzGoToNextFile),
MAKE_SPHAIRA_RESULT_ENUM(UnzOpenCurrentFile),
MAKE_SPHAIRA_RESULT_ENUM(UnzGetCurrentFileInfo64),
MAKE_SPHAIRA_RESULT_ENUM(UnzReadCurrentFile),
MAKE_SPHAIRA_RESULT_ENUM(ZipOpen2_64),
MAKE_SPHAIRA_RESULT_ENUM(ZipOpenNewFileInZip),
MAKE_SPHAIRA_RESULT_ENUM(ZipWriteInFileInZip),
MAKE_SPHAIRA_RESULT_ENUM(MmzBadLocalHeaderSig),
MAKE_SPHAIRA_RESULT_ENUM(MmzBadLocalHeaderRead),
MAKE_SPHAIRA_RESULT_ENUM(FileBrowserFailedUpload),
MAKE_SPHAIRA_RESULT_ENUM(FileBrowserDirNotDaybreak),
MAKE_SPHAIRA_RESULT_ENUM(AppstoreFailedZipDownload),
MAKE_SPHAIRA_RESULT_ENUM(AppstoreFailedMd5),
MAKE_SPHAIRA_RESULT_ENUM(AppstoreFailedParseManifest),
MAKE_SPHAIRA_RESULT_ENUM(GameBadReadForDump),
MAKE_SPHAIRA_RESULT_ENUM(GameEmptyMetaEntries),
MAKE_SPHAIRA_RESULT_ENUM(GameMultipleKeysFound),
MAKE_SPHAIRA_RESULT_ENUM(GameNoNspEntriesBuilt),
MAKE_SPHAIRA_RESULT_ENUM(KeyMissingNcaKeyArea),
MAKE_SPHAIRA_RESULT_ENUM(KeyMissingTitleKek),
MAKE_SPHAIRA_RESULT_ENUM(KeyMissingMasterKey),
MAKE_SPHAIRA_RESULT_ENUM(KeyFailedDecyptETicketDeviceKey),
MAKE_SPHAIRA_RESULT_ENUM(NcaFailedNcaHeaderHashVerify),
MAKE_SPHAIRA_RESULT_ENUM(NcaBadSigKeyGen),
MAKE_SPHAIRA_RESULT_ENUM(GcBadReadForDump),
MAKE_SPHAIRA_RESULT_ENUM(GcEmptyGamecard),
MAKE_SPHAIRA_RESULT_ENUM(GcBadXciMagic),
MAKE_SPHAIRA_RESULT_ENUM(GcBadXciRomSize),
MAKE_SPHAIRA_RESULT_ENUM(GcFailedToGetSecurityInfo),
MAKE_SPHAIRA_RESULT_ENUM(GhdlEmptyAsset),
MAKE_SPHAIRA_RESULT_ENUM(GhdlFailedToDownloadAsset),
MAKE_SPHAIRA_RESULT_ENUM(GhdlFailedToDownloadAssetJson),
MAKE_SPHAIRA_RESULT_ENUM(ThemezerFailedToDownloadThemeMeta),
MAKE_SPHAIRA_RESULT_ENUM(ThemezerFailedToDownloadTheme),
MAKE_SPHAIRA_RESULT_ENUM(MainFailedToDownloadUpdate),
MAKE_SPHAIRA_RESULT_ENUM(UsbDsBadDeviceSpeed),
MAKE_SPHAIRA_RESULT_ENUM(NspBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(XciBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(NcaBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(XciSecurePartitionNotFound),
MAKE_SPHAIRA_RESULT_ENUM(EsBadTitleKeyType),
MAKE_SPHAIRA_RESULT_ENUM(EsPersonalisedTicketDeviceIdMissmatch),
MAKE_SPHAIRA_RESULT_ENUM(EsFailedDecryptPersonalisedTicket),
MAKE_SPHAIRA_RESULT_ENUM(EsBadDecryptedPersonalisedTicketSize),
MAKE_SPHAIRA_RESULT_ENUM(EsBadTicketSize),
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketBadRightsId),
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketFromatVersion),
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketKeyType),
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketKeyRevision),
MAKE_SPHAIRA_RESULT_ENUM(OwoBadArgs),
MAKE_SPHAIRA_RESULT_ENUM(UsbCancelled),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadVersion),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadCount),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadBufferAlign),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadTransferSize),
MAKE_SPHAIRA_RESULT_ENUM(UsbEmptyTransferSize),
MAKE_SPHAIRA_RESULT_ENUM(UsbOverflowTransferSize),
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadExit),
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadCount),
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadTransferSize),
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadTotalSize),
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadCommand),
MAKE_SPHAIRA_RESULT_ENUM(YatiContainerNotFound),
MAKE_SPHAIRA_RESULT_ENUM(YatiNcaNotFound),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaReadSize),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaSigKeyGen),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaMagic),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaSignature0),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaSignature1),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaSha256),
MAKE_SPHAIRA_RESULT_ENUM(YatiNczSectionNotFound),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczSectionCount),
MAKE_SPHAIRA_RESULT_ENUM(YatiNczBlockNotFound),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczBlockVersion),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczBlockType),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczBlockTotal),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczBlockSizeExponent),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczZstdError),
MAKE_SPHAIRA_RESULT_ENUM(YatiTicketNotFound),
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidTicketBadRightsId),
MAKE_SPHAIRA_RESULT_ENUM(YatiCertNotFound),
MAKE_SPHAIRA_RESULT_ENUM(YatiNcmDbCorruptHeader),
MAKE_SPHAIRA_RESULT_ENUM(YatiNcmDbCorruptInfos),
MAKE_SPHAIRA_RESULT_ENUM(NszFailedCreateCctx),
MAKE_SPHAIRA_RESULT_ENUM(NszFailedSetCompressionLevel),
MAKE_SPHAIRA_RESULT_ENUM(NszFailedSetThreadCount),
MAKE_SPHAIRA_RESULT_ENUM(NszFailedSetLongDistanceMode),
MAKE_SPHAIRA_RESULT_ENUM(NszFailedResetCctx),
MAKE_SPHAIRA_RESULT_ENUM(NszFailedCompress2),
MAKE_SPHAIRA_RESULT_ENUM(NszFailedCompressStream2),
MAKE_SPHAIRA_RESULT_ENUM(NszTooManyBlocks),
MAKE_SPHAIRA_RESULT_ENUM(NszMissingBlocks),
};
#undef MAKE_SPHAIRA_RESULT_ENUM
#define R_SUCCEED() return (Result)0
#define R_THROW(_rc) return _rc
@@ -520,19 +851,83 @@ enum NcmError {
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__)
#define ON_SCOPE_EXIT(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }};
#define ON_SCOPE_FAIL(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_FAILED(rc)) { _f; } }};
#define ON_SCOPE_SUCCESS(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_SUCCEEDED(rc)) { _f; } }};
template<typename Function>
struct ScopeGuard {
ScopeGuard(Function&& function) : m_function(std::forward<Function>(function)) {
// threading helpers.
#define PRIO_PREEMPTIVE 0x3B
}
~ScopeGuard() {
m_function();
}
// threading affinity, use with svcSetThreadCoreMask().
#define THREAD_AFFINITY_CORE0 BIT(0)
#define THREAD_AFFINITY_CORE1 BIT(1)
#define THREAD_AFFINITY_CORE2 BIT(2)
#define THREAD_AFFINITY_DEFAULT(core) (BIT(core)|THREAD_AFFINITY_CORE1|THREAD_AFFINITY_CORE2)
#define THREAD_AFFINITY_ALL (THREAD_AFFINITY_CORE0|THREAD_AFFINITY_CORE1|THREAD_AFFINITY_CORE2)
ScopeGuard(const ScopeGuard&) = delete;
void operator=(const ScopeGuard&) = delete;
// mutex helpers.
#define SCOPED_MUTEX(mutex) mutexLock(mutex); ON_SCOPE_EXIT(mutexUnlock(mutex))
private:
const Function m_function;
};
struct ScopedMutex {
ScopedMutex(Mutex* mutex) : m_mutex{mutex} {
mutexLock(m_mutex);
}
~ScopedMutex() {
mutexUnlock(m_mutex);
}
ScopedMutex(const ScopedMutex&) = delete;
void operator=(const ScopedMutex&) = delete;
private:
Mutex* const m_mutex;
};
struct ScopedRMutex {
ScopedRMutex(RMutex* _mutex) : mutex{_mutex} {
rmutexLock(mutex);
}
~ScopedRMutex() {
rmutexUnlock(mutex);
}
ScopedRMutex(const ScopedRMutex&) = delete;
void operator=(const ScopedRMutex&) = delete;
private:
RMutex* const mutex;
};
struct ScopedRwLock {
ScopedRwLock(RwLock* _lock, bool _write) : lock{_lock}, write{_write} {
if (write) {
rwlockWriteLock(lock);
} else {
rwlockReadLock(lock);
}
}
~ScopedRwLock() {
if (write) {
rwlockWriteUnlock(lock);
} else {
rwlockReadUnlock(lock);
}
}
ScopedRwLock(const ScopedRwLock&) = delete;
void operator=(const ScopedRwLock&) = delete;
private:
RwLock* const lock;
bool const write;
};
// #define ON_SCOPE_EXIT(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }};
#define ON_SCOPE_EXIT(_f) ScopeGuard ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }};
#define SCOPED_MUTEX(_m) ScopedMutex ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m}
#define SCOPED_RMUTEX(_m) ScopedRMutex ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m}
#define SCOPED_RWLOCK(_m, _write) ScopedRwLock ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m, _write}
// #define ON_SCOPE_FAIL(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_FAILED(rc)) { _f; } }};
// #define ON_SCOPE_SUCCESS(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_SUCCEEDED(rc)) { _f; } }};

View File

@@ -52,7 +52,7 @@ struct Fields {
struct Header {
Header() = default;
Header(std::initializer_list<std::pair<const std::string, std::string>> p) : m_map{p} {}
Header(std::initializer_list<std::pair<const std::string, std::string>>&& p) : m_map{std::forward<decltype(p)>(p)} {}
std::unordered_map<std::string, std::string> m_map;
auto Find(const std::string& key) const {
@@ -91,7 +91,7 @@ struct UserPass {
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, s64 size, const 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{};
@@ -142,6 +142,7 @@ struct DownloadEventData {
auto Init() -> bool;
void Exit();
void ExitSignal();
// sync functions
auto ToMemory(const Api& e) -> ApiResult;

View File

@@ -1,33 +1,47 @@
#pragma once
#include "fs.hpp"
#include "location.hpp"
#include "ui/progress_box.hpp"
#include <switch.h>
#include <vector>
#include <memory>
#include <functional>
#include <minizip/ioapi.h>
namespace sphaira::dump {
enum DumpLocationType {
// dump using native fs.
DumpLocationType_SdCard,
// dump to usb pc.
DumpLocationType_Usb,
// 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_Usb = 1 << DumpLocationType_Usb,
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,
DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_Usb | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio,
};
struct DumpEntry {
DumpLocationType type;
s32 index;
};
struct DumpLocation {
DumpEntry entry{};
location::StdioEntries stdio{};
};
struct BaseSource {
@@ -36,11 +50,36 @@ struct BaseSource {
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; }
Result Read(const std::string& path, void* buf, s64 off, s64 size) {
u64 bytes_read;
return Read(path, buf, off, size, &bytes_read);
}
};
struct WriteSource {
virtual ~WriteSource() = default;
virtual Result Write(const void* buf, s64 off, s64 size) = 0;
virtual Result SetSize(s64 size) = 0;
};
// called after dump has finished.
using OnExit = std::function<void(Result rc)>;
using OnLocation = std::function<void(const DumpLocation& loc)>;
void Dump(std::shared_ptr<BaseSource> source, const std::vector<fs::FsPath>& paths, OnExit on_exit = [](Result){}, u32 location_flags = DumpLocationFlag_All);
using CustomTransfer = std::function<Result(ui::ProgressBox* pbox, BaseSource* source, WriteSource* writer, const fs::FsPath& path)>;
// prompts the user to select dump location, calls on_loc on success with the selected location.
void DumpGetLocation(const std::string& title, u32 location_flags, const OnLocation& on_loc, const CustomTransfer& custom_transfer = nullptr);
Result Dump(ui::ProgressBox* pbox, const std::shared_ptr<BaseSource>& source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, const CustomTransfer& custom_transfer = nullptr);
// dumps to a fetched location using DumpGetLocation().
void Dump(const std::shared_ptr<BaseSource>& source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, const OnExit& on_exit, const CustomTransfer& custom_transfer = nullptr);
// DumpGetLocation() + Dump() all in one.
void Dump(const std::shared_ptr<BaseSource>& source, const std::vector<fs::FsPath>& paths, const OnExit& on_exit = nullptr, u32 location_flags = DumpLocationFlag_All);
void Dump(const std::shared_ptr<BaseSource>& source, const std::vector<fs::FsPath>& paths, const CustomTransfer& custom_transfer, const OnExit& on_exit = nullptr, u32 location_flags = DumpLocationFlag_All);
void FileFuncWriter(WriteSource* writer, zlib_filefunc64_def* funcs);
} // namespace sphaira::dump

View File

@@ -7,7 +7,6 @@
#include <string>
#include <switch.h>
#include <nxlink.h>
#include <haze.h>
#include "download.hpp"
namespace sphaira::evman {
@@ -24,7 +23,6 @@ struct ExitEventData {
using EventData = std::variant<
LaunchNroEventData,
ExitEventData,
HazeCallbackData,
NxlinkCallbackData,
curl::DownloadEventData
>;

View File

@@ -4,18 +4,36 @@
#include <dirent.h>
#include <cstring>
#include <vector>
#include <span>
#include <string>
#include <string_view>
#include <sys/syslimits.h>
#include "defines.hpp"
namespace fs {
enum OpenMode : u32 {
OpenMode_Read = FsOpenMode_Read,
OpenMode_Write = FsOpenMode_Write,
OpenMode_Append = FsOpenMode_Append,
// enables buffering for stdio based files.
OpenMode_EnableBuffer = 1 << 16,
OpenMode_ReadBuffered = OpenMode_Read | OpenMode_EnableBuffer,
OpenMode_WriteBuffered = OpenMode_Write | OpenMode_EnableBuffer,
OpenMode_AppendBuffered = OpenMode_Append | OpenMode_EnableBuffer,
};
struct FsPath {
FsPath() = default;
constexpr FsPath(const auto& str) { From(str); }
constexpr FsPath(const FsPath& p) { From(p); }
constexpr FsPath(const char* str) { From(str); }
constexpr FsPath(const std::string& str) { From(str); }
constexpr FsPath(const std::string_view& str) { From(str); }
constexpr void From(const FsPath& p) {
*this = p;
From(p.s);
}
constexpr void From(const char* str) {
@@ -56,6 +74,19 @@ struct FsPath {
s[0] = '\0';
}
constexpr auto starts_with(std::string_view str) const -> bool {
return !strncasecmp(s, str.data(), str.length());
}
constexpr auto ends_with(std::string_view str) const -> bool {
const auto len = length();
if (len < str.length()) {
return false;
}
return !strncasecmp(s + len - str.length(), str.data(), str.length());
}
constexpr operator const char*() const { return s; }
constexpr operator char*() { return s; }
constexpr operator std::string() { return s; }
@@ -65,6 +96,11 @@ struct FsPath {
constexpr char& operator[](std::size_t idx) { return s[idx]; }
constexpr const char& operator[](std::size_t idx) const { return s[idx]; }
constexpr FsPath& operator=(const FsPath& p) noexcept {
From(p.s);
return *this;
}
constexpr FsPath operator+(const FsPath& v) const noexcept {
FsPath r{*this};
return r += v;
@@ -116,20 +152,24 @@ struct FsPath {
return *this;
}
static constexpr bool path_equal(std::string_view a, std::string_view b) {
return a.length() == b.length() && !strncasecmp(a.data(), b.data(), a.length());
}
constexpr bool operator==(const FsPath& v) const noexcept {
return !strcasecmp(*this, v);
return path_equal(*this, v);
}
constexpr bool operator==(const char* v) const noexcept {
return !strcasecmp(*this, v);
return path_equal(*this, v);
}
constexpr bool operator==(const std::string& v) const noexcept {
return !strncasecmp(*this, v.data(), v.length());
return path_equal(*this, v);
}
constexpr bool operator==(const std::string_view v) const noexcept {
return !strncasecmp(*this, v.data(), v.length());
return path_equal(*this, v);
}
static consteval bool Test(const auto& str) {
@@ -142,7 +182,7 @@ struct FsPath {
return path[0] == str[0];
}
char s[FS_MAX_PATH]{};
char s[PATH_MAX]{};
};
inline FsPath operator+(const char* v, const FsPath& fp) {
@@ -160,15 +200,41 @@ inline FsPath operator+(const std::string_view& v, const FsPath& fp) {
return r += fp;
}
static_assert(FsPath::Test("abc"));
static_assert(FsPath::Test(std::string_view{"abc"}));
static_assert(FsPath::Test(std::string{"abc"}));
static_assert(FsPath::Test(FsPath{"abc"}));
// Fs seems to be limted to file paths of 255 characters.
// i've disabled this as network mounts will often have very long paths
// that do not have this limit.
// a proper fix would be to return an error if the path is too long and the path
// is native.
struct FsPathReal {
static constexpr inline size_t FS_REAL_MAX_LENGTH = PATH_MAX;
static_assert(FsPath::TestFrom("abc"));
static_assert(FsPath::TestFrom(std::string_view{"abc"}));
static_assert(FsPath::TestFrom(std::string{"abc"}));
static_assert(FsPath::TestFrom(FsPath{"abc"}));
constexpr FsPathReal(const FsPath& str) : FsPathReal{str.s} { }
explicit constexpr FsPathReal(const char* str) {
size_t real = 0;
for (size_t i = 0; str[i]; i++) {
// skip multiple slashes.
if (i && str[i] == '/' && str[i - 1] == '/') {
continue;
}
// save single char.
s[real++] = str[i];
// check if we have exceeded the path.
if (real >= FS_REAL_MAX_LENGTH) {
break;
}
}
// null the end.
s[real] = '\0';
}
constexpr operator const char*() const { return s; }
constexpr operator std::string_view() const { return s; }
char s[PATH_MAX];
};
// fwd
struct Fs;
@@ -185,16 +251,14 @@ struct File {
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{};
u32 m_mode{};
};
struct Dir {
~Dir();
Result GetEntryCount(s64* out);
Result Read(s64 *total_entries, size_t max_entries, FsDirectoryEntry *buf);
Result ReadAll(std::vector<FsDirectoryEntry>& buf);
void Close();
@@ -206,44 +270,38 @@ struct Dir {
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);
Result CreateDirectory(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result CreateFile(FsFileSystem* fs, const FsPathReal& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true);
Result CreateDirectory(FsFileSystem* fs, const FsPathReal& path, bool ignore_read_only = true);
Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result DeleteFile(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result DeleteDirectory(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result DeleteDirectoryRecursively(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result RenameFile(FsFileSystem* fs, const FsPath& src, const FsPath& dst, bool ignore_read_only = true);
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);
Result DeleteFile(FsFileSystem* fs, const FsPathReal& path, bool ignore_read_only = true);
Result DeleteDirectory(FsFileSystem* fs, const FsPathReal& path, bool ignore_read_only = true);
Result DeleteDirectoryRecursively(FsFileSystem* fs, const FsPathReal& path, bool ignore_read_only = true);
Result RenameFile(FsFileSystem* fs, const FsPathReal& src, const FsPathReal& dst, bool ignore_read_only = true);
Result RenameDirectory(FsFileSystem* fs, const FsPathReal& src, const FsPathReal& dst, bool ignore_read_only = true);
Result GetEntryType(FsFileSystem* fs, const FsPathReal& path, FsDirEntryType* out);
Result GetFileTimeStampRaw(FsFileSystem* fs, const FsPathReal& path, FsTimeStampRaw *out);
Result SetTimestamp(FsFileSystem* fs, const FsPathReal& 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);
Result write_entire_file(FsFileSystem* fs, const FsPath& path, const std::vector<u8>& in, bool ignore_read_only = true);
Result copy_entire_file(FsFileSystem* fs, const FsPath& dst, const FsPath& src, bool ignore_read_only = true);
Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true);
Result CreateDirectory(const FsPath& path, bool ignore_read_only = true);
Result CreateFile(const FsPathReal& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true);
Result CreateDirectory(const FsPathReal& path, bool ignore_read_only = true);
Result CreateDirectoryRecursively(const FsPath& path, bool ignore_read_only = true);
Result CreateDirectoryRecursivelyWithPath(const FsPath& path, bool ignore_read_only = true);
Result DeleteFile(const FsPath& path, bool ignore_read_only = true);
Result DeleteDirectory(const FsPath& path, bool ignore_read_only = true);
Result DeleteFile(const FsPathReal& path, bool ignore_read_only = true);
Result DeleteDirectory(const FsPathReal& path, bool ignore_read_only = true);
Result DeleteDirectoryRecursively(const FsPath& path, bool ignore_read_only = true);
Result RenameFile(const FsPath& src, const FsPath& dst, bool ignore_read_only = true);
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);
Result RenameFile(const FsPathReal& src, const FsPathReal& dst, bool ignore_read_only = true);
Result RenameDirectory(const FsPathReal& src, const FsPathReal& dst, bool ignore_read_only = true);
Result GetEntryType(const FsPathReal& path, FsDirEntryType* out);
Result GetFileTimeStampRaw(const FsPathReal& path, FsTimeStampRaw *out);
Result SetTimestamp(const FsPathReal& 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);
Result OpenFile(fs::Fs* fs, const FsPathReal& path, u32 mode, File* f);
Result OpenDirectory(fs::Fs* fs, const FsPathReal& 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
@@ -260,23 +318,12 @@ Result DirGetEntryCount(fs::Fs* fs, const fs::FsPath& path, s64* file_count, s64
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);
static constexpr inline Result ResultNewPathTooLarge = MAKERESULT(FsModule, 2);
static constexpr inline Result ResultInvalidType = MAKERESULT(FsModule, 3);
static constexpr inline Result ResultEmpty = MAKERESULT(FsModule, 4);
static constexpr inline Result ResultAlreadyRoot = MAKERESULT(FsModule, 5);
static constexpr inline Result ResultNoCurrentPath = MAKERESULT(FsModule, 6);
static constexpr inline Result ResultBrokenCurrentPath = MAKERESULT(FsModule, 7);
static constexpr inline Result ResultIndexOutOfBounds = MAKERESULT(FsModule, 8);
static constexpr inline Result ResultFsNotActive = MAKERESULT(FsModule, 9);
static constexpr inline Result ResultNewPathEmpty = MAKERESULT(FsModule, 10);
static constexpr inline Result ResultLoadingCancelled = MAKERESULT(FsModule, 11);
static constexpr inline Result ResultBrokenRoot = MAKERESULT(FsModule, 12);
static constexpr inline Result ResultUnknownStdioError = MAKERESULT(FsModule, 13);
static constexpr inline Result ResultReadOnly = MAKERESULT(FsModule, 14);
// helpers.
Result read_entire_file(Fs* fs, const FsPath& path, std::vector<u8>& out);
Result write_entire_file(Fs* fs, const FsPath& path, std::span<const u8> in, bool ignore_read_only = true);
Result copy_entire_file(Fs* fs, const FsPath& dst, const FsPath& src, bool ignore_read_only = true);
struct Fs {
Fs(bool ignore_read_only = true) : m_ignore_read_only{ignore_read_only} {}
virtual ~Fs() = default;
@@ -292,13 +339,12 @@ struct Fs {
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 Result Commit() = 0;
virtual bool FileExists(const FsPath& path) = 0;
virtual bool DirExists(const FsPath& path) = 0;
virtual bool IsNative() const = 0;
virtual bool IsSd() const { return false; }
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);
@@ -318,6 +364,15 @@ struct Fs {
Result IsDirEmpty(const fs::FsPath& path, bool* out) {
return fs::IsDirEmpty(this, path, out);
}
Result read_entire_file(const FsPath& path, std::vector<u8>& out) {
return fs::read_entire_file(this, path, out);
}
Result write_entire_file(const FsPath& path, std::span<const u8> in) {
return fs::write_entire_file(this, path, in, m_ignore_read_only);
}
Result copy_entire_file(const FsPath& dst, const FsPath& src) {
return fs::copy_entire_file(this, dst, src, m_ignore_read_only);
}
void SetIgnoreReadOnly(bool enable) {
m_ignore_read_only = enable;
@@ -367,6 +422,9 @@ struct FsStdio : Fs {
Result SetTimestamp(const FsPath& path, const FsTimeStampRaw *ts) override {
return fs::SetTimestamp(path, ts);
}
Result Commit() override {
R_SUCCEED();
}
bool FileExists(const FsPath& path) override {
return fs::FileExists(path);
}
@@ -379,15 +437,6 @@ struct FsStdio : Fs {
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);
}
Result write_entire_file(const FsPath& path, const std::vector<u8>& in) override {
return fs::write_entire_file(path, in, m_ignore_read_only);
}
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;
};
@@ -402,10 +451,6 @@ struct FsNative : Fs {
}
}
Result Commit() {
return fsFsCommit(&m_fs);
}
Result GetFreeSpace(const FsPath& path, s64* out) {
return fsFsGetFreeSpace(&m_fs, path, out);
}
@@ -414,36 +459,6 @@ struct FsNative : Fs {
return fsFsGetTotalSpace(&m_fs, path, out);
}
// Result OpenDirectory(const FsPath& path, u32 mode, FsDir *out) {
// return fsFsOpenDirectory(&m_fs, path, mode, out);
// }
// void DirClose(FsDir *d) {
// fsDirClose(d);
// }
// 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 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);
// }
virtual bool IsFsActive() {
return serviceIsActive(&m_fs.s);
}
@@ -488,6 +503,9 @@ struct FsNative : Fs {
Result SetTimestamp(const FsPath& path, const FsTimeStampRaw *ts) override {
return fs::SetTimestamp(&m_fs, path, ts);
}
Result Commit() override {
return fsFsCommit(&m_fs);
}
bool FileExists(const FsPath& path) override {
return fs::FileExists(&m_fs, path);
}
@@ -497,19 +515,10 @@ struct FsNative : Fs {
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);
}
Result write_entire_file(const FsPath& path, const std::vector<u8>& in) override {
return fs::write_entire_file(&m_fs, path, in, m_ignore_read_only);
}
Result copy_entire_file(const FsPath& dst, const FsPath& src) override {
return fs::copy_entire_file(&m_fs, dst, src, m_ignore_read_only);
}
FsFileSystem m_fs{};
Result m_open_result{};
bool m_own{true};
const bool m_own{true};
};
#if 0
@@ -523,31 +532,53 @@ struct FsNativeSd final : FsNative {
FsNativeSd(bool ignore_read_only = true) : FsNative{fsdevGetDeviceFileSystem("sdmc:"), false, ignore_read_only} {
m_open_result = 0;
}
bool IsSd() const override { return true; }
};
#endif
struct FsNativeBis final : FsNative {
FsNativeBis(FsBisPartitionId id, const FsPath& string, bool ignore_read_only = true) : FsNative{ignore_read_only} {
FsNativeBis(FsBisPartitionId id, const FsPath& string) {
m_open_result = fsOpenBisFileSystem(&m_fs, id, string);
}
};
struct FsNativeImage final : FsNative {
FsNativeImage(FsImageDirectoryId id, bool ignore_read_only = true) : FsNative{ignore_read_only} {
FsNativeImage(FsImageDirectoryId id) {
m_open_result = fsOpenImageDirectoryFileSystem(&m_fs, id);
}
};
struct FsNativeContentStorage final : FsNative {
FsNativeContentStorage(FsContentStorageId id, bool ignore_read_only = true) : FsNative{ignore_read_only} {
FsNativeContentStorage(FsContentStorageId id) {
m_open_result = fsOpenContentStorageFileSystem(&m_fs, id);
}
};
struct FsNativeGameCard final : FsNative {
FsNativeGameCard(const FsGameCardHandle* handle, FsGameCardPartition partition, bool ignore_read_only = true) : FsNative{ignore_read_only} {
FsNativeGameCard(const FsGameCardHandle* handle, FsGameCardPartition partition) {
m_open_result = fsOpenGameCardFileSystem(&m_fs, handle, partition);
}
};
struct FsNativeSave final : FsNative {
FsNativeSave(FsSaveDataType data_type, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr, bool read_only) {
if (data_type == FsSaveDataType_System || data_type == FsSaveDataType_SystemBcat) {
m_open_result = fsOpenSaveDataFileSystemBySystemSaveDataId(&m_fs, FsSaveDataSpaceId_System, attr);
} else {
if (read_only) {
m_open_result = fsOpenReadOnlySaveDataFileSystem(&m_fs, save_data_space_id, attr);
} else {
m_open_result = fsOpenSaveDataFileSystem(&m_fs, save_data_space_id, attr);
}
}
}
};
struct FsNativeId final : FsNative {
FsNativeId(u64 program_id, FsFileSystemType type, const FsPath& path, FsContentAttributes attr = FsContentAttributes_All) {
m_open_result = fsOpenFileSystemWithId(&m_fs, program_id, type, path, attr);
}
};
} // namespace fs

View File

@@ -6,12 +6,13 @@ namespace sphaira::ftpsrv {
bool Init();
void Exit();
void ExitSignal();
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)>;
using OnInstallStart = std::function<bool(const char* path)>;
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
using OnInstallClose = std::function<void()>;
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
void InitInstallMode(const OnInstallStart& on_start, const OnInstallWrite& on_write, const OnInstallClose& on_close);
void DisableInstallMode();
unsigned GetPort();

View File

@@ -4,6 +4,7 @@
#include "ui/progress_box.hpp"
#include <string>
#include <memory>
#include <span>
#include <switch.h>
namespace sphaira::hash {
@@ -13,6 +14,7 @@ enum class Type {
Md5,
Sha1,
Sha256,
Null,
};
struct BaseSource {
@@ -24,7 +26,8 @@ struct BaseSource {
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, BaseSource* source, std::string& out);
Result Hash(ui::ProgressBox* pbox, Type type, fs::Fs* fs, const fs::FsPath& path, std::string& out);
Result Hash(ui::ProgressBox* pbox, Type type, std::span<const u8> data, std::string& out);
} // namespace sphaira::hash

View File

@@ -0,0 +1,18 @@
#pragma once
#include <functional>
namespace sphaira::libhaze {
bool Init();
bool IsInit();
void Exit();
using OnInstallStart = std::function<bool(const char* path)>;
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
using OnInstallClose = std::function<void()>;
void InitInstallMode(const OnInstallStart& on_start, const OnInstallWrite& on_write, const OnInstallClose& on_close);
void DisableInstallMode();
} // namespace sphaira::libhaze

View File

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

View File

@@ -3,23 +3,13 @@
#include <string>
#include <vector>
#include <switch.h>
// to import FsEntryFlags.
// todo: this should be part of a smaller header, such as filesystem_types.hpp
#include "ui/menus/filebrowser.hpp"
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);
using FsEntryFlag = ui::menu::filebrowser::FsEntryFlag;
// helper for hdd devices.
// this doesn't really belong in this header, however
@@ -29,8 +19,14 @@ struct StdioEntry {
std::string mount{};
// ums0: (USB Flash Disk)
std::string name{};
// set if read-only.
bool write_protect;
// FsEntryFlag
u32 flags{};
// optional dump path inside the mount point.
std::string dump_path{};
// set to hide for filebrowser.
bool fs_hidden{};
// set to hide in dump list.
bool dump_hidden{};
};
using StdioEntries = std::vector<StdioEntry>;

View File

@@ -12,6 +12,8 @@ extern "C" {
bool log_file_init();
bool log_nxlink_init();
void log_file_exit();
bool log_is_init();
void log_nxlink_exit();
void log_write(const char* s, ...) __attribute__ ((format (printf, 1, 2)));
void log_write_arg(const char* s, va_list* v);

View File

@@ -0,0 +1,32 @@
#pragma once
#include <minizip/ioapi.h>
#include <vector>
#include <span>
#include <switch.h>
#include "fs.hpp"
namespace sphaira::mz {
struct MzMem {
std::vector<u8> buf;
size_t offset;
};
struct MzSpan {
std::span<const u8> buf;
size_t offset;
};
void FileFuncMem(MzMem* mem, zlib_filefunc64_def* funcs);
void FileFuncSpan(MzSpan* span, zlib_filefunc64_def* funcs);
void FileFuncStdio(zlib_filefunc64_def* funcs);
void FileFuncNative(zlib_filefunc64_def* funcs);
// minizip takes 18ms to open a zip and 4ms to parse the first file entry.
// this results in a dropped frame.
// this version simply reads the local header + file name in 2 reads,
// which takes 1-2ms.
Result PeekFirstFileName(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& name);
} // namespace sphaira::mz

View File

@@ -9,8 +9,14 @@
namespace sphaira {
struct NroData {
NroStart start;
NroHeader header;
};
struct Hbini {
u64 timestamp{}; // timestamp of last launch
bool hidden{};
};
struct MiniNacp {
@@ -61,7 +67,7 @@ auto nro_parse(const fs::FsPath& path, NroEntry& entry) -> Result;
* nro found.
* this does nothing if nested=false.
*/
auto nro_scan(const fs::FsPath& path, std::vector<NroEntry>& nros, bool hide_spahira, bool nested = true, bool scan_all_dir = true) -> Result;
auto nro_scan(const fs::FsPath& path, std::vector<NroEntry>& nros, bool nested = true, bool scan_all_dir = true) -> Result;
auto nro_get_icon(const fs::FsPath& path, u64 size, u64 offset) -> std::vector<u8>;
auto nro_get_icon(const fs::FsPath& path) -> std::vector<u8>;

View File

@@ -44,6 +44,9 @@ bool nxlinkInitialize(NxlinkCallback callback);
// signal for the event to close and then join the thread.
void nxlinkExit();
// async the exit, call this first and then call exit later to avoid blocking.
void nxlinkSignalExit();
#ifdef __cplusplus
}
#endif

View File

@@ -7,10 +7,11 @@ namespace sphaira::option {
template<typename T>
struct OptionBase {
OptionBase(const std::string& section, const std::string& name, T default_value)
OptionBase(const std::string& section, const std::string& name, T default_value, bool file = true)
: m_section{section}
, m_name{name}
, m_default_value{default_value}
, m_file{file}
{}
auto Get() -> T;
@@ -29,11 +30,13 @@ private:
const std::string m_section;
const std::string m_name;
const T m_default_value;
const bool m_file;
std::optional<T> m_value;
};
using OptionBool = OptionBase<bool>;
using OptionLong = OptionBase<long>;
using OptionFloat = OptionBase<float>;
using OptionString = OptionBase<std::string>;
} // namespace sphaira::option

View File

@@ -20,17 +20,6 @@ struct OwoConfig {
std::vector<u8> program_nca{};
};
enum {
Module_Owo = 424,
};
enum OwoError {
OwoError_BadArgs = MAKERESULT(Module_Owo, 1),
};
// fwd
// struct ui::ProgressBox;
auto install_forwarder(OwoConfig& config, NcmStorageId storage_id) -> Result;
auto install_forwarder(ui::ProgressBox* pbox, OwoConfig& config, NcmStorageId storage_id) -> Result;

View File

@@ -2,10 +2,11 @@
#include <switch.h>
#include <string>
#include <sys/syslimits.h>
namespace sphaira::swkbd {
Result ShowText(std::string& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = FS_MAX_PATH);
Result ShowNumPad(s64& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = FS_MAX_PATH);
Result ShowText(std::string& out, const char* header = nullptr, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = PATH_MAX);
Result ShowNumPad(s64& out, const char* header = nullptr, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = PATH_MAX);
} // namespace sphaira::swkbd

View File

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

View File

@@ -0,0 +1,83 @@
#pragma once
#include "fs.hpp"
#include <optional>
#include <span>
#include <vector>
#include <memory>
#include <functional>
#include <switch.h>
namespace sphaira::title {
constexpr u32 ContentMetaTypeToContentFlag(u8 meta_type) {
if (meta_type & 0x80) {
return 1 << (meta_type - 0x80);
}
return 0;
}
enum ContentFlag {
ContentFlag_Application = ContentMetaTypeToContentFlag(NcmContentMetaType_Application),
ContentFlag_Patch = ContentMetaTypeToContentFlag(NcmContentMetaType_Patch),
ContentFlag_AddOnContent = ContentMetaTypeToContentFlag(NcmContentMetaType_AddOnContent),
ContentFlag_DataPatch = ContentMetaTypeToContentFlag(NcmContentMetaType_DataPatch),
// nca locations where a control.nacp can exist.
ContentFlag_Nacp = ContentFlag_Application | ContentFlag_Patch,
// all of the above.
ContentFlag_All = ContentFlag_Application | ContentFlag_Patch | ContentFlag_AddOnContent | ContentFlag_DataPatch,
};
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 ThreadResultData {
u64 id{};
std::vector<u8> icon;
NacpLanguageEntry lang{};
NacpLoadStatus status{NacpLoadStatus::None};
};
using MetaEntries = std::vector<NsApplicationContentMetaStatus>;
// starts background thread (ref counted).
Result Init();
// closes the background thread.
void Exit();
// clears cache and empties the result array.
void Clear();
// adds new entry to queue.
void PushAsync(u64 app_id);
void PushAsync(const std::span<const NsApplicationRecord> app_ids);
// gets entry without removing it from the queue.
auto GetAsync(u64 app_id) -> ThreadResultData*;
// single threaded title info fetch.
auto Get(u64 app_id, bool* cached = nullptr) -> ThreadResultData*;
auto GetNcmCs(u8 storage_id) -> NcmContentStorage&;
auto GetNcmDb(u8 storage_id) -> NcmContentMetaDatabase&;
// gets all meta entries for an id.
Result GetMetaEntries(u64 id, MetaEntries& out, u32 flags = ContentFlag_All);
// returns the nca path of a control nca.
Result GetControlPathFromStatus(const NsApplicationContentMetaStatus& status, u64* out_program_id, fs::FsPath* out_path);
// taken from nxdumptool.
void utilsReplaceIllegalCharacters(char *str, bool ascii_only);
// /atmosphere/contents/xxx
auto GetContentsPath(u64 app_id) -> fs::FsPath;
} // namespace sphaira::title

View File

@@ -16,6 +16,8 @@ public:
private:
std::optional<Result> m_code{};
std::string m_message{};
std::string m_code_message{};
std::string m_code_module{};
};
} // namespace sphaira::ui

View File

@@ -10,14 +10,14 @@ struct List final : Object {
GRID,
};
using Callback = std::function<void(NVGcontext* vg, Theme* theme, Vec4 v, s64 index)>;
using Callback = std::function<void(NVGcontext* vg, Theme* theme, const Vec4& v, 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 index, s64 count, TouchCallback callback);
void OnUpdate(Controller* controller, TouchInfo* touch, s64 index, s64 count, const TouchCallback& callback);
void Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
void Draw(NVGcontext* vg, Theme* theme, s64 count, const Callback& callback) const;
auto SetScrollBarPos(float x, float y, float h) {
m_scrollbar.x = x;
@@ -73,10 +73,10 @@ private:
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;
void OnUpdateHome(Controller* controller, TouchInfo* touch, s64 index, s64 count, const TouchCallback& callback);
void OnUpdateGrid(Controller* controller, TouchInfo* touch, s64 index, s64 count, const TouchCallback& callback);
void DrawHome(NVGcontext* vg, Theme* theme, s64 count, const Callback& callback) const;
void DrawGrid(NVGcontext* vg, Theme* theme, s64 count, const Callback& callback) const;
private:
const s64 m_row;

View File

@@ -86,14 +86,16 @@ struct EntryMenu final : MenuBase {
private:
struct Option {
Option(const std::string& dt, const std::string& ct, std::function<void(void)> f)
using Callback = std::function<void(void)>;
Option(const std::string& dt, const std::string& ct, const Callback& f)
: display_text{dt}, confirm_text{ct}, func{f} {}
Option(const std::string& dt, std::function<void(void)> f)
Option(const std::string& dt, const Callback& f)
: display_text{dt}, func{f} {}
std::string display_text{};
std::string confirm_text{};
std::function<void(void)> func{};
const std::string display_text;
const std::string confirm_text;
const Callback func{};
};
Entry& m_entry;
@@ -105,11 +107,14 @@ private:
LazyImage m_banner{};
std::unique_ptr<List> m_list{};
std::shared_ptr<ScrollableText> m_details{};
std::shared_ptr<ScrollableText> m_changelog{};
std::shared_ptr<ScrollableText> m_detail_changelog{};
std::unique_ptr<ScrollableText> m_details{};
std::unique_ptr<ScrollableText> m_changelog{};
ScrollableText* m_detail_changelog{};
std::unique_ptr<ScrollableText> m_manifest_list{};
bool m_show_changlog{};
bool m_show_file_list{};
ImageDownloadState m_file_list_state{};
};
enum Filter {

View File

@@ -0,0 +1,19 @@
#pragma once
#include "ui/menus/filebrowser.hpp"
namespace sphaira::ui::menu::filebrowser::picker {
using Callback = std::function<bool(const fs::FsPath& path)>;
struct Menu final : Base {
explicit Menu(const Callback& cb, const std::vector<std::string>& filter = {}, const fs::FsPath& path = {});
private:
void OnClick(FsView* view, const FsEntry& fs_entry, const FileEntry& entry, const fs::FsPath& path) override;
private:
const Callback m_callback;
};
} // namespace sphaira::ui::menu::filebrowser::picker

View File

@@ -7,7 +7,7 @@
namespace sphaira::ui::menu::fileview {
struct Menu final : MenuBase {
Menu(const fs::FsPath& path);
Menu(fs::Fs* fs, const fs::FsPath& path);
auto GetShortTitle() const -> const char* override { return "File"; };
void Update(Controller* controller, TouchInfo* touch) override;
@@ -15,8 +15,8 @@ struct Menu final : MenuBase {
void OnFocusGained() override;
private:
fs::Fs* const m_fs;
const fs::FsPath m_path;
fs::FsNativeSd m_fs{};
fs::File m_file{};
s64 m_file_size{};
s64 m_file_offset{};

View File

@@ -2,21 +2,48 @@
#include "ui/menus/menu_base.hpp"
#include "ui/scrolling_text.hpp"
#include "ui/progress_box.hpp"
#include "ui/list.hpp"
#include "fs.hpp"
#include "option.hpp"
#include "hasher.hpp"
// #include <optional>
#include "nro.hpp"
#include <span>
namespace sphaira::ui::menu::filebrowser {
enum FsOption : u32 {
FsOption_NONE,
// can split screen.
FsOption_CanSplit = BIT(0),
// can selected multiple files.
FsOption_CanSelect = BIT(1),
// shows the option to install.
FsOption_CanInstall = BIT(2),
// loads file assoc.
FsOption_LoadAssoc = BIT(3),
// do not prompt on exit even if not tabbed.
FsOption_DoNotPrompt = BIT(4),
FsOption_Normal = FsOption_LoadAssoc | FsOption_CanInstall | FsOption_CanSplit | FsOption_CanSelect,
FsOption_All = FsOption_DoNotPrompt | FsOption_Normal,
FsOption_Picker = FsOption_NONE,
};
enum FsEntryFlag {
FsEntryFlag_None,
// write protected.
FsEntryFlag_ReadOnly = 1 << 0,
// supports file assoc.
FsEntryFlag_Assoc = 1 << 1,
// this is an sd card, files can be launched from here.
FsEntryFlag_IsSd = 1 << 2, // todo: remove this.
// do not stat files in this entry (faster for network mount).
FsEntryFlag_NoStatFile = 1 << 3,
FsEntryFlag_NoStatDir = 1 << 4,
FsEntryFlag_NoRandomReads = 1 << 5,
FsEntryFlag_NoRandomWrites = 1 << 6,
};
enum class FsType {
@@ -24,6 +51,7 @@ enum class FsType {
ImageNand,
ImageSd,
Stdio,
Custom,
};
enum class SelectedType {
@@ -62,13 +90,33 @@ struct FsEntry {
return flags & FsEntryFlag_Assoc;
}
auto IsSd() const -> bool {
return flags & FsEntryFlag_IsSd;
}
auto IsNoStatFile() const -> bool {
return flags & FsEntryFlag_NoStatFile;
}
auto IsNoStatDir() const -> bool {
return flags & FsEntryFlag_NoStatDir;
}
auto IsNoRandomReads() const -> bool {
return flags & FsEntryFlag_NoRandomReads;
}
auto IsNoRandomWrites() const -> bool {
return flags & FsEntryFlag_NoRandomWrites;
}
auto IsSame(const FsEntry& e) const {
return root == e.root && type == e.type;
}
};
// roughly 1kib in size per entry
struct FileEntry : FsDirectoryEntry {
struct FileEntry final : FsDirectoryEntry {
std::string extension{}; // if any
std::string internal_name{}; // if any
std::string internal_extension{}; // if any
@@ -78,6 +126,7 @@ struct FileEntry : FsDirectoryEntry {
bool checked_extension{}; // did we already search for an ext?
bool checked_internal_extension{}; // did we already search for an ext?
bool selected{}; // is this file selected?
bool done_stat{}; // have we checked file_size / count.
auto IsFile() const -> bool {
return type == FsDirEntryType_File;
@@ -96,6 +145,11 @@ struct FileEntry : FsDirectoryEntry {
}
auto GetExtension() const -> std::string {
if (!checked_extension) {
if (auto ext = std::strrchr(name, '.')) {
return ext+1;
}
}
return extension;
}
@@ -154,13 +208,16 @@ struct FsDirCollection {
using FsDirCollections = std::vector<FsDirCollection>;
struct Menu;
void SignalChange();
struct Base;
struct FsView final : Widget {
friend class Menu;
friend class Base;
FsView(Menu* menu, ViewSide side);
FsView(Menu* menu, const fs::FsPath& path, const FsEntry& entry, ViewSide side);
FsView(FsView* view, ViewSide side);
FsView(Base* menu, ViewSide side);
FsView(Base* menu, const std::shared_ptr<fs::Fs>& fs, const fs::FsPath& path, const FsEntry& entry, ViewSide side);
~FsView();
void Update(Controller* controller, TouchInfo* touch) override;
@@ -181,7 +238,13 @@ struct FsView final : Widget {
void SetSide(ViewSide side);
private:
static Result DeleteAllCollections(ProgressBox* pbox, fs::Fs* fs, const FsDirCollections& collections, u32 mode = FsDirOpenMode_ReadDirs|FsDirOpenMode_ReadFiles);
static auto get_collection(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
static auto get_collections(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
// private:
void OnClick();
void SetIndex(s64 index);
void InstallForwarder();
@@ -190,7 +253,7 @@ private:
void ZipFiles(fs::FsPath zip_path);
void UploadFiles();
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
auto Scan(fs::FsPath new_path, bool is_walk_up = false) -> Result;
auto GetNewPath(const FileEntry& entry) const -> fs::FsPath {
return GetNewPath(m_path, entry.name);
@@ -237,11 +300,11 @@ private:
}
auto IsSd() const -> bool {
return m_fs_entry.type == FsType::Sd;
return m_fs_entry.IsSd();
}
void Sort();
void SortAndFindLastFile();
void SortAndFindLastFile(bool scan = false);
void SetIndexFromLastFile(const LastFile& last_file);
void OnDeleteCallback();
@@ -249,13 +312,10 @@ private:
void OnRenameCallback();
auto CheckIfUpdateFolder() -> Result;
static auto get_collection(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
static auto get_collections(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
auto get_collection(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
auto get_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);
void SetFs(const std::shared_ptr<fs::Fs>& fs, const fs::FsPath& new_path, const FsEntry& new_entry);
auto GetNative() -> fs::FsNative* {
return (fs::FsNative*)m_fs.get();
@@ -266,11 +326,17 @@ private:
void DisplayOptions();
void DisplayAdvancedOptions();
private:
Menu* m_menu{};
using MountFsFunc = Result(*)(fs::Fs *fs, const fs::FsPath &path, fs::FsPath &out_path);
// using MountFsFunc = std::function<Result(fs::Fs *fs, const fs::FsPath &path, fs::FsPath &out_path)>;
using UmountFsFunc = std::function<void(const fs::FsPath &mount)>;
void MountFileFs(const MountFsFunc& mount_func, const UmountFsFunc& umount_func);
// private:
Base* m_menu{};
ViewSide m_side{};
std::unique_ptr<fs::Fs> m_fs{};
std::shared_ptr<fs::Fs> m_fs{};
FsEntry m_fs_entry{};
fs::FsPath m_path{};
std::vector<FileEntry> m_entries{};
@@ -296,7 +362,7 @@ private:
// 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) {
void Add(FsView* view, SelectedType type, const std::vector<FileEntry>& files, const fs::FsPath& path) {
if (files.empty()) {
Reset();
} else {
@@ -331,28 +397,34 @@ struct SelectedStash {
}
// private:
std::shared_ptr<FsView> m_view{};
FsView* m_view{};
std::vector<FileEntry> m_files{};
fs::FsPath m_path{};
SelectedType m_type{SelectedType::None};
};
struct Menu final : MenuBase {
struct Base : MenuBase {
friend class FsView;
Menu(u32 flags);
~Menu();
Base(u32 flags, u32 options);
Base(const std::shared_ptr<fs::Fs>& fs, const FsEntry& fs_entry, const fs::FsPath& path, bool is_custom, u32 flags, u32 options);
void SetFilter(const std::vector<std::string>& filter) {
m_filter = filter;
}
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;
virtual 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:
virtual void OnClick(FsView* view, const FsEntry& fs_entry, const FileEntry& entry, const fs::FsPath& path);
protected:
auto IsSplitScreen() const {
return m_split_screen;
}
@@ -382,23 +454,40 @@ private:
void PromptIfShouldExit();
auto CanInstall() const {
return m_options & FsOption_CanInstall;
}
auto CreateFs(const FsEntry& fs_entry) -> std::shared_ptr<fs::Fs>;
private:
void Init(const std::shared_ptr<fs::Fs>& fs, const FsEntry& fs_entry, const fs::FsPath& path, bool is_custom);
protected:
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{};
const u32 m_options;
std::shared_ptr<fs::Fs> m_custom_fs{};
FsEntry m_custom_fs_entry{};
FsView* view{};
std::unique_ptr<FsView> view_left{};
std::unique_ptr<FsView> view_right{};
std::vector<FileAssocEntry> m_assoc_entries{};
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{};
s64 m_index{};
s64 m_selected_count{};
std::vector<std::string> m_filter{};
// local copy of nro entries that is loaded in LoadAssocEntriesPath()
// if homebrew::GetNroEntries() returns nothing, usually due to
// the menu not being loaded.
// this is a bit of a hack to support replacing the homebrew menu tab,
// sphaira wasn't really designed for this.
// however this will work for now, until i add support for additional
// nro scan mounts, at which point this won't scale.
std::vector<NroEntry> m_nro_entries{};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
@@ -408,8 +497,39 @@ private:
option::OptionBool m_ignore_read_only{INI_SECTION, "ignore_read_only", false};
bool m_loaded_assoc_entries{};
bool m_is_update_folder{};
bool m_split_screen{};
};
struct Menu final : Base {
Menu(u32 flags, u32 options = FsOption_All) : Base{flags, options} {
}
Menu(const std::shared_ptr<fs::Fs>& fs, const FsEntry& fs_entry, const fs::FsPath& path, u32 options = FsOption_All)
: Base{fs, fs_entry, path, true, MenuFlag_None, options} {
}
};
// case insensitive check
auto IsSamePath(std::string_view a, std::string_view b) -> bool;
auto IsExtension(std::string_view ext1, std::string_view ext2) -> bool;
auto IsExtension(std::string_view ext, std::span<const std::string_view> list) -> bool;
struct FsStdioWrapper final : fs::FsStdio {
using OnExit = std::function<void(void)>;
FsStdioWrapper(const fs::FsPath& root, const OnExit& on_exit) : fs::FsStdio{true, root}, m_on_exit{on_exit} {
}
~FsStdioWrapper() {
if (m_on_exit) {
m_on_exit();
}
}
const OnExit m_on_exit;
};
void MountFsHelper(const std::shared_ptr<fs::Fs>& fs, const fs::FsPath& name);
} // namespace sphaira::ui::menu::filebrowser

View File

@@ -1,56 +1,20 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "yati/source/stream.hpp"
#include "ui/menus/install_stream_menu_base.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 {
struct Menu final : stream::Menu {
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};
void OnDisableInstallMode() override;
private:
const char* m_user{};
const char* m_pass{};
unsigned m_port{};

View File

@@ -2,34 +2,26 @@
#include "ui/menus/grid_menu_base.hpp"
#include "ui/list.hpp"
#include "yati/container/base.hpp"
#include "yati/nx/keys.hpp"
#include "title_info.hpp"
#include "fs.hpp"
#include "option.hpp"
#include <memory>
#include <vector>
#include <span>
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]{};
u8 last_event{};
NacpLanguageEntry lang{};
int image{};
bool selected{};
std::shared_ptr<NsApplicationControlData> control{};
u64 control_size{};
NacpLoadStatus status{NacpLoadStatus::None};
title::NacpLoadStatus status{title::NacpLoadStatus::None};
auto GetName() const -> const char* {
return lang.name;
@@ -38,42 +30,6 @@ struct Entry {
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 {
@@ -87,6 +43,8 @@ enum OrderType {
using LayoutType = grid::LayoutType;
void SignalChange();
struct Menu final : grid::Menu {
Menu(u32 flags);
~Menu();
@@ -128,7 +86,9 @@ private:
}
void DeleteGames();
void DumpGames(u32 flags);
void ExportOptions(bool to_nsz);
void DumpGames(u32 flags, bool to_nsz);
void CreateSaves(AccountUid uid);
private:
static constexpr inline const char* INI_SECTION = "games";
@@ -141,13 +101,79 @@ private:
bool m_is_reversed{};
bool m_dirty{};
ThreadData m_thread_data{};
Thread m_thread{};
// use for detection game card removal to force a refresh.
Event m_gc_event{};
FsEventNotifier m_gc_event_notifier{};
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::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_Grid};
option::OptionBool m_hide_forwarders{INI_SECTION, "hide_forwarders", false};
};
struct NcmMetaData {
// points to global service, do not close manually!
NcmContentStorage* cs{};
NcmContentMetaDatabase* db{};
u64 app_id{};
NcmContentMetaKey key{};
};
Result GetMetaEntries(const Entry& e, title::MetaEntries& out, u32 flags = title::ContentFlag_All);
Result GetNcmMetaFromMetaStatus(const NsApplicationContentMetaStatus& status, NcmMetaData& out);
void DeleteMetaEntries(u64 app_id, int image, const std::string& name, const title::MetaEntries& entries);
struct TikEntry {
FsRightsId id{};
u8 key_gen{};
std::vector<u8> tik_data{};
std::vector<u8> cert_data{};
};
struct NspEntry {
// application name.
std::string application_name{};
// name of the nsp (name [id][v0][BASE].nsp).
fs::FsPath path{};
// tickets and cert data, will be empty if title key crypto isn't used.
std::vector<TikEntry> tickets{};
// all the collections for this nsp, such as nca's and tickets.
std::vector<yati::container::CollectionEntry> collections{};
// raw nsp data (header, file table and string table).
std::vector<u8> nsp_data{};
// size of the entier nsp.
s64 nsp_size{};
// copy of ncm cs, it is not closed.
NcmContentStorage cs{};
// copy of the icon, if invalid, it will use the default icon.
int icon{};
Result Read(void* buf, s64 off, s64 size, u64* bytes_read);
private:
static auto InRange(s64 off, s64 offset, s64 size) -> bool {
return off < offset + size && off >= offset;
}
static auto ClipSize(s64 off, s64 size, s64 file_size) -> s64 {
return std::min(size, file_size - off);
}
};
struct ContentInfoEntry {
NsApplicationContentMetaStatus status{};
std::vector<NcmContentInfo> content_infos{};
std::vector<NcmRightsId> ncm_rights_id{};
};
auto BuildNspPath(const Entry& e, const NsApplicationContentMetaStatus& status, bool to_nsz = false) -> fs::FsPath;
Result BuildContentEntry(const NsApplicationContentMetaStatus& status, ContentInfoEntry& out, bool to_nsz = false);
Result BuildNspEntry(const Entry& e, const ContentInfoEntry& info, const keys::Keys& keys, NspEntry& out, bool to_nsz = false);
Result BuildNspEntries(Entry& e, const title::MetaEntries& meta_entries, std::vector<NspEntry>& out, bool to_nsz = false);
Result BuildNspEntries(Entry& e, u32 flags, std::vector<NspEntry>& out, bool to_nsz = false);
// dumps the array of nsp entries.
void DumpNsp(const std::vector<NspEntry>& entries, bool to_nsz);
} // namespace sphaira::ui::menu::game

View File

@@ -0,0 +1,111 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "ui/menus/game_menu.hpp"
#include "ui/list.hpp"
#include "yati/nx/ncm.hpp"
#include <span>
#include <memory>
namespace sphaira::ui::menu::game::meta {
enum TicketType : u8 {
TicketType_None,
TicketType_Common,
TicketType_Personalised,
TicketType_Missing,
};
struct MiniNacp {
char display_version[0x10];
};
struct MetaEntry {
NsApplicationContentMetaStatus status{};
ncm::ContentMeta content_meta{};
// small version of nacp to speed up loading.
MiniNacp nacp{};
// total size of all ncas.
s64 size{};
// set to the key gen (if possible), only if title key encrypted.
u8 key_gen{};
// set to the ticket type.
u8 ticket_type{TicketType_None};
// set if it has missing ncas.
u8 missing_count{};
// set if selected.
bool selected{};
// set if we have checked the above meta data.
bool checked{};
};
struct Menu final : MenuBase {
Menu(Entry& entry);
~Menu();
auto GetShortTitle() const -> const char* override { return "Meta"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
private:
void SetIndex(s64 index);
void Scan();
void UpdateSubheading();
auto GetSelectedEntries() const {
title::MetaEntries out;
for (auto& e : m_entries) {
if (e.selected) {
out.emplace_back(e.status);
}
}
if (!m_entries.empty() && out.empty()) {
out.emplace_back(m_entries[m_index].status);
}
return out;
}
void ClearSelection() {
for (auto& e : m_entries) {
e.selected = false;
}
m_selected_count = 0;
}
auto GetEntry(u32 index) -> MetaEntry& {
return m_entries[index];
}
auto GetEntry(u32 index) const -> const MetaEntry& {
return m_entries[index];
}
auto GetEntry() -> MetaEntry& {
return GetEntry(m_index);
}
auto GetEntry() const -> const MetaEntry& {
return GetEntry(m_index);
}
void DumpGames(bool to_nsz);
void DeleteGames();
Result ResetRequiredSystemVersion(MetaEntry& entry) const;
Result GetNcmSizeOfMetaStatus(MetaEntry& entry) const;
private:
Entry& m_entry;
std::vector<MetaEntry> m_entries{};
s64 m_index{};
s64 m_selected_count{};
std::unique_ptr<List> m_list{};
bool m_dirty{};
std::vector<FsRightsId> m_common_tickets{};
std::vector<FsRightsId> m_personalised_tickets{};
};
} // namespace sphaira::ui::menu::game::meta

View File

@@ -0,0 +1,92 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "ui/menus/game_meta_menu.hpp"
#include "ui/list.hpp"
#include "yati/nx/nca.hpp"
#include "yati/nx/ncm.hpp"
#include <span>
#include <memory>
namespace sphaira::ui::menu::game::meta_nca {
struct NcaEntry {
NcmContentId content_id{};
u64 size{};
u8 content_type{};
// decrypted nca header.
nca::Header header{};
// set if missing.
bool missing{};
// set if selected.
bool selected{};
// set if we have checked the above meta data.
bool checked{};
};
struct Menu final : MenuBase {
Menu(Entry& entry, const meta::MetaEntry& meta_entry);
~Menu();
auto GetShortTitle() const -> const char* override { return "Nca"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
private:
void SetIndex(s64 index);
void Scan();
void UpdateSubheading();
auto GetSelectedEntries() const {
std::vector<NcaEntry> out;
for (auto& e : m_entries) {
if (e.selected && !e.missing) {
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;
}
auto GetEntry(u32 index) -> NcaEntry& {
return m_entries[index];
}
auto GetEntry(u32 index) const -> const NcaEntry& {
return m_entries[index];
}
auto GetEntry() -> NcaEntry& {
return GetEntry(m_index);
}
auto GetEntry() const -> const NcaEntry& {
return GetEntry(m_index);
}
void DumpNcas();
Result MountNcaFs();
private:
Entry& m_entry;
const meta::MetaEntry& m_meta_entry;
NcmMetaData m_meta{};
std::vector<NcaEntry> m_entries{};
s64 m_index{};
s64 m_selected_count{};
std::unique_ptr<List> m_list{};
};
} // namespace sphaira::ui::menu::game::meta_nca

View File

@@ -7,7 +7,8 @@
#include <span>
#include <memory>
namespace sphaira::ui::menu::gc {
// todo: pr to libnx
extern "C" {
typedef enum {
FsGameCardPartitionRaw_None = -1,
@@ -15,6 +16,13 @@ typedef enum {
FsGameCardPartitionRaw_Secure = 1,
} FsGameCardPartitionRaw;
Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, FsGameCardPartitionRaw partition);
Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier* out);
}
namespace sphaira::ui::menu::gc {
////////////////////////////////////////////////
// The below structs are taken from nxdumptool./
////////////////////////////////////////////////
@@ -88,6 +96,33 @@ typedef struct {
static_assert(sizeof(GameCardInitialData) == 0x200);
/// Encrypted using AES-128-CTR with the key and IV/counter from the `GameCardTitleKeyAreaEncryption` section. Assumed to be all zeroes in retail gamecards.
typedef struct {
u8 titlekey[0x10]; ///< Decrypted titlekey from the `GameCardInitialData` section.
u8 reserved[0xCF0];
} GameCardTitleKeyArea;
static_assert(sizeof(GameCardTitleKeyArea) == 0xD00);
/// Encrypted using RSA-2048-OAEP and a private OAEP key from AuthoringTool. Assumed to be all zeroes in retail gamecards.
typedef struct {
u8 titlekey_encryption_key[0x10]; ///< Used as the AES-128-CTR key for the `GameCardTitleKeyArea` section. Randomly generated during XCI creation by AuthoringTool.
u8 titlekey_encryption_iv[0x10]; ///< Used as the AES-128-CTR IV/counter for the `GameCardTitleKeyArea` section. Randomly generated during XCI creation by AuthoringTool.
u8 reserved[0xE0];
} GameCardTitleKeyAreaEncryption;
static_assert(sizeof(GameCardTitleKeyAreaEncryption) == 0x100);
/// Used to secure communications between the Lotus and the inserted gamecard.
/// Supposedly precedes the gamecard header.
typedef struct {
GameCardInitialData initial_data;
GameCardTitleKeyArea titlekey_area;
GameCardTitleKeyAreaEncryption titlekey_area_encryption;
} GameCardKeyArea;
static_assert(sizeof(GameCardKeyArea) == 0x1000);
typedef struct {
u8 maker_code; ///< GameCardUidMakerCode.
u8 version; ///< TODO: determine whether this matches GameCardVersion or not.
@@ -151,8 +186,7 @@ struct ApplicationEntry {
u64 app_id{};
u32 version{};
u8 key_gen{};
std::unique_ptr<NsApplicationControlData> control{};
u64 control_size{};
std::vector<u8> icon;
NacpLanguageEntry lang_entry{};
std::vector<GcCollections> application{};
@@ -199,6 +233,9 @@ private:
void FreeImage();
void OnChangeIndex(s64 new_index);
Result DumpGames(u32 flags);
Result DumpXcz(u32 flags);
Result MountGcFs();
private:
FsDeviceOperator m_dev_op{};
@@ -221,12 +258,12 @@ private:
FsStorage m_storage{};
// size of normal partition.
s64 m_parition_normal_size{};
s64 m_partition_normal_size{};
// size of secure partition.
s64 m_parition_secure_size{};
s64 m_partition_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.
// total size of m_partition_normal_size + m_partition_secure_size.
s64 m_storage_total_size{};
// reported size via rom_size in the xci header.
s64 m_storage_full_size{};

View File

@@ -32,12 +32,15 @@ struct GhApiAsset {
std::string content_type{};
u64 size{};
u64 download_count{};
std::string updated_at{};
std::string browser_download_url{};
};
struct GhApiEntry {
std::string tag_name{};
std::string name{};
std::string published_at{};
bool prerelease{};
std::vector<GhApiAsset> assets{};
};
@@ -72,4 +75,18 @@ private:
std::unique_ptr<List> m_list{};
};
// creates a popup box on another thread.
void DownloadEntries(const Entry& entry);
// parses the params into entry struct and calls DonwloadEntries
bool Download(const std::string& url, const std::vector<AssetEntry>& assets = {}, const std::string& tag = {}, const std::string& pre_install_message = {}, const std::string& post_install_message = {});
// calls the above function by pushing the asset to an array.
inline bool Download(const std::string& url, const AssetEntry& asset, const std::string& tag = {}, const std::string& pre_install_message = {}, const std::string& post_install_message = {}) {
std::vector<AssetEntry> assets;
assets.emplace_back(asset);
return Download(url, assets, tag, pre_install_message, post_install_message);
}
} // namespace sphaira::ui::menu::gh

View File

@@ -8,6 +8,12 @@
namespace sphaira::ui::menu::homebrew {
enum Filter {
Filter_All,
Filter_HideHidden,
Filter_MAX,
};
enum SortType {
SortType_Updated,
SortType_Alphabetical,
@@ -25,9 +31,10 @@ enum OrderType {
using LayoutType = grid::LayoutType;
auto GetNroEntries() -> std::span<const NroEntry>;
void SignalChange();
struct Menu final : grid::Menu {
Menu();
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "Apps"; };
@@ -42,30 +49,45 @@ struct Menu final : grid::Menu {
static Result InstallHomebrew(const fs::FsPath& path, const std::vector<u8>& icon);
static Result InstallHomebrewFromPath(const fs::FsPath& path);
auto GetEntry(s64 i) -> NroEntry& {
return m_entries[m_entries_current[i]];
}
auto GetEntry() -> NroEntry& {
return GetEntry(m_index);
}
private:
void SetIndex(s64 index);
void InstallHomebrew();
void ScanHomebrew();
void Sort();
void SortAndFindLastFile();
void SortAndFindLastFile(bool scan = false);
void FreeEntries();
void OnLayoutChange();
void DisplayOptions();
auto IsStarEnabled() -> bool {
return m_sort.Get() >= SortType_UpdatedStar;
}
Result MountNroFs();
private:
static constexpr inline const char* INI_SECTION = "homebrew";
std::vector<NroEntry> m_entries{};
std::vector<u32> m_entries_index[Filter_MAX]{};
std::span<u32> m_entries_current{};
s64 m_index{}; // where i am in the array
std::unique_ptr<List> m_list{};
bool m_dirty{};
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};
option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false};
};
} // namespace sphaira::ui::menu::homebrew

View File

@@ -0,0 +1,36 @@
#pragma once
#include "ui/widget.hpp"
#include "fs.hpp"
#include <vector>
namespace sphaira::ui::menu::imageview {
struct Menu final : Widget {
Menu(fs::Fs* fs, const fs::FsPath& path);
~Menu();
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
auto IsMenu() const -> bool override {
return true;
}
void UpdateSize();
private:
const fs::FsPath m_path;
int m_image{};
float m_image_width{};
float m_image_height{};
// for zoom, 0.1 - 1.0
float m_zoom{1};
// for pan.
float m_xoff{};
float m_yoff{};
};
} // namespace sphaira::ui::menu::imageview

View File

@@ -0,0 +1,65 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "yati/source/stream.hpp"
namespace sphaira::ui::menu::stream {
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,
};
using OnInstallStart = std::function<bool(const char* path)>;
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
using OnInstallClose = std::function<void()>;
struct Stream final : yati::source::Stream {
Stream(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();
auto& GetPath() const { return m_path; }
private:
fs::FsPath m_path{};
std::stop_token m_token{};
std::vector<u8> m_buffer{};
CondVar m_can_read{};
CondVar m_can_write{};
public:
Mutex m_mutex{};
std::atomic_bool m_active{};
};
struct Menu : MenuBase {
Menu(const std::string& title, u32 flags);
virtual ~Menu();
virtual void Update(Controller* controller, TouchInfo* touch);
virtual void Draw(NVGcontext* vg, Theme* theme);
virtual void OnDisableInstallMode() = 0;
protected:
bool OnInstallStart(const char* path);
bool OnInstallWrite(const void* buf, size_t size);
void OnInstallClose();
private:
std::unique_ptr<Stream> m_source{};
Thread m_thread{};
Mutex m_mutex{};
State m_state{State::None};
};
} // namespace sphaira::ui::menu::stream

View File

@@ -17,7 +17,7 @@ enum class UpdateState {
Error,
};
using MiscMenuFunction = std::function<std::shared_ptr<ui::menu::MenuBase>(u32 flags)>;
using MiscMenuFunction = std::function<std::unique_ptr<MenuBase>(u32 flags)>;
enum MiscMenuFlag : u8 {
// can be set as the rightside menu.
@@ -31,6 +31,7 @@ struct MiscMenuEntry {
const char* title;
MiscMenuFunction func;
u8 flag;
const char* info;
auto IsShortcut() const -> bool {
return flag & MiscMenuFlag_Shortcut;
@@ -41,7 +42,7 @@ struct MiscMenuEntry {
}
};
auto GetMiscMenuEntries() -> std::span<const MiscMenuEntry>;
auto GetMenuMenuEntries() -> std::span<const MiscMenuEntry>;
// this holds 2 menus and allows for switching between them
struct MainMenu final : Widget {
@@ -58,14 +59,14 @@ struct MainMenu final : Widget {
}
private:
void OnLRPress(std::shared_ptr<MenuBase> menu, Button b);
void OnLRPress(MenuBase* menu, Button b);
void AddOnLRPress();
private:
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::unique_ptr<MenuBase> m_centre_menu{};
std::unique_ptr<MenuBase> m_left_menu{};
std::unique_ptr<MenuBase> m_right_menu{};
MenuBase* m_current_menu{};
std::string m_update_url{};
std::string m_update_version{};

View File

@@ -13,12 +13,14 @@ enum MenuFlag {
struct PolledData {
struct tm tm{};
u32 battery_percetange{};
PsmChargerType charger_type{};
NifmInternetConnectionType type{};
NifmInternetConnectionStatus status{};
u32 strength{};
u32 ip{};
s64 sd_free{1};
s64 sd_total{1};
s64 emmc_free{1};
s64 emmc_total{1};
};
struct MenuBase : Widget {
@@ -33,9 +35,17 @@ struct MenuBase : Widget {
return true;
}
void SetTitle(std::string title);
void SetTitleSubHeading(std::string sub_heading);
void SetSubHeading(std::string sub_heading);
void SetTitle(const std::string& title) {
m_title = title;
}
void SetTitleSubHeading(const std::string& sub_heading) {
m_title_sub_heading = sub_heading;
}
void SetSubHeading(const std::string& sub_heading) {
m_sub_heading = sub_heading;
}
auto GetTitle() const {
return m_title;

View File

@@ -0,0 +1,19 @@
#pragma once
#include "ui/menus/install_stream_menu_base.hpp"
namespace sphaira::ui::menu::mtp {
struct Menu final : stream::Menu {
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "MTP"; };
void Update(Controller* controller, TouchInfo* touch) override;
void OnDisableInstallMode() override;
private:
bool m_was_mtp_enabled{};
};
} // namespace sphaira::ui::menu::mtp

View File

@@ -0,0 +1,124 @@
#pragma once
#include "ui/menus/grid_menu_base.hpp"
#include "ui/list.hpp"
#include "title_info.hpp"
#include "fs.hpp"
#include "option.hpp"
#include "dumper.hpp"
#include <memory>
#include <vector>
#include <span>
namespace sphaira::ui::menu::save {
enum BackupFlag {
BackupFlag_None = 0,
// option to allow the user to set the save file name.
BackupFlag_SetName = 1 << 0,
// set if this is a auto backup (on restore).
BackupFlag_IsAuto = 1 << 1,
};
struct Entry final : FsSaveDataInfo {
NacpLanguageEntry lang{};
int image{};
bool selected{};
title::NacpLoadStatus status{title::NacpLoadStatus::None};
auto GetName() const -> const char* {
return lang.name;
}
auto GetAuthor() const -> const char* {
return lang.author;
}
};
enum SortType {
SortType_Updated,
};
enum OrderType {
OrderType_Descending,
OrderType_Ascending,
};
using LayoutType = grid::LayoutType;
void SignalChange();
struct Menu final : grid::Menu {
Menu(u32 flags);
~Menu();
auto GetShortTitle() const -> const char* override { return "Saves"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
private:
void SetIndex(s64 index);
void ScanHomebrew();
void Sort();
void SortAndFindLastFile(bool scan);
void FreeEntries();
void OnLayoutChange();
auto GetSelectedEntries() const {
std::vector<Entry> out;
for (auto& e : m_entries) {
if (e.selected) {
out.emplace_back(e);
}
}
if (!m_entries.empty() && out.empty()) {
out.emplace_back(m_entries[m_index]);
}
return out;
}
void ClearSelection() {
for (auto& e : m_entries) {
e.selected = false;
}
m_selected_count = 0;
}
void DisplayOptions();
void BackupSaves(std::vector<std::reference_wrapper<Entry>>& entries, u32 flags);
void RestoreSave();
auto BuildSavePath(const Entry& e, u32 flags) const -> fs::FsPath;
Result RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::FsPath& path);
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, Entry& e, u32 flags);
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, std::span<const std::reference_wrapper<Entry>> entries, u32 flags);
Result MountSaveFs();
private:
static constexpr inline const char* INI_SECTION = "saves";
std::vector<Entry> m_entries{};
s64 m_index{}; // where i am in the array
s64 m_selected_count{};
std::unique_ptr<List> m_list{};
bool m_is_reversed{};
bool m_dirty{};
std::vector<AccountProfileBase> m_accounts{};
s64 m_account_index{};
u8 m_data_type{FsSaveDataType_Account};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_Grid};
option::OptionBool m_auto_backup_on_restore{INI_SECTION, "auto_backup_on_restore", true};
option::OptionBool m_compress_save_backup{INI_SECTION, "compress_save_backup", true};
};
} // namespace sphaira::ui::menu::save

View File

@@ -1,3 +1,4 @@
#if 0
#pragma once
#include "ui/menus/menu_base.hpp"
@@ -104,7 +105,7 @@ struct Config {
u32 limit{18};
bool nsfw{false};
void SetQuery(std::string new_query) {
void SetQuery(const std::string& new_query) {
query = new_query;
}
@@ -112,7 +113,7 @@ struct Config {
query.clear();
}
void SetCreator(Creator new_creator) {
void SetCreator(const Creator& new_creator) {
creator = new_creator.id;
}
@@ -138,6 +139,7 @@ struct Menu final : MenuBase {
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
private:
void SetIndex(s64 index) {
m_index = index;
if (!m_index) {
@@ -147,7 +149,7 @@ struct Menu final : MenuBase {
void InvalidateAllPages();
void PackListDownload();
void OnPackListDownload();
void DisplayOptions();
private:
static constexpr inline const char* INI_SECTION = "themezer";
@@ -169,6 +171,9 @@ private:
option::OptionLong m_sort{INI_SECTION, "sort", 0};
option::OptionLong m_order{INI_SECTION, "order", 0};
option::OptionBool m_nsfw{INI_SECTION, "nsfw", false};
bool m_checked_for_nro{};
};
} // namespace sphaira::ui::menu::themezer
#endif

View File

@@ -27,19 +27,16 @@ struct Menu final : MenuBase {
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{};
void ThreadFunction();
private:
std::unique_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::atomic<State> m_state{State::None};
std::vector<std::string> m_names{};
bool m_usb_has_connection{};
};
} // namespace sphaira::ui::menu::usb

View File

@@ -0,0 +1,45 @@
#pragma once
#include "ui/widget.hpp"
#include "ui/scrolling_text.hpp"
#include "fs.hpp"
#include "utils/audio.hpp"
#include <memory>
namespace sphaira::ui::music {
struct Menu final : Widget {
Menu(fs::Fs* fs, const fs::FsPath& path);
~Menu();
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
private:
void PauseToggle();
void SeekForward();
void SeekBack();
void IncreaseVolume();
void DecreaseVolume();
private:
audio::SongID m_song{};
audio::Info m_info{};
audio::Meta m_meta{};
// only set if metadata was loaded.
int m_icon{};
ScrollingText m_scroll_title{};
ScrollingText m_scroll_artist{};
ScrollingText m_scroll_album{};
// from movienx
static constexpr Vec4 osd_progress_bar{400.f, 550, 1280.f - (400.f * 2.f), 10.f};
// static constexpr Vec4 osd_progress_bar{300.f, SCREEN_HEIGHT / 2 - 15 / 2, 1280.f - (300.f * 2.f), 10.f};
static constexpr Vec2 osd_time_text_left{osd_progress_bar.x - 12.f, osd_progress_bar.y - 2.f};
static constexpr Vec2 osd_time_text_right{osd_progress_bar.x + osd_progress_bar.w + 12.f, osd_progress_bar.y - 2.f};
static constexpr Vec4 osd_bar_outline{osd_time_text_left.x - 80, osd_progress_bar.y - 30, osd_progress_bar.w + 80 * 2 + 30, osd_progress_bar.h + 30 + 30};
};
} // namespace sphaira::ui::music

View File

@@ -11,8 +11,7 @@ public:
enum class Side { LEFT, RIGHT };
public:
NotifEntry(std::string text, Side side);
~NotifEntry() = default;
NotifEntry(const std::string& text, Side side);
auto Draw(NVGcontext* vg, Theme* theme, float y) -> bool;
auto GetSide() const noexcept { return m_side; }
@@ -22,17 +21,14 @@ private:
void Draw(NVGcontext* vg, Theme* theme) override;
private:
std::string m_text{};
std::string m_text;
Side m_side;
std::size_t m_count{180}; // count down to zero
Side m_side{};
bool m_bounds_measured{};
};
class NotifMananger final : public Object {
public:
NotifMananger() = default;
~NotifMananger() = default;
void Draw(NVGcontext* vg, Theme* theme) override;
void Push(const NotifEntry& entry);
@@ -45,6 +41,7 @@ private:
private:
void Draw(NVGcontext* vg, Theme* theme, Entries& entries);
auto GetEntries(NotifEntry::Side side) -> Entries&;
private:
Entries m_entries_left{};

View File

@@ -6,8 +6,8 @@
namespace sphaira::ui::gfx {
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 drawImage(NVGcontext*, float x, float y, float w, float h, int texture, float rounded = 0.F, float alpha = 1.0F);
void drawImage(NVGcontext*, const Vec4& v, int texture, float rounded = 0.F, float alpha = 1.0F);
void dimBackground(NVGcontext*);
@@ -30,7 +30,8 @@ void drawTextArgs(NVGcontext*, float x, float y, float size, int align, const NV
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void textBounds(NVGcontext*, float x, float y, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6)));
void textBounds(NVGcontext*, float x, float y, float *bounds, const char* str);
void textBoundsArgs(NVGcontext*, float x, float y, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6)));
auto getButton(Button button) -> const char*;
void drawScrollbar(NVGcontext*, const Theme*, u32 index_off, u32 count, u32 max_per_page);
@@ -41,6 +42,8 @@ void drawScrollbar2(NVGcontext*, const Theme*, s64 index_off, s64 count, s64 row
void drawAppLable(NVGcontext* vg, const Theme*, ScrollingText& st, float x, float y, float w, const char* name);
void drawSpinner(NVGcontext* vg, const Theme*, float cx, float cy, float r, float t);
void updateHighlightAnimation();
void getHighlightAnimation(float* gradientX, float* gradientY, float* color);

View File

@@ -54,7 +54,7 @@ public:
m_pos = { x, y, w, h };
}
auto SetPos(Vec4 v) noexcept -> void {
auto SetPos(const Vec4& v) noexcept -> void {
m_pos = v;
}

View File

@@ -9,7 +9,7 @@ class OptionBoxEntry final : public Widget {
public:
public:
OptionBoxEntry(const std::string& text, Vec4 pos);
OptionBoxEntry(const std::string& text, const Vec4& pos);
auto Update(Controller* controller, TouchInfo* touch) -> void override {}
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
@@ -32,9 +32,10 @@ public:
using Options = std::vector<Option>;
public:
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
OptionBox(const std::string& message, const Option& a, const Callback& cb = [](auto){}, int image = 0, bool own_image = false); // confirm
OptionBox(const std::string& message, const Option& a, const Option& b, const Callback& cb, int image = 0, bool own_image = false); // yesno
OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, const Callback& cb, int image = 0, bool own_image = false); // yesno
~OptionBox();
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
@@ -46,9 +47,10 @@ private:
void SetIndex(s64 index);
private:
std::string m_message{};
Callback m_callback{};
int m_image{};
const std::string m_message;
const Callback m_callback;
const int m_image;
const bool m_own_image;
Vec4 m_spacer_line{};

View File

@@ -13,11 +13,11 @@ public:
using Callback = std::function<void(std::optional<s64>)>;
public:
explicit PopupList(std::string title, Items items, Callback cb, s64 index = 0);
PopupList(std::string title, Items items, Callback cb, std::string index);
PopupList(std::string title, Items items, std::string& index_str_ref, s64& index);
PopupList(std::string title, Items items, std::string& index_ref);
PopupList(std::string title, Items items, s64& index_ref);
explicit PopupList(const std::string& title, const Items& items, const Callback& cb, s64 index = 0);
PopupList(const std::string& title, const Items& items, const Callback& cb, const std::string& index);
PopupList(const std::string& title, const Items& items, std::string& index_str_ref, s64& index);
PopupList(const std::string& title, const Items& items, std::string& index_ref);
PopupList(const std::string& title, const Items& items, s64& index_ref);
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
@@ -29,12 +29,12 @@ private:
private:
static constexpr Vec2 m_title_pos{70.f, 28.f};
static constexpr Vec4 m_block{280.f, 110.f, 720.f, 60.f};
static constexpr Vec4 m_block{280.f, 110.f, SCREEN_HEIGHT, 60.f};
static constexpr float m_text_xoffset{15.f};
static constexpr float m_line_width{1220.f};
std::string m_title{};
Items m_items{};
const std::string m_title;
const Items m_items;
Callback m_callback{};
s64 m_index{}; // index in list array
s64 m_starting_index{};

View File

@@ -11,22 +11,25 @@ namespace sphaira::ui {
struct ProgressBox;
using ProgressBoxCallback = std::function<Result(ProgressBox*)>;
using ProgressBoxDoneCallback = std::function<void(Result rc)>;
// using CancelCallback = std::function<void()>;
struct ProgressBox final : Widget {
ProgressBox(
int image,
const std::string& action,
const std::string& title,
ProgressBoxCallback callback, ProgressBoxDoneCallback done = [](Result rc){},
int cpuid = 1, int prio = PRIO_PREEMPTIVE, int stack_size = 1024*128
const ProgressBoxCallback& callback, const ProgressBoxDoneCallback& done = nullptr
);
~ProgressBox();
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto SetActionName(const std::string& action) -> ProgressBox&;
auto SetTitle(const std::string& title) -> ProgressBox&;
auto NewTransfer(const std::string& transfer) -> ProgressBox&;
// zeros the saved offset.
auto ResetTranfser() -> ProgressBox&;
auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;
// not const in order to avoid copy by using std::swap
auto SetImage(int image) -> ProgressBox&;
@@ -37,15 +40,14 @@ struct ProgressBox final : Widget {
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();
void AddCancelEvent(UEvent* event);
void RemoveCancelEvent(const UEvent* event);
auto GetCpuId() const {
return m_cpuid;
}
// helper functions
auto CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result;
auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result;
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result;
void Yield();
auto OnDownloadProgressCallback() {
return [this](s64 dltotal, s64 dlnow, s64 ultotal, s64 ulnow){
@@ -63,6 +65,11 @@ struct ProgressBox final : Widget {
};
}
// auto-clear = false
auto GetCancelEvent() {
return &m_uevent;
}
private:
void FreeImage();
@@ -74,10 +81,12 @@ public:
};
private:
UEvent m_uevent{};
Mutex m_mutex{};
Thread m_thread{};
ThreadData m_thread_data{};
ProgressBoxDoneCallback m_done{};
std::vector<UEvent*> m_cancel_events{};
// shared data start.
std::string m_action{};
@@ -96,7 +105,6 @@ private:
ScrollingText m_scroll_title{};
ScrollingText m_scroll_transfer{};
int m_cpuid{};
int m_image{};
bool m_own_image{};
};

View File

@@ -2,47 +2,136 @@
#include "ui/widget.hpp"
#include "ui/list.hpp"
#include "ui/scrolling_text.hpp"
#include "option.hpp"
#include <memory>
#include <concepts>
#include <utility>
#include <sys/syslimits.h>
namespace sphaira::ui {
class SidebarEntryBase : public Widget {
public:
SidebarEntryBase(std::string&& title);
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void override;
using DependsCallback = std::function<bool(void)>;
using DependsClickCallback = std::function<void(void)>;
public:
explicit SidebarEntryBase(const std::string& title, const std::string& info);
using Widget::Draw;
virtual void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left);
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
void DrawEntry(NVGcontext* vg, Theme* theme, const std::string& left, const std::string& right, bool use_selected);
void Depends(const DependsCallback& callback, const std::string& depends_info, const DependsClickCallback& depends_click = {}) {
m_depends_callback = callback;
m_depends_info = depends_info;
m_depends_click = depends_click;
}
void Depends(bool& value, const std::string& depends_info, const DependsClickCallback& depends_click = {}) {
m_depends_callback = [&value](){ return value; };
m_depends_info = depends_info;
m_depends_click = depends_click;
}
void Depends(option::OptionBool& value, const std::string& depends_info, const DependsClickCallback& depends_click = {}) {
m_depends_callback = [&value](){ return value.Get(); };
m_depends_info = depends_info;
m_depends_click = depends_click;
}
void SetDirty(bool dirty = true) {
m_dirty = dirty;
}
auto IsDirty() const -> bool {
return m_dirty;
}
protected:
std::string m_title;
auto IsEnabled() const -> bool {
if (m_depends_callback) {
return m_depends_callback();
}
return true;
}
void DependsClick() const {
if (m_depends_click) {
m_depends_click();
}
}
protected:
const std::string m_title;
private:
const std::string m_info;
std::string m_depends_info{};
DependsCallback m_depends_callback{};
DependsClickCallback m_depends_click{};
ScrollingText m_scolling_title{};
ScrollingText m_scolling_value{};
bool m_dirty{};
};
template<typename T>
concept DerivedFromSidebarBase = std::is_base_of_v<SidebarEntryBase, T>;
class SidebarEntryBool final : public SidebarEntryBase {
public:
using Callback = std::function<void(bool&)>;
public:
SidebarEntryBool(std::string title, bool option, Callback cb, std::string true_str = "On", std::string false_str = "Off");
SidebarEntryBool(std::string title, bool& option, std::string true_str = "On", std::string false_str = "Off");
explicit SidebarEntryBool(const std::string& title, bool option, const Callback& cb, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
explicit SidebarEntryBool(const std::string& title, bool& option, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
explicit SidebarEntryBool(const std::string& title, option::OptionBool& option, const Callback& cb, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
explicit SidebarEntryBool(const std::string& title, option::OptionBool& option, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
private:
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
bool m_option;
Callback m_callback;
std::string m_true_str;
std::string m_false_str;
};
class SidebarEntrySlider final : public SidebarEntryBase {
public:
using Callback = std::function<void(float&)>;
public:
explicit SidebarEntrySlider(const std::string& title, float value, float min, float max, int steps, const Callback& cb, const std::string& info = "");
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
private:
float m_value;
float m_min;
float m_max;
int m_steps;
Callback m_callback;
float m_duration;
float m_inc;
};
class SidebarEntryCallback final : public SidebarEntryBase {
public:
using Callback = std::function<void()>;
public:
SidebarEntryCallback(std::string title, Callback cb, bool pop_on_click = false);
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
explicit SidebarEntryCallback(const std::string& title, const Callback& cb, const std::string& info);
explicit SidebarEntryCallback(const std::string& title, const Callback& cb, bool pop_on_click = false, const std::string& info = "");
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
private:
Callback m_callback;
bool m_pop_on_click;
const Callback m_callback;
const bool m_pop_on_click;
};
class SidebarEntryArray final : public SidebarEntryBase {
@@ -52,85 +141,144 @@ public:
using Callback = std::function<void(s64& index)>;
public:
explicit SidebarEntryArray(std::string title, Items items, Callback cb, s64 index = 0);
SidebarEntryArray(std::string title, Items items, Callback cb, std::string index);
SidebarEntryArray(std::string title, Items items, std::string& index);
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
explicit SidebarEntryArray(const std::string& title, const Items& items, const Callback& cb, s64 index = 0, const std::string& info = "");
explicit SidebarEntryArray(const std::string& title, const Items& items, const Callback& cb, const std::string& index, const std::string& info = "");
explicit SidebarEntryArray(const std::string& title, const Items& items, std::string& index, const std::string& info = "");
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
private:
Items m_items;
ListCallback m_list_callback;
Callback m_callback;
const Items m_items;
const Callback m_callback;
s64 m_index;
s64 m_tick{};
float m_text_yoff{};
ListCallback m_list_callback{};
};
template <typename T>
class SidebarEntrySlider final : public SidebarEntryBase {
// single text entry.
// the callback is called when the entry is clicked.
// usually, the within the callback the text will be changed, use SetText().
class SidebarEntryTextBase : public SidebarEntryBase {
public:
SidebarEntrySlider(std::string title, T& value, T min, T max)
: SidebarEntryBase{title}
, m_value{value}
, m_min{min}
, m_max{max} {
using Callback = std::function<void(void)>;
public:
explicit SidebarEntryTextBase(const std::string& title, const std::string& value, const Callback& cb, const std::string& info = "");
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
void SetCallback(const Callback& cb) {
m_callback = cb;
}
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto GetValue() const -> const std::string& {
return m_value;
}
void SetValue(const std::string& value) {
m_value = value;
}
private:
T& m_value;
T m_min;
T m_max;
T m_step{};
Vec4 m_bar{};
Vec4 m_bar_fill{};
std::string m_value;
Callback m_callback;
};
class Sidebar final : public Widget {
class SidebarEntryTextInput final : public SidebarEntryTextBase {
public:
enum class Side { LEFT, RIGHT };
using Items = std::vector<std::shared_ptr<SidebarEntryBase>>;
using Callback = std::function<void(SidebarEntryTextInput* input)>;
public:
Sidebar(std::string title, Side side, Items&& items);
Sidebar(std::string title, Side side);
Sidebar(std::string title, std::string sub, Side side, Items&& items);
Sidebar(std::string title, std::string sub, Side side);
// uses normal keyboard.
explicit SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& header = {}, const std::string& guide = {}, s64 len_min = -1, s64 len_max = PATH_MAX, const std::string& info = "", const Callback& callback = nullptr);
// uses numpad.
explicit SidebarEntryTextInput(const std::string& title, s64 value, const std::string& header = {}, const std::string& guide = {}, s64 len_min = -1, s64 len_max = PATH_MAX, const std::string& info = "", const Callback& callback = nullptr);
auto GetNumValue() const -> s64 {
return std::stoul(GetValue());
}
void SetNumValue(s64 value) {
SetValue(std::to_string(value));
}
private:
const std::string m_header;
const std::string m_guide;
const s64 m_len_min;
const s64 m_len_max;
const Callback m_callback;
};
class SidebarEntryFilePicker final : public SidebarEntryTextBase {
public:
explicit SidebarEntryFilePicker(const std::string& title, const std::string& value, const std::vector<std::string>& filter, const std::string& info = "");
// extension filter.
void SetFilter(const std::vector<std::string>& filter) {
m_filter = filter;
}
private:
std::vector<std::string> m_filter{};
};
class Sidebar : public Widget {
public:
enum class Side { LEFT, RIGHT };
using Items = std::vector<std::unique_ptr<SidebarEntryBase>>;
using OnExitWhenChangedCallback = std::function<void()>;
public:
explicit Sidebar(const std::string& title, Side side, float width = 450.f);
explicit Sidebar(const std::string& title, const std::string& sub, Side side, float width = 450.f);
~Sidebar();
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
void Add(std::shared_ptr<SidebarEntryBase> entry);
auto Add(std::unique_ptr<SidebarEntryBase>&& entry) -> SidebarEntryBase*;
template<DerivedFromSidebarBase T, typename... Args>
auto Add(Args&&... args) -> T* {
return (T*)Add(std::make_unique<T>(std::forward<Args>(args)...));
}
// sets a callback that is called on exit when the any options were changed.
// the change detection isn't perfect, it just checks if the A button was pressed...
void SetOnExitWhenChanged(const OnExitWhenChangedCallback& cb) {
m_on_exit_when_changed = cb;
}
private:
void SetIndex(s64 index);
void SetupButtons();
private:
std::string m_title;
std::string m_sub;
Side m_side;
Items m_items;
const std::string m_title;
const std::string m_sub;
const Side m_side;
Items m_items{};
s64 m_index{};
std::unique_ptr<List> m_list;
std::unique_ptr<List> m_list{};
Vec4 m_top_bar{};
Vec4 m_bottom_bar{};
Vec2 m_title_pos{};
Vec4 m_base_pos{};
OnExitWhenChangedCallback m_on_exit_when_changed{};
static constexpr float m_title_size{28.f};
// static constexpr Vec2 box_size{380.f, 70.f};
static constexpr Vec2 m_box_size{400.f, 70.f};
};
class FormSidebar : public Sidebar {
public:
explicit FormSidebar(const std::string& title) : Sidebar{title, Side::LEFT, 540.f} {
// explicit FormSidebar(const std::string& title) : Sidebar{title, Side::LEFT} {
}
};
} // namespace sphaira::ui

View File

@@ -1,7 +1,6 @@
#pragma once
#include "nanovg.h"
#include "pulsar.h"
#include "fs.hpp"
#include <switch.h>
@@ -56,8 +55,8 @@ struct Vec2 {
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} {}
constexpr Vec4(Vec4 vec0, Vec4 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.w}, h{vec1.h} {}
constexpr Vec4(const Vec2& vec0, const Vec2& vec1) : x{vec0.x}, y{vec0.y}, w{vec1.x}, h{vec1.y} {}
constexpr Vec4(const Vec4& vec0, const Vec4& vec1) : x{vec0.x}, y{vec0.y}, w{vec1.w}, h{vec1.h} {}
float& operator[](std::size_t idx) {
switch (idx) {
@@ -179,6 +178,8 @@ enum ThemeEntryID {
ThemeEntryID_TEXT_SELECTED,
// background colour of a selected item, can be an image (not recommended).
ThemeEntryID_SELECTED_BACKGROUND,
// colour of the split screen and selected item.
ThemeEntryID_FOCUS,
// colour of line separators in a list.
ThemeEntryID_LINE,
@@ -227,6 +228,7 @@ struct ThemeMeta {
struct Theme {
ThemeMeta meta;
ElementEntry elements[ThemeEntryID_MAX];
fs::FsPath music_path;
auto GetColour(ThemeEntryID id) const {
return elements[id].colour;
@@ -272,6 +274,13 @@ enum class Button : u64 {
START = static_cast<u64>(HidNpadButton_Plus),
SELECT = static_cast<u64>(HidNpadButton_Minus),
SL_LEFT = static_cast<u64>(HidNpadButton_LeftSL),
SR_LEFT = static_cast<u64>(HidNpadButton_LeftSR),
SL_RIGHT = static_cast<u64>(HidNpadButton_RightSL),
SR_RIGHT = static_cast<u64>(HidNpadButton_RightSR),
SL_ANY = SL_LEFT | SL_RIGHT,
SR_ANY = SR_LEFT | SR_RIGHT,
// todo:
DPAD_LEFT = static_cast<u64>(HidNpadButton_Left),
DPAD_RIGHT = static_cast<u64>(HidNpadButton_Right),
@@ -282,11 +291,13 @@ enum class Button : u64 {
LS_RIGHT = static_cast<u64>(HidNpadButton_StickLRight),
LS_UP = static_cast<u64>(HidNpadButton_StickLUp),
LS_DOWN = static_cast<u64>(HidNpadButton_StickLDown),
LS_ANY = LS_LEFT | LS_RIGHT | LS_UP | LS_DOWN,
RS_LEFT = static_cast<u64>(HidNpadButton_StickRLeft),
RS_RIGHT = static_cast<u64>(HidNpadButton_StickRRight),
RS_UP = static_cast<u64>(HidNpadButton_StickRUp),
RS_DOWN = static_cast<u64>(HidNpadButton_StickRDown),
RS_ANY = RS_LEFT | RS_RIGHT | RS_UP | RS_DOWN,
ANY_LEFT = static_cast<u64>(HidNpadButton_AnyLeft),
ANY_RIGHT = static_cast<u64>(HidNpadButton_AnyRight),
@@ -330,10 +341,10 @@ struct Action final {
CallbackWithBool
>;
Action(Callback cb) : Action{ActionType::DOWN, "", cb} {}
Action(std::string hint, Callback cb) : Action{ActionType::DOWN, hint, cb} {}
Action(u8 type, Callback cb) : Action{type, "", cb} {}
Action(u8 type, std::string hint, Callback cb) : m_type{type}, m_callback{cb}, m_hint{hint} {}
explicit Action(const Callback& cb) : Action{ActionType::DOWN, "", cb} {}
explicit Action(const std::string& hint, const Callback& cb) : Action{ActionType::DOWN, hint, cb} {}
explicit Action(u8 type, const Callback& cb) : Action{type, "", cb} {}
explicit Action(u8 type, const std::string& hint, const Callback& cb) : m_type{type}, m_callback{cb}, m_hint{hint} {}
auto IsHidden() const noexcept { return m_hint.empty(); }
@@ -355,6 +366,81 @@ struct Action final {
std::string m_hint{};
};
struct GenericHidState {
GenericHidState() {
Reset();
}
void Reset() {
buttons_cur = 0;
buttons_old = 0;
}
u64 GetButtons() const {
return buttons_cur;
}
u64 GetButtonsDown() const {
return buttons_cur & ~buttons_old;
}
u64 GetButtonsUp() const {
return ~buttons_cur & buttons_old;
}
virtual void Update() = 0;
protected:
u64 buttons_cur;
u64 buttons_old;
};
struct KeyboardState final : GenericHidState {
struct MapEntry {
HidKeyboardKey key;
u64 button;
};
using Map = std::span<const MapEntry>;
void Init(Map map) {
m_map = map;
Reset();
}
void Update() override {
buttons_old = buttons_cur;
buttons_cur = 0;
if (!hidGetKeyboardStates(&m_state, 1)) {
return;
}
const auto ctrl = m_state.modifiers & HidKeyboardModifier_Control;
const auto shift = m_state.modifiers & HidKeyboardModifier_Shift;
for (const auto& map : m_map) {
if (hidKeyboardStateGetKey(&m_state, map.key)) {
if (shift && map.button == static_cast<u64>(Button::L)) {
buttons_cur |= static_cast<u64>(Button::L2);
} else if (shift && map.button == static_cast<u64>(Button::R)) {
buttons_cur |= static_cast<u64>(Button::R2);
} else if (ctrl && map.button == static_cast<u64>(Button::L)) {
buttons_cur |= static_cast<u64>(Button::L3);
} else if (ctrl && map.button == static_cast<u64>(Button::R)) {
buttons_cur |= static_cast<u64>(Button::R3);
} else {
buttons_cur |= map.button;
}
}
}
}
private:
Map m_map{};
HidKeyboardState m_state{};
};
struct Controller {
u64 m_kdown{};
u64 m_kheld{};
@@ -386,26 +472,44 @@ struct Controller {
m_kup = 0;
}
void UpdateButtonHeld(u64 buttons) {
void UpdateButtonHeld(u64 buttons, double delta) {
if (m_kdown & buttons) {
m_step = 50;
m_step_max = m_MAX_STEP;
m_step = m_INC_STEP;
m_counter = 0;
m_step_max_counter = 0;
} else if (m_kheld & buttons) {
m_counter += m_step;
m_counter += m_step * delta;
// if we are at the max, ignore the delta and go as fast as the frame rate.
if (m_step_max == m_MAX) {
m_counter = m_MAX;
}
if (m_counter >= m_MAX) {
m_kdown |= m_kheld & buttons;
m_counter = 0;
m_step = std::min(m_step + 50, m_MAX_STEP);
m_step = std::min(m_step + m_INC_STEP, m_step_max);
// slowly speed up until we reach 1 button down per frame.
m_step_max_counter++;
if (m_step_max_counter >= 5) {
m_step_max_counter = 0;
m_step_max = std::min(m_step_max + m_INC_STEP, m_MAX);
}
}
}
}
private:
static constexpr int m_MAX = 1000;
static constexpr int m_MAX_STEP = 250;
int m_step = 50;
int m_counter = 0;
static constexpr double m_MAX = 1000;
static constexpr double m_MAX_STEP = 250;
static constexpr double m_INC_STEP = 50;
double m_step_max = m_MAX_STEP;
double m_step = m_INC_STEP;
double m_counter = 0;
int m_step_max_counter = 0;
};
} // namespace sphaira

View File

@@ -5,15 +5,19 @@
#include <memory>
#include <map>
#include <unordered_map>
#include <concepts>
namespace sphaira::ui {
struct uiButton final : Object {
uiButton(Button button, Action action) : m_button{button}, m_action{action} {}
uiButton(Button button, const std::string& button_str, const std::string& action_str);
uiButton(Button button, const std::string& action_str);
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
Button m_button;
Action m_action;
std::string m_button_str;
std::string m_action_str;
Vec4 m_button_pos{};
Vec4 m_hint_pos{};
};
@@ -44,8 +48,8 @@ struct Widget : public Object {
}
auto HasAction(Button button) const -> bool;
void SetAction(Button button, Action action);
void SetActions(std::same_as<std::pair<Button, Action>> auto ...args) {
void SetAction(Button button, const Action& action);
void SetActions(std::same_as<std::pair<Button, Action>> auto&& ...args) {
const std::array list = {args...};
for (const auto& [button, action] : list) {
SetAction(button, action);
@@ -62,6 +66,12 @@ struct Widget : public Object {
m_actions.clear();
}
void RemoveActions(const Actions& actions) {
for (auto& e : actions) {
RemoveAction(e.first);
}
}
auto FireAction(Button button, u8 type = ActionType::DOWN) -> bool;
void SetPop(bool pop = true) {
@@ -77,6 +87,8 @@ struct Widget : public Object {
}
auto GetUiButtons() const -> uiButtons;
static void SetupUiButtons(uiButtons& buttons, const Vec2& button_pos = {1220, 675});
static auto GetUiButtons(const Actions& actions, const Vec2& button_pos = {1220, 675}) -> uiButtons;
Actions m_actions{};
Vec2 m_button_pos{1220, 675};
@@ -84,4 +96,7 @@ struct Widget : public Object {
bool m_pop{false};
};
template<typename T>
concept DerivedFromWidget = std::is_base_of_v<Widget, T>;
} // namespace sphaira::ui

View File

@@ -2,18 +2,12 @@
#include <vector>
#include <string>
#include <new>
#include <memory>
#include <switch.h>
namespace sphaira::usb {
struct Base {
enum { USBModule = 523 };
enum : Result {
Result_Cancelled = MAKERESULT(USBModule, 100),
};
Base(u64 transfer_timeout);
virtual ~Base();
@@ -45,43 +39,10 @@ struct Base {
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,
@@ -96,7 +57,7 @@ protected:
private:
u64 m_transfer_timeout{};
UEvent m_uevent{};
PageAlignedVector m_aligned{};
std::unique_ptr<u8*> m_aligned{};
};
} // namespace sphaira::usb

View File

@@ -1,57 +0,0 @@
#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

Some files were not shown because too many files have changed in this diff Show More