192 Commits

Author SHA1 Message Date
ITotalJustice
d50bcb650f fix crash if nro has corrupted asset entry, bump version for new release 0.10.2 -> 0.10.3
the nro that caused this was ClkrstQuery.nro

fixes #141
2025-05-21 17:29:18 +01:00
⭐️NINIKA⭐️
4244be9592 Fix handling of unicode filenames in usb_install_pc.py (#143) 2025-05-20 12:45:59 +01:00
Ny'hrarr
a7fc19e28a Pt patches (#140)
* Update pt.json

* Translate new keys

* Translate FTP, USB and GameCard menus

* Update pt.json

* Update pt.json

* Add missing comma and translate more entries

* Fix case

* Update pt.json

* Update pt.json
2025-05-20 09:00:39 +01:00
ITotalJustice
cf192fca85 fix hbmenu not being updated due to faulty string compare, bump version 0.10.1 -> 0.10.2 2025-05-19 20:34:22 +01:00
xHR
041bb2bbe5 Added Ukranian language (#139) 2025-05-19 19:57:15 +01:00
ITotalJustice
df558d5dcc bump version for new release 0.10.0 -> 0.10.1 2025-05-19 17:06:49 +01:00
ITotalJustice
33de03a923 fix sd card dumps due to the folder not being created. 2025-05-19 17:04:49 +01:00
ITotalJustice
1000b9c8ec fix sphaira not detecting latest update as we went from 0.9 to 0.10. 2025-05-19 16:23:45 +01:00
ITotalJustice
74fddecebc bump version for new release 0.9.1 -> 0.10.0 2025-05-19 16:09:33 +01:00
ITotalJustice
a64d4dce7a remove ns event from games menu (see below).
turns out that the event is auto cleared when waited.
this meant that if sphaira handled the event before qlaunch got chance to handle it,
then qlaunch won't update when app records changed.

this can result in a gamecard not being mounted, deleted games still apearing, installed games
not being displayed etc...
2025-05-19 16:07:24 +01:00
ITotalJustice
5daca4354c add support for webdav uploads by creating missing folders, game now dumps to title/title[id].nsp 2025-05-19 16:00:03 +01:00
ITotalJustice
da9235f58e fix upload url path not being encoded, add seek api for uploads. 2025-05-19 12:06:43 +01:00
ITotalJustice
bd6566524c enable ftp in the ftp menu if it isn't already enabled. 2025-05-19 08:32:05 +01:00
ITotalJustice
eadc46b0e4 add support for file uploads in the file browser, optimise curl single thread download.
- curl now keeps the handle alive for single threaded downloads, rather than creating it each time.
2025-05-18 23:00:51 +01:00
ITotalJustice
71df5317be add game dump uploading, fix download progress using u32 instead of s64, add progress and title for usb game dump.
- added support for custom upload locations, set in /config/sphaira/locations.ini
- add support for various auth options for download/upload (port, pub/priv key, user/pass, bearer).
2025-05-18 20:30:04 +01:00
ITotalJustice
bd7eadc6a0 add game dumping, add game transfer (switch2switch) via usb, add multi game selecting, fix bugs (see below).
- added more es commands.
- fixed usb install potential hang if the exit command is sent, but the client stops responding (timeout is now 3s).
- added multi select to the games menu.
- added game dumping.
- added switch2switch support by having a switch act as a usb client to transfer games.
- replace std::find with std::ranges (in a few places).
- fix rounding of icon in progress box being too round.
- fix file copy helper in progress box not updating the progress bar.
2025-05-18 13:46:10 +01:00
ITotalJustice
544272925d fix IsEmunand() failing due to the paths not being page aligned. 2025-05-15 16:13:35 +01:00
ITotalJustice
70a31be134 don't fail if the control nca cannot be parsed during install, as it may depend on ticket not yet installed. 2025-05-15 15:25:03 +01:00
ITotalJustice
55ae2a63d9 fix installing failing during setup if prod.keys isn't found. 2025-05-15 15:14:02 +01:00
ITotalJustice
5a53947a3e fix crash in list layout caused by rendering all previous hidden entries. 2025-05-14 18:00:56 +01:00
BIGBIGSUI
3bbb5ccb3c Update zh.json (#136)
A latest zh.json. Hope it helps.
2025-05-14 00:29:04 +01:00
ITotalJustice
83472f1020 fix game menu forcefully disabled manual loading of control data.
this was done during testing / benchmarking, but i forgot to undo this.
2025-05-14 00:06:36 +01:00
ITotalJustice
0167bf034c gc menu now tries to load control data from ns cache before manually loading.
on fw 19 and below, loading from cache takes ~5ms, whereas manually loading takes ~20ms.
manually loading is still faster than relying on ns to load control from storage (~50ms).
2025-05-14 00:04:47 +01:00
ITotalJustice
35abe363a6 optimise theme meta loading. 2025-05-13 23:52:34 +01:00
ITotalJustice
97d3fd396e optimise game menu for fw 20
- loading the control data is ran on its own thread, it does not block the main thread. allows for smooth scrolling like nintendos home menu.
- on fw20+, sphaira manually parses the control data, rather than using ns. manually parsing takes 20-40ms, which is faster than ms which can take 50-500ms.
- on fw19 and below, if the control data is not in ns cache, sphaira will manually parse the data as its twice as fast as ns. You can see how fast this is by loading the gamecard menu as that manually parses everything, and it loads the gamecard faster than the home menu
2025-05-13 23:51:06 +01:00
ITotalJustice
b98ccb927e fix appstore status icons no longer being rounded (bug added in e279a70) 2025-05-11 20:19:43 +01:00
ITotalJustice
db23f072a2 add sysmmc / emummc install enable options.
allows the user to enable installs for one config and disable it for the other.
by default, it will load the install option found in the config, if found.
otherwise, it will load from the new config option.
2025-05-11 20:14:34 +01:00
ITotalJustice
4d3d7e81d4 update libhaze to silence gcc 15 warning and run on core2 instead of core0. 2025-05-11 03:45:48 +01:00
ITotalJustice
441807bc53 fix building for gcc 15 2025-05-11 03:00:04 +01:00
ITotalJustice
20e2d85843 remove bubbles, no one likes easter eggs apparently.
fixes #138
2025-05-11 02:41:56 +01:00
ITotalJustice
e279a70606 add layout options to grid based menues. 2025-05-11 02:39:03 +01:00
ITotalJustice
5d9e24af31 use ns application event to detect when to re-scan for record changes. 2025-05-04 20:42:57 +01:00
Ny'hrarr
078627e07b Update pt.json (#133)
* Update pt.json

* Translate new keys

* Translate FTP, USB and GameCard menus
2025-05-03 22:27:31 +01:00
ITotalJustice
365ae2d0cb fix freeze if the usb menu is closed whilst a usb cable is not connected, 0.9.0 -> 0.9.1 2025-05-03 21:15:21 +01:00
ITotalJustice
5b6e09b926 bump version for new release 0.8.2 -> 0.9.0 2025-05-03 18:01:49 +01:00
ITotalJustice
7072647611 reduce usb install exit latency by waiting on a cancel uevent, rather than relying on a timeout. 2025-05-03 17:17:53 +01:00
ITotalJustice
30cf4826f8 add code for calculating games size, stubbed for now as the ns calls are too slow to be usable. 2025-05-03 15:30:40 +01:00
ITotalJustice
ca47fc1f89 add (limited) sort options to game menu.
getting the list of title_ids is very fast (less than 1ms), however parsing the control info, such as title names
is very slow.
depending on how many games the user has, blocking until we read all control info can take several seconds...
we would only need to block if the user wants to sort by name.
normally, we lazy load the control data, so we don't suffer from slow load times at all.
i decided that its not worth slowing the whole system down just to give the option to sort by name.
2025-05-03 15:08:25 +01:00
ITotalJustice
16a2c84edd simplify right-side shortcuts impl, add gamecard and themezer to shortcut list, fix l2/r2 using wrong icons, sort l2/r2 so l2 displays first.
some other changes:
- shorten the next page and prev page to just next/prev in themezer.
- remove misc shortcut name. the function itself still exists.
2025-05-03 14:39:20 +01:00
ITotalJustice
df5e27dd06 fix filebrowser crash caused when trying to select all files (L2) whilst a hidden file/folder exists.
this crash was found by @WE1ZARD.
to trigger it, press L2 in the filebrowser whilst a hidden file exists and the hide hidden is enabled.
the was due to GetEntry(i) internally using m_entries_current, and the select all was using the index from m_entries_current.
this would result in an index that goes oob, and as its a write, it crashes.
2025-05-03 13:25:09 +01:00
ITotalJustice
d95226f8c0 i18n::get should accept a string_view rather than char*, simplifies calling. 2025-05-03 13:25:09 +01:00
ITotalJustice
164fec5b73 fix right-side shortcut not displaying the correct name (again) when using translations. 2025-05-03 13:25:09 +01:00
glitched_nx
8dad96f39f Update de.json (#130)
Commit changes to language file (de.json), with many improvements and corrections.

The changes include:
- Modification and correction of existing translations for greater clarity and accuracy.
- Updated translations to match the terminology for homebrew and Nintendo Switch UI elements.
- Added new 'de' translation for the “ 12h time format” setting function.
2025-05-02 22:48:48 +01:00
ITotalJustice
2244e73c53 change "yes, no" for boolean options to "on, off", to match N's sidebar. 2025-05-02 00:57:11 +01:00
ITotalJustice
456cb02d2a [breaking change] update forwarder id generation, add "hide forwarders" to game menu. 2025-05-02 00:52:08 +01:00
ITotalJustice
f310704472 bump nca key generation values for fw 20.0.0 2025-05-02 00:02:09 +01:00
ITotalJustice
96e5a7081b clip rect and text drawing that go offscreen.
this is already handled by the gpu, but cpu side still has to do some work.
this wasn't a performance issue (we only use 4%) but its a free win, so we might as well.
2025-05-01 23:49:01 +01:00
ITotalJustice
1c93e18822 replace all "enabled" and "disabled" options with the default "On" and "Off". 2025-05-01 22:10:47 +01:00
ITotalJustice
ac152454f0 fix menu shortcut setting translated names for the config. 2025-05-01 22:00:40 +01:00
ITotalJustice
7851f7f400 add option to extract zip to root, better name extract / compress options. 2025-05-01 18:28:35 +01:00
ITotalJustice
2b561dd438 add option to change the right-side menu. 2025-05-01 18:06:10 +01:00
ITotalJustice
3545f557fc add scrolling text to popup list, handling clipping inside scrolling text, game menu changes
- added delete entity in game menu
- added list meta records to game menu
2025-05-01 15:14:50 +01:00
ITotalJustice
8dfb9b9ba6 reduce time for scrolling text from 1.5s to 1s. 2025-05-01 00:04:32 +01:00
ITotalJustice
7cf36cd25f reduces rounding of icons in grid based menus (15 -> 5). 2025-04-30 23:59:05 +01:00
ITotalJustice
c53692022b add scrolling text to all grid based menus.
fixes #122
2025-04-30 23:56:40 +01:00
ITotalJustice
0f3b7da0b2 fix memleak when deleting homebrew, add game menu. 2025-04-30 22:45:52 +01:00
ITotalJustice
e22daefb08 slightly round edges for progress and option box, add image support to option boxes 2025-04-30 18:19:13 +01:00
ITotalJustice
6fb5319da3 bump version for new release 0.8.1 -> 0.8.2 2025-04-30 17:21:04 +01:00
ITotalJustice
6970fec554 irs connect to first available handle, irs display connected pad in the title. 2025-04-30 17:16:59 +01:00
ITotalJustice
36be56647f Revert "remove IRS menu"
This reverts commit 1dafa2748c.
2025-04-30 17:05:44 +01:00
ITotalJustice
cca6326314 filebrowser add select al option by pressing L2 2025-04-30 16:55:33 +01:00
ITotalJustice
9176c6780a filebrowser move install forwarder option out of the advanced menu. 2025-04-30 16:49:19 +01:00
ITotalJustice
b1a6b12cf3 add zip extraction, add zip creation, themezer now displays the file name its extracting. 2025-04-30 16:42:05 +01:00
ITotalJustice
c7cc11cc98 only add etag is dst file already exists, enable curl --compressed option.
curl/libcurl does not send Accept-Encoding by default.
many servers support sending compressed versions of files, to speed up transfers.
this is ideal for the switch as its io is shit, but the cpu is mostly idle (4% cpu usage for sphaira).

github and appstore support sending gzip json files, themezer doesn't seem to.
2025-04-30 00:40:04 +01:00
ITotalJustice
ec4b96b95d remove stale etag if the server stops sending etags back
workaround for appstore images which stopped sending etags back.
2025-04-29 22:41:33 +01:00
ITotalJustice
a2e343daa7 improve popup_list to highlight the currently selected item. 2025-04-29 22:40:32 +01:00
BIGBIGSUI
b811c9e3cd Update zh.json (#129)
A latest zh.json. Hope it will be helpful
2025-04-29 20:35:05 +01:00
ITotalJustice
8ffaa56bc3 bump version for new release 0.8.0 -> 0.8.1 2025-04-29 20:03:23 +01:00
ITotalJustice
eca3358e57 add option to download default music. 2025-04-29 20:01:51 +01:00
ITotalJustice
757e380e08 play sound effect when gamecard is inserted. 2025-04-29 19:23:37 +01:00
ITotalJustice
6c1b5de932 label the shortcut for misc 2025-04-29 19:06:33 +01:00
ITotalJustice
d79ac126f7 remove all strings and error codes in error_box.cpp, reduce binary by a further 60k. 2025-04-29 18:56:55 +01:00
ITotalJustice
2d7763444e remove Web menu
the web browser on the switch is really bad, it shouldnt be used.
i am removing this menu because its another option that gets in the way of other options, and code bloat.
2025-04-29 18:42:32 +01:00
ITotalJustice
1dafa2748c remove IRS menu
i added the irs menu when i wanted to mess around with the sensor on the joycon.
since then, i have used it a total of 0 times, and i don't think any users use it either.
2025-04-29 18:37:43 +01:00
ITotalJustice
9f7bf9581c add boost mode option for installing 2025-04-29 18:33:02 +01:00
ITotalJustice
8f39acbaa2 replace usb protocol with tinfoils protocol, in order to support applications supporting said protocol.
- replace the python script with the one included with tinfoil, minor changes such as changing the supported extension,
  removing unused imports.
- tested with the included script, fluffy and ns-usbloader on linux.
  a user was unable to get it working on mac however...
- added build instructions to the readme, i think they're correct.
- added install instructions to the readme.
2025-04-29 18:11:07 +01:00
ITotalJustice
81469d0ac9 remove PageAlignedVector from yati as it's no longer needed due to previous commit.
the previous commit changed usb transfers to always transfer to/from page aligned buffers.
i wanted to keep the commits seperate so that its easier revert or git bisect later on, if needed.
2025-04-29 14:19:37 +01:00
ITotalJustice
1eae35f072 simplify the usb transfer process by using an aligned buffer to transfer to/from. 2025-04-29 14:17:12 +01:00
ITotalJustice
5b82e07b1c fix building due to previous commit 2025-04-29 13:08:32 +01:00
ITotalJustice
73886c28ae add gc event waiting, fix control nca mounting, better skip nca support.
- gamecards now wait for an event to change, rather than polling each frame.
  this reduces cpu load on core 3 slightly (3-4% less).
- my understanding of fsOpenFileSystemWithId() was wrong, i thought it used the app_id for the id param.
  turns out it needs the program id (found in the nca header), this is why mounting some control ncas
  would fail.
  fs (and ncm) have a call in 17+ to get the program id, it does so by parsing the nca header.
  in yati, we already have the header so we can avoid the call.
  for the gamecard menu, we don't. so we can parse the nca header, or use the id offset (which we already have)
  to form the program id.
- std::find_if in yati now takes args by ref rather than by value, avoid quite large copies.
- stream installs can now parse the control nca.
- if an nca is already installed, it is now skipped. this is regardless to whether it is not in ncm db.
- nca skipping is technically supported for stream installs, however it is disabled for now as there needs
  to be a way to allow for the stream to continue reading and discarding data until the stream has finished.
  currently, if a ftp (stream) install is skipped, it will close the progress box and cause spahira to hang.
  this is because sphaira expects the stream to only be closed upon all data being read, so there's nothing more
  to process.
- renamed the title_id field in nca header to program_id.
2025-04-29 12:47:38 +01:00
ITotalJustice
eea09f6e57 [appstore] make author search case insensitive. 2025-04-28 22:06:44 +01:00
ITotalJustice
282c6e5493 bump version for release 0.7.0 -> 0.8.0 2025-04-27 21:01:45 +01:00
ITotalJustice
2c2f602d14 add gc_menu, add progress, icon, time remaining to progress bar (see full commit message).
- fix ignore distribution bit doing nothing.
- fix yati failing to parse control nca causing the transfer to abort.
- yati now uses ncm rather than ns to get the latest app version.
- improve ui::list input handling (it handles directional buttons now).
- progress bar displays speed and time remaining.
- added gc menu (taken from my gc installer nx and gci).
2025-04-27 20:01:13 +01:00
ITotalJustice
f7f1254699 Merge pull request #128 from ITotalJustice/stream_installs
Stream installs (FTP), and many fixes
2025-04-23 01:02:26 +01:00
ITotalJustice
90f8a62823 display useful info in ftp menu (ip, port, user, pass, ssid, passphrase) 2025-04-23 01:00:36 +01:00
ITotalJustice
e2a1c8b5e3 fix yati not setting correct version, add support for using zip name when creating forwarder, remove some dead code.
fixes #126
fixes #127
2025-04-22 23:15:16 +01:00
ITotalJustice
21f6f4b74d [skip ci] fix file assoc always using internal name, fix menu showing wrong time
fixes #126
2025-04-22 00:08:26 +01:00
ITotalJustice
75d3b3ee0d [skip-ci] initial support for stream installs, add ftp installs.
do NOT build or release binaries of this version, it is not complete and there will be dragons.
2025-04-21 23:23:59 +01:00
ITotalJustice
0dde379932 don't return from usb menu on error, wait until the user presses B 2025-04-21 13:33:36 +01:00
ITotalJustice
9800bbecdf add basic support for gamecard installing 2025-04-21 13:30:46 +01:00
ITotalJustice
60e915c255 enable screenshot permissions in applet mode. 2025-04-21 12:40:37 +01:00
ITotalJustice
786f8a42fa send file name and size via usb, add requirements.txt for usb.py 2025-04-21 01:41:20 +01:00
ITotalJustice
5a4a0f75f2 add support for mame and neogeo, as well as alias for rom folder names 2025-04-20 22:03:53 +01:00
ITotalJustice
5aca92a2cc fix usb menu name being set as irs (copy-paste bug). 2025-04-20 18:11:17 +01:00
ITotalJustice
7471885119 bump version for release 0.6.3 -> 0.7.0 2025-04-20 18:08:55 +01:00
ITotalJustice
5038fb0c28 add basic usb install support (see commit message).
transfers seems to work, although i have done very little testing.
i plan to extend the usb script so that it supports normal usb transfers, such as uploading and
downloading files to and from the switch.

however, most users are likely better off using mtp for said transfers.

the usb transfer code was taken from Haze, which is part of Atmosphere.
2025-04-20 18:04:35 +01:00
ITotalJustice
ff9f493460 fix skwbd numpa not showing 2025-04-20 14:18:02 +01:00
ITotalJustice
89e82927ee add basic support for title installing 2025-04-20 14:12:12 +01:00
ITotalJustice
651d9fa495 remove theme shuffle option
fixes #121
2025-04-19 22:55:08 +01:00
ITotalJustice
3141100457 fix themezer search...by actually *doing* the search.
fixes #111
2025-04-19 22:50:56 +01:00
ITotalJustice
6b4e81c935 bump version for new release 0.6.3 2025-04-18 13:18:14 +01:00
ITotalJustice
e243d5b64e fix themezer 2025-04-18 13:16:17 +01:00
shadow2560
252cd0cee6 Add 12 our time string in translation files and translated it for French language. (#120)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2025-04-09 18:05:14 +01:00
therealbungus
14abcc50b5 add 12 hour clock (#113) 2025-04-08 12:04:22 +01:00
BIGBIGSUI
134aadad5a Update zh.json (#110)
This is my updated zh.json file, I hope it helps.
2025-04-08 09:39:31 +01:00
Ny'hrarr
a56bc9e4fa Update pt.json (#109)
* Update pt.json

* Update pt.json
2025-04-08 09:39:18 +01:00
shadow2560
5bd466a9b6 Update french language. (#108)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2025-04-08 09:38:58 +01:00
LNLenost
16c58512ec Edited README, updated Italian translations (#117)
* Update README.md

* Update README.md

* Italian translations 1/2

* Italian translations 2/2
2025-04-08 09:38:32 +01:00
ITotalJustice
b1b0b13f2a fix filebrowser FsDirOpenMode_NoFileSize being passed
looks like fs ignores this flag as it was reporting the filesize regardless. however, if a file is created using
FsCreateOption_BigFile, then the filesize would return 0.
2025-01-22 13:01:38 +00:00
ITotalJustice
03e77faf06 draw "applet mode" text using error colour
fixes #104
2025-01-22 12:59:54 +00:00
ITotalJustice
7e381924ab fix forwarder creation bug on ams 1.7.1, fix cmake project version bug
i was checking against ams 1.7.1, however the change was introduced in ams 1.8.0.
fixes #106

as for cmake, i have no idea why this bug happens, but sometimes it will use the project version for hbl.
which results in sphaira having a version number of 3.0.0.
this only seemed to happen when building the zip with no current build.
2025-01-22 12:54:30 +00:00
spkats1
5763610e54 [Theme] alt icons + theme (#102)
* adding "sp icons"
2025-01-22 12:49:15 +00:00
HenryBaby
49956a3f84 Updated se.json with the latest added string. (#103)
Co-authored-by: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com>
2025-01-22 12:48:00 +00:00
Yorunokyujitsu
b2915a8142 add new string, update ko, ja.json (#101) 2025-01-22 12:47:04 +00:00
ITotalJustice
e002aa9ec2 only disable audio in applet mode if an app is suspended, bump version. 2025-01-17 03:52:32 +00:00
ITotalJustice
0aaf460dbf bump version for release (i forgot to do this before making a new release...) 2025-01-16 21:48:40 +00:00
ITotalJustice
76c8b806d0 bump ftpsrv version to 1.2.2 which fixes mdtm. 2025-01-16 21:40:24 +00:00
glitched_nx
61783bc530 update de.json with missing translations and corrections (#95) 2025-01-16 21:38:47 +00:00
ITotalJustice
a3a2a04991 fix hbmenu restore prompt not triggering if /hbmenu.nro does not exist
fixes #99
2025-01-16 21:28:13 +00:00
ITotalJustice
b6304fca75 fix deko3d mem leak when using docked mode
fixes #97
2025-01-16 21:24:21 +00:00
ITotalJustice
5612ae5691 disable audio in applet mode due to audren fatal.
fixes #92
2025-01-16 21:03:26 +00:00
ITotalJustice
657c160599 enable warning flags, fix all warning, default init all vars, bump stb libs used in nanovg
fixes #98
2025-01-16 21:01:17 +00:00
HenryBaby
f66494aeb5 Fixed the "decending" typo. (#91)
Co-authored-by: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com>
2025-01-16 05:17:59 +00:00
shadow2560
650e7812e5 Update french language. (#94)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2025-01-16 05:13:31 +00:00
Ny'hrarr
cca54340a2 Update pt.json (#93) 2025-01-16 05:13:08 +00:00
cucholix
8161b52e7b Update es.json (#90)
Updated es.json localization, missing strings translated
2025-01-16 05:12:41 +00:00
Yorunokyujitsu
9390bd3865 add new strings and update ko.json, ja.json (#88) 2025-01-14 16:06:28 +00:00
ITotalJustice
483be133a5 mention discord server in readme
fixes #70
2025-01-14 16:01:39 +00:00
ITotalJustice
e2022eac4c progress box should use stop source for requesting exit 2025-01-14 15:54:34 +00:00
ITotalJustice
977331c3b2 remove download non-thread_queue code, fix thread queue exit bug
due to the previous commit, requesting the stop token to exit during a download
would cause the thread queue itself to exit.
2025-01-14 15:45:52 +00:00
ITotalJustice
64a40ae672 use stop token to manage object lifetime across async callbacks, such as download async 2025-01-14 15:35:09 +00:00
ITotalJustice
4e5e1a801b bump version for new release 2025-01-12 23:26:29 +00:00
ITotalJustice
01e06a79a5 use strstr to find sphaira within update zip, force restart upon update success. 2025-01-12 23:22:20 +00:00
ITotalJustice
c762dafc67 add text scrolling to sidebar array
see #87
2025-01-12 23:16:12 +00:00
shadow2560
fd1d461ea8 Fix update when homebrew nro is not /switch/sphaira/sphaira.nro. (#64)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2025-01-12 23:11:56 +00:00
ITotalJustice
2e14e4b09b Merge pull request #82 from ITotalJustice/theme_v2
Theme v2
2025-01-12 22:51:27 +00:00
ITotalJustice
fb7b37736b Merge branch 'master' into theme_v2 2025-01-12 22:48:38 +00:00
ITotalJustice
12e5069168 Merge remote-tracking branch 'refs/remotes/origin/theme_v2' into theme_v2 2025-01-12 22:48:02 +00:00
Ny'hrarr
b81bc51b1c Update pt.json (#86) 2025-01-12 22:22:57 +00:00
ITotalJustice
e3f846c9ec Change a few colors of the theme. (#87)
Co-authored-by: Yorunokyujitsu <Yorunokyujitsu@gmail.com>
2025-01-12 22:21:54 +00:00
ITotalJustice
7d5876d881 add bubbles
not used yet
2025-01-12 19:16:55 +00:00
BIGBIGSUI
990948b912 Update zh.json (#85)
This updates the zh.json for the project. I carefully reviewed the text to ensure it aligns with the intent of the original content. I hope this contribution will be helpful and appreciated.
2025-01-12 19:10:37 +00:00
ITotalJustice
91a08d36b4 tweek popup_list height to match qlaunch, change white/abyss scrollbar colour, fix menu using text instead of line colours 2025-01-09 19:34:16 +00:00
ITotalJustice
abc7a0799d Merge branch 'master' into theme_v2 2025-01-09 16:19:26 +00:00
LNLenost
ab973a3f99 Upated Italian Translations (#80)
* Update it.json
2025-01-09 16:17:37 +00:00
Funz-001
d0179b8719 Create vi.json (#79)
add vietnamese
2025-01-09 16:16:45 +00:00
ITotalJustice
78ecdc014b add Vietnamese language support
needed for #79 and #81
2025-01-09 16:15:57 +00:00
ITotalJustice
0751fa9a2e optimise theme inherit, load default_music.bfstm if available. 2025-01-09 15:55:46 +00:00
ITotalJustice
f05230e870 initial work on theme v2
see #78
2025-01-09 15:03:51 +00:00
ITotalJustice
9915307be0 fix themezer crash due to accessing list before creating it 2025-01-07 04:10:43 +00:00
ITotalJustice
62183f4524 remove old index_offset code, fix popup_list initial index being offscreen 2025-01-07 02:45:42 +00:00
ITotalJustice
ca1b31329d prompt user to restart sphaira upon language change 2025-01-07 02:38:27 +00:00
ITotalJustice
3a3b8008a1 add theme inheritance, fix broken theme when no longer installed
fixes #74
2025-01-07 01:20:54 +00:00
ITotalJustice
e9f0d2349c add back pulsing select box
this was commented out during testing, i forgot to undo said change before pushing the change
2025-01-06 22:53:22 +00:00
Miguel Alexandre Uhlein
26f195b54f update pt.json (#75) 2025-01-06 22:50:10 +00:00
ITotalJustice
aa48c1696d bump libpulsar version, improves read speed.
this was needed to support large background music files.
2025-01-06 22:49:09 +00:00
Yorunokyujitsu
e526f376fe Add new string to all lang.json (#72)
* Add new string to all lang.json

Add new string to all lang.json
Update Korean, Japanese translation.

* Updates Swedish and French translations.

Swedish - https://github.com/ITotalJustice/sphaira/pull/71
French - https://github.com/ITotalJustice/sphaira/pull/72#issuecomment-2568976996

* Update languages.json
2025-01-06 22:39:16 +00:00
ITotalJustice
78bda75985 add touch support (#77)
* initial work on touch support

* add touch support to all objects

* add touch scrolling, fix scrollbar, fix appstore search

- when fireing an action, the action array may change. so the loop should break early as soon as an action is handled.
  this fixes the appstore search when pressing B.
- scrollbar no longer goes oob. fixes #76

currently, scrolling has no acceleration.
2025-01-06 22:37:38 +00:00
ITotalJustice
cf95128f0b allow for github actions to run on all branches 2025-01-01 17:50:08 +00:00
ITotalJustice
6dbf48d73c fix badly formatted string for i18n
fixes #68
2025-01-01 17:42:07 +00:00
ITotalJustice
1614c8e2e4 remove debug code in homebrew menu 2025-01-01 17:38:07 +00:00
ITotalJustice
cdebcad4fe correctly path npdm kc flags on ams 1.7.1 or greater
fixes #67
2025-01-01 17:34:54 +00:00
ITotalJustice
54c63d6f3b add support for mounting different fs.
currently, this feature isn't very useful as you cannot copy/move files across different fs.
2024-12-31 23:58:49 +00:00
ITotalJustice
d840a8ddba re-do how protected files work, by default everything is writeable, aside from fs.
the design in now opt-out rather than opt-in.

for fs, it is still opt-in. this is because the risk of a user deciding to delete a file / folder in the filebrowser menu.
this can now be toggled in the the advanced options menu within filebrowser.
2024-12-31 22:52:45 +00:00
ITotalJustice
c3b31d0fdd initial work on support custom fs mount points 2024-12-31 22:23:50 +00:00
ITotalJustice
dd1a6eb25b initial work on patching npdm debug flags, stubbed for now
stubbed as bit(19) doesn't seem to do what i thought it would.

fixes #67
2024-12-31 22:05:04 +00:00
ITotalJustice
271fab66f5 add option to restore hbmenu when disabling "Replace hbmenu on exit".
fixes #66
2024-12-31 18:13:30 +00:00
Yorunokyujitsu
87642e914e Update korean, japanese language. (#65) 2024-12-31 10:17:22 +00:00
ITotalJustice
45aa7c4e62 fix forwarder icon passing in the wrong size, fix github zip detection / extraction, add back url in json
- i was passing the wrong size for icon, so it always failed to load.
- i didnt realise zips can have many content types, so now i search for the name "zip" which works well.
- extracting a zip will fail if the files are all in the root, ie, no folder, and the subfolders do
  not already exist.
- fix misspelling of download.
2024-12-31 10:15:30 +00:00
ITotalJustice
9b1788d1ec i forgot to enable buffered io for ftp... 2024-12-31 05:52:51 +00:00
shadow2560
389a4cfef5 Update french language. (#63)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2024-12-31 05:06:08 +00:00
ITotalJustice
ac06631156 fix building 2024-12-31 04:53:33 +00:00
ITotalJustice
bc39e668eb add pre/post install message options for github json 2024-12-31 04:41:19 +00:00
ITotalJustice
e452615c77 fix etag cache not being returned upon the 2nd request, add jump page support for all menus.
- all menus feature page jumping, using L2/R2 (or DPAD_LEFT/DPAD_RIGHT in list menus)
- successive calls to fetch the etag would fail, this was seen in themezer and github menus.
- add limit the number of icons loaded per frame in homebrew menu.
- display default icon the image is not ready to be loaded / invalid.

fixes #53
2024-12-31 03:57:08 +00:00
ITotalJustice
588eb01379 poll main_menu vars (ip, charge% time) every 1s, rather than every frame. 2024-12-30 21:28:28 +00:00
ITotalJustice
4855a01f1a further simplify download cache by using a single file
this slightly improves lookup time for each cached entry (etag and last-modified)
by setting both entries next to each other, meaning when the one is found, the other
is already loaded in memory.
2024-12-30 21:12:29 +00:00
ITotalJustice
cb7fb0e506 use ftpsrv config and mountpoints, improve ftp performance by using its vfs. 2024-12-30 21:09:32 +00:00
ITotalJustice
cdb38f27a7 simplify etag caching requests 2024-12-30 02:27:52 +00:00
ITotalJustice
7804bbbcbc add support for finding daybreak in non-standard paths (#62) 2024-12-29 11:52:34 +00:00
ITotalJustice
5db5f93af1 fix building 2024-12-29 01:15:37 +00:00
Ny'hrarr
bab4bfce84 Update pt.json (#61) 2024-12-29 01:02:33 +00:00
Yorunokyujitsu
ec06763e50 Fixed some translations of ko.json (#58)
* Fixed some translations of ko.json

* Add new string to lang.json and update ko, ja.

---------

Co-authored-by: Yorunokyujitsu <seonmini1315@gamil.com>
Co-authored-by: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com>
2024-12-29 01:02:04 +00:00
ITotalJustice
5e315bd65f many fixes and performance improvements for network requests (see commit details)
- add etag support
- add last-modified support

with the above 2 changes, this means that all downloads can be cached. when attempting to download a file,
if the file is an image, load from cache. after, the download is processed with the above tags sent. if a 304 code
is received, then the file hasn't changed. otherwise, the new tags are saved and the downloaded file is now used (in the
case of an image, the new image is now loaded over the cached one).

this results in a *huge* speed improvement and overall a huge amount of bandwidth is saved for both the client and server.

- themezer requests now only request the data needed.

this results in a json file that is 4-5x smaller, meaning a much faster download and parsing time.

- loading images is capped to 2 images a frame. this was done to avoid fs being the bottle neck.
  a 9 page listing will take 5 frames. scrolling through lists is more responsive.

- downloads are pushed to the front of the queue as they're added. the point of this is to prioritise
  data that we need now.

- fix potential crash when sorting files based on names as its possible for a file to have the same name
  in the metadata. this fallsback to sorting by path, which is unique.

- add timeout for processing events. this was done in order to not block the main thread for too long.

- github json files have changed from a name + url to a repo + author pair.
- drawing widgets now starts from the last file in the array. as a menu takes up the whole screen, it
 is pointless drawing menu's underneath. this halves gpu usage.
- download url caching has been removed. this was added to fix a race condition when opening /
  closing a widget which starts a download when created. this would result in 2 same files being
  downloaded at the same time. this is no longer an issue and was overhead per download request.
2024-12-29 00:33:31 +00:00
ITotalJustice
2edfe91ad6 re-write download code to support headers, needed for etag support.
etag support will be added later. github supports it and themezer probably does as well.
appstore does not sadly...
2024-12-27 02:28:44 +00:00
ITotalJustice
7005118876 Create FUNDING.yml 2024-12-26 19:23:39 +00:00
DDinghoya
087d44fb40 Update ko.json (#55)
. Fixed some awkward words
. If there is no abbreviation "...", fixed progressive tense to noun.
2024-12-26 18:34:47 +00:00
ITotalJustice
e3722f2591 include name when prompting the user to select which asset to download 2024-12-26 18:29:03 +00:00
ITotalJustice
47855ce7b4 change workflow to only build MinSizeRel 2024-12-26 18:11:53 +00:00
ITotalJustice
ec7caabdbd add GitHub downloader, fix yyjson helper missing break, hide popup list when out of focus 2024-12-26 18:11:03 +00:00
Yorunokyujitsu
adf0a3b2cd Update ko.json and ja.json and add MTP/FTP strings (#54)
Co-authored-by: Yorunokyujitsu <seeonmini1315@gmail.com>
2024-12-26 04:58:17 +00:00
Ny'hrarr
f88e354ae8 Translate missing fields (Portuguese) (#51)
* Update pt.json
2024-12-26 03:29:33 +00:00
cucholix
df3d8d3990 Update es.json (#49)
Further improvements for ver 0.5.0
2024-12-26 03:29:08 +00:00
168 changed files with 17079 additions and 5938 deletions

15
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# These are supported funding model platforms
github: ITotalJustice
patreon: totaljustice
open_collective: # Replace with a single Open Collective username
ko_fi: totaljustice
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,10 +1,6 @@
name: build name: build
on: on: [push, pull_request]
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs: jobs:
build: build:
@@ -12,7 +8,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
preset: [Release, RelWithDebInfo, MinSizeRel, Debug] preset: [MinSizeRel]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
container: devkitpro/devkita64:latest container: devkitpro/devkita64:latest
@@ -24,7 +20,7 @@ jobs:
- name: Configure CMake - name: Configure CMake
run: | run: |
cmake --preset ${{ matrix.preset }} cmake --preset ${{ matrix.preset }} -DUSE_VFS_GC=0
- name: Build - name: Build
run: cmake --build --preset ${{ matrix.preset }} --parallel 4 run: cmake --build --preset ${{ matrix.preset }} --parallel 4

3
.gitignore vendored
View File

@@ -11,6 +11,7 @@ old_code
created_ncas created_ncas
assets/romfs/shaders assets/romfs/shaders
.vscode/settings.json .vscode/settings.json
.idea
info/ info/
romfs/shaders romfs/shaders
assets/unused assets/unused
@@ -22,3 +23,5 @@ libs/tweeny
compile_commands.json compile_commands.json
out out
usb_test/

View File

@@ -1,10 +1,12 @@
# sphaira # Sphaira
A homebrew menu for the switch. A homebrew menu for the Nintendo Switch.
[See the gbatemp thread for more details / discussion](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/). [See the GBATemp thread for more details / discussion](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/).
## showcase [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
| | | | | |
:-------------------------:|:-------------------------: :-------------------------:|:-------------------------:
@@ -13,28 +15,29 @@ A homebrew menu for the switch.
![Img](assets/screenshots/2024121522505300-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121522502300-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/2024121523033200-879193CD6A8B96CD00931A628B1187CB.jpg) | ![Img](assets/screenshots/2024121523070300-879193CD6A8B96CD00931A628B1187CB.jpg)
## bug reports ## Bug reports
for any bug reports, please use the issues tab and explain in as much detail as possible! For any bug reports, please use the issues tab and explain in as much detail as possible!
please include: Please include:
- CFW type (i assume Atmosphere, but someone out there is still using Rajnx) - CFW type (i assume Atmosphere, but someone out there is still using Rajnx);
- CFW version - CFW version;
- FW version - FW version;
- The bug itself and how to reproduce it - The bug itself and how to reproduce it.
## ftp ## FTP
ftp can be enabled via the network menu and listens on port 5000, no username or password is required. FTP can be enabled via the network menu. It uses the same config as ftpsrv `/config/ftpsrv/config.ini`. [See here for the full list
of all configs available](https://github.com/ITotalJustice/ftpsrv/blob/master/assets/config.ini.template).
## mtp ## MTP
mtp can be enabled via the network menu. MTP can be enabled via the Network menu.
## file assoc ## File association
sphaira has file assoc support. lets say your app supports loading .png files, then you could write an assoc file, then when using the file browser, clicking on a .png file will launch your app along with the .png file as argv[1]. This was primarly added for rom loading support for emulators / frontends such as retroarch, melonds, mgba etc. Sphaira has file association support. Let's say your app supports loading .png files, then you could write an association file, then when using the file browser, clicking on a .png file will launch your app along with the .png file as argv[1]. This was primarly added for rom loading support for emulators / frontends such as RetroArch, MelonDS, mGBA etc.
```ini ```ini
[config] [config]
@@ -42,9 +45,43 @@ path=/switch/your_app.nro
supported_extensions=jpg|png|mp4|mp3 supported_extensions=jpg|png|mp4|mp3
``` ```
the `path` field is optional. if left out, it will use the name of the ini to find the nro. For example, if the ini is called mgba.ini, it will try to find the nro in /switch/mgba.nro and /switch/folder/mgba.nro. The `path` field is optional. If left out, it will use the name of the ini to find the nro. For example, if the ini is called mgba.ini, it will try to find the nro in /switch/mgba.nro and /switch/folder/mgba.nro.
see `assets/romfs/assoc/` for more examples of file assoc entries See `assets/romfs/assoc/` for more examples of file assoc entries.
## Installing (applications)
Sphaira can install applications (nsp, xci, nsz, xcz) from various sources (sd card, gamecard, ftp, usb).
For informantion about the install options, [see the wiki](https://github.com/ITotalJustice/sphaira/wiki/Install).
### Usb (install)
The USB protocol is the same as tinfoil, so tools such as [ns-usbloader](https://github.com/developersu/ns-usbloader) and [fluffy](https://github.com/fourminute/Fluffy) should work with sphaira. You may also use the provided python script found [here](tools/usb_install_pc.py).
### Ftp (install)
Once you have connected your ftp client to your switch, you can upload files to install into the `install` folder.
## Building from source
You will first need to install [devkitPro](https://devkitpro.org/wiki/Getting_Started).
Next you will need to install the dependencies:
```sh
sudo pacman -S switch-dev deko3d switch-cmake switch-curl switch-glm switch-zlib
```
Once devkitPro and all dependencies are installed, you can now build sphaira.
```sh
git clone https://github.com/ITotalJustice/sphaira.git
cd sphaira
cmake --preset MinSizeRel
cmake --build --preset MinSizeRel
```
The output will be found in `build/MinSizeRel/sphaira.nro`
## Credits ## Credits
@@ -56,7 +93,7 @@ see `assets/romfs/assoc/` for more examples of file assoc entries
- deko3d-nanovg - deko3d-nanovg
- libpulsar - libpulsar
- minIni - minIni
- gbatemp - GBATemp
- hb-appstore - hb-appstore
- haze - haze
- everyone who has contributed to this project! - Everyone who has contributed to this project!

View File

@@ -2,3 +2,4 @@
path=/retroarch/cores/fbneo_libretro_libnx.nro path=/retroarch/cores/fbneo_libretro_libnx.nro
supported_extensions=zip|7z|cue|ccd supported_extensions=zip|7z|cue|ccd
database=FBNeo - Arcade Games database=FBNeo - Arcade Games
use_base_name=true

View File

@@ -2,3 +2,4 @@
path=/retroarch/cores/mame2000_libretro_libnx.nro path=/retroarch/cores/mame2000_libretro_libnx.nro
supported_extensions=zip|7z supported_extensions=zip|7z
database=MAME 2000 database=MAME 2000
use_base_name=true

View File

@@ -2,3 +2,4 @@
path=/retroarch/cores/mame2003_libretro_libnx.nro path=/retroarch/cores/mame2003_libretro_libnx.nro
supported_extensions=zip supported_extensions=zip
database=MAME 2003 database=MAME 2003
use_base_name=true

View File

@@ -2,3 +2,4 @@
path=/retroarch/cores/mame2003_plus_libretro_libnx.nro path=/retroarch/cores/mame2003_plus_libretro_libnx.nro
supported_extensions=zip supported_extensions=zip
database=MAME 2003-Plus database=MAME 2003-Plus
use_base_name=true

View File

@@ -2,3 +2,4 @@
path=/retroarch/cores/xrick_libretro_libnx.nro path=/retroarch/cores/xrick_libretro_libnx.nro
supported_extensions=zip supported_extensions=zip
database=Rick Dangerous database=Rick Dangerous
use_base_name=true

View File

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

View File

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

View File

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

View File

@@ -1,75 +1,84 @@
{ {
"[Applet Mode]": "[Applet-Modus]", "[Applet Mode]": " | Applet Modus |",
"No Internet": "Keine Internetverbindung", "No Internet": "Kein Internet",
"Files": "Dateien", "Files": "Dateien",
"Apps": "Apps", "Apps": "hb-Apps",
"Store": "Store", "Store": "hb-Store",
"Menu": "Menü", "Menu": "Menü",
"Options": "Optionen", "Options": "Optionen",
"OK": "OK", "OK": "OK",
"Back": "Zurück", "Back": "Zurück",
"Select": "Auswählen", "Select": "Auswählen",
"Open": "Öffnen", "Open": "Öffne",
"Launch": "Starten", "Launch": "Starte",
"Info": "Info", "Info": "Info",
"Install": "Installieren", "Install": "Installieren",
"Delete": "Löschen", "Delete": "Löschen",
"Restart": "Neustart", "Restart": "Neustart",
"Changelog": "Changelog", "Changelog": "Neuerungen",
"Details": "Details", "Details": "Details",
"Update": "Update", "Update": "Update",
"Remove": "Entfernen", "Remove": "Entfernen",
"Restore": "Wiederherstellen",
"Download": "Download", "Download": "Download",
"Next Page": "Nächste Seite", "Next Page": "Nächste Seite",
"Prev Page": "Vorherige Seite", "Prev Page": "Vorherige Seite",
"Unstar": "Favorit entfernen", "Unstar": "Kein Favorit",
"Star": "Favorit", "Star": "Favorit",
"System memory": "System-Speicher", "System memory": "NAND Systemspeicher",
"microSD card": "microSD-Karte", "microSD card": "SD-Karte",
"Sd": "SD-Karte | Root-Verzeichnis",
"Image System memory": "Album | NAND Systemspeicher",
"Image microSD card": "Album | SD-Karte",
"Slow": "Niedrig",
"Normal": "Mittel",
"Fast": "Hoch",
"Yes": "Ja", "Yes": "Ja",
"No": "Nein", "No": "Nein",
"Enabled": "Aktiviert", "Enabled": "An",
"Disabled": "Deaktiviert", "Disabled": "Aus",
"Sort By": "Sortieren nach", "Sort By": "Sortierung",
"Sort Options": "Sortieroptionen", "Sort Options": " Sortierung | Optionen",
"Filter": "Filter", "Filter": "Rubrik",
"Sort": "Sortieren", "Sort": "Sortiert nach",
"Order": "Reihenfolge", "Order": "Anordnung",
"Search": "Suchen", "Search": "Suchen",
"Updated": "Aktualisiert", "Updated": "zuletzt aktualisiert",
"Updated (Star)": "Aktualisiert (Favoriten)", "Updated (Star)": "Favorit | zuletzt aktualisiert",
"Downloads": "Downloads", "Downloads": "Downloads",
"Size": "Größe", "Size": "Größe",
"Size (Star)": "Größe (Favoriten)", "Size (Star)": "Favorit | Größe",
"Alphabetical": "Alphabetisch", "Alphabetical": "Name",
"Alphabetical (Star)": "Alphabetisch (Favoriten)", "Alphabetical (Star)": "Favorit | Name",
"Likes": "Likes", "Likes": "Beliebtheit",
"ID": "ID", "ID": "Theme | Paket ID",
"Decending": "Absteigend", "Descending": "Absteigend",
"Descending (down)": "Absteigend", "Descending (down)": "Absteigend",
"Desc": "Abst.", "Desc": "",
"Ascending": "Aufsteigend", "Ascending": "Aufsteigend",
"Ascending (Up)": "Aufsteigend", "Ascending (Up)": "Aufsteigend",
"Asc": "Aufst.", "Asc": "",
"Menu Options": "Menü-Optionen", "Menu Options": " Menü | Optionen",
"Header": "Header", "Theme": "Themes",
"Theme": "Theme", "Theme Options": " Themes | Optionen",
"Theme Options": "Theme-Optionen", "Select Theme": "Theme wählen",
"Select Theme": "Theme auswählen",
"Shuffle": "Zufällig", "Shuffle": "Zufällig",
"Music": "Musik", "Music": "Musik",
"Network": "Netzwerk", "12 Hour Time": "12-Std Zeitformat",
"Network Options": "Netzwerk-Optionen", "Network": "Konnektivität",
"Nxlink": "Nxlink", "Network Options": "Konnektivität | Optionen",
"Nxlink Connected": "Nxlink verbunden", "Ftp": "FTP",
"Nxlink Upload": "Nxlink Upload", "Mtp": "MTP",
"Nxlink Finished": "Nxlink abgeschlossen", "Nxlink": "NXLink",
"Switch-Handheld!": "Switch-Handheld!", "Nxlink Connected": "NXLink | Verbunden",
"Switch-Docked!": "Switch-Dock-Modus!", "Nxlink Upload": "NXLink | wird hochgeladen...",
"Nxlink Finished": "NXLink | Hochladen beendet",
"Switch-Handheld!": "Handheld!",
"Switch-Docked!": "Angedockt!",
"Language": "Sprache", "Language": "Sprache",
"Auto": "Auto", "Auto": "Systemsprache",
"English": "English", "English": "English",
"Japanese": "日本語", "Japanese": "日本語",
"French": "Français", "French": "Français",
@@ -78,51 +87,55 @@
"Spanish": "Español", "Spanish": "Español",
"Chinese": "中文", "Chinese": "中文",
"Korean": "한국어", "Korean": "한국어",
"Dutch": "Dutch", "Dutch": "Nederlands",
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Logging": "Logging", "Vietnamese": "tiếng Việt",
"Replace hbmenu on exit": "hbmenu beim Beenden ersetzen", "Logging": "Protokollieren",
"Misc": "Sonstiges", "Replace hbmenu on exit": "hbmenu durch sphaira ersetzen",
"Misc Options": "Weitere Optionen", "Misc": "Extras",
"Web": "Web", "Misc Options": " Extras | Optionen",
"Web": "WEB Browser",
"Install forwarders": "Forwarder installieren", "Install forwarders": "Forwarder installieren",
"Install location": "Installationsort", "Install location": "Einhängepunkt",
"Show install warning": "Installationswarnung anzeigen", "Show install warning": "Warnungen anzeigen",
"Text scroll speed": "Laufschrift Tempo",
"FileBrowser": "Datei-Browser", "FileBrowser": "Datei-Manager",
"%zd files": "%zd Dateien", "%zd files": "%zd Dateien",
"%zd dirs": "%zd Ordner", "%zd dirs": "%zd Ordner",
"File Options": "Datei-Optionen", "File Options": "Datei - Ordner | Optionen",
"Show Hidden": "Versteckte anzeigen", "Show Hidden": "Versteckte zeigen",
"Folders First": "Ordner zuerst", "Folders First": "Ordner zuerst",
"Hidden Last": "Versteckte zuletzt", "Hidden Last": "Versteckte zuletzt",
"Cut": "Ausschneiden", "Cut": "Ausschneiden",
"Copy": "Kopieren", "Copy": "Kopieren",
"Paste": "Einfügen", "Paste": "Einfügen",
"Paste ": "Einfügen ", "Paste ": "Einfügen von: ",
" file(s)?": " Datei(en)?", " file(s)?": " Datei/en?",
"Rename": "Umbenennen", "Rename": "Umbenennen",
"Set New File Name": "Neuen Dateinamen eingeben", "Set New File Name": "Neuen Dateinamen festlegen",
"Advanced": "Erweitert", "Advanced": "Erweitert...",
"Advanced Options": "Erweiterte Optionen", "Advanced Options": " Erweitert | Optionen",
"Create File": "Datei erstellen", "Create File": "Neue Datei",
"Set File Name": "Dateinamen eingeben", "Set File Name": "Dateiname festlegen",
"Create Folder": "Ordner erstellen", "Create Folder": "Neuer Ordner",
"Set Folder Name": "Ordnernamen eingeben", "Set Folder Name": "Ordner umbenennen",
"View as text (unfinished)": "Als Text anzeigen (Beta)", "View as text (unfinished)": "Als Text anzeigen",
"Empty...": "Leer...", "Ignore read only": "Schreibschutz umgehen?",
"Open with DayBreak?": "Mit DayBreak öffnen?", "Mount": "Einhängen",
"Launch ": "Starten ", "Empty...": "Keine Daten...",
"Launch option for: ": "Startoption für: ", "Open with DayBreak?": "Mit Daybreak öffnen?",
"Select launcher for: ": "Launcher auswählen für: ", "Launch ": "Starte ",
"Launch option for: ": "Start Option für: ",
"Select launcher for: ": "Wähle Launcher für: ",
"Homebrew": "Homebrew", "Homebrew": "hbmenu",
"Homebrew Options": "Homebrew-Optionen", "Homebrew Options": " hbmenu | Optionen",
"Hide Sphaira": "Sphaira ausblenden", "Hide Sphaira": "Verstecke sphaira",
"Install Forwarder": "Forwarder installieren", "Install Forwarder": "Forwarder installieren",
"WARNING: Installing forwarders will lead to a ban!": "WARNUNG: Installation von Forwardern führt zum Ban!", "WARNING: Installing forwarders will lead to a ban!": "Installiere Forwarder-NSP´s mit VORSICHT.\nEs erhöht das Risiko eines Konsolen-Banns!",
"Installing Forwarder": "Installiere Forwarder", "Installing Forwarder": "Installiere Forwarder",
"Creating Program": "Erstelle Programm", "Creating Program": "Erstelle Programm",
"Creating Control": "Erstelle Control", "Creating Control": "Erstelle Control",
@@ -131,26 +144,26 @@
"Updating ncm databse": "Aktualisiere NCM-Datenbank", "Updating ncm databse": "Aktualisiere NCM-Datenbank",
"Pushing application record": "Übertrage Anwendungsdaten", "Pushing application record": "Übertrage Anwendungsdaten",
"Installed!": "Installiert!", "Installed!": "Installiert!",
"Failed to install forwarder": "Forwarder-Installation fehlgeschlagen", "Failed to install forwarder": "Fehler beim installieren des Forwarders",
"Unstarred ": "Favorit entfernt ", "Unstarred ": "Favorit entfernt ",
"Starred ": "Favorit hinzugefügt ", "Starred ": "Favorit ",
"AppStore": "AppStore", "AppStore": "hb-AppStore",
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortierung: %s | Reihenfolge: %s", "Filter: %s | Sort: %s | Order: %s": "Rubrik: %s | Sort.nach.: %s | Ordnung: %s",
"AppStore Options": "AppStore-Optionen", "AppStore Options": " hb-AppStore | Optionen",
"All": "Alle", "All": "Alles anzeigen",
"Games": "Spiele", "Games": "Spiele",
"Emulators": "Emulatoren", "Emulators": "Emulatoren",
"Tools": "Tools", "Tools": "Tools",
"Themes": "Themes", "Themes": "Themes",
"Legacy": "Legacy", "Legacy": "Älteres",
"version: %s": "Version: %s", "version: %s": "Version: %s",
"updated: %s": "Aktualisiert: %s", "updated: %s": "Letztes Update am: %s",
"category: %s": "Kategorie: %s", "category: %s": "Rubrik: %s",
"extracted: %.2f MiB": "Entpackt: %.2f MiB", "extracted: %.2f MiB": "Größe: %.2f MiB",
"app_dls: %s": "Downloads: %s", "app_dls: %s": "Anzahl Downloads: %s",
"More by Author": "Mehr vom Entwickler", "More by Author": "Weitere Apps des Entwicklers",
"Leave Feedback": "Feedback geben", "Leave Feedback": "Feedback hinterlassen",
"Irs": "IR-Sensor", "Irs": "IR-Sensor",
"Ambient Noise Level: ": "Umgebungsrauschen: ", "Ambient Noise Level: ": "Umgebungsrauschen: ",
@@ -190,41 +203,55 @@
"External Light Filter": "Externes Lichtfilter", "External Light Filter": "Externes Lichtfilter",
"Load Default": "Standard laden", "Load Default": "Standard laden",
"Themezer": "Themezer", "Themezer": "Themezer | NX Themes",
"Themezer Options": "Themezer-Optionen", "Themezer Options": " Themezer | Optionen",
"Nsfw": "NSFW", "Nsfw": "NSFW",
"Page": "Seite", "Page": "Seiten Nr. wählen ",
"Page %zu / %zu": "Seite %zu / %zu", "Page %zu / %zu": " %zu / %zu",
"Enter Page Number": "Seitenzahl eingeben", "Enter Page Number": "Zu Seite Nr.: ___",
"Bad Page": "Ungültige Seite", "Bad Page": "Seite nicht gefunden",
"Download theme?": "Theme herunterladen?", "Download theme?": "Theme herunterladen?",
"Installing ": "Installiere ", "GitHub": "GitHub",
"Uninstalling ": "Deinstalliere ", "Downloading json": "Lade JSON-File",
"Deleting ": "Lösche ", "Select asset to download for ": "Wähle Asset für den Download von ",
"Deleting": "Lösche",
"Pasting ": "Füge ein ", "Installing ": "Installiert wird: ",
"Pasting": "Füge ein", "Uninstalling ": "Deinstalliert wird: ",
"Removing ": "Entferne ", "Deleting ": "Gelöscht wird: ",
"Scanning ": "Scanne ", "Deleting": "Gelöscht wurde:",
"Creating ": "Erstelle ", "Pasting ": "Eingefügt wird: ",
"Copying ": "Kopiere ", "Pasting": "Eingefügt wurde:",
"Trying to load ": "Lade ", "Removing ": "Entfernt wird: ",
"Downloading ": "Lade herunter ", "Scanning ": "Gescannt wird: ",
"Checking MD5": "Prüfe MD5", "Creating ": "Erstellt wird: ",
"Loading...": "Lade...", "Copying ": "Kopiert wird: ",
"Loading": "Lade", "Trying to load ": "Versucht zu laden wird: ",
"Empty!": "Leer!", "Downloading ": "Heruntergeladen wird: ",
"Downloaded ": "Heruntergeladen wurde: ",
"Removed ": "Entfernt wurde: ",
"Checking MD5": "Checke MD5 Prüfsumme",
"Loading...": "Wird geladen...",
"Loading": "Wird geladen",
"Empty!": "Keine Daten!",
"Not Ready...": "Nicht bereit...", "Not Ready...": "Nicht bereit...",
"Error loading page!": "Fehler beim Laden!", "Error loading page!": "Ladefehler!",
"Update avaliable: ": "Update verfügbar: ", "Update avaliable: ": "Update verfügbar: ",
"Download update: ": "Update herunterladen: ", "Download update: ": " Herunterladen des Updates: ",
"Updated to ": "Aktualisiert auf ", "Updated to ": "Aktualisiert auf: ",
"Restart Sphaira?": "Sphaira neustarten?", "Press OK to restart Sphaira": "Drücke OK um sphaira erneut zustarten",
"Failed to download update": "Update-Download fehlgeschlagen", "Restart Sphaira?": "sphaira erneut starten?",
"Delete Selected files?": "Ausgewählte Dateien löschen?", "Failed to download update": "Herunterladen des Updates fehlgeschlagen!",
"Completely remove ": "Vollständig entfernen ", "Restore hbmenu?": "hbmenu wiederherstellen?",
"Are you sure you want to delete ": "Wirklich löschen ", "Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Die Datei /switch/hbmenu.nro fehlt.\nInstalliere hbmenu über den hb-AppStore.",
"Are you sure you wish to cancel?": "Wirklich abbrechen?", "Failed to restore hbmenu, please re-download hbmenu": "Fehler, hbmenu nicht wiederhergrstellt!\nInstalliere hbmenu über den hb-AppStore.",
"Failed to restore hbmenu, using sphaira instead": "Fehler, hbmenu nicht wiederhergrstellt!\nVerwende weiter sphaira",
"Restored hbmenu, closing sphaira": "hbmenu wurde wiederhergestellt, schließe sphaira",
"Restored hbmenu": "hbmenu wurde wiederhergestellt",
"Delete Selected files?": "Ausgewähle Dateien löschen?",
"Completely remove ": "Komplett gelöscht wird: ",
"Are you sure you want to delete ": "Bist du sicher zu löschen? Bestätige Löschung von: ",
"Are you sure you wish to cancel?": "Bist du sicher dass du abbrechen willst?",
"Audio disabled due to suspended game": "Audio deaktivert wegen Spielabbruch",
"If this message appears repeatedly, please open an issue.": "Bei wiederholtem Auftreten bitte Issue erstellen." "If this message appears repeatedly, please open an issue.": "Bei wiederholtem Auftreten bitte Issue erstellen."
} }

View File

@@ -19,6 +19,7 @@
"Details": "Details", "Details": "Details",
"Update": "Update", "Update": "Update",
"Remove": "Remove", "Remove": "Remove",
"Restore": "Restore",
"Download": "Download", "Download": "Download",
"Next Page": "Next Page", "Next Page": "Next Page",
"Prev Page": "Prev Page", "Prev Page": "Prev Page",
@@ -26,6 +27,12 @@
"Star": "Star", "Star": "Star",
"System memory": "System memory", "System memory": "System memory",
"microSD card": "microSD card", "microSD card": "microSD card",
"Sd": "Sd",
"Image System memory": "Image System memory",
"Image microSD card": "Image microSD card",
"Slow": "Slow",
"Normal": "Normal",
"Fast": "Fast",
"Yes": "Yes", "Yes": "Yes",
"No": "No", "No": "No",
"Enabled": "Enabled", "Enabled": "Enabled",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "Alphabetical (Star)", "Alphabetical (Star)": "Alphabetical (Star)",
"Likes": "Likes", "Likes": "Likes",
"ID": "ID", "ID": "ID",
"Decending": "Decending", "Descending": "Descending",
"Descending (down)": "Descending (down)", "Descending (down)": "Descending (down)",
"Desc": "Desc", "Desc": "Desc",
"Ascending": "Ascending", "Ascending": "Ascending",
@@ -54,14 +61,16 @@
"Asc": "Asc", "Asc": "Asc",
"Menu Options": "Menu Options", "Menu Options": "Menu Options",
"Header": "Header",
"Theme": "Theme", "Theme": "Theme",
"Theme Options": "Theme Options", "Theme Options": "Theme Options",
"Select Theme": "Select Theme", "Select Theme": "Select Theme",
"Shuffle": "Shuffle", "Shuffle": "Shuffle",
"Music": "Music", "Music": "Music",
"12 Hour Time": "12 Hour Time",
"Network": "Network", "Network": "Network",
"Network Options": "Network Options", "Network Options": "Network Options",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink Connected", "Nxlink Connected": "Nxlink Connected",
"Nxlink Upload": "Nxlink Upload", "Nxlink Upload": "Nxlink Upload",
@@ -82,6 +91,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Logging", "Logging": "Logging",
"Replace hbmenu on exit": "Replace hbmenu on exit", "Replace hbmenu on exit": "Replace hbmenu on exit",
"Misc": "Misc", "Misc": "Misc",
@@ -90,6 +100,7 @@
"Install forwarders": "Install forwarders", "Install forwarders": "Install forwarders",
"Install location": "Install location", "Install location": "Install location",
"Show install warning": "Show install warning", "Show install warning": "Show install warning",
"Text scroll speed": "Text scroll speed",
"FileBrowser": "FileBrowser", "FileBrowser": "FileBrowser",
"%zd files": "%zd files", "%zd files": "%zd files",
@@ -112,6 +123,8 @@
"Create Folder": "Create Folder", "Create Folder": "Create Folder",
"Set Folder Name": "Set Folder Name", "Set Folder Name": "Set Folder Name",
"View as text (unfinished)": "View as text (unfinished)", "View as text (unfinished)": "View as text (unfinished)",
"Ignore read only": "Ignore read only",
"Mount": "Mount",
"Empty...": "Empty...", "Empty...": "Empty...",
"Open with DayBreak?": "Open with DayBreak?", "Open with DayBreak?": "Open with DayBreak?",
"Launch ": "Launch ", "Launch ": "Launch ",
@@ -199,6 +212,10 @@
"Bad Page": "Bad Page", "Bad Page": "Bad Page",
"Download theme?": "Download theme?", "Download theme?": "Download theme?",
"GitHub": "GitHub",
"Downloading json": "Downloading json",
"Select asset to download for ": "Select asset to download for ",
"Installing ": "Installing ", "Installing ": "Installing ",
"Uninstalling ": "Uninstalling ", "Uninstalling ": "Uninstalling ",
"Deleting ": "Deleting ", "Deleting ": "Deleting ",
@@ -211,6 +228,8 @@
"Copying ": "Copying ", "Copying ": "Copying ",
"Trying to load ": "Trying to load ", "Trying to load ": "Trying to load ",
"Downloading ": "Downloading ", "Downloading ": "Downloading ",
"Downloaded ": "Downloaded ",
"Removed ": "Removed ",
"Checking MD5": "Checking MD5", "Checking MD5": "Checking MD5",
"Loading...": "Loading...", "Loading...": "Loading...",
"Loading": "Loading", "Loading": "Loading",
@@ -220,11 +239,19 @@
"Update avaliable: ": "Update avaliable: ", "Update avaliable: ": "Update avaliable: ",
"Download update: ": "Download update: ", "Download update: ": "Download update: ",
"Updated to ": "Updated to ", "Updated to ": "Updated to ",
"Press OK to restart Sphaira": "Press OK to restart Sphaira",
"Restart Sphaira?": "Restart Sphaira?", "Restart Sphaira?": "Restart Sphaira?",
"Failed to download update": "Failed to download update", "Failed to download update": "Failed to download update",
"Restore hbmenu?": "Restore hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Failed to restore hbmenu, please re-download hbmenu",
"Failed to restore hbmenu, using sphaira instead": "Failed to restore hbmenu, using sphaira instead",
"Restored hbmenu, closing sphaira": "Restored hbmenu, closing sphaira",
"Restored hbmenu": "Restored hbmenu",
"Delete Selected files?": "Delete Selected files?", "Delete Selected files?": "Delete Selected files?",
"Completely remove ": "Completely remove ", "Completely remove ": "Completely remove ",
"Are you sure you want to delete ": "Are you sure you want to delete ", "Are you sure you want to delete ": "Are you sure you want to delete ",
"Are you sure you wish to cancel?": "Are you sure you wish to cancel?", "Are you sure you wish to cancel?": "Are you sure you wish to cancel?",
"Audio disabled due to suspended game": "Audio disabled due to suspended game",
"If this message appears repeatedly, please open an issue.": "If this message appears repeatedly, please open an issue." "If this message appears repeatedly, please open an issue.": "If this message appears repeatedly, please open an issue."
} }

View File

@@ -9,23 +9,30 @@
"OK": "OK", "OK": "OK",
"Back": "Atrás", "Back": "Atrás",
"Select": "Seleccionar", "Select": "Seleccionar",
"Open": "Abierto", "Open": "Abrir",
"Launch": "Ejecutar", "Launch": "Ejecutar",
"Info": "Información", "Info": "Información",
"Install": "Instalar", "Install": "Instalar",
"Delete": "Borrar", "Delete": "Borrar",
"Restart": "", "Restart": "Reiniciar",
"Changelog": "Log de Cambios", "Changelog": "Log de cambios",
"Details": "Detalles", "Details": "Detalles",
"Update": "Actualizar", "Update": "Actualizar",
"Remove": "Borrar", "Remove": "Borrar",
"Restore": "Restaurar",
"Download": "Descargar", "Download": "Descargar",
"Next Page": "Página siguiente", "Next Page": "Página siguiente",
"Prev Page": "Página anterior", "Prev Page": "Página anterior",
"Unstar": "", "Unstar": "Quitar favorito",
"Star": "", "Star": "Favorito",
"System memory": "", "System memory": "Memoria de sistema",
"microSD card": "", "microSD card": "microSD",
"Sd": "SD",
"Image System memory": "Imagen memoria interna",
"Image microSD card": "Imagen tarjeta microSD",
"Slow": "Lento",
"Normal": "Normal",
"Fast": "Rápido",
"Yes": "Sí", "Yes": "Sí",
"No": "No", "No": "No",
"Enabled": "Activado", "Enabled": "Activado",
@@ -38,36 +45,38 @@
"Order": "Orden", "Order": "Orden",
"Search": "Buscar", "Search": "Buscar",
"Updated": "Actualizado", "Updated": "Actualizado",
"Updated (Star)": "Actualizado (Star)", "Updated (Star)": "Actualizado (favorito)",
"Downloads": "Descargas", "Downloads": "Descargas",
"Size": "Tamaño", "Size": "Tamaño",
"Size (Star)": "Tamaño (Star)", "Size (Star)": "Tamaño (favorito)",
"Alphabetical": "Alfabético", "Alphabetical": "Alfabético",
"Alphabetical (Star)": "Alfabético (Star)", "Alphabetical (Star)": "Alfabético (favorito)",
"Likes": "Me Gusta", "Likes": "Me Gusta",
"ID": "ID", "ID": "ID",
"Decending": "Descendente", "Descending": "Descendente",
"Descending (down)": "Descendente (abajo)", "Descending (down)": "Descendente (abajo)",
"Desc": "Descendente", "Desc": "Descendente",
"Ascending": "Ascendente", "Ascending": "Ascendente",
"Ascending (Up)": "Ascendente (arriba)", "Ascending (Up)": "Ascendente (arriba)",
"Asc": "Ascendente", "Asc": "Ascendente",
"Menu Options": "Opciones de Menú", "Menu Options": "Opciones de menú",
"Header": "Encabezamiento",
"Theme": "Tema", "Theme": "Tema",
"Theme Options": "Opciones de Tema", "Theme Options": "Opciones de tema",
"Select Theme": "Seleccionar Tema", "Select Theme": "Seleccionar tema",
"Shuffle": "Barajar", "Shuffle": "Barajar",
"Music": "Música", "Music": "Música",
"12 Hour Time": "",
"Network": "Red", "Network": "Red",
"Network Options": "Opciones de Red", "Network Options": "Opciones de red",
"Nxlink": "Nxlink", "Ftp": "FTP",
"Nxlink Connected": "Nxlink Conectado", "Mtp": "MTP",
"Nxlink Upload": "Nxlink Subida", "Nxlink": "NXlink",
"Nxlink Finished": "Nxlink Finalizado", "Nxlink Connected": "NXlink conectado",
"Switch-Handheld!": "", "Nxlink Upload": "NXlink subida",
"Switch-Docked!": "", "Nxlink Finished": "NXlink finalizado",
"Switch-Handheld!": "¡Switch-Modo-Portátil!",
"Switch-Docked!": "¡Switch-Modo-TV!",
"Language": "Idioma", "Language": "Idioma",
"Auto": "Automático", "Auto": "Automático",
"English": "English", "English": "English",
@@ -82,43 +91,47 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Logging": "Explotación florestal", "Vietnamese": "Vietnamese",
"Replace hbmenu on exit": "Reemplazar hbmenu al salir", "Logging": "Registro",
"Replace hbmenu on exit": "Reemplazar hbmenu",
"Misc": "Varios", "Misc": "Varios",
"Misc Options": "Opciones varias", "Misc Options": "Opciones varias",
"Web": "Web", "Web": "Web",
"Install forwarders": "", "Install forwarders": "Instalar forwarders",
"Install location": "", "Install location": "Dispositivo de instalación",
"Show install warning": "", "Show install warning": "Precaución de instalación",
"Text scroll speed": "Velocidad de scroll",
"FileBrowser": "Explorador de Archivos", "FileBrowser": "Explorador de archivos",
"%zd files": "%zd files", "%zd files": "%zd archivos",
"%zd dirs": "%zd dirs", "%zd dirs": "%zd carpetas",
"File Options": "Opciones de Tema", "File Options": "Opciones de archivo",
"Show Hidden": "Mostrar Oculto", "Show Hidden": "Mostrar archivos ocultos",
"Folders First": "Carpetas primero", "Folders First": "Carpetas primero",
"Hidden Last": "Oculto último", "Hidden Last": "Ocultos al final",
"Cut": "Cortar ", "Cut": "Cortar",
"Copy": "Copiar", "Copy": "Copiar",
"Paste": "Pegar", "Paste": "Pegar",
"Paste ": "Pegar ", "Paste ": "Pegar ",
" file(s)?": " ¿archivo(s)?", " file(s)?": " ¿archivo(s)?",
"Rename": "Renombrar", "Rename": "Renombrar",
"Set New File Name": "Establecer Nuevo Nombre de Archivo", "Set New File Name": "Establecer nuevo nombre de archivo",
"Advanced": "Avanzado", "Advanced": "Avanzado",
"Advanced Options": "Opciones Avanzadas", "Advanced Options": "Opciones avanzadas",
"Create File": "Crear archivo", "Create File": "Crear archivo",
"Set File Name": "Establecer Nombre de Archivo", "Set File Name": "Establecer nombre de archivo",
"Create Folder": "Crear carpeta", "Create Folder": "Crear carpeta",
"Set Folder Name": "Establecer Nombre de Carpeta", "Set Folder Name": "Establecer nombre de carpeta",
"View as text (unfinished)": "Ver como texto (sin terminar)", "View as text (unfinished)": "Ver como texto (sin terminar)",
"Ignore read only": "Ignorar sólo lectura",
"Mount": "Montar",
"Empty...": "Vacío...", "Empty...": "Vacío...",
"Open with DayBreak?": "Abrir con DayBreak", "Open with DayBreak?": "¿Abrir con DayBreak?",
"Launch ": "", "Launch ": "Abrir ",
"Launch option for: ": "Opción de ejecución para: ", "Launch option for: ": "Opción de abrir con: ",
"Select launcher for: ": "", "Select launcher for: ": "Seleccionar abrir con: ",
"Homebrew": "Honebrew", "Homebrew": "Homebrew",
"Homebrew Options": "Opciones de Homebrew", "Homebrew Options": "Opciones de Homebrew",
"Hide Sphaira": "Ocultar Sphaira", "Hide Sphaira": "Ocultar Sphaira",
"Install Forwarder": "Instalar Forwarder", "Install Forwarder": "Instalar Forwarder",
@@ -128,16 +141,16 @@
"Creating Control": "Creando Control", "Creating Control": "Creando Control",
"Creating Meta": "Creando Meta", "Creating Meta": "Creando Meta",
"Writing Nca": "Creando NCA", "Writing Nca": "Creando NCA",
"Updating ncm databse": "Actualizando base de datos ncm ", "Updating ncm databse": "Actualizando base de datos ncm",
"Pushing application record": "", "Pushing application record": "Registro de aplicación",
"Installed!": "¡Instalado!", "Installed!": "¡Instalado!",
"Failed to install forwarder": "Fallo al instalar forwarder", "Failed to install forwarder": "Fallo al instalar forwarder",
"Unstarred ": "", "Unstarred ": "Quitar Favorito",
"Starred ": "", "Starred ": "Favorito",
"AppStore": "AppStore", "AppStore": "Tienda",
"Filter: %s | Sort: %s | Order: %s": "Filtrar: %s | Clasificar: %s | Orden: %s", "Filter: %s | Sort: %s | Order: %s": "Filtrar: %s | Clasificar: %s | Orden: %s",
"AppStore Options": "Opciones de la AppStore", "AppStore Options": "Opciones de la Tienda",
"All": "Todo", "All": "Todo",
"Games": "Juegos", "Games": "Juegos",
"Emulators": "Emuladores", "Emulators": "Emuladores",
@@ -145,60 +158,64 @@
"Themes": "Temas", "Themes": "Temas",
"Legacy": "Legado", "Legacy": "Legado",
"version: %s": "version: %s", "version: %s": "version: %s",
"updated: %s": "updated: %s", "updated: %s": "actualizado: %s",
"category: %s": "category: %s", "category: %s": "categoría: %s",
"extracted: %.2f MiB": "extracted: %.2f MiB", "extracted: %.2f MiB": "extraído: %.2f MiB",
"app_dls: %s": "app_dls: %s", "app_dls: %s": "app_dls: %s",
"More by Author": "Mostrar mas del Autor", "More by Author": "Mostrar mas del Autor",
"Leave Feedback": "Dejar Mensaje", "Leave Feedback": "Dejar Mensaje",
"Irs": "IRS", "Irs": "IRS",
"Ambient Noise Level: ": "Nivel de Ruido", "Ambient Noise Level: ": "Nivel de Ruido Ambiente",
"Controller": "Control", "Controller": "Control",
"Pad ": "Almohadilla ", "Pad ": "GamePad ",
" (Available)": " (Disponible)", " (Available)": " (Disponible)",
" (Unsupported)": "", " (Unsupported)": "(No Compatible)",
" (Unconnected)": " (Desconectado)", " (Unconnected)": " (Desconectado)",
"HandHeld": "Portátil", "HandHeld": "Portátil",
"Rotation": "Rotación", "Rotation": "Rotación",
"0 (Sideways)": "0 (De Lado)", "0 (Sideways)": "0° (De lado)",
"90 (Flat)": "90 (Plano)", "90 (Flat)": "90° (Plano)",
"180 (-Sideways)": "180 (-De Lado)", "180 (-Sideways)": "180° (De lado)",
"270 (Upside down)": "270 (Al Revés)", "270 (Upside down)": "270° (Al revés)",
"Colour": "Color", "Colour": "Color",
"Grey": "Gris", "Grey": "Gris",
"Ironbow": "Paleta Térmica", "Ironbow": "Paleta térmica",
"Green": "Verde", "Green": "Verde",
"Red": "Rojo", "Red": "Rojo",
"Blue": "Azul", "Blue": "Azul",
"Light Target": "Objetivo de Luz", "Light Target": "Objetivo de luz",
"All leds": "Todos los leds", "All leds": "Todos los leds",
"Bright group": "Grupo brillante", "Bright group": "Grupo brillo",
"Dim group": "Grupo tenue", "Dim group": "Grupo tenue",
"None": "Ninguno", "None": "Ninguno",
"Gain": "Ganancia", "Gain": "Ganancia",
"Negative Image": "Imagen Negativa", "Negative Image": "Imagen negativa",
"Normal image": "Imagen Normal", "Normal image": "Imagen normal",
"Negative image": "Imagen Negativa", "Negative image": "Imagen negativa",
"Format": "Formato", "Format": "Formato",
"320x240": "320×240", "320x240": "320×240",
"160x120": "160×120", "160x120": "160×120",
"80x60": "80×60", "80x60": "80×60",
"40x30": "40×30", "40x30": "40×30",
"20x15": "20×15", "20x15": "20×15",
"Trimming Format": "Formato de Recorte", "Trimming Format": "Formato de recorte",
"External Light Filter": "Filtro de Luz Externa", "External Light Filter": "Filtro de luz externa",
"Load Default": "Cargar Predeterminado", "Load Default": "Cargar predeterminado",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Opciones de Themezer", "Themezer Options": "Opciones de Themezer",
"Nsfw": "NSFW", "Nsfw": "NSFW",
"Page": "Página", "Page": "Página",
"Page %zu / %zu": "Page %zu / %zu", "Page %zu / %zu": "Pág. %zu / %zu",
"Enter Page Number": "Ingresar Número de Página", "Enter Page Number": "Ingresar Número de Página",
"Bad Page": "Página Errónea", "Bad Page": "Página Errónea",
"Download theme?": "¿Descargar Tema?", "Download theme?": "¿Descargar Tema?",
"GitHub": "GitHub",
"Downloading json": "Descargando json",
"Select asset to download for ": "Seleccionar recurso a descargar para ",
"Installing ": "Instalando ", "Installing ": "Instalando ",
"Uninstalling ": "Desinstalando ", "Uninstalling ": "Desinstalando ",
"Deleting ": "Borrando ", "Deleting ": "Borrando ",
@@ -209,22 +226,32 @@
"Scanning ": "Escaneando ", "Scanning ": "Escaneando ",
"Creating ": "Creando ", "Creating ": "Creando ",
"Copying ": "Copiando ", "Copying ": "Copiando ",
"Trying to load ": "", "Trying to load ": "Intentando cargar ",
"Downloading ": "Descargando ", "Downloading ": "Descargando ",
"Checking MD5": "Chqueando MD5", "Downloaded ": "Descargado ",
"Removed ": "Removido ",
"Checking MD5": "Chequeando MD5",
"Loading...": "Cargando...", "Loading...": "Cargando...",
"Loading": "Cargando", "Loading": "Cargando",
"Empty!": "¡Vacío!", "Empty!": "¡Vacío!",
"Not Ready...": "No Listo Aún...", "Not Ready...": "No listo aún...",
"Error loading page!": "¡Error cargando la página!", "Error loading page!": "¡Error cargando la página!",
"Update avaliable: ": "Actualización disponible: ", "Update avaliable: ": "Actualización disponible: ",
"Download update: ": "Descargar actualización: ", "Download update: ": "Descargar actualización: ",
"Updated to ": "", "Updated to ": "Actualizado a ",
"Restart Sphaira?": "", "Press OK to restart Sphaira": "Presiona OK para reiniciar sphaira",
"Restart Sphaira?": "¿Reiniciar sphaira?",
"Failed to download update": "Fallo al descargar actualización", "Failed to download update": "Fallo al descargar actualización",
"Delete Selected files?": "¿Eliminar archivos Seleccionados?", "Restore hbmenu?": "¿Restaurar hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Fallo al encontrar /switch/hbmenu.nro\nUsar la Tienda para reinstalar hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Fallo al restaurar hbmenu, por favor volver a descargar hbmenu",
"Failed to restore hbmenu, using sphaira instead": "Fallo al restaurar hbmenu, se usará sphaira",
"Restored hbmenu, closing sphaira": "hbmenu restaurado, cerrando sphaira",
"Restored hbmenu": "hbmenu restaurado",
"Delete Selected files?": "¿Eliminar archivos seleccionados?",
"Completely remove ": "Eliminar completamente", "Completely remove ": "Eliminar completamente",
"Are you sure you want to delete ": "¿Estás seguro que quieres eliminar? ", "Are you sure you want to delete ": "¿Estás seguro que quieres eliminar? ",
"Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?", "Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?",
"If this message appears repeatedly, please open an issue.": "" "Audio disabled due to suspended game": "",
"If this message appears repeatedly, please open an issue.": "Si este mensaje aparece repetidamente, por favor abrir un 'issue'."
} }

View File

@@ -19,6 +19,7 @@
"Details": "Détails", "Details": "Détails",
"Update": "Mise à jour", "Update": "Mise à jour",
"Remove": "Supprimer", "Remove": "Supprimer",
"Restore": "Restaurer",
"Download": "Télécharger", "Download": "Télécharger",
"Next Page": "Page Suiv.", "Next Page": "Page Suiv.",
"Prev Page": "Page Préc.", "Prev Page": "Page Préc.",
@@ -26,6 +27,12 @@
"Star": "Ajouter aux favories", "Star": "Ajouter aux favories",
"System memory": "Mémoire système", "System memory": "Mémoire système",
"microSD card": "Carte microSD", "microSD card": "Carte microSD",
"Sd": "Sd",
"Image System memory": "Image de la mémoire System",
"Image microSD card": "Image de la Carte microSD",
"Slow": "Lent",
"Normal": "Normal",
"Fast": "Rapide",
"Yes": "Oui", "Yes": "Oui",
"No": "Non", "No": "Non",
"Enabled": "Activé(e)", "Enabled": "Activé(e)",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "Alphabétique (Favories)", "Alphabetical (Star)": "Alphabétique (Favories)",
"Likes": "Likes", "Likes": "Likes",
"ID": "ID", "ID": "ID",
"Decending": "Décroissant", "Descending": "Décroissant",
"Descending (down)": "Décroissant", "Descending (down)": "Décroissant",
"Desc": "Décroissant", "Desc": "Décroissant",
"Ascending": "Croissant", "Ascending": "Croissant",
@@ -54,14 +61,16 @@
"Asc": "Croissant", "Asc": "Croissant",
"Menu Options": "Options des Menus", "Menu Options": "Options des Menus",
"Header": "En-tête",
"Theme": "Thème", "Theme": "Thème",
"Theme Options": "Options de Thème", "Theme Options": "Options de Thème",
"Select Theme": "Choisir un Thème", "Select Theme": "Choisir un Thème",
"Shuffle": "Aléatoire", "Shuffle": "Aléatoire",
"Music": "Musique", "Music": "Musique",
"12 Hour Time": "Temps sur 12 heures",
"Network": "Réseau", "Network": "Réseau",
"Network Options": "Options Réseau", "Network Options": "Options Réseau",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink Connecté", "Nxlink Connected": "Nxlink Connecté",
"Nxlink Upload": "Nxlink téléversement", "Nxlink Upload": "Nxlink téléversement",
@@ -82,6 +91,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Journalisation", "Logging": "Journalisation",
"Replace hbmenu on exit": "Remplacer hbmenu quand quitté", "Replace hbmenu on exit": "Remplacer hbmenu quand quitté",
"Misc": "Divers", "Misc": "Divers",
@@ -90,6 +100,7 @@
"Install forwarders": "Installer les Forwarders", "Install forwarders": "Installer les Forwarders",
"Install location": "Emplacement d'installation", "Install location": "Emplacement d'installation",
"Show install warning": "Afficher l'avertissement d'installation", "Show install warning": "Afficher l'avertissement d'installation",
"Text scroll speed": "Vitesse de défilement du texte",
"FileBrowser": "Explorateur de Fichiers", "FileBrowser": "Explorateur de Fichiers",
"%zd files": "%zd fichiers", "%zd files": "%zd fichiers",
@@ -112,6 +123,8 @@
"Create Folder": "Créer un Dossier", "Create Folder": "Créer un Dossier",
"Set Folder Name": "Nommer Le Dossier", "Set Folder Name": "Nommer Le Dossier",
"View as text (unfinished)": "Afficher sous forme de texte (inachevé)", "View as text (unfinished)": "Afficher sous forme de texte (inachevé)",
"Ignore read only": "Ignorer lecture seule",
"Mount": "Monter",
"Empty...": "Vide...", "Empty...": "Vide...",
"Open with DayBreak?": "Ouvrir avec DayBreak?", "Open with DayBreak?": "Ouvrir avec DayBreak?",
"Launch ": "Lancer ", "Launch ": "Lancer ",
@@ -199,6 +212,10 @@
"Bad Page": "Page inexistante", "Bad Page": "Page inexistante",
"Download theme?": "Télécharger le thème?", "Download theme?": "Télécharger le thème?",
"GitHub": "GitHub",
"Downloading json": "Téléchargement du json",
"Select asset to download for ": "Sélectionner l'asset pour télécharger ",
"Installing ": "Installation ", "Installing ": "Installation ",
"Uninstalling ": "Désinstallation ", "Uninstalling ": "Désinstallation ",
"Deleting ": "Suppression ", "Deleting ": "Suppression ",
@@ -211,6 +228,8 @@
"Copying ": "Copie ", "Copying ": "Copie ",
"Trying to load ": "Tente de charger ", "Trying to load ": "Tente de charger ",
"Downloading ": "Téléchargement ", "Downloading ": "Téléchargement ",
"Downloaded ": "Téléchargé",
"Removed ": "Supprimé ",
"Checking MD5": "Vérification MD5", "Checking MD5": "Vérification MD5",
"Loading...": "Chargement...", "Loading...": "Chargement...",
"Loading": "Chargement", "Loading": "Chargement",
@@ -220,11 +239,19 @@
"Update avaliable: ": "Mise à jour disponible: ", "Update avaliable: ": "Mise à jour disponible: ",
"Download update: ": "Télécharger la mise à jour: ", "Download update: ": "Télécharger la mise à jour: ",
"Updated to ": "Mis à jour vers ", "Updated to ": "Mis à jour vers ",
"Press OK to restart Sphaira": "Appuyez sur OK pour redémarrer Sphaira",
"Restart Sphaira?": "Redémarrer Sphaira?", "Restart Sphaira?": "Redémarrer Sphaira?",
"Failed to download update": "Echec du téléchargement de la mise à jour", "Failed to download update": "Echec du téléchargement de la mise à jour",
"Restore hbmenu?": "Restaurer hbmenu?",
"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é",
"Delete Selected files?": "Supprimer les fichiers sélectionnés?", "Delete Selected files?": "Supprimer les fichiers sélectionnés?",
"Completely remove ": "Supprimer totalement ", "Completely remove ": "Supprimer totalement ",
"Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ", "Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ",
"Are you sure you wish to cancel?": "Souhaitez-vous vraiment annuler?", "Are you sure you wish to cancel?": "Souhaitez-vous vraiment annuler?",
"Audio disabled due to suspended game": "Audio désactivé à cause d'un jeu suspendu",
"If this message appears repeatedly, please open an issue.": "Si ce message apparait en boucle veuillez ouvrir une issue." "If this message appears repeatedly, please open an issue.": "Si ce message apparait en boucle veuillez ouvrir une issue."
} }

View File

@@ -1,31 +1,38 @@
{ {
"[Applet Mode]": "[Modalità applet]", "[Applet Mode]": "[Modalità applet]",
"No Internet": "Niente Internet", "No Internet": "Niente Internet",
"Files": "", "Files": "File",
"Apps": "", "Apps": "App",
"Store": "", "Store": "Store",
"Menu": "Menu", "Menu": "Menu",
"Options": "Opzioni", "Options": "Opzioni",
"OK": "", "OK": "OK",
"Back": "Indietro", "Back": "Indietro",
"Select": "", "Select": "Seleziona",
"Open": "Apri", "Open": "Apri",
"Launch": "Lancia", "Launch": "Lancia",
"Info": "Informazioni", "Info": "Informazioni",
"Install": "Installa", "Install": "Installa",
"Delete": "Elimina", "Delete": "Elimina",
"Restart": "", "Restart": "Riavvia",
"Changelog": "", "Changelog": "Patch notes",
"Details": "", "Details": "Dettagli",
"Update": "", "Update": "Aggiorna",
"Remove": "", "Remove": "Rimuovi",
"Restore": "Ripristina",
"Download": "Download", "Download": "Download",
"Next Page": "Pagina successiva", "Next Page": "Pagina successiva",
"Prev Page": "Pagina precedente", "Prev Page": "Pagina precedente",
"Unstar": "", "Unstar": "Rimuovi dai preferiti",
"Star": "", "Star": "Aggiungi ai preferiti",
"System memory": "", "System memory": "Memoria di sistema",
"microSD card": "", "microSD card": "Scheda microSD",
"Sd": "SD",
"Image System memory": "Immagine memoria di sistema",
"Image microSD card": "Immagine scheda microSD",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Sì", "Yes": "Sì",
"No": "No", "No": "No",
"Enabled": "Abilitato", "Enabled": "Abilitato",
@@ -40,13 +47,13 @@
"Updated": "Aggiornato", "Updated": "Aggiornato",
"Updated (Star)": "", "Updated (Star)": "",
"Downloads": "Download", "Downloads": "Download",
"Size": "Misurare", "Size": "Dimensione",
"Size (Star)": "", "Size (Star)": "Dimensione (Preferiti)",
"Alphabetical": "Alfabetico", "Alphabetical": "Alfabetico",
"Alphabetical (Star)": "", "Alphabetical (Star)": "Alfabetico (Preferiti)",
"Likes": "", "Likes": "Mi Piace",
"ID": "", "ID": "ID",
"Decending": "Decrescente", "Descending": "Decrescente",
"Descending (down)": "Decrescente", "Descending (down)": "Decrescente",
"Desc": "Decrescente", "Desc": "Decrescente",
"Ascending": "Crescente", "Ascending": "Crescente",
@@ -54,22 +61,24 @@
"Asc": "Crescente", "Asc": "Crescente",
"Menu Options": "Opzioni menu", "Menu Options": "Opzioni menu",
"Header": "Intestazione",
"Theme": "Tema", "Theme": "Tema",
"Theme Options": "Opzioni tema", "Theme Options": "Opzioni tema",
"Select Theme": "Seleziona tema", "Select Theme": "Seleziona tema",
"Shuffle": "Mescola", "Shuffle": "Mescola",
"Music": "Musica", "Music": "Musica",
"12 Hour Time": "",
"Network": "Rete", "Network": "Rete",
"Network Options": "Opzioni di rete", "Network Options": "Opzioni di rete",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "", "Nxlink Connected": "Nxlink connesso",
"Nxlink Upload": "", "Nxlink Upload": "Nxlink upload",
"Nxlink Finished": "", "Nxlink Finished": "Nxlink finito",
"Switch-Handheld!": "", "Switch-Handheld!": "Switch Portatile",
"Switch-Docked!": "", "Switch-Docked!": "Switch Dock",
"Language": "Lingua", "Language": "Lingua",
"Auto": "", "Auto": "Auto",
"English": "English", "English": "English",
"Japanese": "日本語", "Japanese": "日本語",
"French": "Français", "French": "Français",
@@ -82,14 +91,16 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Logging", "Logging": "Logging",
"Replace hbmenu on exit": "Sostituisci hbmenu all'uscita", "Replace hbmenu on exit": "Sostituisci hbmenu all'uscita",
"Misc": "Varie", "Misc": "Varie",
"Misc Options": "Opzioni varie", "Misc Options": "Opzioni varie",
"Web": "Rete", "Web": "Rete",
"Install forwarders": "", "Install forwarders": "Installa forwarder",
"Install location": "", "Install location": "Installa posizione",
"Show install warning": "", "Show install warning": "Mostra avvertimento installazione",
"Text scroll speed": "",
"FileBrowser": "FileBrowser", "FileBrowser": "FileBrowser",
"%zd files": "%zd files", "%zd files": "%zd files",
@@ -100,23 +111,25 @@
"Hidden Last": "Ultimo nascosto", "Hidden Last": "Ultimo nascosto",
"Cut": "Taglia", "Cut": "Taglia",
"Copy": "Copia", "Copy": "Copia",
"Paste": "", "Paste": "Incolla",
"Paste ": "", "Paste ": "Incolla ",
" file(s)?": "", " file(s)?": "(i)file?",
"Rename": "Rinomina", "Rename": "Rinomina",
"Set New File Name": "", "Set New File Name": "Imposta nuovo nome",
"Advanced": "Avanzato", "Advanced": "Avanzato",
"Advanced Options": "Opzioni avanzate", "Advanced Options": "Opzioni avanzate",
"Create File": "Crea file", "Create File": "Crea file",
"Set File Name": "", "Set File Name": "Imposta nome",
"Create Folder": "Crea cartella", "Create Folder": "Crea cartella",
"Set Folder Name": "", "Set Folder Name": "Imposta nome",
"View as text (unfinished)": "Visualizza come testo (non finito)", "View as text (unfinished)": "Visualizza come testo (non finito)",
"Empty...": "", "Ignore read only": "Ignora read only",
"Open with DayBreak?": "", "Mount": "Monta",
"Launch ": "", "Empty...": "Vuoto...",
"Launch option for: ": "", "Open with DayBreak?": "Vuoi aprire con Daybreak?",
"Select launcher for: ": "", "Launch ": "Lancia",
"Launch option for: ": "Lancia opzione per",
"Select launcher for: ": "Scegli launcher per",
"Homebrew": "Homebrew", "Homebrew": "Homebrew",
"Homebrew Options": "Opzioni Homebrew", "Homebrew Options": "Opzioni Homebrew",
@@ -191,40 +204,54 @@
"Load Default": "Carica predefinito", "Load Default": "Carica predefinito",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "", "Themezer Options": "Impostazioni Themezer",
"Nsfw": "", "Nsfw": "NSFW",
"Page": "", "Page": "Pagina",
"Page %zu / %zu": "Page %zu / %zu", "Page %zu / %zu": "Pagina %zu / %zu",
"Enter Page Number": "", "Enter Page Number": "Inserisci il numero della pagina",
"Bad Page": "", "Bad Page": "Pagina invalida",
"Download theme?": "", "Download theme?": "Vuoi scaricare il tema?",
"Installing ": "", "GitHub": "GitHub",
"Uninstalling ": "", "Downloading json": "Scaricamento json",
"Deleting ": "", "Select asset to download for": "Scegli l'asset da scaricare per",
"Deleting": "",
"Pasting ": "", "Installing ": "Installazione",
"Pasting": "", "Uninstalling ": "Disinstallazione",
"Removing ": "", "Deleting ": "Eliminazione",
"Scanning ": "", "Deleting": "Eliminazione",
"Creating ": "", "Pasting ": "Incollo",
"Copying ": "", "Pasting": "Incollo",
"Trying to load ": "", "Removing ": "Rimozione",
"Downloading ": "", "Scanning ": "Scan",
"Checking MD5": "", "Creating ": "Creazione",
"Loading...": "", "Copying ": "Copio",
"Loading": "", "Trying to load ": "Cercando di caricare",
"Empty!": "", "Downloading ": "Scaricando",
"Not Ready...": "", "Downloaded ": "Scaricato",
"Error loading page!": "", "Removed ": ""Rimosso,
"Update avaliable: ": "", "Checking MD5": "Controllo MD5",
"Download update: ": "", "Loading...": "Caricamento...",
"Updated to ": "", "Loading": "Caricamento",
"Restart Sphaira?": "", "Empty!": "Vuoto!",
"Failed to download update": "", "Not Ready...": "Non pronto...",
"Delete Selected files?": "", "Error loading page!": "Errore nel caricare la pagina!",
"Completely remove ": "", "Update avaliable: ": "Aggiornamento disponibile",
"Download update: ": "Scarica aggiornamento",
"Updated to ": "Aggiornato a",
"Press OK to restart Sphaira": "Premi OK per riavviare Sphaira",
"Restart Sphaira?": "Vuoi riavviare Sphaira?",
"Failed to download update": "Download aggiornamento fallito",
"Restore hbmenu?": "Vuoi ripristinare hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Impossibile trovare /switch/hbmenu.nro\nUsa l'Appstore per reinstallare hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Impossibile ripristinare hbmenu, per favore riscaricalo",
"Failed to restore hbmenu, using sphaira instead": "Impossibile ripristinare hbmenu, uso Sphaira invece",
"Restored hbmenu, closing sphaira": "hbmenu ripristinato, chiudo Sphaira",
"Restored hbmenu": "hbmenu ripristinato",
"Delete Selected files?": "Vuoi rimuovere i file selezionati?",
"Completely remove ": "Elimina definitivamente",
"Are you sure you want to delete ": "Sei sicuro di voler eliminare? ", "Are you sure you want to delete ": "Sei sicuro di voler eliminare? ",
"Are you sure you wish to cancel?": "", "Are you sure you wish to cancel?": "Sei sicuro di voler annullare?",
"If this message appears repeatedly, please open an issue.": "" "Audio disabled due to suspended game": "Audio disabilitato poichè un app è in pausa",
} "If this message appears repeatedly, please open an issue.": "Se questo messaggio appare frequentemente, segnala il bug."
}

View File

@@ -19,6 +19,7 @@
"Details": "詳細", "Details": "詳細",
"Update": "アップデート", "Update": "アップデート",
"Remove": "除去", "Remove": "除去",
"Restore": "復元",
"Download": "ダウンロード", "Download": "ダウンロード",
"Next Page": "次のページ", "Next Page": "次のページ",
"Prev Page": "前のページ", "Prev Page": "前のページ",
@@ -26,6 +27,12 @@
"Star": "お気に入り", "Star": "お気に入り",
"System memory": "システムメモリ", "System memory": "システムメモリ",
"microSD card": "SDメモリーカード", "microSD card": "SDメモリーカード",
"Sd": "SDメモリーカード",
"Image System memory": "システムメモリイメージ",
"Image microSD card": "SDイメージ",
"Slow": "遅い",
"Normal": "普通",
"Fast": "速い",
"Yes": "はい", "Yes": "はい",
"No": "いいえ", "No": "いいえ",
"Enabled": "", "Enabled": "",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "アルファベット順(お気に入り)", "Alphabetical (Star)": "アルファベット順(お気に入り)",
"Likes": "いいね順", "Likes": "いいね順",
"ID": "デベロッパー順", "ID": "デベロッパー順",
"Decending": "降順", "Descending": "降順",
"Descending (down)": "降順", "Descending (down)": "降順",
"Desc": "降順", "Desc": "降順",
"Ascending": "上昇", "Ascending": "上昇",
@@ -54,14 +61,16 @@
"Asc": "上昇", "Asc": "上昇",
"Menu Options": "メニュー設定", "Menu Options": "メニュー設定",
"Header": "ヘッダー",
"Theme": "テーマ", "Theme": "テーマ",
"Theme Options": "テーマ設定", "Theme Options": "テーマ設定",
"Select Theme": "テーマを選ぶ", "Select Theme": "テーマを選ぶ",
"Shuffle": "シャッフル", "Shuffle": "シャッフル",
"Music": "BGM", "Music": "BGM",
"12 Hour Time": "",
"Network": "ネットワーク", "Network": "ネットワーク",
"Network Options": "ネットワーク設定", "Network Options": "ネットワーク設定",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink 接続", "Nxlink Connected": "Nxlink 接続",
"Nxlink Upload": "Nxlink アップロード", "Nxlink Upload": "Nxlink アップロード",
@@ -82,6 +91,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "ログの取得", "Logging": "ログの取得",
"Replace hbmenu on exit": "終了時に hbmenu を置き換える", "Replace hbmenu on exit": "終了時に hbmenu を置き換える",
"Misc": "その他", "Misc": "その他",
@@ -90,6 +100,7 @@
"Install forwarders": "Forwarderのインストール機能", "Install forwarders": "Forwarderのインストール機能",
"Install location": "インストール経路", "Install location": "インストール経路",
"Show install warning": "警告文を示す", "Show install warning": "警告文を示す",
"Text scroll speed": "流れる文字の速さ",
"FileBrowser": "ファイルブラウザ", "FileBrowser": "ファイルブラウザ",
"%zd files": "%zd個のファイル", "%zd files": "%zd個のファイル",
@@ -112,6 +123,8 @@
"Create Folder": "フォルダーの作成", "Create Folder": "フォルダーの作成",
"Set Folder Name": "名前を入力", "Set Folder Name": "名前を入力",
"View as text (unfinished)": "テキストとして表示 (未完成)", "View as text (unfinished)": "テキストとして表示 (未完成)",
"Ignore read only": "読み取り専用を無視する",
"Mount": "マウント",
"Empty...": "このフォルダーは空です", "Empty...": "このフォルダーは空です",
"Open with DayBreak?": "DayBreakで開きますか?", "Open with DayBreak?": "DayBreakで開きますか?",
"Launch ": "起動しますか", "Launch ": "起動しますか",
@@ -199,6 +212,10 @@
"Bad Page": "ページが見つかりません", "Bad Page": "ページが見つかりません",
"Download theme?": "テーマをインストールしますか?", "Download theme?": "テーマをインストールしますか?",
"GitHub": "GitHub",
"Downloading json": "JSONからダウンロード",
"Select asset to download for ": "ダウンロードアイテムを選択 ",
"Installing ": "インストール中 ", "Installing ": "インストール中 ",
"Uninstalling ": "アンインストール中 ", "Uninstalling ": "アンインストール中 ",
"Deleting ": "削除中 ", "Deleting ": "削除中 ",
@@ -211,6 +228,8 @@
"Copying ": "コピー中 ", "Copying ": "コピー中 ",
"Trying to load ": "サムネイルを取得中 ", "Trying to load ": "サムネイルを取得中 ",
"Downloading ": "ダウンロード中 ", "Downloading ": "ダウンロード中 ",
"Downloaded ": "ダウンロード完了 ",
"Removed ": "除去完了 ",
"Checking MD5": "MD5を確認中 ", "Checking MD5": "MD5を確認中 ",
"Loading...": "ロード中", "Loading...": "ロード中",
"Loading": "ロード中", "Loading": "ロード中",
@@ -220,11 +239,19 @@
"Update avaliable: ": "アップデート可能: ", "Update avaliable: ": "アップデート可能: ",
"Download update: ": "アップデートをダウンロード: ", "Download update: ": "アップデートをダウンロード: ",
"Updated to ": "アップデート: ", "Updated to ": "アップデート: ",
"Press OK to restart Sphaira": "確認ボタンを押してSphairaを再起動",
"Restart Sphaira?": "Sphairaを再起動しますか?", "Restart Sphaira?": "Sphairaを再起動しますか?",
"Failed to download update": "アップデートのダウンロード失敗", "Failed to download update": "アップデートのダウンロード失敗",
"Restore hbmenu?": "hbmenuに戻しますか?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmemu.nroが見つかりません\nAppstoreから再インストールしてください",
"Failed to restore hbmenu, please re-download hbmenu": "hbmenuを復元できませんでした、再インストールしてください",
"Failed to restore hbmenu, using sphaira instead": "hbmenuを復元できませんでした、sphairaを引き続き使います",
"Restored hbmenu, closing sphaira": "hbmenuに復元されました、sphairaを終了します",
"Restored hbmenu": "hbmenuに復元されました",
"Delete Selected files?": "本当に削除しますか?", "Delete Selected files?": "本当に削除しますか?",
"Completely remove ": "除去しますか ", "Completely remove ": "除去しますか ",
"Are you sure you want to delete ": "消去してもよろしいですか ", "Are you sure you want to delete ": "消去してもよろしいですか ",
"Are you sure you wish to cancel?": "本当に取り消しますか?", "Are you sure you wish to cancel?": "本当に取り消しますか?",
"Audio disabled due to suspended game": "ゲームが一時停止状態の場合、オーディオは無効になります",
"If this message appears repeatedly, please open an issue.": "このメッセージが繰り返し表示される場合は、問題を開いてください。" "If this message appears repeatedly, please open an issue.": "このメッセージが繰り返し表示される場合は、問題を開いてください。"
} }

View File

@@ -1,230 +1,257 @@
{ {
"[Applet Mode]": "[애플릿 모드]", "[Applet Mode]": "[ 애플릿 모드 ]",
"No Internet": "네트워크 연결 없음", "No Internet": "인터넷 연결 없음",
"Files": "파일 탐색기", "Files": "파일 탐색기",
"Apps": "홈브류", "Apps": "홈브류",
"Store": "앱스토어", "Store": "앱스토어",
"Menu": "메뉴", "Menu": "메뉴",
"Options": "설정", "Options": "설정",
"OK": "확인", "OK": "확인",
"Back": "뒤로", "Back": "뒤로",
"Select": "선택", "Select": "선택",
"Open": "열기", "Open": "열기",
"Launch": "실행", "Launch": "실행",
"Info": "정보", "Info": "정보",
"Install": "설치", "Install": "설치",
"Delete": "삭제", "Delete": "삭제",
"Restart": "재시작", "Restart": "재시작",
"Changelog": "변경 내역", "Changelog": "변경 내역",
"Details": "상세", "Details": "상세",
"Update": "업데이트", "Update": "업데이트",
"Remove": "제거", "Remove": "제거",
"Download": "다운로드", "Restore": "복원",
"Next Page": "다음 페이지", "Download": "다운로드",
"Prev Page": "이전 페이지", "Next Page": "다음 페이지",
"Unstar": "즐겨찾기 해제", "Prev Page": "이전 페이지",
"Star": "즐겨찾기", "Unstar": "즐겨찾기 해제",
"System memory": "낸드 저장소", "Star": "즐겨찾기",
"microSD card": "SD 카드", "System memory": "낸드 저장소",
"Yes": "", "microSD card": "SD 카드",
"No": "아니요", "Sd": "SD 카드",
"Enabled": "", "Image System memory": "낸드 이미지",
"Disabled": "", "Image microSD card": "SD 이미지",
"Slow": "느림",
"Sort By": "정렬", "Normal": "보통",
"Sort Options": "정렬 설정", "Fast": "빠름",
"Filter": "필터", "Yes": "",
"Sort": "분류", "No": "아니요",
"Order": "정렬", "Enabled": "",
"Search": "검색", "Disabled": "",
"Updated": "업데이트순",
"Updated (Star)": "업데이트순 (즐겨찾기)", "Sort By": "정렬",
"Downloads": "다운로드순", "Sort Options": "정렬 옵션",
"Size": "크기순", "Filter": "필터",
"Size (Star)": "크기순 (즐겨찾기)", "Sort": "분류",
"Alphabetical": "알파벳순", "Order": "정렬",
"Alphabetical (Star)": "알파벳순 (즐겨찾기)", "Search": "검색",
"Likes": "좋아요순", "Updated": "업데이트순",
"ID": "작성자순", "Updated (Star)": "업데이트순 (즐겨찾기)",
"Decending": "내림차순", "Downloads": "다운로드순",
"Descending (down)": "내림차순", "Size": "크기순",
"Desc": "내림차순", "Size (Star)": "크기순 (즐겨찾기)",
"Ascending": "오름차순", "Alphabetical": "알파벳순",
"Ascending (Up)": "오름차순", "Alphabetical (Star)": "알파벳순 (즐겨찾기)",
"Asc": "오름차순", "Likes": "좋아요순",
"ID": "ID순",
"Menu Options": "메뉴", "Descending": "내림차순",
"Header": "헤더", "Descending (down)": "내림차순",
"Theme": "테마", "Desc": "내림차순",
"Theme Options": "테마 설정", "Ascending": "오름차순",
"Select Theme": "테마 선택", "Ascending (Up)": "오름차순",
"Shuffle": "셔플", "Asc": "오름차순",
"Music": "BGM",
"Network": "네트워크", "Menu Options": "메뉴",
"Network Options": "네트워크 설정", "Theme": "테마",
"Nxlink": "Nxlink", "Theme Options": "테마 옵션",
"Nxlink Connected": "Nxlink 연결됨", "Select Theme": "테마 선택",
"Nxlink Upload": "Nxlink 업로드", "Shuffle": "셔플",
"Nxlink Finished": "Nxlink 종료됨", "Music": "BGM",
"Switch-Handheld!": "휴대모드로 전환되었습니다!", "12 Hour Time": "",
"Switch-Docked!": "독 모드로 전환되었습니다!", "Network": "네트워크",
"Language": "언어", "Network Options": "네트워크 옵션",
"Auto": "자동", "Ftp": "FTP (무선)",
"English": "English", "Mtp": "MTP (유선)",
"Japanese": "日本語", "Nxlink": "Nxlink",
"French": "Français", "Nxlink Connected": "Nxlink 연결됨",
"German": "Deutsch", "Nxlink Upload": "Nxlink 업로드",
"Italian": "Italiano", "Nxlink Finished": "Nxlink 종료됨",
"Spanish": "Español", "Switch-Handheld!": "휴대모드로 전환됨!",
"Chinese": "中文", "Switch-Docked!": "독 모드로 전환됨!",
"Korean": "한국어", "Language": "어",
"Dutch": "Dutch", "Auto": "자동",
"Portuguese": "Português", "English": "English",
"Russian": "Русский", "Japanese": "日本語",
"Swedish": "Svenska", "French": "Français",
"Logging": "로깅", "German": "Deutsch",
"Replace hbmenu on exit": "hbmenu  sphaira 교체", "Italian": "Italiano",
"Misc": "기타", "Spanish": "Español",
"Misc Options": "기타", "Chinese": "中文",
"Web": "웹 브라우저", "Korean": "한국어",
"Install forwarders": "바로가기 설치 기능", "Dutch": "Dutch",
"Install location": "설치 경로", "Portuguese": "Português",
"Show install warning": "바로가기 설치 경고문 표시", "Russian": "Русский",
"Swedish": "Svenska",
"FileBrowser": "파일 탐색기", "Vietnamese": "Vietnamese",
"%zd files": "%zd개의 파일", "Logging": "로깅",
"%zd dirs": "%zd개의 폴더", "Replace hbmenu on exit": "종료 시 hbmenu 교체",
"File Options": "파일 설정", "Misc": "기타",
"Show Hidden": "숨겨진 항목 표시", "Misc Options": "기타 옵션",
"Folders First": "폴더 우선 정렬", "Web": "웹 브라우저",
"Hidden Last": "숨겨진 항목 후순 정렬", "Install forwarders": "바로가기 설치",
"Cut": "잘라내기", "Install location": "설치 위치",
"Copy": "복사", "Show install warning": "경고 메시지",
"Paste": "붙여넣기", "Text scroll speed": "흐르는 텍스트 속도",
"Paste ": " ",
" file(s)?": "개 항목을 붙여넣으시겠습니까?", "FileBrowser": "파일 탐색기",
"Rename": "이름 바꾸기", "%zd files": "%zd 개 파일",
"Set New File Name": "새 파일명 입력", "%zd dirs": "%zd 개 폴더",
"Advanced": "고급", "File Options": "파일 옵션",
"Advanced Options": "고급 설정", "Show Hidden": "숨겨진 항목 표시",
"Create File": "파일 생성", "Folders First": "폴더 우선 정렬",
"Set File Name": "파일명 입력", "Hidden Last": "숨겨진 항목 후순 정렬",
"Create Folder": "새 폴더", "Cut": "잘라내기",
"Set Folder Name": "폴더명 입력", "Copy": "복사",
"View as text (unfinished)": "텍스트로 보기 (미완성)", "Paste": "붙여넣기",
"Empty...": "비어있습니다...", "Paste ": " ",
"Open with DayBreak?": "DayBreak로 여시겠습니까?", " file(s)?": "개 항목을 붙여넣을까요?",
"Launch ": "실행하시겠습니까 ", "Rename": "이름 바꾸기",
"Launch option for: ": "실행 설정: ", "Set New File Name": "새 파일명 입력",
"Select launcher for: ": "실행 런처: ", "Advanced": "고급",
"Advanced Options": "고급 옵션",
"Homebrew": "홈브류", "Create File": "새 파일",
"Homebrew Options": "홈브류 설정", "Set File Name": "파일명 입력",
"Hide Sphaira": "Sphaira 숨기기", "Create Folder": "새 폴더",
"Install Forwarder": "바로가기 설치", "Set Folder Name": "폴더명 입력",
"WARNING: Installing forwarders will lead to a ban!": "주의: 시스낸드에서 바로가기 설치시 BAN 위험이 있습니다!", "View as text (unfinished)": "텍스트로 보기 (미완성)",
"Installing Forwarder": "바로가기 설치중...", "Ignore read only": "읽기 전용 설정 무시",
"Creating Program": "프로그램 작성중...", "Mount": "마운트",
"Creating Control": "컨트롤 작성중...", "Empty...": "비어있음...",
"Creating Meta": "메타 작성중...", "Open with DayBreak?": "DayBreak로 열까요?",
"Writing Nca": "Nca 쓰는중...", "Launch ": "실행할까요 ",
"Updating ncm databse": "ncm 데이터베이스 업데이트중...", "Launch option for: ": "실행 옵션: ",
"Pushing application record": "응용 프로그램 기록 푸시중...", "Select launcher for: ": "실행 런처: ",
"Installed!": "설치 완료!",
"Failed to install forwarder": "바로가기 설치 실패", "Homebrew": "홈브류",
"Unstarred ": "즐겨찾기 해제: ", "Homebrew Options": "홈브류 옵션",
"Starred ": "즐겨찾기 적용: ", "Hide Sphaira": "Sphaira 숨기기",
"Install Forwarder": "바로가기 설치",
"AppStore": "앱스토어", "WARNING: Installing forwarders will lead to a ban!": "경고: 시스낸드에 바로가기 설치 시, 밴 위험이 있습니다!",
"Filter: %s | Sort: %s | Order: %s": "필터: %s | 분류: %s | 정렬: %s", "Installing Forwarder": "바로가기 설치",
"AppStore Options": "앱스토어 설정", "Creating Program": "프로그램 생성",
"All": "전체", "Creating Control": "컨트롤 생성",
"Games": "게임", "Creating Meta": "메타 생성",
"Emulators": "에뮬레이터", "Writing Nca": "Nca 쓰기",
"Tools": "도구", "Updating ncm databse": "Ncm 데이터베이스 업데이트",
"Themes": "테마", "Pushing application record": "응용 프로그램 기록 푸싱",
"Legacy": "레거시", "Installed!": "설치 완료!",
"version: %s": "버전: %s", "Failed to install forwarder": "바로가기 설치 실패",
"updated: %s": "갱신일: %s", "Unstarred ": "즐겨찾기 해제: ",
"category: %s": "카테고리: %s", "Starred ": "즐겨찾기 적용: ",
"extracted: %.2f MiB": "용량: %.2f MiB",
"app_dls: %s": "다운로드 횟수: %s", "AppStore": "앱스토어",
"More by Author": "개발자의 다른 앱 더보기", "Filter: %s | Sort: %s | Order: %s": "필터: %s | 분류: %s | 정렬: %s",
"Leave Feedback": "피드백 남기기", "AppStore Options": "앱스토어 옵션",
"All": "모두",
"Irs": "Joy-Con IR 카메라", "Games": "게임",
"Ambient Noise Level: ": "노이즈 레벨: ", "Emulators": "에뮬레이터",
"Controller": "컨트롤러", "Tools": "도구",
"Pad ": "조이콘 ", "Themes": "테마",
" (Available)": " (사용 가능)", "Legacy": "레거시",
" (Unsupported)": " (지원 안됨)", "version: %s": "버전: %s",
" (Unconnected)": " (연결 없음)", "updated: %s": "업데이트: %s",
"HandHeld": "- 본체 연결", "category: %s": "카테고리: %s",
"Rotation": "화면 회전", "extracted: %.2f MiB": "용량: %.2f MiB",
"0 (Sideways)": "0 (좌회전)", "app_dls: %s": "다운로드 횟수: %s",
"90 (Flat)": "90 (정방향)", "More by Author": "개발자의 다른 앱 더 보기",
"180 (-Sideways)": "180 (우회전)", "Leave Feedback": "피드백 남기기",
"270 (Upside down)": "270 (역전)",
"Colour": "컬러 팔레트", "Irs": "조이콘 적외선 카메라",
"Grey": "그레이", "Ambient Noise Level: ": "주변 노이즈 레벨: ",
"Ironbow": "아이언보우", "Controller": "컨트롤러",
"Green": "그린", "Pad ": "조이콘 ",
"Red": "레드", " (Available)": " (사용 가능)",
"Blue": "블루", " (Unsupported)": " (지원 안됨)",
"Light Target": "반사 표적", " (Unconnected)": " (연결 없음)",
"All leds": "모든 LED 켜기", "HandHeld": "본체 연결",
"Bright group": "Bright LED 켜기", "Rotation": "화면 회전",
"Dim group": "Dim LED 켜기", "0 (Sideways)": "반시계방향 90° 회전",
"None": "LED 끄기", "90 (Flat)": "정방향",
"Gain": "대비", "180 (-Sideways)": "시계방향 90° 회전",
"Negative Image": "화상 이미지", "270 (Upside down)": "상하반전",
"Normal image": "일반", "Colour": "색상",
"Negative image": "반전", "Grey": "회색",
"Format": "해상도", "Ironbow": "아이언보우",
"320x240": "320×240", "Green": "초록색",
"160x120": "160×120", "Red": "빨간색",
"80x60": "80×60", "Blue": "파란색",
"40x30": "40×30", "Light Target": "반사 표적",
"20x15": "20×15", "All leds": "모든 LED 켜기",
"Trimming Format": "트리밍 해상도", "Bright group": "Bright LED 켜기",
"External Light Filter": "외부 조명 필터", "Dim group": "Dim LED 켜기",
"Load Default": "기본값으로 설정", "None": "LED 끄기",
"Gain": "대비",
"Themezer": "Themezer", "Negative Image": "화상 이미지",
"Themezer Options": "Themezer 설정", "Normal image": "일반",
"Nsfw": "선정성 테마", "Negative image": "반전",
"Page": "페이지", "Format": "해상도",
"Page %zu / %zu": "페이지 %zu / %zu", "320x240": "320×240",
"Enter Page Number": "페이지 번호 입력", "160x120": "160×120",
"Bad Page": "잘못된 페이지", "80x60": "80×60",
"Download theme?": "테마를 내려받으시겠습니까?", "40x30": "40×30",
"20x15": "20×15",
"Installing ": "설치중... ", "Trimming Format": "트리밍 해상도",
"Uninstalling ": "설치 제거중... ", "External Light Filter": "외부 조명 필터",
"Deleting ": "삭제중... ", "Load Default": "기본값으로 설정",
"Deleting": "삭제중...",
"Pasting ": "붙여넣는중... ", "Themezer": "Themezer",
"Pasting": "붙여넣는중...", "Themezer Options": "Themezer 옵션",
"Removing ": "제거중... ", "Nsfw": "선정성 테마",
"Scanning ": "스캔중... ", "Page": "페이지",
"Creating ": "작성중... ", "Page %zu / %zu": "페이지 %zu / %zu",
"Copying ": "복사중... ", "Enter Page Number": "페이지 번호 입력",
"Trying to load ": "썸네일 받아오는 중... ", "Bad Page": "잘못된 페이지",
"Downloading ": "다운로드중... ", "Download theme?": "테마를 다운로드할까요?",
"Checking MD5": "MD5 확인중... ",
"Loading...": "로딩중...", "GitHub": "GitHub",
"Loading": "로딩중...", "Downloading json": "JSON에서 다운로드",
"Empty!": "찾을 수 없습니다!", "Select asset to download for ": "다운로드 아이템: ",
"Not Ready...": "준비되지 않았습니다...",
"Error loading page!": "페이지 로딩 오류!", "Installing ": "설치 ",
"Update avaliable: ": "업데이트 가능: ", "Uninstalling ": "설치 제거 ",
"Download update: ": "업데이트 다운로드: ", "Deleting ": "삭제 ",
"Updated to ": "업데이트: ", "Deleting": "삭제",
"Restart Sphaira?": "Sphaira를 재시작 하시겠습니까?", "Pasting ": "붙여넣기 ",
"Failed to download update": "업데이트 다운로드 실패!", "Pasting": "붙여넣기",
"Delete Selected files?": "정말 삭제하시겠습니까?", "Removing ": "제거 ",
"Completely remove ": "제거하시겠습니까 ", "Scanning ": "스캔 ",
"Are you sure you want to delete ": "정말 삭제하시겠습니까 ", "Creating ": "생성 ",
"Are you sure you wish to cancel?": "정말 취소하시겠습니까?", "Copying ": "복사 ",
"If this message appears repeatedly, please open an issue.": "해당 메시지가 반복해서 나타나는 경우, 이슈를 열어주세요." "Trying to load ": "썸네일 받아오는 중... ",
"Downloading ": "다운로드 ",
"Downloaded ": "다운로드 완료: ",
"Removed ": "제거 됨: ",
"Checking MD5": "MD5 확인",
"Loading...": "로딩 중...",
"Loading": "로딩 중...",
"Empty!": "찾을 수 없습니다!",
"Not Ready...": "준비되지 않음...",
"Error loading page!": "페이지 로딩 오류!",
"Update avaliable: ": "업데이트 가능: ",
"Download update: ": "업데이트 다운로드: ",
"Updated to ": "업데이트: ",
"Press OK to restart Sphaira": "확인 버튼 입력하여 Sphaira 재시작",
"Restart Sphaira?": "Sphaira를 재시작할까요?",
"Failed to download update": "업데이트 다운로드 실패함",
"Restore hbmenu?": "hbmenu로 교체할까요?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmemu.nro를 찾을 수 없습니다\n앱스토어에서 다시 설치하세요",
"Failed to restore hbmenu, please re-download hbmenu": "hbmenu 교체 실패함, hbmenu를 다시 다운로드하세요",
"Failed to restore hbmenu, using sphaira instead": "hbmenu 교체 실패함, sphaira를 계속 사용합니다",
"Restored hbmenu, closing sphaira": "hbmenu로 교체됨, sphaira를 종료합니다",
"Restored hbmenu": "hbmenu로 교체됨",
"Delete Selected files?": "선택한 파일을 삭제할까요?",
"Completely remove ": "정말 삭제할까요 ",
"Are you sure you want to delete ": "정말 삭제할까요 ",
"Are you sure you wish to cancel?": "정말 취소할까요?",
"Audio disabled due to suspended game": "게임 실행 중에는 BGM이 비활성화 됩니다",
"If this message appears repeatedly, please open an issue.": "해당 메시지가 반복해서 나타나는 경우, 이슈를 등록하세요."
} }

View File

@@ -19,6 +19,7 @@
"Details": "", "Details": "",
"Update": "", "Update": "",
"Remove": "", "Remove": "",
"Restore": "",
"Download": "Downloaden", "Download": "Downloaden",
"Next Page": "Volgende pagina", "Next Page": "Volgende pagina",
"Prev Page": "Vorige pagina", "Prev Page": "Vorige pagina",
@@ -26,6 +27,12 @@
"Star": "", "Star": "",
"System memory": "", "System memory": "",
"microSD card": "", "microSD card": "",
"Sd": "",
"Image System memory": "",
"Image microSD card": "",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Ja", "Yes": "Ja",
"No": "Nee", "No": "Nee",
"Enabled": "Ingeschakeld", "Enabled": "Ingeschakeld",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "", "Alphabetical (Star)": "",
"Likes": "", "Likes": "",
"ID": "", "ID": "",
"Decending": "Aflopend", "Descending": "Aflopend",
"Descending (down)": "Aflopend", "Descending (down)": "Aflopend",
"Desc": "Aflopend", "Desc": "Aflopend",
"Ascending": "Oplopend", "Ascending": "Oplopend",
@@ -54,14 +61,16 @@
"Asc": "Oplopend", "Asc": "Oplopend",
"Menu Options": "Menu-opties", "Menu Options": "Menu-opties",
"Header": "Koptekst",
"Theme": "Thema", "Theme": "Thema",
"Theme Options": "Thema Opties", "Theme Options": "Thema Opties",
"Select Theme": "Selecteer Thema", "Select Theme": "Selecteer Thema",
"Shuffle": "Schudden", "Shuffle": "Schudden",
"Music": "Muziek", "Music": "Muziek",
"12 Hour Time": "",
"Network": "Netwerk", "Network": "Netwerk",
"Network Options": "Netwerkopties", "Network Options": "Netwerkopties",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "", "Nxlink Connected": "",
"Nxlink Upload": "", "Nxlink Upload": "",
@@ -82,6 +91,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Loggen", "Logging": "Loggen",
"Replace hbmenu on exit": "Vervang hbmenu bij afsluiten", "Replace hbmenu on exit": "Vervang hbmenu bij afsluiten",
"Misc": "Diversen", "Misc": "Diversen",
@@ -90,6 +100,7 @@
"Install forwarders": "", "Install forwarders": "",
"Install location": "", "Install location": "",
"Show install warning": "", "Show install warning": "",
"Text scroll speed": "",
"FileBrowser": "Bestandsbrowser", "FileBrowser": "Bestandsbrowser",
"%zd files": "%zd files", "%zd files": "%zd files",
@@ -112,6 +123,8 @@
"Create Folder": "Map maken", "Create Folder": "Map maken",
"Set Folder Name": "", "Set Folder Name": "",
"View as text (unfinished)": "Bekijk als tekst (onvoltooid)", "View as text (unfinished)": "Bekijk als tekst (onvoltooid)",
"Ignore read only": "",
"Mount": "",
"Empty...": "", "Empty...": "",
"Open with DayBreak?": "", "Open with DayBreak?": "",
"Launch ": "", "Launch ": "",
@@ -199,6 +212,10 @@
"Bad Page": "", "Bad Page": "",
"Download theme?": "", "Download theme?": "",
"GitHub": "",
"Downloading json": "",
"Select asset to download for ": "",
"Installing ": "", "Installing ": "",
"Uninstalling ": "", "Uninstalling ": "",
"Deleting ": "", "Deleting ": "",
@@ -211,6 +228,8 @@
"Copying ": "", "Copying ": "",
"Trying to load ": "", "Trying to load ": "",
"Downloading ": "", "Downloading ": "",
"Downloaded ": "",
"Removed ": "",
"Checking MD5": "", "Checking MD5": "",
"Loading...": "", "Loading...": "",
"Loading": "", "Loading": "",
@@ -220,11 +239,19 @@
"Update avaliable: ": "", "Update avaliable: ": "",
"Download update: ": "", "Download update: ": "",
"Updated to ": "", "Updated to ": "",
"Press OK to restart Sphaira": "",
"Restart Sphaira?": "", "Restart Sphaira?": "",
"Failed to download update": "", "Failed to download update": "",
"Restore hbmenu?": "",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "",
"Failed to restore hbmenu, please re-download hbmenu": "",
"Failed to restore hbmenu, using sphaira instead": "",
"Restored hbmenu, closing sphaira": "",
"Restored hbmenu": "",
"Delete Selected files?": "", "Delete Selected files?": "",
"Completely remove ": "", "Completely remove ": "",
"Are you sure you want to delete ": "Weet u zeker dat u wilt verwijderen ", "Are you sure you want to delete ": "Weet u zeker dat u wilt verwijderen ",
"Are you sure you wish to cancel?": "", "Are you sure you wish to cancel?": "",
"Audio disabled due to suspended game": "",
"If this message appears repeatedly, please open an issue.": "" "If this message appears repeatedly, please open an issue.": ""
} }

View File

@@ -1,75 +1,94 @@
{ {
"[Applet Mode]": "[Modo Applet]", "[Applet Mode]": "[Modo applet]",
"No Internet": "Sem Internet", "No Internet": "Sem internet",
"Files": "", "Files": "Arquivos",
"Apps": "", "Apps": "Aplicativos",
"Store": "", "Store": "Loja",
"Menu": "Menu", "Menu": "Menu",
"Options": "Opções", "Options": "Opções",
"OK": "", "OK": "OK",
"Back": "Voltar", "Back": "Voltar",
"Select": "", "Select": "Selecionar",
"Open": "Abrir", "Open": "Abrir",
"Launch": "Iniciar", "Launch": "Iniciar",
"Info": "Informações", "Info": "Informações",
"Install": "Instalar", "Install": "Instalar",
"Delete": "Excluir", "Delete": "Excluir",
"Restart": "", "Restart": "Reiniciar",
"Changelog": "", "Changelog": "Alterações",
"Details": "", "Details": "Detalhes",
"Update": "", "Update": "Atualizar",
"Remove": "", "Remove": "Remover",
"Download": "Download", "Restore": "Restaurar",
"Download": "Baixar",
"Next": "Prómixo",
"Prev": "Anterior",
"Next Page": "Próxima página", "Next Page": "Próxima página",
"Prev Page": "Página anterior", "Prev Page": "Página anterior",
"Unstar": "", "Unstar": "Desfavoritar",
"Star": "", "Star": "Favoritar",
"System memory": "", "System memory": "Memória do console",
"microSD card": "", "microSD card": "Cartão microSD",
"Sd": "SD",
"Image System memory": "Imagem (memória do console)",
"Image microSD card": "Imagem (cartão microSD)",
"Slow": "Lenta",
"Normal": "Normal",
"Fast": "Rápida",
"Yes": "Sim", "Yes": "Sim",
"No": "Não", "No": "Não",
"Enabled": "Habilitado", "On": "Sim",
"Disabled": "Desabilitado", "Off": "Não",
"Enable": "Habilitar",
"Enabled": "Sim",
"Disabled": "Não",
"Sort By": "Ordenar por", "Sort By": "Ordernar/Organizar",
"Sort Options": "Opções de classificação", "Sort Options": "Ordernar/Organizar",
"Filter": "Filtro", "Filter": "Filtro",
"Sort": "Organizar", "Sort": "Organizar por",
"Order": "Ordem", "Order": "Ordem",
"Search": "Procurar", "Layout": "Exibição",
"Search": "Buscar",
"Updated": "Atualizado", "Updated": "Atualizado",
"Updated (Star)": "", "Updated (Star)": "Atualizado (favoritos)",
"Downloads": "Downloads", "Downloads": "Nº de downloads",
"Size": "Tamanho", "Size": "Tamanho",
"Size (Star)": "", "Size (Star)": "Tamanho (favoritos)",
"Alphabetical": "Alfabético", "Alphabetical": "Ordem alfabética",
"Alphabetical (Star)": "", "Alphabetical (Star)": "Ordem alfabética (favoritos)",
"Likes": "", "Likes": "Nº de curtidas",
"ID": "", "ID": "ID",
"Decending": "Decrescente", "Descending": "Decrescente",
"Descending (down)": "Decrescente", "Descending (down)": "Decrescente (baixo)",
"Desc": "Decrescente", "Desc": "Decr.",
"Ascending": "Ascendente", "Ascending": "Ascendente",
"Ascending (Up)": "Ascendente", "Ascending (Up)": "Ascendente (cima)",
"Asc": "Ascendente", "Asc": "Asc.",
"List": "Lista",
"Icon": "Ícones",
"Grid": "Grade",
"Menu Options": "Opções do menu", "Menu Options": "Opções do menu",
"Header": "Cabeçalho",
"Theme": "Tema", "Theme": "Tema",
"Theme Options": "Opções de tema", "Theme Options": "Opções de tema",
"Select Theme": "Selecionar tema", "Select Theme": "Tema atual",
"Shuffle": "Embaralhar", "Shuffle": "Embaralhar temas",
"Music": "Música", "Music": "Música",
"12 Hour Time": "Relógio de 12 horas",
"Download Default Music": "Baixar música padrão",
"Network": "Rede", "Network": "Rede",
"Network Options": "Opções de rede", "Network Options": "Opções de rede",
"Ftp": "Servidor FTP",
"Mtp": "Escuta MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "", "Nxlink Connected": "Nxlink conectado",
"Nxlink Upload": "", "Nxlink Upload": "Envio Nxlink",
"Nxlink Finished": "", "Nxlink Finished": "Nxlink finalizado",
"Switch-Handheld!": "", "Switch-Handheld!": "Switch-Portátil",
"Switch-Docked!": "", "Switch-Docked!": "Switch-Docado",
"Language": "Idioma", "Language": "Idioma",
"Auto": "", "Auto": "Automático",
"English": "English", "English": "English",
"Japanese": "日本語", "Japanese": "日本語",
"French": "Français", "French": "Français",
@@ -82,96 +101,126 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Logging": "Logging", "Vietnamese": "Vietnamese",
"Replace hbmenu on exit": "Substitua hbmenu ao sair", "Logging": "Registro de depuração",
"Replace hbmenu on exit": "Substituir hbmenu ao sair",
"Misc": "Diversos", "Misc": "Diversos",
"Misc Options": "Opções diversas", "Misc Options": "Opções diversas",
"Web": "Rede", "Web": "Navegador de internet",
"Install forwarders": "", "Install forwarders": "Instalar atalhos (forwarders)",
"Install location": "", "Install location": "Local de instalação",
"Show install warning": "", "Show install warning": "Mostrar aviso de instalação",
"Text scroll speed": "Rolagem do texto",
"Set right-side menu": "Menu do botão R",
"FileBrowser": "Navegador de arquivos", "FileBrowser": "Arquivos",
"%zd files": "%zd files", "%zd files": "%zd arquivo(s)",
"%zd dirs": "%zd dirs", "%zd dirs": "%zd diretório(s)",
"File Options": "Opções de arquivo", "File Options": "Opções de arquivo",
"Show Hidden": "Mostrar oculto", "Show Hidden": "Mostrar ocultos",
"Folders First": "Pastas primeiro", "Folders First": "Pastas primeiro",
"Hidden Last": "Oculto por último", "Hidden Last": "Ocultos por último",
"Cut": "Cortar", "Cut": "Recortar",
"Copy": "Copiar", "Copy": "Copiar",
"Paste": "", "Paste": "Colar",
"Paste ": "", "Paste ": "Colar ",
" file(s)?": "", " file(s)?": " arquivo(s)?",
"Rename": "Renomear", "Rename": "Renomear",
"Set New File Name": "", "Compress to zip": "Comprimir em zip",
"Set New File Name": "Defina o nome do novo arquivo",
"Advanced": "Avançado", "Advanced": "Avançado",
"Advanced Options": "Criar arquivo", "Advanced Options": "Opções avançadas",
"Create File": "Criar arquivo", "Create File": "Criar arquivo",
"Set File Name": "", "Set File Name": "Defina o nome do arquivo",
"Create Folder": "Criar pasta", "Create Folder": "Criar pasta",
"Set Folder Name": "", "Set Folder Name": "Defina o nome da pasta",
"View as text (unfinished)": "Ver como texto (inacabado)", "View as text (unfinished)": "Ver como texto (inacabado)",
"Empty...": "", "Ignore read only": "Ignorar somente leitura",
"Open with DayBreak?": "", "Mount": "Montar",
"Launch ": "", "Empty...": "Vazio",
"Launch option for: ": "", "Open with DayBreak?": "Abrir com DayBreak?",
"Select launcher for: ": "", "Launch ": "Iniciar ",
"Launch option for: ": "Opções de inicialização para: ",
"Select launcher for: ": "Selecionar launcher para: ",
"Homebrew": "Homebrew", "Homebrew": "Aplicativos",
"Homebrew Options": "Opções do Homebrew", "Homebrew Options": "Opções de aplicativo",
"Hide Sphaira": "Esconder Sphaira", "Hide Sphaira": "Esconder sphaira",
"Install Forwarder": "Instalar forwarder", "Install Forwarder": "Instalar atalho (forwarder)",
"WARNING: Installing forwarders will lead to a ban!": "AVISO: Isso pode resultar em um banimento!", "WARNING: Installing forwarders will lead to a ban!": "AVISO: Instalar atalhos pode resultar em um banimento!",
"Installing Forwarder": "", "Installing Forwarder": "Instalando forwarder...",
"Creating Program": "", "Creating Program": "Criando Program",
"Creating Control": "", "Creating Control": "Criando Control",
"Creating Meta": "", "Creating Meta": "Criando Meta",
"Writing Nca": "", "Writing Nca": "Escrevendo NCA",
"Updating ncm databse": "", "Updating ncm databse": "Atualizando base de dados NCM",
"Pushing application record": "", "Pushing application record": "Aplicando registro do aplicativo",
"Installed!": "", "Installed!": "Instalado!",
"Failed to install forwarder": "", "Failed to install forwarder": "Falha ao instalar forwarder",
"Unstarred ": "", "Unstarred ": "Desfavoritado ",
"Starred ": "", "Starred ": "Favoritado ",
"AppStore": "", "AppStore": "Loja",
"Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Organizar: %s | Ordem: %s", "Appstore": "Loja",
"AppStore Options": "Opções da AppStore", "Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Por: %s | Ordem: %s",
"AppStore Options": "Opções da loja",
"All": "Todos", "All": "Todos",
"Games": "Jogos", "Games": "Softwares",
"Emulators": "Emuladores", "Emulators": "Emuladores",
"Tools": "Ferramentas", "Tools": "Ferramentas",
"Themes": "Temas", "Themes": "Temas",
"Legacy": "Legado", "Legacy": "Legado",
"version: %s": "version: %s", "version: %s": "versão: %s",
"updated: %s": "updated: %s", "updated: %s": "atualizado: %s",
"category: %s": "category: %s", "category: %s": "categoria: %s",
"extracted: %.2f MiB": "extracted: %.2f MiB", "extracted: %.2f MiB": "tam. extraído: %.2f MiB",
"app_dls: %s": "app_dls: %s", "app_dls: %s": "downloads: %s",
"More by Author": "", "More by Author": "Mais deste autor",
"Leave Feedback": "", "Leave Feedback": "Deixar um feedback",
"Game Options": "Opções de software",
"Launch random game": "Iniciar um software aleatório",
"List meta records": "Registro de conteúdos",
"Entries": "Entradas",
"Hide forwarders": "Ocultar atalhos (forwarders)",
"Dump": "Exportar",
"Select content to dump": "Exportação de conteúdo",
"Dump All": "Exportar tudo",
"Dump Application": "Exportar software base",
"Dump Patch": "Exportar atualização",
"Dump AddOnContent": "Exportar DLCs",
"Dump DataPatch": "Exportar patch de dados",
"Select dump location": "Selecione o local de exportação",
"microSD card (/dumps/NSP/)": "Cartão microSD (/dump/NSP/)",
"USB transfer (Switch 2 Switch)": "Transferência via USB (Switch 2 Switch)",
"/dev/null (Speed Test)": "Teste de velocidade (/dev/null)",
"Dumping": "Extraindo...",
"Dump successfull!": "Extração foi concluída com sucesso.",
"Dump failed!": "Extração falhou.",
"Irs": "Irs", "Irs": "Câmera de movimento IR",
"Ambient Noise Level: ": "", "IRS": "Câmera de movimento IR",
"IRS (Infrared Joycon Camera)": "Câmera de movimento IR",
"Ambient Noise Level: ": "Nível de ruído ambiente: ",
"Controller": "Controle", "Controller": "Controle",
"Pad ": "Pad ", "Pad ": "Pad ",
" (Available)": " (Disponível)", " (Available)": " (disponível)",
" (Unsupported)": "", " (Unsupported)": "(não suportado)",
" (Unconnected)": " (Desconectado)", " (Unconnected)": " (desconectado)",
"HandHeld": "Portátil", "HandHeld": "Portátil",
"Rotation": "Rotação", "Rotation": "Rotação",
"0 (Sideways)": "0 (Lateralmente)", "0 (Sideways)": "0 (lateralmente)",
"90 (Flat)": "90 (plano)", "90 (Flat)": "90 (plano)",
"180 (-Sideways)": "180 (-Lateralmente)", "180 (-Sideways)": "180 (-lateralmente)",
"270 (Upside down)": "270 (De cabeça para baixo)", "270 (Upside down)": "270 (de cabeça para baixo)",
"Colour": "Cor", "Colour": "Cor",
"Grey": "Cinza", "Grey": "Cinza",
"Ironbow": "Arco de ferro", "Ironbow": "Ferro",
"Green": "Verde", "Green": "Verde",
"Red": "Vermelho", "Red": "Vermelho",
"Blue": "Azul", "Blue": "Azul",
"Light Target": "Alvo leve", "Light Target": "Alvo de luz",
"All leds": "Todos os LEDs", "All leds": "Todos os LEDs",
"Bright group": "Grupo claro", "Bright group": "Grupo claro",
"Dim group": "Grupo escuro", "Dim group": "Grupo escuro",
@@ -180,51 +229,122 @@
"Negative Image": "Imagem negativa", "Negative Image": "Imagem negativa",
"Normal image": "Imagem normal", "Normal image": "Imagem normal",
"Negative image": "Imagem negativa", "Negative image": "Imagem negativa",
"Format": "Formatar", "Format": "Formato",
"320x240": "320×240", "320x240": "320×240",
"160x120": "160×120", "160x120": "160×120",
"80x60": "80×60", "80x60": "80×60",
"40x30": "40×30", "40x30": "40×30",
"20x15": "20×15", "20x15": "20×15",
"Trimming Format": "Formato de corte", "Trimming Format": "Formato do recorte",
"External Light Filter": "Filtro de luz externo", "External Light Filter": "Filtro de luz externa",
"Load Default": "Carregar padrão", "Load Default": "Restaurar padrão",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "", "Themezer Options": "Opções do Themezer",
"Nsfw": "", "Nsfw": "Temas 18+ (NSFW)",
"Page": "", "Page": "Ir para página",
"Page %zu / %zu": "Page %zu / %zu", "Page %zu / %zu": "Página %zu / %zu",
"Enter Page Number": "", "Enter Page Number": "Número da página",
"Bad Page": "", "Bad Page": "Página inválida",
"Download theme?": "", "Download theme?": "Baixar tema?",
"Installing ": "", "GitHub": "GitHub",
"Uninstalling ": "", "Downloading json": "Baixando JSON",
"Deleting ": "", "Select asset to download for ": "Selecione o recurso para baixar de ",
"Deleting": "",
"Pasting ": "", "Install Options": "Opções de instalação",
"Pasting": "", "Install options": "Opções de instalação",
"Removing ": "", "Enable sysmmc": "Habilitar sysMMC",
"Scanning ": "", "Enable emummc": "Habilitar emuMMC",
"Creating ": "", "Boost CPU clock": "Aumentar clock da CPU",
"Copying ": "", "Allow downgrade": "Permitir downgrade",
"Trying to load ": "", "Skip if already installed": "Pular se já instalado",
"Downloading ": "", "Ticket only": "Instalar apenas ticket",
"Checking MD5": "", "Patch ticket": "Fazer patch de ticket",
"Loading...": "", "Skip base": "Pular software base",
"Loading": "", "Skip patch": "Pular atualizações",
"Empty!": "", "Skip dlc": "Pular DLCs",
"Not Ready...": "", "Skip data patch": "Pular patch de dados",
"Error loading page!": "", "Skip ticket": "Pular ticket",
"Update avaliable: ": "", "skip NCA hash verify": "Pular checagem de hash NCA",
"Download update: ": "", "Skip RSA header verify": "Pular checagem de header RSA",
"Updated to ": "", "Skip RSA NPDM verify": "Pular checagem de NPDM RSA",
"Restart Sphaira?": "", "Ignore distribution bit": "Ignorar bit de distribuição",
"Failed to download update": "", "Convert to standard crypto": "Convertr para crypto padrão",
"Delete Selected files?": "", "Lower master key": "Reduzir a master key",
"Completely remove ": "", "Lower system version": "Reduzir versão do sistema",
"Are you sure you want to delete ": "Excluir ", "Install Selected files?": "Instalar os arquivos selecionados?",
"Are you sure you wish to cancel?": "", "Installed ": "Instalado ",
"If this message appears repeatedly, please open an issue.": "" "FTP Install": "Instalação via FTP",
} "USB Install": "Instalação via USB",
"GameCard Install": "Instalação de cartão de jogo",
"FTP Install (EXPERIMENTAL)": "Instalação via FTP (EXPERIMENTAL)",
"USB": "USB",
"GameCard": "Cartão de jogo",
"Disable MTP for usb install": "Escuta MTP desabilitada temporáriamente.",
"Re-enabled MTP": "Escuta MTP reabilitada.",
"Waiting for connection...": "Aguardando conexão...",
"Transferring data...": "Transferindo dados...",
"Ftp install success!": "Instalação via FTP concluída com sucesso.",
"Ftp install failed!": "Instalação via FTP falhou.",
"Usb install success!": "Instalação via USB concluída com sucesso.",
"Usb install failed!": "Instalação via USB falhou.",
"Gc install success!": "Instalação de cartão de jogo concluída com sucesso.",
"Gc install failed!": "Instalação de cartão de jogo falhou.",
"Installed via usb": "Instalado via USB",
"Failed to install via FTP, press B to exit...": "Falha ao instalar via FTP,\naperte B para sair.",
"Failed to init usb, press B to exit...": "Falha ao instalar via USB,\naperte B para sair.",
"Press B to exit...": "Aperte B para sair.",
"Connection Type: WiFi | Strength: ": "Conexão por rede Wi-Fi | Intensidade do sinal: ",
"Connection Type: Ethernet": "Conexão por cabo (ethernet)",
"Connection Type: None": "Sem conexão",
"Host:": "Host:",
"Port:": "Porta:",
"Username:": "Nome de usuário:",
"Password:": "Senha:",
"SSID:": "SSID:",
"Passphrase:": "Senha:",
"Installing ": "Instalando ",
"Uninstalling ": "Desinstalando ",
"Deleting ": "Excluindo ",
"Deleting": "Excluindo...",
"Pasting ": "Colando ",
"Pasting": "Colando ",
"Removing ": "Removendo ",
"Scanning ": "Analisando ",
"Creating ": "Criando ",
"Copying ": "Copiando ",
"Trying to load ": "Tentando carregar ",
"Downloading ": "Baixando ",
"Downloaded ": "Baixado ",
"Removed ": "Removido ",
"Checking MD5": "Checando MD5",
"Loading...": "Carregando...",
"Loading": "Carregando",
"Empty!": "Vazio",
"Not Ready...": "Não está pronto...",
"Error loading page!": "Erro ao carregar página.",
"Update avaliable: ": "Atualização disponível: ",
"Download update: ": "Baixar autalização: ",
"Updated to ": "Atualizado para ",
"Press OK to restart Sphaira": "Selecione OK para reiniciar o sphaira.",
"Restart Sphaira?": "Reiniciar sphaira?",
"Failed to download update": "Falha ao baixar a atualização.",
"Restore hbmenu?": "Restaurar hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Falha ao buscar /switch/hbmenu.nro\nUse a loja (AppStore) para reinstalar o hbmenu.",
"Failed to restore hbmenu, please re-download hbmenu": "Falha ao restaurar o hbmenu, baixe o hbmenu novamente.",
"Failed to restore hbmenu, using sphaira instead": "Falha ao restaurar hbmenu, usando sphaira.",
"Restored hbmenu, closing sphaira": "hbmenu restaurado, fechando sphaira.",
"Restored hbmenu": "hbmenu restaurado.",
"Delete Selected files?": "Excluir os arquivos selecionados?",
"Completely remove ": "Remover completamente ",
"Delete successfull!": "Exclusão foi concluída com sucesso.",
"Delete failed!": "Exclusão falhou",
"Are you sure you want to delete ": "Você tem certeza que quer excluir ",
"Are you sure you wish to cancel?": "Você tem certeza que quer cancelar?",
"Audio disabled due to suspended game": "Áudio desativado devido ao software suspenso.",
"If this message appears repeatedly, please open an issue.": "Se esta mensagem aparecer repetidamente, abra um issue."
}

View File

@@ -19,6 +19,7 @@
"Details": "", "Details": "",
"Update": "", "Update": "",
"Remove": "", "Remove": "",
"Restore": "",
"Download": "Скачать", "Download": "Скачать",
"Next Page": "Следующая страница", "Next Page": "Следующая страница",
"Prev Page": "Предыдущая страница", "Prev Page": "Предыдущая страница",
@@ -26,6 +27,12 @@
"Star": "", "Star": "",
"System memory": "", "System memory": "",
"microSD card": "", "microSD card": "",
"Sd": "",
"Image System memory": "",
"Image microSD card": "",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Да", "Yes": "Да",
"No": "Нет", "No": "Нет",
"Enabled": "Включено", "Enabled": "Включено",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "", "Alphabetical (Star)": "",
"Likes": "", "Likes": "",
"ID": "", "ID": "",
"Decending": "По убыванию", "Descending": "По убыванию",
"Descending (down)": "По убыванию", "Descending (down)": "По убыванию",
"Desc": "По убыванию", "Desc": "По убыванию",
"Ascending": "По возрастанию", "Ascending": "По возрастанию",
@@ -54,14 +61,16 @@
"Asc": "По возрастанию", "Asc": "По возрастанию",
"Menu Options": "Параметры меню", "Menu Options": "Параметры меню",
"Header": "Заголовок",
"Theme": "Тема", "Theme": "Тема",
"Theme Options": "Параметры темы", "Theme Options": "Параметры темы",
"Select Theme": "Выберите тему", "Select Theme": "Выберите тему",
"Shuffle": "Перетасовать", "Shuffle": "Перетасовать",
"Music": "Музыка", "Music": "Музыка",
"12 Hour Time": "",
"Network": "Сеть", "Network": "Сеть",
"Network Options": "Параметры сети", "Network Options": "Параметры сети",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "", "Nxlink Connected": "",
"Nxlink Upload": "", "Nxlink Upload": "",
@@ -82,6 +91,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Журналирование", "Logging": "Журналирование",
"Replace hbmenu on exit": "Заменить hbmenu при выходе", "Replace hbmenu on exit": "Заменить hbmenu при выходе",
"Misc": "Прочее", "Misc": "Прочее",
@@ -90,6 +100,7 @@
"Install forwarders": "", "Install forwarders": "",
"Install location": "", "Install location": "",
"Show install warning": "", "Show install warning": "",
"Text scroll speed": "",
"FileBrowser": "Файловый менеджер", "FileBrowser": "Файловый менеджер",
"%zd files": "%zd files", "%zd files": "%zd files",
@@ -112,6 +123,8 @@
"Create Folder": "Создать папку", "Create Folder": "Создать папку",
"Set Folder Name": "", "Set Folder Name": "",
"View as text (unfinished)": "Посмотреть как текст (незакончено)", "View as text (unfinished)": "Посмотреть как текст (незакончено)",
"Ignore read only": "",
"Mount": "",
"Empty...": "", "Empty...": "",
"Open with DayBreak?": "", "Open with DayBreak?": "",
"Launch ": "", "Launch ": "",
@@ -199,6 +212,10 @@
"Bad Page": "", "Bad Page": "",
"Download theme?": "", "Download theme?": "",
"GitHub": "",
"Downloading json": "",
"Select asset to download for ": "",
"Installing ": "", "Installing ": "",
"Uninstalling ": "", "Uninstalling ": "",
"Deleting ": "", "Deleting ": "",
@@ -211,6 +228,8 @@
"Copying ": "", "Copying ": "",
"Trying to load ": "", "Trying to load ": "",
"Downloading ": "", "Downloading ": "",
"Downloaded ": "",
"Removed ": "",
"Checking MD5": "", "Checking MD5": "",
"Loading...": "", "Loading...": "",
"Loading": "", "Loading": "",
@@ -220,11 +239,19 @@
"Update avaliable: ": "", "Update avaliable: ": "",
"Download update: ": "", "Download update: ": "",
"Updated to ": "", "Updated to ": "",
"Press OK to restart Sphaira": "",
"Restart Sphaira?": "", "Restart Sphaira?": "",
"Failed to download update": "", "Failed to download update": "",
"Restore hbmenu?": "",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "",
"Failed to restore hbmenu, please re-download hbmenu": "",
"Failed to restore hbmenu, using sphaira instead": "",
"Restored hbmenu, closing sphaira": "",
"Restored hbmenu": "",
"Delete Selected files?": "", "Delete Selected files?": "",
"Completely remove ": "", "Completely remove ": "",
"Are you sure you want to delete ": "Вы уверены, что хотите удалить ", "Are you sure you want to delete ": "Вы уверены, что хотите удалить ",
"Are you sure you wish to cancel?": "", "Are you sure you wish to cancel?": "",
"Audio disabled due to suspended game": "",
"If this message appears repeatedly, please open an issue.": "" "If this message appears repeatedly, please open an issue.": ""
} }

View File

@@ -1,6 +1,6 @@
{ {
"[Applet Mode]": "[Applet-läge]", "[Applet Mode]": "[Applet-läge]",
"No Internet": "Ingen internetanslutning", "No Internet": "Ingen Internetanslutning",
"Files": "Filer", "Files": "Filer",
"Apps": "Appar", "Apps": "Appar",
"Store": "Butik", "Store": "Butik",
@@ -14,60 +14,69 @@
"Info": "Info", "Info": "Info",
"Install": "Installera", "Install": "Installera",
"Delete": "Radera", "Delete": "Radera",
"Restart": "", "Restart": "Starta om",
"Changelog": "Ändringslogg", "Changelog": "Ändringslogg",
"Details": "Detaljer", "Details": "Detaljer",
"Update": "Uppdatera", "Update": "Uppdatera",
"Remove": "Ta bort", "Remove": "Ta bort",
"Restore": "Återställ",
"Download": "Ladda ner", "Download": "Ladda ner",
"Next Page": "Nästa sida", "Next Page": "Nästa sida",
"Prev Page": "Föregående sida", "Prev Page": "Föregående sida",
"Unstar": "", "Unstar": "Avmarkera stjärna",
"Star": "", "Star": "Markera stjärna",
"System memory": "", "System memory": "Systemminne",
"microSD card": "", "microSD card": "microSD-kort",
"Sd": "Sd",
"Image System memory": "Avbild Systemminne",
"Image microSD card": "Avbild microSD-kort",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Ja", "Yes": "Ja",
"No": "Nej", "No": "Nej",
"Enabled": "Aktiverad", "Enabled": "Aktiverad",
"Disabled": "Avaktiverad", "Disabled": "Inaktiverad",
"Sort By": "Sortera efter", "Sort By": "Sortera efter",
"Sort Options": "Sorteringsalternativ", "Sort Options": "Sorteringsalternativ",
"Filter": "Filter", "Filter": "Filtrera",
"Sort": "Sortera", "Sort": "Sortera",
"Order": "Ordning", "Order": "Ordning",
"Search": "Sök", "Search": "Sök",
"Updated": "Uppdaterad", "Updated": "Uppdaterad",
"Updated (Star)": "", "Updated (Star)": "Uppdaterad (Stjärna)",
"Downloads": "Nedladdningar", "Downloads": "Nedladdningar",
"Size": "Storlek", "Size": "Storlek",
"Size (Star)": "", "Size (Star)": "Storlek (Stjärna)",
"Alphabetical": "Alfabetisk", "Alphabetical": "Alfabetisk",
"Alphabetical (Star)": "", "Alphabetical (Star)": "Alfabetisk (Stjärna)",
"Likes": "Gillar", "Likes": "Gillar",
"ID": "ID", "ID": "ID",
"Decending": "Fallande", "Descending": "Fallande",
"Descending (down)": "Fallande (nedåt)", "Descending (down)": "Fallande (nedåt)",
"Desc": "Fallande", "Desc": "Fall",
"Ascending": "Stigande", "Ascending": "Stigande",
"Ascending (Up)": "Stigande (uppåt)", "Ascending (Up)": "Stigande (uppåt)",
"Asc": "Stigande", "Asc": "Stig",
"Menu Options": "Menyalternativ", "Menu Options": "Menyalternativ",
"Header": "Rubrik",
"Theme": "Tema", "Theme": "Tema",
"Theme Options": "Temaalternativ", "Theme Options": "Temaalternativ",
"Select Theme": "Välj tema", "Select Theme": "Välj tema",
"Shuffle": "Blanda", "Shuffle": "Blanda",
"Music": "Musik", "Music": "Musik",
"12 Hour Time": "",
"Network": "Nätverk", "Network": "Nätverk",
"Network Options": "Nätverksalternativ", "Network Options": "Nätverksalternativ",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink ansluten", "Nxlink Connected": "Nxlink ansluten",
"Nxlink Upload": "Nxlink uppladdning", "Nxlink Upload": "Nxlink överför",
"Nxlink Finished": "Nxlink klar", "Nxlink Finished": "Nxlink klar",
"Switch-Handheld!": "", "Switch-Handheld!": "Switch Handhållen!",
"Switch-Docked!": "", "Switch-Docked!": "Switch Dockad!",
"Language": "Språk", "Language": "Språk",
"Auto": "Auto", "Auto": "Auto",
"English": "Engelska", "English": "Engelska",
@@ -82,15 +91,17 @@
"Portuguese": "Portugisiska", "Portuguese": "Portugisiska",
"Russian": "Ryska", "Russian": "Ryska",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Loggning", "Logging": "Loggning",
"Replace hbmenu on exit": "Ersätt hbmenu vid avslut", "Replace hbmenu on exit": "Ersätt hbmenu vid avslut",
"Misc": "Övrigt", "Misc": "Övrigt",
"Misc Options": "Övriga alternativ", "Misc Options": "Övriga alternativ",
"Web": "Webb", "Web": "Webb",
"Install forwarders": "", "Install forwarders": "Installera genvägar",
"Install location": "", "Install location": "Installationsplats",
"Show install warning": "", "Show install warning": "Visa installationsvarning",
"Text scroll speed": "",
"FileBrowser": "Filbläddrare", "FileBrowser": "Filbläddrare",
"%zd files": "%zd filer", "%zd files": "%zd filer",
"%zd dirs": "%zd kataloger", "%zd dirs": "%zd kataloger",
@@ -112,29 +123,31 @@
"Create Folder": "Skapa mapp", "Create Folder": "Skapa mapp",
"Set Folder Name": "Ange mappnamn", "Set Folder Name": "Ange mappnamn",
"View as text (unfinished)": "Visa som text (ofärdig)", "View as text (unfinished)": "Visa som text (ofärdig)",
"Ignore read only": "Ignorera skrivskydd",
"Mount": "Montera",
"Empty...": "Tom...", "Empty...": "Tom...",
"Open with DayBreak?": "Öppna med DayBreak?", "Open with DayBreak?": "Öppna med DayBreak?",
"Launch ": "", "Launch ": "Starta ",
"Launch option for: ": "Startalternativ för: ", "Launch option for: ": "Startalternativ för: ",
"Select launcher for: ": "", "Select launcher for: ": "Välj startprogram för: ",
"Homebrew": "Homebrew", "Homebrew": "Homebrew",
"Homebrew Options": "Homebrew-alternativ", "Homebrew Options": "Homebrew-alternativ",
"Hide Sphaira": "Dölj Sphaira", "Hide Sphaira": "Dölj Sphaira",
"Install Forwarder": "Installera forwarder", "Install Forwarder": "Installera genväg",
"WARNING: Installing forwarders will lead to a ban!": "VARNING: Att installera forwarders leder till en avstängning!", "WARNING: Installing forwarders will lead to a ban!": "VARNING: Att installera genvägar kan leda till avstängning!",
"Installing Forwarder": "Installerar forwarder", "Installing Forwarder": "Installerar genväg",
"Creating Program": "Skapar program", "Creating Program": "Skapar program",
"Creating Control": "Skapar kontroll", "Creating Control": "Skapar kontroll",
"Creating Meta": "Skapar meta", "Creating Meta": "Skapar metadata",
"Writing Nca": "Skriver Nca", "Writing Nca": "Skriver Nca",
"Updating ncm databse": "Uppdaterar ncm-databas", "Updating ncm databse": "Uppdaterar ncm-databas",
"Pushing application record": "Lägger till applikationspost", "Pushing application record": "Skickar programpost",
"Installed!": "Installerad!", "Installed!": "Installerad!",
"Failed to install forwarder": "Misslyckades med att installera forwarder", "Failed to install forwarder": "Misslyckades att installera genväg",
"Unstarred ": "", "Unstarred ": "Avmarkerad ",
"Starred ": "", "Starred ": "Markerad ",
"AppStore": "AppStore", "AppStore": "AppStore",
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortering: %s | Ordning: %s", "Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortering: %s | Ordning: %s",
"AppStore Options": "AppStore-alternativ", "AppStore Options": "AppStore-alternativ",
@@ -149,21 +162,21 @@
"category: %s": "kategori: %s", "category: %s": "kategori: %s",
"extracted: %.2f MiB": "extraherad: %.2f MiB", "extracted: %.2f MiB": "extraherad: %.2f MiB",
"app_dls: %s": "app_nedladdningar: %s", "app_dls: %s": "app_nedladdningar: %s",
"More by Author": "Mer från författaren", "More by Author": "Mer av författaren",
"Leave Feedback": "Lämna feedback", "Leave Feedback": "Lämna feedback",
"Irs": "Irs", "Irs": "Irs",
"Ambient Noise Level: ": "Omgivningsljudnivå: ", "Ambient Noise Level: ": "Omgivningsljudnivå: ",
"Controller": "Kontroll", "Controller": "Kontroller",
"Pad ": "Handkontroll ", "Pad ": "Handkontroll ",
" (Available)": " (Tillgänglig)", " (Available)": " (Tillgänglig)",
" (Unsupported)": "", " (Unsupported)": " (Ej stödd)",
" (Unconnected)": " (Ej ansluten)", " (Unconnected)": " (Ej ansluten)",
"HandHeld": "Handhållen", "HandHeld": "Handhållen",
"Rotation": "Rotation", "Rotation": "Rotation",
"0 (Sideways)": "0 (Sido)", "0 (Sideways)": "0 (Sidan)",
"90 (Flat)": "90 (Platt)", "90 (Flat)": "90 (Platt)",
"180 (-Sideways)": "180 (-Sido)", "180 (-Sideways)": "180 (-Sidan)",
"270 (Upside down)": "270 (Upp och ner)", "270 (Upside down)": "270 (Upp och ner)",
"Colour": "Färg", "Colour": "Färg",
"Grey": "Grå", "Grey": "Grå",
@@ -173,8 +186,8 @@
"Blue": "Blå", "Blue": "Blå",
"Light Target": "Ljusmål", "Light Target": "Ljusmål",
"All leds": "Alla lysdioder", "All leds": "Alla lysdioder",
"Bright group": "Ljusstark grupp", "Bright group": "Ljus grupp",
"Dim group": "Dämpad grupp", "Dim group": "Dimma grupp",
"None": "Ingen", "None": "Ingen",
"Gain": "Förstärkning", "Gain": "Förstärkning",
"Negative Image": "Negativ bild", "Negative Image": "Negativ bild",
@@ -186,10 +199,10 @@
"80x60": "80×60", "80x60": "80×60",
"40x30": "40×30", "40x30": "40×30",
"20x15": "20×15", "20x15": "20×15",
"Trimming Format": "Trimformat", "Trimming Format": "Trimningsformat",
"External Light Filter": "Extern ljusfilter", "External Light Filter": "Externt ljusfilter",
"Load Default": "Ladda standardinställningar", "Load Default": "Ladda standard",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Themezer-alternativ", "Themezer Options": "Themezer-alternativ",
"Nsfw": "Nsfw", "Nsfw": "Nsfw",
@@ -198,7 +211,11 @@
"Enter Page Number": "Ange sidnummer", "Enter Page Number": "Ange sidnummer",
"Bad Page": "Ogiltig sida", "Bad Page": "Ogiltig sida",
"Download theme?": "Ladda ner tema?", "Download theme?": "Ladda ner tema?",
"GitHub": "GitHub",
"Downloading json": "Laddar ner JSON",
"Select asset to download for ": "Välj tillgång att ladda ner för ",
"Installing ": "Installerar ", "Installing ": "Installerar ",
"Uninstalling ": "Avinstallerar ", "Uninstalling ": "Avinstallerar ",
"Deleting ": "Raderar ", "Deleting ": "Raderar ",
@@ -209,22 +226,32 @@
"Scanning ": "Skannar ", "Scanning ": "Skannar ",
"Creating ": "Skapar ", "Creating ": "Skapar ",
"Copying ": "Kopierar ", "Copying ": "Kopierar ",
"Trying to load ": "", "Trying to load ": "Försöker ladda ",
"Downloading ": "Laddar ner ", "Downloading ": "Laddar ner ",
"Downloaded ": "Nedladdad ",
"Removed ": "Borttagen ",
"Checking MD5": "Kontrollerar MD5", "Checking MD5": "Kontrollerar MD5",
"Loading...": "Laddar...", "Loading...": "Laddar...",
"Loading": "Laddar", "Loading": "Laddar",
"Empty!": "Tomt!", "Empty!": "Tomt!",
"Not Ready...": "Ej redo...", "Not Ready...": "Inte redo...",
"Error loading page!": "Fel vid laddning av sida!", "Error loading page!": "Fel vid laddning av sida!",
"Update avaliable: ": "Uppdatering tillgänglig: ", "Update avaliable: ": "Uppdatering tillgänglig: ",
"Download update: ": "Ladda ner uppdatering: ", "Download update: ": "Ladda ner uppdatering: ",
"Updated to ": "", "Updated to ": "Uppdaterad till ",
"Restart Sphaira?": "", "Press OK to restart Sphaira": "",
"Failed to download update": "Misslyckades med att ladda ner uppdatering", "Restart Sphaira?": "Starta om Sphaira?",
"Failed to download update": "Misslyckades att ladda ner uppdatering",
"Restore hbmenu?": "Återställ hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Kunde inte hitta /switch/hbmenu.nro\nInstallera om hbmenu från Appstore ",
"Failed to restore hbmenu, please re-download hbmenu": "Misslyckades med att återställa hbmenu, vänligen ladda ner hbmenu igen.",
"Failed to restore hbmenu, using sphaira instead": "Misslyckades med att återställa hbmenu, använder istället sphaira.",
"Restored hbmenu, closing sphaira": "Återställde hbmenu, stänger sphaira.",
"Restored hbmenu": "Återställde hbmenu.",
"Delete Selected files?": "Radera valda filer?", "Delete Selected files?": "Radera valda filer?",
"Completely remove ": "Ta bort helt ", "Completely remove ": "Ta bort helt ",
"Are you sure you want to delete ": "Är du säker på att du vill radera ", "Are you sure you want to delete ": "Är du säker på att du vill radera ",
"Are you sure you wish to cancel?": "Är du säker på att du vill avbryta?", "Are you sure you wish to cancel?": "Är du säker på att du vill avbryta?",
"If this message appears repeatedly, please open an issue.": "" "Audio disabled due to suspended game": "Ljud är avstängt på grund av bakgrundsprogram",
"If this message appears repeatedly, please open an issue.": "Om detta meddelande visas upprepade gånger, vänligen öppna en felanmälan.",
} }

326
assets/romfs/i18n/uk.json Normal file
View File

@@ -0,0 +1,326 @@
{
"[Applet Mode]": "[Режим Аплету]",
"No Internet": "Без інтернету",
"Files": "Файли",
"Apps": "Програми",
"Store": "Магазин",
"Menu": "Меню",
"Options": "Налаштування",
"OK": "ОК",
"Back": "Назад",
"Select": "Вибрати",
"Open": "Відкрити",
"Launch": "Запустити",
"Info": "Інфо",
"Install": "Встановити",
"Delete": "Видалити",
"Restart": "Перезапустити",
"Changelog": "Журнал змін",
"Details": "Деталі",
"Update": "Оновити",
"Remove": "Видалити",
"Restore": "Відновити",
"Download": "Завантажити",
"Next": "Наступний",
"Prev": "Попередній",
"Next Page": "Наступна сторінка",
"Prev Page": "Попередня сторінка",
"Unstar": "Прибрати з обраного",
"Star": "Позначити зіркою",
"System memory": "Пам'ять консолі",
"microSD card": "SD-карта",
"Sd": "SD-карта",
"Image System memory": "Фото | Пам'ять консолі",
"Image microSD card": "Фото | SD-карта",
"Slow": "Повільно",
"Normal": "Нормально",
"Fast": "Швидко",
"Yes": "Так",
"No": "Ні",
"On": "Увімк.",
"Off": "Вимк.",
"Enable": "Увімк.",
"Enabled": "Увімк.",
"Disabled": "Вимк.",
"Sort By": "Сортувати за",
"Sort Options": "Опції сортування",
"Filter": "Фільтр",
"Sort": "Сортування",
"Order": "Порядок",
"Search": "Пошук",
"Updated": "Оновлено",
"Updated (Star)": "Оновлено (Зірка)",
"Downloads": "Завантаження",
"Size": "Розмір",
"Size (Star)": "Розмір (Зірка)",
"Alphabetical": "За алфавітом",
"Alphabetical (Star)": "За алфавітом (Зірка)",
"Likes": "Вподобання",
"ID": "ID",
"Descending": "За спаданням",
"Descending (down)": "За спаданням (вниз)",
"Desc": "Спад.",
"Ascending": "За зростанням",
"Ascending (Up)": "За зростанням (вгору)",
"Asc": "Зрост.",
"Menu Options": "Опції меню",
"Theme": "Тема",
"Theme Options": "Опції теми",
"Select Theme": "Вибрати тему",
"Shuffle": "Перемішати",
"Music": "Музика",
"12 Hour Time": "12-годинний формат часу",
"Download Default Music": "Завантажити музику за замовчуванням",
"Network": "Мережа",
"Network Options": "Опції мережі",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink підключено",
"Nxlink Upload": "Nxlink | Завантаження",
"Nxlink Finished": "Nxlink | Завершено",
"Switch-Handheld!": "Switch - Портатив!",
"Switch-Docked!": "Switch - Докований!",
"Language": "Мова",
"Auto": "Автоматично",
"English": "English",
"Japanese": "日本語",
"French": "Français",
"German": "Deutsch",
"Italian": "Italiano",
"Spanish": "Español",
"Chinese": "中文",
"Korean": "한국어",
"Dutch": "Nederlands",
"Portuguese": "Português",
"Russian": "Русский",
"Swedish": "Svenska",
"Vietnamese": "Tiếng Việt",
"Logging": "Логування",
"Replace hbmenu on exit": "Заміна hbmenu при виході",
"Misc": "Різне",
"Misc Options": "Опції різного",
"Web": "Веб",
"Install forwarders": "Встановити форвардери",
"Install location": "Місце встановлення",
"Show install warning": "Попередж. при встанов.",
"Text scroll speed": "Швидк. прокрутки",
"Set right-side menu": "Праве меню",
"FileBrowser": "Файловий менеджер",
"%zd files": "%zd файл(и)",
"%zd dirs": "%zd тек(и)",
"File Options": "Опції файлів",
"Show Hidden": "Показати приховані",
"Folders First": "Теки спочатку",
"Hidden Last": "Приховані в кінці",
"Cut": "Вирізати",
"Copy": "Копіювати",
"Paste": "Вставити",
"Paste ": "Вставити: ",
" file(s)?": " файл(и)?",
"Rename": "Перейменувати",
"Compress to zip": "Стиснути в zip",
"Set New File Name": "Введіть нове ім'я файлу",
"Advanced": "Додатково",
"Advanced Options": "Додаткові опції",
"Create File": "Створити файл",
"Set File Name": "Введіть ім'я файлу",
"Create Folder": "Створити теку",
"Set Folder Name": "Введіть ім'я теки",
"View as text (unfinished)": "Переглянути як текст (незавершено)",
"Ignore read only": "Ігнорувати лише читання",
"Mount": "Монтувати",
"Empty...": "Пусто...",
"Open with DayBreak?": "Відкрити за допомогою DayBreak?",
"Launch ": "Запустити ",
"Launch option for: ": "Опція запуску для: ",
"Select launcher for: ": "Виберіть лаунчер для: ",
"Homebrew": "Домашні програми",
"Homebrew Options": "Опції домашніх програм",
"Hide Sphaira": "Приховати Sphaira",
"Install Forwarder": "Встановити форвардер",
"WARNING: Installing forwarders will lead to a ban!": "УВАГА: Встановлення форвардерів може призвести до бану!",
"Installing Forwarder": "Встановлення форвардера",
"Creating Program": "Створення програми",
"Creating Control": "Створення контролера",
"Creating Meta": "Створення метаданих",
"Writing Nca": "Запис NCA",
"Updating ncm databse": "Оновлення бази даних NCM",
"Pushing application record": "Запис даних програми",
"Installed!": "Встановлено!",
"Failed to install forwarder": "Не вдалося встановити форвардер",
"Unstarred ": "Знято зірку з ",
"Starred ": "Позначено зіркою ",
"AppStore": "Магазин програм",
"Filter: %s | Sort: %s | Order: %s": "Фільтр: %s | Сорт.: %s | Порядок: %s",
"AppStore Options": "Опції магазину програм",
"All": "Всі",
"Games": "Ігри",
"Emulators": "Емулятори",
"Tools": "Інструменти",
"Themes": "Теми",
"Legacy": "Доступні оновлення",
"version: %s": "версія: %s",
"updated: %s": "оновлено: %s",
"category: %s": "категорія: %s",
"extracted: %.2f MiB": "розмір: %.2f MiB",
"app_dls: %s": "завантажень: %s",
"More by Author": "Більше від автора",
"Leave Feedback": "Залишити відгук",
"Game Options": "Опції ігор",
"Launch random game": "Запустити випадкову гру",
"List meta records": "Список метаданих записів",
"Entries": "Записи",
"Delete entity": "Видалити сутність",
"Hide forwarders": "Приховати форвардери",
"Irs": "ІЧ-сенсор",
"IRS (Infrared Joycon Camera)": "ІЧ (Інфрачервона камера Joycon)",
"Ambient Noise Level: ": "Рівень навколишнього шуму: ",
"Controller": "Контролер",
"Pad ": "Геймпад ",
" (Available)": " (Доступно)",
" (Unsupported)": " (Не підтримується)",
" (Unconnected)": " (Не підключено)",
"HandHeld": "Портативний режим",
"Rotation": "Обертання",
"0 (Sideways)": "0° (Збоку)",
"90 (Flat)": "90° (Плоско)",
"180 (-Sideways)": "180° (-Збоку)",
"270 (Upside down)": "270° (Догори дном)",
"Colour": "Колір",
"Grey": "Сірий",
"Ironbow": "Ironbow",
"Green": "Зелений",
"Red": "Червоний",
"Blue": "Синій",
"Light Target": "Ціль освітлення",
"All leds": "Всі світлодіоди",
"Bright group": "Яскрава група",
"Dim group": "Тьмяна група",
"None": "Немає",
"Gain": "Підсилення",
"Negative Image": "Негативне зображення",
"Normal image": "Нормальне зображення",
"Trimming Format": "Формат обрізки",
"External Light Filter": "Фільтр зовнішнього освітлення",
"Load Default": "Завантажити типові",
"Format": "Формат",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"Themezer": "Themezer",
"Themezer Options": "Опції Themezer",
"Nsfw": "NSFW",
"Page": "Сторінка",
"Page %zu / %zu": "Сторінка %zu / %zu",
"Enter Page Number": "Введіть номер сторінки",
"Bad Page": "Неправильна сторінка",
"Download theme?": "Завантажити тему?",
"GitHub": "GitHub",
"Downloading json": "Завантаження JSON",
"Select asset to download for ": "Виберіть ресурс для завантаження для ",
"Install Options": "Опції встановлення",
"Install options": "Опції встановлення",
"Boost CPU clock": "Розігнати CPU",
"Allow downgrade": "Дозволити відкат",
"Skip if already installed": "Пропуск, якщо встановл.",
"Ticket only": "Тільки тікет",
"Patch ticket": "Змінити тікет",
"Skip base": "Пропустити базу",
"Skip patch": "Пропустити патч",
"Skip dlc": "Пропустити DLC",
"Skip data patch": "Пропустити патч даних",
"Skip ticket": "Пропустити тікет",
"skip NCA hash verify": "Пропуск перевірку хешу NCA",
"Skip RSA header verify": "Пропуск перевірку заголовка RSA",
"Skip RSA NPDM verify": "Пропуск перевірку NPDM RSA",
"Ignore distribution bit": "Ігнорувати біт розподілу",
"Convert to standard crypto": "Конвертувати у стандартне шифрування",
"Lower master key": "Знизити майстер-ключ",
"Lower system version": "Знизити версію системи",
"Install Selected files?": "Встановити вибрані файли?",
"Installed": "Встановлено",
"Installed ": "Встановлено ",
"FTP Install": "Встановлення через FTP",
"USB Install": "Встановлення через USB",
"GameCard Install": "Встановлення з картриджа",
"FTP Install (EXPERIMENTAL)": "Встановлення через FTP (ЕКСПЕРИМЕНТАЛЬНО)",
"USB": "USB",
"GameCard": "Картридж",
"Disable MTP for usb install": "Вимкнути MTP для встановлення через USB",
"Re-enabled MTP": "MTP знову увімкнено",
"Waiting for connection...": "Очікування підключення...",
"Transferring data...": "Передача даних...",
"Ftp install success!": "Встановлення через FTP успішно завершено.",
"Ftp install failed!": "Встановлення через FTP не вдалося.",
"Usb install success!": "Встановлення через USB успішно завершено.",
"Usb install failed!": "Встановлення через USB не вдалося.",
"Gc install success!": "Встановлення з картриджа успішно завершено.",
"Gc install failed!": "Встановлення з картриджа не вдалося.",
"Installed via usb": "Встановлено через USB",
"Failed to install via FTP, press B to exit...": "Не вдалося встановити через FTP, натисніть B для виходу...",
"Failed to init usb, press B to exit...": "Не вдалося ініціалізувати USB, натисніть B для виходу...",
"Press B to exit...": "Натисніть B для виходу...",
"Connection Type: WiFi | Strength:": "Тип підключення: WiFi | Сила сигналу:",
"Connection Type: WiFi | Strength: ": "Тип підключення: WiFi | Сила сигналу: ",
"Connection Type: Ethernet": "Тип підключення: Ethernet",
"Connection Type: None": "Тип підключення: Немає",
"Host:": "Хост:",
"Port:": "Порт:",
"Username:": "Ім'я користувача:",
"Password:": "Пароль:",
"SSID:": "SSID:",
"Passphrase:": "Кодова фраза:",
"Installing ": "Встановлення ",
"Uninstalling ": "Видалення ",
"Deleting ": "Видалення ",
"Deleting": "Видалення",
"Pasting ": "Вставлення ",
"Pasting": "Вставлення",
"Removing ": "Видалення ",
"Scanning ": "Сканування ",
"Creating ": "Створення ",
"Copying ": "Копіювання ",
"Trying to load ": "Спроба завантажити ",
"Downloading ": "Завантаження ",
"Downloaded ": "Завантажено ",
"Removed ": "Видалено ",
"Checking MD5": "Перевірка MD5",
"Loading...": "Завантаження...",
"Loading": "Завантаження",
"Empty!": "Пусто!",
"Not Ready...": "Не готово...",
"Error loading page!": "Помилка завантаження сторінки!",
"Update avaliable: ": "Доступне оновлення: ",
"Download update: ": "Завантажити оновлення: ",
"Updated to ": "Оновлено до ",
"Press OK to restart Sphaira": "Натисніть OK для перезапуску Sphaira",
"Restart Sphaira?": "Перезапустити Sphaira?",
"Failed to download update": "Не вдалося завантажити оновлення",
"Restore hbmenu?": "Відновити hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Не вдалося знайти /switch/hbmenu.nro\nВикористовуйте Магазин програм для перевстановлення hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Не вдалося відновити hbmenu, будь ласка, завантажте hbmenu знову",
"Failed to restore hbmenu, using sphaira instead": "Не вдалося відновити hbmenu, замість цього використовується Sphaira",
"Restored hbmenu, closing sphaira": "hbmenu відновлено, закриття Sphaira",
"Restored hbmenu": "hbmenu відновлено",
"Delete Selected files?": "Видалити вибрані файли?",
"Completely remove ": "Повністю видалити ",
"Are you sure you want to delete ": "Ви впевнені, що хочете видалити ",
"Are you sure you wish to cancel?": "Ви впевнені, що хочете скасувати?",
"Audio disabled due to suspended game": "Аудіо вимкнено через призупинену програму",
"If this message appears repeatedly, please open an issue.": "Якщо це повідомлення з'являється повторно, будь ласка, повідомте про проблему."
}

257
assets/romfs/i18n/vi.json Normal file
View File

@@ -0,0 +1,257 @@
{
"[Applet Mode]": "[Applet Mode]",
"No Internet": "Không có Internet",
"Files": "Tập tin",
"Apps": "Ứng dụng",
"Store": "Cửa hàng",
"Menu": "Menu",
"Options": "Tuỳ chọn",
"OK": "OK",
"Back": "Trở về",
"Select": "Chọn",
"Open": "Mở",
"Launch": "Chạy",
"Info": "Thông tin",
"Install": "Cài đặt",
"Delete": "Xoá",
"Restart": "Khởi động lại",
"Changelog": "Thay đổi",
"Details": "Chi tiết",
"Update": "Cập nhật",
"Remove": "Gỡ",
"Restore": "Khôi phục",
"Download": "Tải về",
"Next Page": "Trang kế",
"Prev Page": "Trang trước",
"Unstar": "Xoá yêu thích",
"Star": "Yêu thích",
"System memory": "Bộ nhớ máy",
"microSD card": "Thẻ nhớ",
"Sd": "Sd",
"Image System memory": "Bộ nhớ hệ thống hình ảnh",
"Image microSD card": "Thẻ nhớ hệ thống hình ảnh",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Có",
"No": "Không",
"Enabled": "Bật",
"Disabled": "Tắt",
"Sort By": "Sắp xếp bởi",
"Sort Options": "Tuỳ chọn sắp xếp",
"Filter": "Lọc",
"Sort": "Sắp xếp",
"Order": "Thứ tự",
"Search": "Tìm kiếm",
"Updated": "Updated",
"Updated (Star)": "Đã cập nhật (Yêu thích)",
"Downloads": "Danh sách tải về",
"Size": "Kích thước",
"Size (Star)": "Kích thước (Yêu thích)",
"Alphabetical": "A-Z",
"Alphabetical (Star)": "A-Z (Yêu thích)",
"Likes": "Thích",
"ID": "ID",
"Descending": "Giảm dần",
"Descending (down)": "Giảm dần (xuống)",
"Desc": "Giảm",
"Ascending": "Tăng dần",
"Ascending (Up)": "Tăng dần (lên)",
"Asc": "Tăng",
"Menu Options": "Menu tuỳ chọn",
"Theme": "Theme",
"Theme Options": "Theme tuỳ chọn",
"Select Theme": "Chọn Theme",
"Shuffle": "Trộn",
"Music": "Âm nhạc",
"12 Hour Time": "",
"Network": "Mạng",
"Network Options": "Tuỳ chọn mạng",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink Kết Nối",
"Nxlink Upload": "Nxlink Đăng Tải",
"Nxlink Finished": "Nxlink Hoàn Thành",
"Switch-Handheld!": "Switch-Handheld!",
"Switch-Docked!": "Switch-Docked!",
"Language": "Ngôn ngữ",
"Auto": "Tự động",
"English": "English",
"Japanese": "日本語",
"French": "Français",
"German": "Deutsch",
"Italian": "Italiano",
"Spanish": "Español",
"Chinese": "中文",
"Korean": "한국어",
"Dutch": "Dutch",
"Portuguese": "Português",
"Russian": "Русский",
"Swedish": "Svenska",
"Vietnamese": "Việt Nam",
"Logging": "Logging",
"Replace hbmenu on exit": "Thay thế hbmenu khi thoát",
"Misc": "Tiện ích",
"Misc Options": "Tiện ích mở rộng",
"Web": "Web",
"Install forwarders": "Cài ra màn hình",
"Install location": "Vị trí cài đặt",
"Show install warning": "Hiển thị cảnh báo cài đặt",
"Text scroll speed": "",
"FileBrowser": "Duyệt tập tin",
"%zd files": "%zd tập tin",
"%zd dirs": "%zd thư mục",
"File Options": "Tuỳ chọn tập tin",
"Show Hidden": "Hiển thị tập tin ẩn",
"Folders First": "Thư mục đầu tiên",
"Hidden Last": "Ẩn cuối",
"Cut": "Cắt",
"Copy": "Sao chép",
"Paste": "Dán",
"Paste ": "Paste ",
" file(s)?": " tập tin(nhiều)?",
"Rename": "Đổi tên",
"Set New File Name": "Đặt tên mới cho tập tin",
"Advanced": "Mở rộng",
"Advanced Options": "Tuỳ chọn mở rộng",
"Create File": "Tạo tập tin",
"Set File Name": "Đặt tên cho tập tin",
"Create Folder": "Tạo thư mục",
"Set Folder Name": "Đặt tên thư mục",
"View as text (unfinished)": "Xem dạng văn bản (chưa xong)",
"Ignore read only": "Bỏ qua chỉ đọc",
"Mount": "Gắn",
"Empty...": "Rỗng...",
"Open with DayBreak?": "Mở với DayBreak?",
"Launch ": "Chạy ",
"Launch option for: ": "Chạy với tuỳ chọn cho: ",
"Select launcher for: ": "Chọn trình chạy cho: ",
"Homebrew": "Homebrew",
"Homebrew Options": "Tuỳ chọn Homebrew",
"Hide Sphaira": "Ẩn Sphaira",
"Install Forwarder": "Cài ra ngoài màn hình",
"WARNING: Installing forwarders will lead to a ban!": "CẢNH BÁO: Bạn có chắn muốn cài ra ngoài màn hình!",
"Installing Forwarder": "Đang cài đặt ra ngoài màn hình",
"Creating Program": "Tạo chương trình",
"Creating Control": "Tạo điều khiển",
"Creating Meta": "Tạo Meta",
"Writing Nca": "Ghi Nca",
"Updating ncm databse": "Cập nhật ncm databse",
"Pushing application record": "Đẩy ứng dụng",
"Installed!": "Đã cài xong!",
"Failed to install forwarder": "Cài đặt ra ngoài màn hình thất bại",
"Unstarred ": "Bỏ yêu thích ",
"Starred ": "Đã yêu thích ",
"AppStore": "AppStore",
"Filter: %s | Sort: %s | Order: %s": "Lọc: %s | Sắp xếp: %s | Thứ tự: %s",
"AppStore Options": "Tuỳ chọn AppStore",
"All": "Tất cả",
"Games": "Games",
"Emulators": "Emulators",
"Tools": "Tools",
"Themes": "Themes",
"Legacy": "Legacy",
"version: %s": "version: %s",
"updated: %s": "updated: %s",
"category: %s": "category: %s",
"extracted: %.2f MiB": "extracted: %.2f MiB",
"app_dls: %s": "app_dls: %s",
"More by Author": "Xem thêm tác giả",
"Leave Feedback": "Để lại phản hồi",
"Irs": "Irs",
"Ambient Noise Level: ": "Mức ồn xung quanh: ",
"Controller": "Điều khiển",
"Pad ": "Pad ",
" (Available)": " (Có sẵn)",
" (Unsupported)": " (Không hỗ trợ)",
" (Unconnected)": " (Không kết nối)",
"HandHeld": "Cầm tay",
"Rotation": "Xoay",
"0 (Sideways)": "0 (Đi ngang)",
"90 (Flat)": "90 (Phẳng)",
"180 (-Sideways)": "180 (-Đi ngang)",
"270 (Upside down)": "270 (Lộn ngược)",
"Colour": "Màu sắc",
"Grey": "Xám",
"Ironbow": "Ironbow",
"Green": "Xanh",
"Red": "Đỏ",
"Blue": "Xanh dương",
"Light Target": "Điểm sáng",
"All leds": "Tất cả đèn led",
"Bright group": "Nhóm sáng",
"Dim group": "Nhóm tối",
"None": "Không có",
"Gain": "Tăng",
"Negative Image": "Ảnh âm bản",
"Normal image": "Ảnh bình thường",
"Negative image": "Ảnh âm bản",
"Format": "Định dạng",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"Trimming Format": "Định dạng cắt tỉa",
"External Light Filter": "Bộ lộc ánh sáng bên ngoài",
"Load Default": "Tải mặc định",
"Themezer": "Themezer",
"Themezer Options": "Tuỳ chọn Themezer",
"Nsfw": "18+",
"Page": "Trang",
"Page %zu / %zu": "Trang %zu / %zu",
"Enter Page Number": "Nhập số trang",
"Bad Page": "Trang không tồn tại",
"Download theme?": "Tải theme?",
"GitHub": "GitHub",
"Downloading json": "Đang tải json",
"Select asset to download for ": "Chọn nội dung để tải xuống cho ",
"Installing ": "Đang cài đặt ",
"Uninstalling ": "Đang gỡ cài đặt ",
"Deleting ": "Đang xoá ",
"Deleting": "Đang xoá",
"Pasting ": "Đang dán ",
"Pasting": "Đang dán",
"Removing ": "Đang gỡ ",
"Scanning ": "Đang quét ",
"Creating ": "Đang tạo ",
"Copying ": "Đang sao chép ",
"Trying to load ": "Đang cố gắn mở ",
"Downloading ": "Đang tải xuống ",
"Downloaded ": "Đã tải xong ",
"Removed ": "Đã gỡ ",
"Checking MD5": "Kiểm tra MD5",
"Loading...": "Đang tải...",
"Loading": "Đang tải",
"Empty!": "Trống!",
"Not Ready...": "Chưa sẵn sàng...",
"Error loading page!": "Lỗi tải trang!",
"Update avaliable: ": "Cập nhậc có sẵn: ",
"Download update: ": "Tải cập nhật: ",
"Updated to ": "Đã cập nhật ",
"Press OK to restart Sphaira": "",
"Restart Sphaira?": "Khởi động lại Sphaira?",
"Failed to download update": "Cập nhật thất bại",
"Restore hbmenu?": "Khôi phục hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Không tìm thấy /switch/hbmenu.nro\nSử dụng AppStore để cài lại hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Không thể khôi phục hbmenu, Vui lòng tải lại hbmenu",
"Failed to restore hbmenu, using sphaira instead": "Không thể khôi phục hbmenu, thay vào đó sử dụng Sphira",
"Restored hbmenu, closing sphaira": "Khôi mục hbmenu thành công, đóng sphaira",
"Restored hbmenu": "Đã khôi phục hbmenu",
"Delete Selected files?": "Xoá những tập tin được chọn?",
"Completely remove ": "Đã gỡ thành công ",
"Are you sure you want to delete ": "Bạn có muốn xoá ",
"Are you sure you wish to cancel?": "Bạn có chắn muốn huỷ không?",
"Audio disabled due to suspended game": "",
"If this message appears repeatedly, please open an issue.": "Nếu thấy tin nhắn này, hãy báo lỗi."
}

View File

@@ -14,18 +14,27 @@
"Info": "信息", "Info": "信息",
"Install": "安装", "Install": "安装",
"Delete": "删除", "Delete": "删除",
"Restart": "", "Restart": "重启",
"Changelog": "更新日志", "Changelog": "更新日志",
"Details": "详情", "Details": "详情",
"Update": "更新", "Update": "更新",
"Remove": "删除", "Remove": "删除",
"Restore": "恢复",
"Download": "下载", "Download": "下载",
"Next": "下一项",
"Prev": "上一项",
"Next Page": "下一页", "Next Page": "下一页",
"Prev Page": "上一页", "Prev Page": "上一页",
"Unstar": "取消星标", "Unstar": "取消星标",
"Star": "星标", "Star": "星标",
"System memory": "主机内存", "System memory": "主机内存",
"microSD card": "SD卡", "microSD card": "SD卡",
"Sd": "SD卡",
"Image System memory": "主机内存图像",
"Image microSD card": "SD卡图像",
"Slow": "慢",
"Normal": "正常",
"Fast": "快",
"Yes": "是", "Yes": "是",
"No": "否", "No": "否",
"Enabled": "启用", "Enabled": "启用",
@@ -46,7 +55,7 @@
"Alphabetical (Star)": "按字母顺序(星标优先)", "Alphabetical (Star)": "按字母顺序(星标优先)",
"Likes": "点赞量", "Likes": "点赞量",
"ID": "ID", "ID": "ID",
"Decending": "降序", "Descending": "降序",
"Descending (down)": "降序", "Descending (down)": "降序",
"Desc": "降序", "Desc": "降序",
"Ascending": "升序", "Ascending": "升序",
@@ -54,20 +63,23 @@
"Asc": "升序", "Asc": "升序",
"Menu Options": "菜单选项", "Menu Options": "菜单选项",
"Header": "标题",
"Theme": "主题", "Theme": "主题",
"Theme Options": "主题选项", "Theme Options": "主题选项",
"Select Theme": "选择主题", "Select Theme": "选择主题",
"Shuffle": "随机播放", "Shuffle": "随机播放",
"Music": "音乐", "Music": "音乐",
"12 Hour Time": "12小时制时间",
"Download Default Music": "下载默认音乐",
"Network": "网络", "Network": "网络",
"Network Options": "网络选项", "Network Options": "网络选项",
"Nxlink": "Nxlink", "Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink插件提交",
"Nxlink Connected": "Nxlink 已连接", "Nxlink Connected": "Nxlink 已连接",
"Nxlink Upload": "Nxlink 上传中", "Nxlink Upload": "Nxlink 上传中",
"Nxlink Finished": "Nxlink 已结束", "Nxlink Finished": "Nxlink 已结束",
"Switch-Handheld!": "", "Switch-Handheld!": "切换至掌机模式!",
"Switch-Docked!": "", "Switch-Docked!": "切换至底座模式!",
"Language": "语言", "Language": "语言",
"Auto": "自动", "Auto": "自动",
"English": "English", "English": "English",
@@ -82,14 +94,16 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "日志", "Logging": "日志",
"Replace hbmenu on exit": "退出后用Sphaira替换hbmenu", "Replace hbmenu on exit": "退出后用Sphaira替换hbmenu",
"Misc": "杂项", "Misc": "拓展",
"Misc Options": "杂项设置", "Misc Options": "拓展设置",
"Web": "网页浏览器", "Web": "网页浏览器",
"Install forwarders": "允许安装前端应用", "Install forwarders": "允许安装前端应用",
"Install location": "安装位置", "Install location": "安装位置",
"Show install warning": "显示安装警告", "Show install warning": "显示安装警告",
"Text scroll speed": "文本滚动速度",
"FileBrowser": "文件浏览", "FileBrowser": "文件浏览",
"%zd files": "%zd 个文件", "%zd files": "%zd 个文件",
@@ -104,6 +118,7 @@
"Paste ": "粘贴 ", "Paste ": "粘贴 ",
" file(s)?": "个文件(夹)", " file(s)?": "个文件(夹)",
"Rename": "重命名", "Rename": "重命名",
"Compress to zip": "压缩到zip",
"Set New File Name": "输入新命名", "Set New File Name": "输入新命名",
"Advanced": "高级", "Advanced": "高级",
"Advanced Options": "高级选项", "Advanced Options": "高级选项",
@@ -112,12 +127,14 @@
"Create Folder": "新建文件夹", "Create Folder": "新建文件夹",
"Set Folder Name": "输入文件夹名", "Set Folder Name": "输入文件夹名",
"View as text (unfinished)": "以文本形式查看(未完善)", "View as text (unfinished)": "以文本形式查看(未完善)",
"Ignore read only": "忽略只读",
"Mount": "挂载",
"Empty...": "空...", "Empty...": "空...",
"Open with DayBreak?": "使用DayBreak打开", "Open with DayBreak?": "使用DayBreak打开",
"Launch ": "", "Launch ": "启动 ",
"Launch option for: ": "启动选项:", "Launch option for: ": "启动选项:",
"Select launcher for: ": "", "Select launcher for: ": "选择启动器用于:",
"Homebrew": "应用列表", "Homebrew": "应用列表",
"Homebrew Options": "应用选项", "Homebrew Options": "应用选项",
"Hide Sphaira": "在应用列表中隐藏Sphaira", "Hide Sphaira": "在应用列表中隐藏Sphaira",
@@ -132,8 +149,8 @@
"Pushing application record": "正在推送应用记录", "Pushing application record": "正在推送应用记录",
"Installed!": "安装完成!", "Installed!": "安装完成!",
"Failed to install forwarder": "前端应用安装失败", "Failed to install forwarder": "前端应用安装失败",
"Unstarred ": "", "Unstarred ": "取消星标 ",
"Starred ": "", "Starred ": "已星标 ",
"AppStore": "应用商店", "AppStore": "应用商店",
"Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s", "Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s",
@@ -152,12 +169,19 @@
"More by Author": "作者更多作品", "More by Author": "作者更多作品",
"Leave Feedback": "留言反馈", "Leave Feedback": "留言反馈",
"Game Options": "游戏选项",
"Launch random game": "开启随机游戏",
"List meta records": "列出元数据记录",
"Entries": "条目",
"Delete entity": "删除整体",
"Hide forwarders": "隐藏前端启动",
"Irs": "红外成像", "Irs": "红外成像",
"Ambient Noise Level: ": "环境噪声等级:", "Ambient Noise Level: ": "环境噪声等级:",
"Controller": "控制器", "Controller": "控制器",
"Pad ": "手柄 ", "Pad ": "手柄 ",
" (Available)": " (可用的)", " (Available)": " (可用的)",
" (Unsupported)": "", " (Unsupported)": " (不支持的)",
" (Unconnected)": " (未连接)", " (Unconnected)": " (未连接)",
"HandHeld": "掌机模式", "HandHeld": "掌机模式",
"Rotation": "旋转", "Rotation": "旋转",
@@ -176,7 +200,7 @@
"Bright group": "亮色组", "Bright group": "亮色组",
"Dim group": "暗色组", "Dim group": "暗色组",
"None": "无", "None": "无",
"Gain": "增益", "Gain": "曝光",
"Negative Image": "负片图像", "Negative Image": "负片图像",
"Normal image": "正常图像", "Normal image": "正常图像",
"Negative image": "负片图像", "Negative image": "负片图像",
@@ -199,6 +223,62 @@
"Bad Page": "错误的页面", "Bad Page": "错误的页面",
"Download theme?": "下载该主题?", "Download theme?": "下载该主题?",
"GitHub": "GitHub",
"Downloading json": "正在下载 json",
"Select asset to download for ": "选择要下载的资源用于 ",
"Install Options": "安装选项",
"Install options": "安装选项",
"Boost CPU clock": "提升 CPU 频率",
"Allow downgrade": "允许降级",
"Skip if already installed": "若已安装则跳过",
"Ticket only": "仅安装票据",
"Patch ticket": "修补票据",
"Skip base": "跳过基础部分",
"Skip patch": "跳过补丁",
"Skip dlc": "跳过 DLC可下载内容",
"Skip data patch": "跳过数据补丁",
"Skip ticket": "跳过票据",
"skip NCA hash verify": "跳过 NCA 哈希验证",
"Skip RSA header verify": "跳过 RSA 头部验证",
"Skip RSA NPDM verify": "跳过 RSA NPDM 验证",
"Ignore distribution bit": "忽略分布位",
"Convert to standard crypto": "转换为标准加密方式",
"Lower master key": "降低主密钥",
"Lower system version": "降低系统版本",
"Install Selected files?": "安装所选文件?",
"Installed": "已安装",
"FTP Install": "通过 FTP 安装",
"USB Install": "通过 USB 安装",
"GameCard Install": "卡带安装",
"FTP Install (EXPERIMENTAL)": "通过 FTP 安装(实验性)",
"USB": "USB",
"GameCard": "卡带",
"Disable MTP for usb install": "暂时禁用 USB 安装的 MTP 功能",
"Re-enabled MTP": "重新启用 MTP",
"Waiting for connection...": "等待连接中...",
"Transferring data...": "正在传输数据...",
"Ftp install success!": "通过 FTP 安装成功。",
"Ftp install failed!": "通过 FTP 安装失败。",
"Usb install success!": "通过 USB 安装成功。",
"Usb install failed!": "通过 USB 安装失败。",
"Gc install success!": "游戏安装成功。",
"Gc install failed!": "游戏安装失败。",
"Installed via usb": "通过 USB 安装",
"Failed to install via FTP, press B to exit...": "通过 FTP 安装失败,按 B 键退出...",
"Failed to init usb, press B to exit...": "USB 初始化失败,按 B 键退出...",
"Press B to exit...": "按 B 键退出...",
"Connection Type: WiFi | Strength:": "连接类型WiFi | 信号强度:",
"Connection Type: Ethernet": "连接类型:以太网",
"Connection Type: None": "连接类型:无",
"Host:": "主机:",
"Port:": "端口:",
"Username:": "用户名:",
"Password:": "密码:",
"SSID:": "网络名称:",
"Passphrase:": "密码:",
"Installing ": "正在安装 ", "Installing ": "正在安装 ",
"Uninstalling ": "正在卸载 ", "Uninstalling ": "正在卸载 ",
"Deleting ": "正在删除 ", "Deleting ": "正在删除 ",
@@ -209,22 +289,32 @@
"Scanning ": "正在扫描 ", "Scanning ": "正在扫描 ",
"Creating ": "正在创建 ", "Creating ": "正在创建 ",
"Copying ": "正在复制 ", "Copying ": "正在复制 ",
"Trying to load ": "", "Trying to load ": "尝试加载 ",
"Downloading ": "正在下载 ", "Downloading ": "正在下载 ",
"Downloaded ": "已下载 ",
"Removed ": "已移除 ",
"Checking MD5": "正在校验 MD5", "Checking MD5": "正在校验 MD5",
"Loading...": "加载中...", "Loading...": "加载中...",
"Loading": "加载中", "Loading": "加载中",
"Empty!": "空!", "Empty!": "空空如野",
"Not Ready...": "尚未准备好...", "Not Ready...": "尚未准备好...",
"Error loading page!": "页面加载失败!", "Error loading page!": "页面加载失败!",
"Update avaliable: ": "有可用更新!", "Update avaliable: ": "有可用更新!",
"Download update: ": "下载更新:", "Download update: ": "下载更新:",
"Updated to ": "", "Updated to ": "更新至 ",
"Restart Sphaira?": "", "Press OK to restart Sphaira": "按OK键以重启shphaira菜单",
"Restart Sphaira?": "重启 Sphaira",
"Failed to download update": "更新下载失败", "Failed to download update": "更新下载失败",
"Restore hbmenu?": "恢复 hbmenu",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "未能找到 /switch/hbmenu.nro\n请使用应用商店重新安装 hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "恢复 hbmenu 失败,请重新下载 hbmenu",
"Failed to restore hbmenu, using sphaira instead": "恢复 hbmenu 失败,改用 Sphaira",
"Restored hbmenu, closing sphaira": "已恢复 hbmenu正在关闭 Sphaira",
"Restored hbmenu": "已恢复 hbmenu",
"Delete Selected files?": "删除选中的文件?", "Delete Selected files?": "删除选中的文件?",
"Completely remove ": "彻底删除 ", "Completely remove ": "彻底删除 ",
"Are you sure you want to delete ": "您确定要删除吗 ", "Are you sure you want to delete ": "您确定要删除吗 ",
"Are you sure you wish to cancel?": "您确定要取消吗?", "Are you sure you wish to cancel?": "您确定要取消吗?",
"If this message appears repeatedly, please open an issue.": "" "Audio disabled due to suspended game": "由于游戏暂停,音频已禁用",
} "If this message appears repeatedly, please open an issue.": "若此消息反复出现,请提交问题报告。"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -1,22 +1,23 @@
[meta] [meta]
name=Abyss name=Abyss
author=TotalJustice author=TotalJustice
version=1.0.0 version=1.1.0
; unused currently inherit=romfs:/themes/base_black_theme.ini
preview=romfs:/theme/preview.jpg
[theme] [theme]
background=0x0f111aff background = 0x0f111a
grid=0x0f115c30 grid = 0x0f115c30
selected=0x0f115cff popup = 0x0f115c
selected_overlay=0x529cffff
text=0xffbc41ff
text_selected=0x529cffff
icon_audio=romfs:/theme/icon_audio.png line = 0xffbc41
icon_video=romfs:/theme/icon_video.png line_seperator = 0xffbc41
icon_image=romfs:/theme/icon_image.png
icon_file=romfs:/theme/icon_file.png text = 0xffbc41
icon_folder=romfs:/theme/icon_folder.png text_info = 0xd79f36
icon_zip=romfs:/theme/icon_zip.png text_selected = 0x529cff
icon_nro=romfs:/theme/icon_nro.png selected_background = 0x0f115c
scrollbar = 0x529cff
scrollbar_background = ; hide the background
progressbar = 0x3250f0

View File

@@ -0,0 +1,33 @@
[theme]
background = 0x2d2d2d
grid = 0x46464630
popup = 0x2d2d2d
error = 0xfa5a3a
line = 0xfbfbfb
line_separator = 0x707070
text = 0xfbfbfb
text_info = 0xd1d1d1
text_selected = 0x00ffc8
selected_background = 0x212227
sidebar = 0x000000dc
scrollbar = 0x00ffc8
scrollbar_background = ; hide the background
; scrollbar_background = 0x464646
progressbar = 0x00ffc8
progressbar_background = 0x464646
highlight_1 = 0x1989c6
highlight_2 = 0x89f0f2
icon_audio = romfs:/theme/icon_audio.png
icon_video = romfs:/theme/icon_video.png
icon_image = romfs:/theme/icon_image.png
icon_file = romfs:/theme/icon_file.png
icon_folder = romfs:/theme/icon_folder.png
icon_zip = romfs:/theme/icon_zip.png
icon_nro = romfs:/theme/icon_nro.png

View File

@@ -0,0 +1,34 @@
[theme]
background = 0xebebeb
grid = 0xf0f0f0
popup = 0xebebeb
error = 0xfa5a3a
line = 0x373737
line_separator = 0x6d787a
text = 0x373737
text_info = 0x808080
text_selected = 0x3250f0
selected_background = 0xfdfdfd
sidebar = 0xe2e2e2f5
scrollbar = 0xB0B0B0
scrollbar_background = ; hide the background
; scrollbar_background = 0xababab
progressbar = 0x3250f0
progressbar_background = 0x808080
highlight_1 = 0x1989c6
highlight_2 = 0x89f0f2
icon_colour = 0x6d787a
icon_audio = romfs:/theme/icon_audio.png
icon_video = romfs:/theme/icon_video.png
icon_image = romfs:/theme/icon_image.png
icon_file = romfs:/theme/icon_file.png
icon_folder = romfs:/theme/icon_folder.png
icon_zip = romfs:/theme/icon_zip.png
icon_nro = romfs:/theme/icon_nro.png

View File

@@ -1,23 +1,5 @@
[meta] [meta]
name=Black name=Black
author=TotalJustice author=TotalJustice
version=1.0.0 version=1.1.0
preview=romfs:/theme/preview.jpg inherit=romfs:/themes/base_black_theme.ini
[theme]
background=0x2d2d2dff
cursor=romfs:/theme/cursor.png
cursor_drag=romfs:/theme/cursor_drag.png
grid=0x46464630
selected=0x464646ff
selected_overlay=0x00ffc8ff
text=0xfbfbfbff
text_selected=0x00ffc8ff
icon_audio=romfs:/theme/icon_audio.png
icon_video=romfs:/theme/icon_video.png
icon_image=romfs:/theme/icon_image.png
icon_file=romfs:/theme/icon_file.png
icon_folder=romfs:/theme/icon_folder.png
icon_zip=romfs:/theme/icon_zip.png
icon_nro=romfs:/theme/icon_nro.png

View File

@@ -0,0 +1,15 @@
[meta]
name=Black alt-icons-SP
author=spkatsi
version=1.0.0
inherit=romfs:/themes/base_black_theme.ini
[theme]
icon_audio = romfs:/theme/icons-sp/icon_SP_audio.png
icon_video = romfs:/theme/icons-sp/icon_SP_video.png
icon_image = romfs:/theme/icons-sp/icon_SP_image.png
icon_file = romfs:/theme/icons-sp/icon_SP_file.png
icon_folder = romfs:/theme/icons-sp/icon_SP_folder.png
icon_zip = romfs:/theme/icons-sp/icon_SP_zip.png
icon_nro = romfs:/theme/icons-sp/icon_SP_nro.png

View File

@@ -1,23 +1,13 @@
[meta] [meta]
name=OLED Black name=OLED Black
author=iTotalJustice/Sanras author=TotalJustice/Sanras
version=1.0.0 version=1.1.0
preview=romfs:/theme/preview.jpg inherit=romfs:/themes/base_black_theme.ini
[theme] [theme]
background=0x000000ff background = 0x000000
cursor=romfs:/theme/cursor.png grid = 0x46464640
cursor_drag=romfs:/theme/cursor_drag.png popup = 0x323232
grid=0x46464640 text = 0xfbfbfb
selected=0x323232ff text_selected = 0x00ffc8
selected_overlay=0x00ffc8ff selected_background = 0x323232
text=0xfbfbfbff
text_selected=0x00ffc8ff
icon_audio=romfs:/theme/icon_audio.png
icon_video=romfs:/theme/icon_video.png
icon_image=romfs:/theme/icon_image.png
icon_file=romfs:/theme/icon_file.png
icon_folder=romfs:/theme/icon_folder.png
icon_zip=romfs:/theme/icon_zip.png
icon_nro=romfs:/theme/icon_nro.png

View File

@@ -0,0 +1,5 @@
[meta]
name=White
author=TotalJustice/Yorunokyujitsu
version=1.0.0
inherit=romfs:/themes/base_white_theme.ini

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
set(sphaira_VERSION 0.5.0) set(sphaira_VERSION 0.10.3)
project(sphaira project(sphaira
VERSION ${sphaira_VERSION} VERSION ${sphaira_VERSION}
@@ -45,18 +45,24 @@ add_executable(sphaira
source/ui/menus/main_menu.cpp source/ui/menus/main_menu.cpp
source/ui/menus/menu_base.cpp source/ui/menus/menu_base.cpp
source/ui/menus/themezer.cpp source/ui/menus/themezer.cpp
source/ui/menus/ghdl.cpp
source/ui/menus/usb_menu.cpp
source/ui/menus/ftp_menu.cpp
source/ui/menus/gc_menu.cpp
source/ui/menus/game_menu.cpp
source/ui/menus/grid_menu_base.cpp
source/ui/error_box.cpp source/ui/error_box.cpp
source/ui/notification.cpp source/ui/notification.cpp
source/ui/nvg_util.cpp source/ui/nvg_util.cpp
source/ui/option_box.cpp source/ui/option_box.cpp
source/ui/option_list.cpp
source/ui/popup_list.cpp source/ui/popup_list.cpp
source/ui/progress_box.cpp source/ui/progress_box.cpp
source/ui/scrollable_text.cpp source/ui/scrollable_text.cpp
source/ui/scrollbar.cpp
source/ui/sidebar.cpp source/ui/sidebar.cpp
source/ui/widget.cpp source/ui/widget.cpp
source/ui/list.cpp
source/ui/scrolling_text.cpp
source/app.cpp source/app.cpp
source/download.cpp source/download.cpp
@@ -64,20 +70,83 @@ add_executable(sphaira
source/evman.cpp source/evman.cpp
source/fs.cpp source/fs.cpp
source/image.cpp source/image.cpp
source/location.cpp
source/log.cpp source/log.cpp
source/main.cpp source/main.cpp
source/nro.cpp source/nro.cpp
source/nxlink.cpp source/nxlink.cpp
source/owo.cpp source/owo.cpp
source/swkbd.cpp source/swkbd.cpp
source/web.cpp
source/i18n.cpp source/i18n.cpp
source/ftpsrv_helper.cpp source/ftpsrv_helper.cpp
source/usb/base.cpp
source/usb/usbds.cpp
source/usb/usbhs.cpp
source/usb/usb_uploader.cpp
source/yati/yati.cpp
source/yati/container/nsp.cpp
source/yati/container/xci.cpp
source/yati/source/file.cpp
source/yati/source/stdio.cpp
source/yati/source/usb.cpp
source/yati/source/stream.cpp
source/yati/source/stream_file.cpp
source/yati/nx/es.cpp
source/yati/nx/keys.cpp
source/yati/nx/nca.cpp
source/yati/nx/ncm.cpp
source/yati/nx/ns.cpp
source/yati/nx/nxdumptool_rsa.c
) )
target_compile_definitions(sphaira PRIVATE target_compile_definitions(sphaira PRIVATE
-DAPP_VERSION="${sphaira_VERSION}" -DAPP_VERSION="${sphaira_VERSION}"
-DAPP_VERSION_HASH="${sphaira_VERSION_HASH}" -DAPP_VERSION_HASH="${sphaira_VERSION_HASH}"
-DCURL_NO_OLDIES=1
)
target_compile_options(sphaira PRIVATE
-Wall
-Wextra
# unsure if it's a good idea to enable these by default as
# it may cause breakage upon compiler updates.
# -Werror
# -Wfatal-errors
# disabled as nx uses s64 for size and offset, however stl uses size_t instead, thus
# there being a lot of warnings.
-Wno-sign-compare
# disabled as many overriden methods don't use the params.
-Wno-unused-parameter
# pedantic warning, missing fields are set to 0.
-Wno-missing-field-initializers
# disabled as it warns for strcat 2 paths together, but it will never
# overflow due to fs enforcing a max path len anyway.
-Wno-format-truncation
# the below are taken from my gba emulator, they've served me well ;)
-Wformat-overflow=2
-Wundef
-Wmissing-include-dirs
-fstrict-aliasing
-Wstrict-overflow=2
-Walloca
-Wduplicated-cond
-Wwrite-strings
-Wdate-time
-Wlogical-op
-Wpacked
-Wcast-qual
-Wcast-align
-Wimplicit-fallthrough=5
-Wsuggest-final-types
-Wuninitialized
-fimplicit-constexpr
-Wmissing-requires
) )
include(FetchContent) include(FetchContent)
@@ -85,22 +154,24 @@ set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(ftpsrv FetchContent_Declare(ftpsrv
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
GIT_TAG 8d5a14e # GIT_TAG 1.2.2
GIT_TAG f8a30fd
SOURCE_SUBDIR NONE
) )
FetchContent_Declare(libhaze FetchContent_Declare(libhaze
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
GIT_TAG 3244b9e GIT_TAG 04f1526
) )
FetchContent_Declare(libpulsar FetchContent_Declare(libpulsar
GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git
GIT_TAG d729be3 GIT_TAG de656e4
) )
FetchContent_Declare(nanovg FetchContent_Declare(nanovg
GIT_REPOSITORY https://github.com/ITotalJustice/nanovg-deko3d.git GIT_REPOSITORY https://github.com/ITotalJustice/nanovg-deko3d.git
GIT_TAG 1902b38 GIT_TAG 845c9fc
) )
FetchContent_Declare(stb FetchContent_Declare(stb
@@ -113,12 +184,30 @@ FetchContent_Declare(yyjson
GIT_TAG 0.10.0 GIT_TAG 0.10.0
) )
FetchContent_Declare(minIni-sphaira FetchContent_Declare(minIni
GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git
GIT_TAG 63ec295 GIT_TAG 11cac8b
) )
set(MININI_LIB_NAME minIni-sphaira) FetchContent_Declare(zstd
GIT_REPOSITORY https://github.com/facebook/zstd.git
GIT_TAG v1.5.7
SOURCE_SUBDIR build/cmake
)
set(USE_NEW_ZSTD ON)
set(ZSTD_BUILD_STATIC ON)
set(ZSTD_BUILD_SHARED OFF)
set(ZSTD_BUILD_COMPRESSION OFF)
set(ZSTD_BUILD_DECOMPRESSION ON)
set(ZSTD_BUILD_DICTBUILDER OFF)
set(ZSTD_LEGACY_SUPPORT OFF)
set(ZSTD_MULTITHREAD_SUPPORT OFF)
set(ZSTD_BUILD_PROGRAMS OFF)
set(ZSTD_BUILD_TESTS OFF)
set(MININI_LIB_NAME minIni)
set(MININI_USE_STDIO ON) set(MININI_USE_STDIO ON)
set(MININI_USE_NX ON) set(MININI_USE_NX ON)
set(MININI_USE_FLOAT OFF) set(MININI_USE_FLOAT OFF)
@@ -135,92 +224,67 @@ set(NANOVG_NO_GIF ON)
set(NANOVG_NO_HDR ON) set(NANOVG_NO_HDR ON)
set(NANOVG_NO_PIC ON) set(NANOVG_NO_PIC ON)
set(NANOVG_NO_PNM ON) set(NANOVG_NO_PNM ON)
set(NANOVG_STBI_STATIC OFF)
set(NANOVG_STBTT_STATIC ON)
set(YYJSON_DISABLE_READER OFF) set(YYJSON_DISABLE_READER OFF)
set(YYJSON_DISABLE_WRITER ON) set(YYJSON_DISABLE_WRITER OFF)
set(YYJSON_DISABLE_UTILS ON) set(YYJSON_DISABLE_UTILS ON)
set(YYJSON_DISABLE_FAST_FP_CONV ON) set(YYJSON_DISABLE_FAST_FP_CONV ON)
set(YYJSON_DISABLE_NON_STANDARD ON) set(YYJSON_DISABLE_NON_STANDARD ON)
set(YYJSON_DISABLE_UTF8_VALIDATION ON) set(YYJSON_DISABLE_UTF8_VALIDATION ON)
set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF) set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF)
set(FTPSRV_LIB_BUILD TRUE)
set(FTPSRV_LIB_SOCK_UNISTD TRUE)
set(FTPSRV_LIB_VFS_CUSTOM ${CMAKE_CURRENT_SOURCE_DIR}/include/ftpsrv_helper.hpp)
set(FTPSRV_LIB_PATH_SIZE 0x301)
set(FTPSRV_LIB_SESSIONS 32)
set(FTPSRV_LIB_BUF_SIZE 1024*64)
FetchContent_MakeAvailable( FetchContent_MakeAvailable(
ftpsrv ftpsrv
libhaze libhaze
libpulsar libpulsar
nanovg nanovg
stb stb
minIni-sphaira minIni
yyjson yyjson
zstd
) )
# todo: upstream cmake set(FTPSRV_LIB_BUILD TRUE)
add_library(libhaze set(FTPSRV_LIB_SOCK_UNISTD TRUE)
${libhaze_SOURCE_DIR}/source/async_usb_server.cpp set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
${libhaze_SOURCE_DIR}/source/device_properties.cpp set(FTPSRV_LIB_PATH_SIZE 0x301)
${libhaze_SOURCE_DIR}/source/event_reactor.cpp set(FTPSRV_LIB_SESSIONS 32)
${libhaze_SOURCE_DIR}/source/haze.cpp set(FTPSRV_LIB_BUF_SIZE 1024*64)
${libhaze_SOURCE_DIR}/source/ptp_object_database.cpp
${libhaze_SOURCE_DIR}/source/ptp_object_heap.cpp # workaround until a64 container has latest libnx release.
${libhaze_SOURCE_DIR}/source/ptp_responder_android_operations.cpp if (NOT DEFINED USE_VFS_GC)
${libhaze_SOURCE_DIR}/source/ptp_responder_mtp_operations.cpp set(USE_VFS_GC TRUE)
${libhaze_SOURCE_DIR}/source/ptp_responder_ptp_operations.cpp endif()
${libhaze_SOURCE_DIR}/source/ptp_responder.cpp
${libhaze_SOURCE_DIR}/source/usb_session.cpp set(FTPSRV_LIB_CUSTOM_DEFINES
) USE_VFS_SAVE=$<BOOL:TRUE>
target_include_directories(libhaze PUBLIC ${libhaze_SOURCE_DIR}/include) USE_VFS_STORAGE=$<BOOL:TRUE>
set_target_properties(libhaze PROPERTIES USE_VFS_GC=$<BOOL:${USE_VFS_GC}>
C_STANDARD 11 USE_VFS_USBHSFS=$<BOOL:FALSE>
C_EXTENSIONS ON VFS_NX_BUFFER_IO=$<BOOL:TRUE>
CXX_STANDARD 20
CXX_EXTENSIONS ON
# force optimisations in debug mode as otherwise vapor errors
# due to force_inline attribute failing...
COMPILE_OPTIONS "$<$<CONFIG:Debug>:-Os>"
) )
# todo: upstream cmake add_subdirectory(${ftpsrv_SOURCE_DIR} binary_dir)
add_library(libpulsar
${libpulsar_SOURCE_DIR}/src/archive/archive_file.c add_library(ftpsrv_helper
${libpulsar_SOURCE_DIR}/src/archive/archive.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.c
${libpulsar_SOURCE_DIR}/src/bfgrp/bfgrp_location.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c
${libpulsar_SOURCE_DIR}/src/bfgrp/bfgrp.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_file.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_group.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_save.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_sound.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_string.c ${ftpsrv_SOURCE_DIR}/src/platform/nx/utils.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_wave_archive.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar.c
${libpulsar_SOURCE_DIR}/src/bfstm/bfstm_channel.c
${libpulsar_SOURCE_DIR}/src/bfstm/bfstm_info.c
${libpulsar_SOURCE_DIR}/src/bfstm/bfstm.c
${libpulsar_SOURCE_DIR}/src/bfwar/bfwar_file.c
${libpulsar_SOURCE_DIR}/src/bfwar/bfwar.c
${libpulsar_SOURCE_DIR}/src/bfwav/bfwav_info.c
${libpulsar_SOURCE_DIR}/src/bfwav/bfwav.c
${libpulsar_SOURCE_DIR}/src/bfwsd/bfwsd_sound_data.c
${libpulsar_SOURCE_DIR}/src/bfwsd/bfwsd_wave_id.c
${libpulsar_SOURCE_DIR}/src/bfwsd/bfwsd.c
${libpulsar_SOURCE_DIR}/src/player/player_load_formats.c
${libpulsar_SOURCE_DIR}/src/player/player_load_lookup.c
${libpulsar_SOURCE_DIR}/src/player/player_load.c
${libpulsar_SOURCE_DIR}/src/player/player.c
)
target_include_directories(libpulsar PUBLIC ${libpulsar_SOURCE_DIR}/include)
set_target_properties(libpulsar PROPERTIES
C_STANDARD 11
C_EXTENSIONS ON
) )
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv)
target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platform)
if (USE_VFS_GC)
target_sources(ftpsrv_helper PRIVATE
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_gc.c
)
endif()
add_library(stb INTERFACE) add_library(stb INTERFACE)
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR}) target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
@@ -232,6 +296,11 @@ find_package(CURL REQUIRED)
find_path(mbedtls_inc mbedtls REQUIRED) find_path(mbedtls_inc mbedtls REQUIRED)
find_library(mbedcrypto_lib mbedcrypto REQUIRED) find_library(mbedcrypto_lib mbedcrypto REQUIRED)
if (NOT USE_NEW_ZSTD)
find_path(zstd_inc zstd.h REQUIRED)
find_library(zstd_lib zstd REQUIRED)
endif()
set_target_properties(sphaira PROPERTIES set_target_properties(sphaira PROPERTIES
C_STANDARD 11 C_STANDARD 11
C_EXTENSIONS ON C_EXTENSIONS ON
@@ -240,10 +309,10 @@ set_target_properties(sphaira PROPERTIES
) )
target_link_libraries(sphaira PRIVATE target_link_libraries(sphaira PRIVATE
ftpsrv ftpsrv_helper
libhaze libhaze
libpulsar libpulsar
minIni-sphaira minIni
nanovg nanovg
stb stb
yyjson yyjson
@@ -254,6 +323,15 @@ target_link_libraries(sphaira PRIVATE
${mbedcrypto_lib} ${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 target_include_directories(sphaira PRIVATE
include include
${minizip_inc} ${minizip_inc}
@@ -288,7 +366,7 @@ nx_generate_nacp(
OUTPUT sphaira.nacp OUTPUT sphaira.nacp
NAME ${CMAKE_PROJECT_NAME} NAME ${CMAKE_PROJECT_NAME}
AUTHOR TotalJustice AUTHOR TotalJustice
VERSION ${CMAKE_PROJECT_VERSION} VERSION ${sphaira_VERSION}
) )
# create nro # create nro

View File

@@ -8,6 +8,7 @@
#include "owo.hpp" #include "owo.hpp"
#include "option.hpp" #include "option.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "log.hpp"
#include <switch.h> #include <switch.h>
#include <vector> #include <vector>
@@ -25,6 +26,7 @@ enum SoundEffect {
SoundEffect_Startup, SoundEffect_Startup,
SoundEffect_Install, SoundEffect_Install,
SoundEffect_Error, SoundEffect_Error,
SoundEffect_MAX,
}; };
enum class LaunchType { enum class LaunchType {
@@ -33,7 +35,9 @@ enum class LaunchType {
Forwader_Sphaira, Forwader_Sphaira,
}; };
// todo: why is this global???
void DrawElement(float x, float y, float w, float h, ThemeEntryID id); void DrawElement(float x, float y, float w, float h, ThemeEntryID id);
void DrawElement(const Vec4&, ThemeEntryID id);
class App { class App {
public: public:
@@ -41,10 +45,14 @@ public:
~App(); ~App();
void Loop(); void Loop();
static App* GetApp();
static void Exit(); static void Exit();
static void ExitRestart(); static void ExitRestart();
static auto GetVg() -> NVGcontext*; static auto GetVg() -> NVGcontext*;
static void Push(std::shared_ptr<ui::Widget>); static void Push(std::shared_ptr<ui::Widget>);
// pops all widgets above a menu
static void PopToMenu();
// this is thread safe // this is thread safe
static void Notify(std::string text, ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT); static void Notify(std::string text, ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
@@ -54,8 +62,10 @@ public:
static void NotifyFlashLed(); static void NotifyFlashLed();
static auto GetThemeMetaList() -> std::span<ThemeMeta>; static auto GetThemeMetaList() -> std::span<ThemeMeta>;
static void SetTheme(u64 theme_index); static void SetTheme(s64 theme_index);
static auto GetThemeIndex() -> u64; static auto GetThemeIndex() -> s64;
static auto GetDefaultImage() -> int;
// returns argv[0] // returns argv[0]
static auto GetExePath() -> fs::FsPath; static auto GetExePath() -> fs::FsPath;
@@ -68,29 +78,41 @@ public:
static auto GetLogEnable() -> bool; static auto GetLogEnable() -> bool;
static auto GetReplaceHbmenuEnable() -> bool; static auto GetReplaceHbmenuEnable() -> bool;
static auto GetInstallEnable() -> bool; static auto GetInstallEnable() -> bool;
static auto GetInstallSysmmcEnable() -> bool;
static auto GetInstallEmummcEnable() -> bool;
static auto GetInstallSdEnable() -> bool; static auto GetInstallSdEnable() -> bool;
static auto GetInstallPrompt() -> bool; static auto GetInstallPrompt() -> bool;
static auto GetThemeShuffleEnable() -> bool;
static auto GetThemeMusicEnable() -> bool; static auto GetThemeMusicEnable() -> bool;
static auto Get12HourTimeEnable() -> bool;
static auto GetLanguage() -> long; static auto GetLanguage() -> long;
static auto GetTextScrollSpeed() -> long;
static void SetMtpEnable(bool enable); static void SetMtpEnable(bool enable);
static void SetFtpEnable(bool enable); static void SetFtpEnable(bool enable);
static void SetNxlinkEnable(bool enable); static void SetNxlinkEnable(bool enable);
static void SetLogEnable(bool enable); static void SetLogEnable(bool enable);
static void SetReplaceHbmenuEnable(bool enable); static void SetReplaceHbmenuEnable(bool enable);
static void SetInstallEnable(bool enable); static void SetInstallSysmmcEnable(bool enable);
static void SetInstallEmummcEnable(bool enable);
static void SetInstallSdEnable(bool enable); static void SetInstallSdEnable(bool enable);
static void SetInstallPrompt(bool enable); static void SetInstallPrompt(bool enable);
static void SetThemeShuffleEnable(bool enable);
static void SetThemeMusicEnable(bool enable); static void SetThemeMusicEnable(bool enable);
static void Set12HourTimeEnable(bool enable);
static void SetLanguage(long index); static void SetLanguage(long index);
static void SetTextScrollSpeed(long index);
static auto Install(OwoConfig& config) -> Result; static auto Install(OwoConfig& config) -> Result;
static auto Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result; static auto Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result;
static void PlaySoundEffect(SoundEffect effect); static void PlaySoundEffect(SoundEffect effect);
static void DisplayThemeOptions(bool left_side = true);
// todo:
static void DisplayNetworkOptions(bool left_side = true);
static void DisplayMiscOptions(bool left_side = true);
static void DisplayAdvancedOptions(bool left_side = true);
static void DisplayInstallOptions(bool left_side = true);
void Draw(); void Draw();
void Update(); void Update();
void Poll(); void Poll();
@@ -98,18 +120,52 @@ public:
// void DrawElement(float x, float y, float w, float h, ui::ThemeEntryID id); // void DrawElement(float x, float y, float w, float h, ui::ThemeEntryID id);
auto LoadElementImage(std::string_view value) -> ElementEntry; auto LoadElementImage(std::string_view value) -> ElementEntry;
auto LoadElementColour(std::string_view value) -> ElementEntry; auto LoadElementColour(std::string_view value) -> ElementEntry;
auto LoadElement(std::string_view data) -> ElementEntry; auto LoadElement(std::string_view data, ElementType type) -> ElementEntry;
void LoadTheme(const fs::FsPath& path); void LoadTheme(const ThemeMeta& meta);
void CloseTheme(); void CloseTheme();
void ScanThemes(const std::string& path); void ScanThemes(const std::string& path);
void ScanThemeEntries(); void ScanThemeEntries();
// helper that converts 1.2.3 to a u32 used for comparisons.
static auto GetVersionFromString(const char* str) -> u32;
static auto IsVersionNewer(const char* current, const char* new_version) -> u32;
static auto IsApplication() -> bool { static auto IsApplication() -> bool {
const auto type = appletGetAppletType(); const auto type = appletGetAppletType();
return type == AppletType_Application || type == AppletType_SystemApplication; return type == AppletType_Application || type == AppletType_SystemApplication;
} }
static auto IsApplet() -> bool {
return !IsApplication();
}
// returns true if launched in applet mode with a title suspended in the background.
static auto IsAppletWithSuspendedApp() -> bool {
R_UNLESS(IsApplet(), false);
R_TRY_RESULT(pmdmntInitialize(), false);
ON_SCOPE_EXIT(pmdmntExit());
u64 pid;
return R_SUCCEEDED(pmdmntGetApplicationProcessId(&pid));
}
static auto IsEmunand() -> bool {
alignas(0x1000) struct EmummcPaths {
char unk[0x80];
char nintendo[0x80];
} paths{};
SecmonArgs args{};
args.X[0] = 0xF0000404; /* smcAmsGetEmunandConfig */
args.X[1] = 0; /* EXO_EMUMMC_MMC_NAND*/
args.X[2] = (u64)&paths; /* out path */
svcCallSecureMonitor(&args);
return (paths.unk[0] != '\0') || (paths.nintendo[0] != '\0');
}
// private: // private:
static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini"; static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini";
static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini"; static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini";
@@ -119,6 +175,7 @@ public:
u64 m_start_timestamp{}; u64 m_start_timestamp{};
u64 m_prev_timestamp{}; u64 m_prev_timestamp{};
fs::FsPath m_prev_last_launch{}; fs::FsPath m_prev_last_launch{};
int m_default_image{};
bool m_is_launched_via_sphaira_forwader{}; bool m_is_launched_via_sphaira_forwader{};
@@ -138,7 +195,7 @@ public:
Theme m_theme{}; Theme m_theme{};
fs::FsPath theme_path{}; fs::FsPath theme_path{};
std::size_t m_theme_index{}; s64 m_theme_index{};
bool m_quit{}; bool m_quit{};
@@ -147,15 +204,37 @@ public:
option::OptionBool m_ftp_enabled{INI_SECTION, "ftp_enabled", false}; option::OptionBool m_ftp_enabled{INI_SECTION, "ftp_enabled", false};
option::OptionBool m_log_enabled{INI_SECTION, "log_enabled", false}; option::OptionBool m_log_enabled{INI_SECTION, "log_enabled", false};
option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false}; option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false};
option::OptionBool m_install{INI_SECTION, "install", false}; option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};
option::OptionBool m_12hour_time{INI_SECTION, "12hour_time", false};
option::OptionLong m_language{INI_SECTION, "language", 0}; // auto
option::OptionString m_right_side_menu{INI_SECTION, "right_side_menu", "Appstore"};
// install options
option::OptionBool m_install_sysmmc{INI_SECTION, "install_sysmmc", false};
option::OptionBool m_install_emummc{INI_SECTION, "install_emummc", false};
option::OptionBool m_install_sd{INI_SECTION, "install_sd", true}; option::OptionBool m_install_sd{INI_SECTION, "install_sd", true};
option::OptionLong m_install_prompt{INI_SECTION, "install_prompt", true}; option::OptionLong m_install_prompt{INI_SECTION, "install_prompt", true};
option::OptionBool m_theme_shuffle{INI_SECTION, "theme_shuffle", false}; option::OptionLong m_boost_mode{INI_SECTION, "boost_mode", false};
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true}; option::OptionBool m_allow_downgrade{INI_SECTION, "allow_downgrade", false};
option::OptionLong m_language{INI_SECTION, "language", 0}; // auto option::OptionBool m_skip_if_already_installed{INI_SECTION, "skip_if_already_installed", true};
option::OptionBool m_ticket_only{INI_SECTION, "ticket_only", false};
option::OptionBool m_skip_base{INI_SECTION, "skip_base", false};
option::OptionBool m_skip_patch{INI_SECTION, "skip_patch", false};
option::OptionBool m_skip_addon{INI_SECTION, "skip_addon", false};
option::OptionBool m_skip_data_patch{INI_SECTION, "skip_data_patch", false};
option::OptionBool m_skip_ticket{INI_SECTION, "skip_ticket", false};
option::OptionBool m_skip_nca_hash_verify{INI_SECTION, "skip_nca_hash_verify", false};
option::OptionBool m_skip_rsa_header_fixed_key_verify{INI_SECTION, "skip_rsa_header_fixed_key_verify", false};
option::OptionBool m_skip_rsa_npdm_fixed_key_verify{INI_SECTION, "skip_rsa_npdm_fixed_key_verify", false};
option::OptionBool m_ignore_distribution_bit{INI_SECTION, "ignore_distribution_bit", false};
option::OptionBool m_convert_to_standard_crypto{INI_SECTION, "convert_to_standard_crypto", false};
option::OptionBool m_lower_master_key{INI_SECTION, "lower_master_key", false};
option::OptionBool m_lower_system_version{INI_SECTION, "lower_system_version", false};
PLSR_BFSAR m_qlaunch_bfsar{}; // todo: move this into it's own menu
PLSR_PlayerSoundId m_sound_ids[24]{}; option::OptionLong m_text_scroll_speed{"accessibility", "text_scroll_speed", 1}; // normal
PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
private: // from nanovg decko3d example by adubbz private: // from nanovg decko3d example by adubbz
static constexpr unsigned NumFramebuffers = 2; static constexpr unsigned NumFramebuffers = 2;

View File

@@ -1,41 +1,357 @@
#pragma once #pragma once
#include "fs.hpp"
#include <vector> #include <vector>
#include <string> #include <string>
#include <functional> #include <functional>
#include <unordered_map>
#include <algorithm>
#include <stop_token>
#include <switch.h> #include <switch.h>
namespace sphaira { namespace sphaira::curl {
using DownloadCallback = std::function<void(std::vector<u8>& data, bool success)>; enum {
using ProgressCallback = std::function<bool(u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow)>; Flag_None = 0,
enum class DownloadPriority { // requests to download send etag in the header.
// the received etag is then saved on success.
// this api is only available on downloading to file.
Flag_Cache = 1 << 0,
// sets CURLOPT_NOBODY.
Flag_NoBody = 1 << 1,
};
enum class Priority {
Normal, // gets pushed to the back of the queue Normal, // gets pushed to the back of the queue
High, // gets pushed to the front of the queue High, // gets pushed to the front of the queue
}; };
struct DownloadEventData { struct Api;
DownloadCallback callback; struct ApiResult;
std::vector<u8> data;
bool result; using Path = fs::FsPath;
using OnComplete = std::function<void(ApiResult& result)>;
using OnProgress = std::function<bool(s64 dltotal, s64 dlnow, s64 ultotal, s64 ulnow)>;
using OnUploadCallback = std::function<size_t(void *ptr, size_t size)>;
using OnUploadSeek = std::function<bool(s64 offset)>;
using StopToken = std::stop_token;
struct Url {
Url() = default;
Url(const std::string& str) : m_str{str} {}
std::string m_str;
}; };
auto DownloadInit() -> bool; struct Fields {
void DownloadExit(); Fields() = default;
Fields(const std::string& str) : m_str{str} {}
std::string m_str;
};
struct Header {
Header() = default;
Header(std::initializer_list<std::pair<const std::string, std::string>> p) : m_map{p} {}
std::unordered_map<std::string, std::string> m_map;
auto Find(const std::string& key) const {
return std::find_if(m_map.cbegin(), m_map.cend(), [&key](auto& e) {
return !strcasecmp(key.c_str(), e.first.c_str());
});
}
};
struct Flags {
Flags() = default;
Flags(u32 flags) : m_flags{flags} {}
u32 m_flags{Flag_None};
};
struct Port {
Port() = default;
Port(u16 port) : m_port{port} {}
u16 m_port{};
};
struct CustomRequest {
CustomRequest() = default;
CustomRequest(const std::string& str) : m_str{str} {}
std::string m_str;
};
struct UserPass {
UserPass() = default;
UserPass(const std::string& user) : m_user{user} {}
UserPass(const std::string& user, const std::string& pass) : m_user{user}, m_pass{pass} {}
std::string m_user;
std::string m_pass;
};
struct UploadInfo {
UploadInfo() = default;
UploadInfo(const std::string& name) : m_name{name} {}
UploadInfo(const std::string& name, s64 size, OnUploadCallback cb) : m_name{name}, m_size{size}, m_callback{cb} {}
UploadInfo(const std::string& name, const std::vector<u8>& data) : m_name{name}, m_data{data} {}
std::string m_name{};
std::vector<u8> m_data{};
s64 m_size{};
OnUploadCallback m_callback{};
};
struct Bearer {
Bearer() = default;
Bearer(const std::string& str) : m_str{str} {}
std::string m_str;
};
struct PubKey {
PubKey() = default;
PubKey(const std::string& str) : m_str{str} {}
std::string m_str;
};
struct PrivKey {
PrivKey() = default;
PrivKey(const std::string& str) : m_str{str} {}
std::string m_str;
};
struct ApiResult {
bool success;
long code;
Header header; // returned headers in request
std::vector<u8> data; // empty if downloaded a file
fs::FsPath path; // empty if downloaded memory
};
struct DownloadEventData {
OnComplete callback;
ApiResult result;
StopToken stoken;
};
// helper that generates the api using an location.
#define CURL_LOCATION_TO_API(loc) \
curl::Url{loc.url}, \
curl::UserPass{loc.user, loc.pass}, \
curl::Bearer{loc.bearer}, \
curl::PubKey{loc.pub_key}, \
curl::PrivKey{loc.priv_key}, \
curl::Port(loc.port)
auto Init() -> bool;
void Exit();
// sync functions // sync functions
auto DownloadMemory(const std::string& url, const std::string& post, ProgressCallback pcallback = nullptr) -> std::vector<u8>; auto ToMemory(const Api& e) -> ApiResult;
auto DownloadFile(const std::string& url, const std::string& out, const std::string& post, ProgressCallback pcallback = nullptr) -> bool; auto ToFile(const Api& e) -> ApiResult;
auto FromMemory(const Api& e) -> ApiResult;
auto FromFile(const Api& e) -> ApiResult;
// async functions // async functions
// starts the downloads in a new thread, pushes an event when complete auto ToMemoryAsync(const Api& e) -> bool;
// then, the callback will be called on the main thread. auto ToFileAsync(const Api& e) -> bool;
// auto DownloadMemoryAsync(const std::string& url, DownloadCallback callback, DownloadPriority prio = DownloadPriority::Normal) -> bool; auto FromMemoryAsync(const Api& e) -> bool;
// auto DownloadFileAsync(const std::string& url, const std::string& out, DownloadCallback callback, DownloadPriority prio = DownloadPriority::Normal) -> bool; auto FromFileAsync(const Api& e) -> bool;
auto DownloadMemoryAsync(const std::string& url, const std::string& post, DownloadCallback callback, ProgressCallback pcallback = nullptr, DownloadPriority prio = DownloadPriority::Normal) -> bool; // uses curl to convert string to their %XX
auto DownloadFileAsync(const std::string& url, const std::string& out, const std::string& post, DownloadCallback callback, ProgressCallback pcallback = nullptr, DownloadPriority prio = DownloadPriority::Normal) -> bool; auto EscapeString(const std::string& str) -> std::string;
void DownloadClearCache(const std::string& url); struct Api {
Api() = default;
} // namespace sphaira template <typename... Ts>
Api(Ts&&... ts) {
Api::set_option(std::forward<Ts>(ts)...);
}
template <typename... Ts>
auto To(Ts&&... ts) {
if constexpr(std::disjunction_v<std::is_same<Path, Ts>...>) {
return ToFile(std::forward<Ts>(ts)...);
} else {
return ToMemory(std::forward<Ts>(ts)...);
}
}
template <typename... Ts>
auto From(Ts&&... ts) {
if constexpr(std::disjunction_v<std::is_same<Path, Ts>...>) {
return FromFile(std::forward<Ts>(ts)...);
} else {
return FromMemory(std::forward<Ts>(ts)...);
}
}
template <typename... Ts>
auto ToAsync(Ts&&... ts) {
if constexpr(std::disjunction_v<std::is_same<Path, Ts>...>) {
return ToFileAsync(std::forward<Ts>(ts)...);
} else {
return ToMemoryAsync(std::forward<Ts>(ts)...);
}
}
template <typename... Ts>
auto FromAsync(Ts&&... ts) {
if constexpr(std::disjunction_v<std::is_same<Path, Ts>...>) {
return FromFileAsync(std::forward<Ts>(ts)...);
} else {
return FromMemoryAsync(std::forward<Ts>(ts)...);
}
}
template <typename... Ts>
auto ToMemory(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::ToMemory(*this);
}
template <typename... Ts>
auto FromMemory(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<UploadInfo, Ts>...>, "UploadInfo must be specified");
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::FromMemory(*this);
}
template <typename... Ts>
auto ToFile(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::ToFile(*this);
}
template <typename... Ts>
auto FromFile(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
static_assert(std::disjunction_v<std::is_same<UploadInfo, Ts>...>, "UploadInfo must be specified");
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::FromFile(*this);
}
template <typename... Ts>
auto ToMemoryAsync(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::ToMemoryAsync(*this);
}
template <typename... Ts>
auto FromMemoryAsync(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<UploadInfo, Ts>...>, "UploadInfo must be specified");
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::FromMemoryAsync(*this);
}
template <typename... Ts>
auto ToFileAsync(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::ToFileAsync(*this);
}
template <typename... Ts>
auto FromFileAsync(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
static_assert(std::disjunction_v<std::is_same<UploadInfo, Ts>...>, "UploadInfo must be specified");
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::FromFileAsync(*this);
}
void SetUpload(bool enable) { m_is_upload = enable; }
auto IsUpload() const { return m_is_upload; }
auto& GetUrl() const { return m_url.m_str; }
auto& GetFields() const { return m_fields.m_str; }
auto& GetHeader() const { return m_header; }
auto& GetFlags() const { return m_flags.m_flags; }
auto& GetPath() const { return m_path; }
auto& GetPort() const { return m_port.m_port; }
auto& GetCustomRequest() const { return m_custom_request.m_str; }
auto& GetUserPass() const { return m_userpass; }
auto& GetBearer() const { return m_bearer.m_str; }
auto& GetPubKey() const { return m_pub_key.m_str; }
auto& GetPrivKey() const { return m_priv_key.m_str; }
auto& GetUploadInfo() const { return m_info; }
auto& GetOnComplete() const { return m_on_complete; }
auto& GetOnProgress() const { return m_on_progress; }
auto& GetOnUploadSeek() const { return m_on_upload_seek; }
auto& GetPriority() const { return m_prio; }
auto& GetToken() const { return m_stoken; }
void SetOption(Url&& v) { m_url = v; }
void SetOption(Fields&& v) { m_fields = v; }
void SetOption(Header&& v) { m_header = v; }
void SetOption(Flags&& v) { m_flags = v; }
void SetOption(Path&& v) { m_path = v; }
void SetOption(Port&& v) { m_port = v; }
void SetOption(CustomRequest&& v) { m_custom_request = v; }
void SetOption(UserPass&& v) { m_userpass = v; }
void SetOption(Bearer&& v) { m_bearer = v; }
void SetOption(PubKey&& v) { m_pub_key = v; }
void SetOption(PrivKey&& v) { m_priv_key = v; }
void SetOption(UploadInfo&& v) { m_info = v; }
void SetOption(OnComplete&& v) { m_on_complete = v; }
void SetOption(OnProgress&& v) { m_on_progress = v; }
void SetOption(OnUploadSeek&& v) { m_on_upload_seek = v; }
void SetOption(Priority&& v) { m_prio = v; }
void SetOption(StopToken&& v) { m_stoken = v; }
template <typename T>
void set_option(T&& t) {
SetOption(std::forward<T>(t));
}
template <typename T, typename... Ts>
void set_option(T&& t, Ts&&... ts) {
set_option(std::forward<T>(t));
set_option(std::forward<Ts>(ts)...);
}
private:
Url m_url{};
Fields m_fields{};
Header m_header{};
Flags m_flags{};
Path m_path{};
Port m_port{};
CustomRequest m_custom_request{};
UserPass m_userpass{};
Bearer m_bearer{};
PubKey m_pub_key{};
PrivKey m_priv_key{};
UploadInfo m_info{};
OnComplete m_on_complete{};
OnProgress m_on_progress{};
OnUploadSeek m_on_upload_seek{};
Priority m_prio{Priority::High};
std::stop_source m_stop_source{};
StopToken m_stoken{m_stop_source.get_token()};
bool m_is_upload{};
};
} // namespace sphaira::curl

View File

@@ -26,7 +26,7 @@ using EventData = std::variant<
ExitEventData, ExitEventData,
HazeCallbackData, HazeCallbackData,
NxlinkCallbackData, NxlinkCallbackData,
DownloadEventData curl::DownloadEventData
>; >;
// returns number of events // returns number of events

View File

@@ -171,39 +171,39 @@ static_assert(FsPath::TestFrom(FsPath{"abc"}));
FsPath AppendPath(const fs::FsPath& root_path, const fs::FsPath& file_path); 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 = false); 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 = false); Result CreateDirectory(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = false); Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = false); Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result DeleteFile(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = false); Result DeleteFile(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result DeleteDirectory(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = false); Result DeleteDirectory(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
Result DeleteDirectoryRecursively(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = false); 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 = false); 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 = false); 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 GetEntryType(FsFileSystem* fs, const FsPath& path, FsDirEntryType* out);
Result GetFileTimeStampRaw(FsFileSystem* fs, const FsPath& path, FsTimeStampRaw *out); Result GetFileTimeStampRaw(FsFileSystem* fs, const FsPath& path, FsTimeStampRaw *out);
bool FileExists(FsFileSystem* fs, const FsPath& path); bool FileExists(FsFileSystem* fs, const FsPath& path);
bool DirExists(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 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 = false); 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 = false); 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 = false); 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 = false); Result CreateDirectory(const FsPath& path, bool ignore_read_only = true);
Result CreateDirectoryRecursively(const FsPath& path, bool ignore_read_only = false); Result CreateDirectoryRecursively(const FsPath& path, bool ignore_read_only = true);
Result CreateDirectoryRecursivelyWithPath(const FsPath& path, bool ignore_read_only = false); Result CreateDirectoryRecursivelyWithPath(const FsPath& path, bool ignore_read_only = true);
Result DeleteFile(const FsPath& path, bool ignore_read_only = false); Result DeleteFile(const FsPath& path, bool ignore_read_only = true);
Result DeleteDirectory(const FsPath& path, bool ignore_read_only = false); Result DeleteDirectory(const FsPath& path, bool ignore_read_only = true);
Result DeleteDirectoryRecursively(const FsPath& path, bool ignore_read_only = false); Result DeleteDirectoryRecursively(const FsPath& path, bool ignore_read_only = true);
Result RenameFile(const FsPath& src, const FsPath& dst, bool ignore_read_only = false); 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 = false); Result RenameDirectory(const FsPath& src, const FsPath& dst, bool ignore_read_only = true);
Result GetEntryType(const FsPath& path, FsDirEntryType* out); Result GetEntryType(const FsPath& path, FsDirEntryType* out);
Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out); Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out);
bool FileExists(const FsPath& path); bool FileExists(const FsPath& path);
bool DirExists(const FsPath& path); bool DirExists(const FsPath& path);
Result read_entire_file(const FsPath& path, std::vector<u8>& out); 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 = false); 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 = false); Result copy_entire_file(const FsPath& dst, const FsPath& src, bool ignore_read_only = true);
struct Fs { struct Fs {
static constexpr inline u32 FsModule = 505; static constexpr inline u32 FsModule = 505;
@@ -222,51 +222,64 @@ struct Fs {
static constexpr inline Result ResultUnknownStdioError = MAKERESULT(FsModule, 13); static constexpr inline Result ResultUnknownStdioError = MAKERESULT(FsModule, 13);
static constexpr inline Result ResultReadOnly = MAKERESULT(FsModule, 14); static constexpr inline Result ResultReadOnly = MAKERESULT(FsModule, 14);
virtual Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0, bool ignore_read_only = false) = 0; Fs(bool ignore_read_only = true) : m_ignore_read_only{ignore_read_only} {}
virtual Result CreateDirectory(const FsPath& path, bool ignore_read_only = false) = 0; virtual ~Fs() = default;
virtual Result CreateDirectoryRecursively(const FsPath& path, bool ignore_read_only = false) = 0;
virtual Result CreateDirectoryRecursivelyWithPath(const FsPath& path, bool ignore_read_only = false) = 0; virtual Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0) = 0;
virtual Result DeleteFile(const FsPath& path, bool ignore_read_only = false) = 0; virtual Result CreateDirectory(const FsPath& path) = 0;
virtual Result DeleteDirectory(const FsPath& path, bool ignore_read_only = false) = 0; virtual Result CreateDirectoryRecursively(const FsPath& path) = 0;
virtual Result DeleteDirectoryRecursively(const FsPath& path, bool ignore_read_only = false) = 0; virtual Result CreateDirectoryRecursivelyWithPath(const FsPath& path) = 0;
virtual Result RenameFile(const FsPath& src, const FsPath& dst, bool ignore_read_only = false) = 0; virtual Result DeleteFile(const FsPath& path) = 0;
virtual Result RenameDirectory(const FsPath& src, const FsPath& dst, bool ignore_read_only = false) = 0; virtual Result DeleteDirectory(const FsPath& path) = 0;
virtual Result DeleteDirectoryRecursively(const FsPath& path) = 0;
virtual Result RenameFile(const FsPath& src, const FsPath& dst) = 0;
virtual Result RenameDirectory(const FsPath& src, const FsPath& dst) = 0;
virtual Result GetEntryType(const FsPath& path, FsDirEntryType* out) = 0; virtual Result GetEntryType(const FsPath& path, FsDirEntryType* out) = 0;
virtual Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out) = 0; virtual Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out) = 0;
virtual bool FileExists(const FsPath& path) = 0; virtual bool FileExists(const FsPath& path) = 0;
virtual bool DirExists(const FsPath& path) = 0; virtual bool DirExists(const FsPath& path) = 0;
virtual Result read_entire_file(const FsPath& path, std::vector<u8>& out) = 0; 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, bool ignore_read_only = false) = 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, bool ignore_read_only = false) = 0; virtual Result copy_entire_file(const FsPath& dst, const FsPath& src) = 0;
void SetIgnoreReadOnly(bool enable) {
m_ignore_read_only = enable;
}
protected:
bool m_ignore_read_only;
}; };
struct FsStdio : Fs { struct FsStdio : Fs {
Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0, bool ignore_read_only = false) override { FsStdio(bool ignore_read_only = true) : Fs{ignore_read_only} {}
return fs::CreateFile(path, size, option, ignore_read_only); virtual ~FsStdio() = default;
Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0) override {
return fs::CreateFile(path, size, option, m_ignore_read_only);
} }
Result CreateDirectory(const FsPath& path, bool ignore_read_only = false) override { Result CreateDirectory(const FsPath& path) override {
return fs::CreateDirectory(path, ignore_read_only); return fs::CreateDirectory(path, m_ignore_read_only);
} }
Result CreateDirectoryRecursively(const FsPath& path, bool ignore_read_only = false) override { Result CreateDirectoryRecursively(const FsPath& path) override {
return fs::CreateDirectoryRecursively(path, ignore_read_only); return fs::CreateDirectoryRecursively(path, m_ignore_read_only);
} }
Result CreateDirectoryRecursivelyWithPath(const FsPath& path, bool ignore_read_only = false) override { Result CreateDirectoryRecursivelyWithPath(const FsPath& path) override {
return fs::CreateDirectoryRecursivelyWithPath(path, ignore_read_only); return fs::CreateDirectoryRecursivelyWithPath(path, m_ignore_read_only);
} }
Result DeleteFile(const FsPath& path, bool ignore_read_only = false) override { Result DeleteFile(const FsPath& path) override {
return fs::DeleteFile(path, ignore_read_only); return fs::DeleteFile(path, m_ignore_read_only);
} }
Result DeleteDirectory(const FsPath& path, bool ignore_read_only = false) override { Result DeleteDirectory(const FsPath& path) override {
return fs::DeleteDirectory(path, ignore_read_only); return fs::DeleteDirectory(path, m_ignore_read_only);
} }
Result DeleteDirectoryRecursively(const FsPath& path, bool ignore_read_only = false) override { Result DeleteDirectoryRecursively(const FsPath& path) override {
return fs::DeleteDirectoryRecursively(path, ignore_read_only); return fs::DeleteDirectoryRecursively(path, m_ignore_read_only);
} }
Result RenameFile(const FsPath& src, const FsPath& dst, bool ignore_read_only = false) override { Result RenameFile(const FsPath& src, const FsPath& dst) override {
return fs::RenameFile(src, dst, ignore_read_only); return fs::RenameFile(src, dst, m_ignore_read_only);
} }
Result RenameDirectory(const FsPath& src, const FsPath& dst, bool ignore_read_only = false) override { Result RenameDirectory(const FsPath& src, const FsPath& dst) override {
return fs::RenameDirectory(src, dst, ignore_read_only); return fs::RenameDirectory(src, dst, m_ignore_read_only);
} }
Result GetEntryType(const FsPath& path, FsDirEntryType* out) override { Result GetEntryType(const FsPath& path, FsDirEntryType* out) override {
return fs::GetEntryType(path, out); return fs::GetEntryType(path, out);
@@ -283,17 +296,17 @@ struct FsStdio : Fs {
Result read_entire_file(const FsPath& path, std::vector<u8>& out) override { Result read_entire_file(const FsPath& path, std::vector<u8>& out) override {
return fs::read_entire_file(path, out); return fs::read_entire_file(path, out);
} }
Result write_entire_file(const FsPath& path, const std::vector<u8>& in, bool ignore_read_only = false) override { Result write_entire_file(const FsPath& path, const std::vector<u8>& in) override {
return fs::write_entire_file(path, in, ignore_read_only); return fs::write_entire_file(path, in, m_ignore_read_only);
} }
Result copy_entire_file(const FsPath& dst, const FsPath& src, bool ignore_read_only = false) override { Result copy_entire_file(const FsPath& dst, const FsPath& src) override {
return fs::copy_entire_file(dst, src, ignore_read_only); return fs::copy_entire_file(dst, src, m_ignore_read_only);
} }
}; };
struct FsNative : Fs { struct FsNative : Fs {
FsNative() = default; explicit FsNative(bool ignore_read_only = true) : Fs{ignore_read_only} {}
FsNative(FsFileSystem* fs, bool own) : m_fs{*fs}, m_own{own} {} explicit FsNative(FsFileSystem* fs, bool own, bool ignore_read_only = true) : Fs{ignore_read_only}, m_fs{*fs}, m_own{own} {}
virtual ~FsNative() { virtual ~FsNative() {
if (m_own) { if (m_own) {
@@ -355,32 +368,32 @@ struct FsNative : Fs {
return m_open_result; return m_open_result;
} }
Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0, bool ignore_read_only = false) override { Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0) override {
return fs::CreateFile(&m_fs, path, size, option, ignore_read_only); return fs::CreateFile(&m_fs, path, size, option, m_ignore_read_only);
} }
Result CreateDirectory(const FsPath& path, bool ignore_read_only = false) override { Result CreateDirectory(const FsPath& path) override {
return fs::CreateDirectory(&m_fs, path, ignore_read_only); return fs::CreateDirectory(&m_fs, path, m_ignore_read_only);
} }
Result CreateDirectoryRecursively(const FsPath& path, bool ignore_read_only = false) override { Result CreateDirectoryRecursively(const FsPath& path) override {
return fs::CreateDirectoryRecursively(&m_fs, path, ignore_read_only); return fs::CreateDirectoryRecursively(&m_fs, path, m_ignore_read_only);
} }
Result CreateDirectoryRecursivelyWithPath(const FsPath& path, bool ignore_read_only = false) override { Result CreateDirectoryRecursivelyWithPath(const FsPath& path) override {
return fs::CreateDirectoryRecursivelyWithPath(&m_fs, path, ignore_read_only); return fs::CreateDirectoryRecursivelyWithPath(&m_fs, path, m_ignore_read_only);
} }
Result DeleteFile(const FsPath& path, bool ignore_read_only = false) override { Result DeleteFile(const FsPath& path) override {
return fs::DeleteFile(&m_fs, path, ignore_read_only); return fs::DeleteFile(&m_fs, path, m_ignore_read_only);
} }
Result DeleteDirectory(const FsPath& path, bool ignore_read_only = false) override { Result DeleteDirectory(const FsPath& path) override {
return fs::DeleteDirectory(&m_fs, path, ignore_read_only); return fs::DeleteDirectory(&m_fs, path, m_ignore_read_only);
} }
Result DeleteDirectoryRecursively(const FsPath& path, bool ignore_read_only = false) override { Result DeleteDirectoryRecursively(const FsPath& path) override {
return fs::DeleteDirectoryRecursively(&m_fs, path, ignore_read_only); return fs::DeleteDirectoryRecursively(&m_fs, path, m_ignore_read_only);
} }
Result RenameFile(const FsPath& src, const FsPath& dst, bool ignore_read_only = false) override { Result RenameFile(const FsPath& src, const FsPath& dst) override {
return fs::RenameFile(&m_fs, src, dst, ignore_read_only); return fs::RenameFile(&m_fs, src, dst, m_ignore_read_only);
} }
Result RenameDirectory(const FsPath& src, const FsPath& dst, bool ignore_read_only = false) override { Result RenameDirectory(const FsPath& src, const FsPath& dst) override {
return fs::RenameDirectory(&m_fs, src, dst, ignore_read_only); return fs::RenameDirectory(&m_fs, src, dst, m_ignore_read_only);
} }
Result GetEntryType(const FsPath& path, FsDirEntryType* out) override { Result GetEntryType(const FsPath& path, FsDirEntryType* out) override {
return fs::GetEntryType(&m_fs, path, out); return fs::GetEntryType(&m_fs, path, out);
@@ -397,11 +410,11 @@ struct FsNative : Fs {
Result read_entire_file(const FsPath& path, std::vector<u8>& out) override { Result read_entire_file(const FsPath& path, std::vector<u8>& out) override {
return fs::read_entire_file(&m_fs, path, out); return fs::read_entire_file(&m_fs, path, out);
} }
Result write_entire_file(const FsPath& path, const std::vector<u8>& in, bool ignore_read_only = false) override { Result write_entire_file(const FsPath& path, const std::vector<u8>& in) override {
return fs::write_entire_file(&m_fs, path, in, ignore_read_only); return fs::write_entire_file(&m_fs, path, in, m_ignore_read_only);
} }
Result copy_entire_file(const FsPath& dst, const FsPath& src, bool ignore_read_only = false) override { Result copy_entire_file(const FsPath& dst, const FsPath& src) override {
return fs::copy_entire_file(&m_fs, dst, src, ignore_read_only); return fs::copy_entire_file(&m_fs, dst, src, m_ignore_read_only);
} }
FsFileSystem m_fs{}; FsFileSystem m_fs{};
@@ -417,43 +430,34 @@ struct FsNativeSd final : FsNative {
}; };
#else #else
struct FsNativeSd final : FsNative { struct FsNativeSd final : FsNative {
FsNativeSd() : FsNative{fsdevGetDeviceFileSystem("sdmc:"), false} { FsNativeSd(bool ignore_read_only = true) : FsNative{fsdevGetDeviceFileSystem("sdmc:"), false, ignore_read_only} {
m_open_result = 0; m_open_result = 0;
} }
}; };
#endif #endif
struct FsNativeBis final : FsNative { struct FsNativeBis final : FsNative {
FsNativeBis(FsBisPartitionId id, const FsPath& string) { FsNativeBis(FsBisPartitionId id, const FsPath& string, bool ignore_read_only = true) : FsNative{ignore_read_only} {
m_open_result = fsOpenBisFileSystem(&m_fs, id, string); m_open_result = fsOpenBisFileSystem(&m_fs, id, string);
} }
}; };
struct FsNativeImage final : FsNative { struct FsNativeImage final : FsNative {
FsNativeImage(FsImageDirectoryId id) { FsNativeImage(FsImageDirectoryId id, bool ignore_read_only = true) : FsNative{ignore_read_only} {
m_open_result = fsOpenImageDirectoryFileSystem(&m_fs, id); m_open_result = fsOpenImageDirectoryFileSystem(&m_fs, id);
} }
}; };
struct FsNativeContentStorage final : FsNative { struct FsNativeContentStorage final : FsNative {
FsNativeContentStorage(FsContentStorageId id) { FsNativeContentStorage(FsContentStorageId id, bool ignore_read_only = true) : FsNative{ignore_read_only} {
m_open_result = fsOpenContentStorageFileSystem(&m_fs, id); m_open_result = fsOpenContentStorageFileSystem(&m_fs, id);
} }
}; };
// auto file_exists(const FsPath& path) -> bool; struct FsNativeGameCard final : FsNative {
// auto create_file(const FsPath& path, u64 size = 0) -> Result; FsNativeGameCard(const FsGameCardHandle* handle, FsGameCardPartition partition, bool ignore_read_only = true) : FsNative{ignore_read_only} {
// auto delete_file(const FsPath& path) -> Result; m_open_result = fsOpenGameCardFileSystem(&m_fs, handle, partition);
// auto create_directory(const FsPath& path) -> Result; }
// auto create_directory_recursively(const FsPath& path) -> Result; };
// auto delete_directory(const FsPath& path) -> Result;
// auto delete_directory_recursively(const FsPath& path) -> Result;
// auto rename_file(const FsPath& src, const FsPath& dst) -> Result;
// auto rename_directory(const FsPath& src, const FsPath& dst) -> Result;
// auto read_entire_file(const FsPath& path, std::vector<u8>& out) -> Result;
// auto write_entire_file(const FsPath& path, const std::vector<u8>& in) -> Result;
// // single threaded one shot copy, only use for very small files!
// auto copy_entire_file(const FsPath& dst, const FsPath& src) -> Result;
} // namespace fs } // namespace fs

View File

@@ -1,33 +1,22 @@
#pragma once #pragma once
#include <switch.h> #include <functional>
struct FtpVfsFile {
FsFile fd;
s64 off;
s64 buf_off;
s64 buf_size;
bool is_write;
bool is_valid;
u8 buf[1024 * 1024 * 1];
};
struct FtpVfsDir {
FsDir dir;
bool is_valid;
};
struct FtpVfsDirEntry {
FsDirectoryEntry buf;
};
#ifdef __cplusplus
namespace sphaira::ftpsrv { namespace sphaira::ftpsrv {
bool Init(); bool Init();
void Exit(); void Exit();
} // namespace sphaira::ftpsrv 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)>;
#endif // __cplusplus void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
void DisableInstallMode();
unsigned GetPort();
bool IsAnon();
const char* GetUser();
const char* GetPass();
} // namespace sphaira::ftpsrv

View File

@@ -1,18 +1,19 @@
#pragma once #pragma once
#include <string> #include <string>
#include <string_view>
namespace sphaira::i18n { namespace sphaira::i18n {
bool init(long index); bool init(long index);
void exit(); void exit();
std::string get(const char* str); std::string get(std::string_view str);
} // namespace sphaira::i18n } // namespace sphaira::i18n
inline namespace literals { inline namespace literals {
std::string operator"" _i18n(const char* str, size_t len); std::string operator""_i18n(const char* str, size_t len);
} // namespace literals } // namespace literals

View File

@@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <vector>
#include <switch.h>
namespace sphaira::location {
struct Entry {
std::string name{};
std::string url{};
std::string user{};
std::string pass{};
std::string bearer{};
std::string pub_key{};
std::string priv_key{};
u16 port{};
};
using Entries = std::vector<Entry>;
auto Load() -> Entries;
void Add(const Entry& e);
} // namespace sphaira::location

View File

@@ -1,21 +1,33 @@
#pragma once #pragma once
#ifdef __cplusplus
extern "C" {
#endif
#define sphaira_USE_LOG 1 #define sphaira_USE_LOG 1
#include <stdarg.h>
#if sphaira_USE_LOG #if sphaira_USE_LOG
auto log_file_init() -> bool; bool log_file_init();
auto log_nxlink_init() -> bool; bool log_nxlink_init();
void log_file_exit(); void log_file_exit();
void log_nxlink_exit(); void log_nxlink_exit();
void log_write(const char* s, ...) __attribute__ ((format (printf, 1, 2))); void log_write(const char* s, ...) __attribute__ ((format (printf, 1, 2)));
void log_write_arg(const char* s, va_list* v);
#else #else
inline auto log_file_init() -> bool { inline bool log_file_init() {
return true; return true;
} }
inline auto log_nxlink_init() -> bool { inline bool log_nxlink_init() {
return true; return true;
} }
#define log_file_exit() #define log_file_exit()
#define log_nxlink_exit() #define log_nxlink_exit()
#define log_write(...) #define log_write(...)
#define log_write_arg(...)
#endif
#ifdef __cplusplus
}
#endif #endif

View File

@@ -19,7 +19,6 @@ struct NroEntry {
s64 size{}; s64 size{};
NacpStruct nacp{}; NacpStruct nacp{};
std::vector<u8> icon{};
u64 icon_size{}; u64 icon_size{};
u64 icon_offset{}; u64 icon_offset{};
@@ -76,4 +75,10 @@ auto nro_add_arg_file(std::string arg) -> std::string;
// strips sdmc: // strips sdmc:
auto nro_normalise_path(const std::string& p) -> std::string; auto nro_normalise_path(const std::string& p) -> std::string;
// helpers to find nro entry, will be made methods soon once i convert vector into a struct.
auto nro_find(std::span<const NroEntry> array, std::string_view name, std::string_view author, const fs::FsPath& path) -> std::optional<NroEntry>;
auto nro_find_name(std::span<const NroEntry> array, std::string_view name) -> std::optional<NroEntry>;
auto nro_find_author(std::span<const NroEntry> array, std::string_view author) -> std::optional<NroEntry>;
auto nro_find_path(std::span<const NroEntry> array, const fs::FsPath& path) -> std::optional<NroEntry>;
} // namespace sphaira } // namespace sphaira

View File

@@ -14,8 +14,12 @@ struct OptionBase {
{} {}
auto Get() -> T; auto Get() -> T;
auto GetOr(const char* name) -> T;
void Set(T value); void Set(T value);
private:
auto GetInternal(const char* name) -> T;
private: private:
const std::string m_section; const std::string m_section;
const std::string m_name; const std::string m_name;

View File

@@ -8,16 +8,14 @@ namespace sphaira::ui {
class ErrorBox final : public Widget { class ErrorBox final : public Widget {
public: public:
ErrorBox(Result code, const std::string& message); ErrorBox(Result code, const std::string& message);
ErrorBox(const std::string& message);
auto Update(Controller* controller, TouchInfo* touch) -> void override; auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
private: private:
Result m_code; std::optional<Result> m_code{};
std::string m_message; std::string m_message{};
std::string m_module_str;
std::string m_description_str;
}; };
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -0,0 +1,90 @@
#pragma once
#include "ui/object.hpp"
namespace sphaira::ui {
struct List final : Object {
enum class Layout {
HOME,
GRID,
};
using Callback = std::function<void(NVGcontext* vg, Theme* theme, Vec4 v, s64 index)>;
using TouchCallback = std::function<void(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 Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
auto SetScrollBarPos(float x, float y, float h) {
m_scrollbar.x = x;
m_scrollbar.y = y;
m_scrollbar.h = h;
}
auto ScrollDown(s64& index, s64 step, s64 count) -> bool;
auto ScrollUp(s64& index, s64 step, s64 count) -> bool;
auto GetYoff() const {
return m_yoff;
}
void SetYoff(float y = 0) {
m_yoff = y;
}
auto GetMaxY() const {
return m_v.h + m_pad.y;
}
auto GetMaxX() const {
return m_v.w + m_pad.x;
}
auto GetLayout() const {
return m_layout;
}
void SetLayout(Layout layout) {
m_layout = layout;
}
auto GetRow() const {
return m_row;
}
auto GetPage() const {
return m_page;
}
private:
auto Draw(NVGcontext* vg, Theme* theme) -> void override {}
auto ClampX(float x, s64 count) const -> float;
auto ClampY(float y, s64 count) const -> float;
void OnUpdateHome(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
void OnUpdateGrid(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
void DrawHome(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
void DrawGrid(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
private:
const s64 m_row;
const s64 m_page;
Vec4 m_v{};
Vec2 m_pad{};
Vec4 m_scrollbar{};
// current y offset.
float m_yoff{};
// in progress y offset, used when scrolling.
float m_y_prog{};
Layout m_layout{Layout::GRID};
};
} // namespace sphaira::ui

View File

@@ -1,9 +1,11 @@
#pragma once #pragma once
#include "ui/menus/menu_base.hpp" #include "ui/menus/grid_menu_base.hpp"
#include "ui/scrollable_text.hpp" #include "ui/scrollable_text.hpp"
#include "nro.hpp" #include "ui/scrolling_text.hpp"
#include "ui/list.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "option.hpp"
#include <span> #include <span>
namespace sphaira::ui::menu::appstore { namespace sphaira::ui::menu::appstore {
@@ -27,6 +29,8 @@ struct LazyImage {
~LazyImage(); ~LazyImage();
int image{}; int image{};
int w{}, h{}; int w{}, h{};
bool tried_cache{};
bool cached{};
ImageDownloadState state{ImageDownloadState::None}; ImageDownloadState state{ImageDownloadState::None};
u8 first_pixel[4]{}; u8 first_pixel[4]{};
}; };
@@ -39,26 +43,26 @@ enum class EntryStatus {
}; };
struct Entry { struct Entry {
std::string category; // todo: lable std::string category{}; // todo: lable
std::string binary; // optional, only valid for .nro std::string binary{}; // optional, only valid for .nro
std::string updated; // date of update std::string updated{}; // date of update
std::string name; std::string name{};
std::string license; // optional std::string license{}; // optional
std::string title; // same as name but with spaces std::string title{}; // same as name but with spaces
std::string url; // url of repo (optional?) std::string url{}; // url of repo (optional?)
std::string description; std::string description{};
std::string author; std::string author{};
std::string changelog; // optional std::string changelog{}; // optional
u64 screens; // number of screenshots u64 screens{}; // number of screenshots
u64 extracted; // extracted size in KiB u64 extracted{}; // extracted size in KiB
std::string version; std::string version{};
u64 filesize; // compressed size in KiB u64 filesize{}; // compressed size in KiB
std::string details; std::string details{};
u64 app_dls; u64 app_dls{};
std::string md5; // md5 of the zip std::string md5{}; // md5 of the zip
LazyImage image; LazyImage image{};
u32 updated_num; u32 updated_num{};
EntryStatus status{EntryStatus::Get}; EntryStatus status{EntryStatus::Get};
}; };
@@ -70,12 +74,13 @@ struct EntryMenu final : MenuBase {
EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu); EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu);
~EntryMenu(); ~EntryMenu();
auto GetShortTitle() const -> const char* override { return "Entry"; };
void Update(Controller* controller, TouchInfo* touch) override; void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
// void OnFocusGained() override; // void OnFocusGained() override;
void ShowChangelogAction(); void ShowChangelogAction();
void SetIndex(std::size_t index); void SetIndex(s64 index);
void UpdateOptions(); void UpdateOptions();
@@ -95,14 +100,14 @@ private:
const LazyImage& m_default_icon; const LazyImage& m_default_icon;
Menu& m_menu; Menu& m_menu;
std::size_t m_index{}; // where i am in the array s64 m_index{}; // where i am in the array
std::vector<Option> m_options; std::vector<Option> m_options{};
LazyImage m_banner; LazyImage m_banner{};
std::vector<LazyImage> m_screens; std::unique_ptr<List> m_list{};
std::shared_ptr<ScrollableText> m_details; std::shared_ptr<ScrollableText> m_details{};
std::shared_ptr<ScrollableText> m_changelog; std::shared_ptr<ScrollableText> m_changelog{};
std::shared_ptr<ScrollableText> m_detail_changelog; std::shared_ptr<ScrollableText> m_detail_changelog{};
bool m_show_changlog{}; bool m_show_changlog{};
}; };
@@ -127,60 +132,29 @@ enum SortType {
}; };
enum OrderType { enum OrderType {
OrderType_Decending, OrderType_Descending,
OrderType_Ascending, OrderType_Ascending,
}; };
struct FeedbackEntry { using LayoutType = grid::LayoutType;
u32 id;
u64 time;
std::string package; // name of package
std::string content; // the feedback message that was sent
std::string reply; // the reply, "" if no reply yet :)
};
struct FeedbackMenu final : MenuBase { struct Menu final : grid::Menu {
FeedbackMenu(const std::vector<Entry>& package_entries, LazyImage& default_image); Menu();
~FeedbackMenu();
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
void SetIndex(std::size_t index);
void ScanHomebrew();
void Sort();
private:
const std::vector<Entry>& m_package_entries;
LazyImage& m_default_image;
std::vector<FeedbackEntry> m_entries;
std::size_t m_start{};
std::size_t m_index{}; // where i am in the array
ImageDownloadState m_repo_download_state{ImageDownloadState::None};
};
struct Menu final : MenuBase {
Menu(const std::vector<NroEntry>& nro_entries);
~Menu(); ~Menu();
auto GetShortTitle() const -> const char* override { return "Store"; };
void Update(Controller* controller, TouchInfo* touch) override; void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override; void OnFocusGained() override;
void SetIndex(std::size_t index);
void ScanHomebrew();
void Sort();
void SetFilter(Filter filter);
void SetSort(SortType sort);
void SetOrder(OrderType order);
void SetSearch(const std::string& term);
void SetAuthor(); void SetAuthor();
auto GetEntry(s64 i) -> Entry& {
return m_entries[m_entries_current[i]];
}
auto GetEntry() -> Entry& { auto GetEntry() -> Entry& {
return m_entries[m_entries_current[m_index]]; return GetEntry(m_index);
} }
auto SetDirty() { auto SetDirty() {
@@ -188,30 +162,41 @@ struct Menu final : MenuBase {
} }
private: private:
const std::vector<NroEntry>& m_nro_entries; void SetIndex(s64 index);
std::vector<Entry> m_entries; void ScanHomebrew();
std::vector<EntryMini> m_entries_index[Filter_MAX]; void Sort();
std::vector<EntryMini> m_entries_index_author; void SortAndFindLastFile();
std::vector<EntryMini> m_entries_index_search; void SetFilter();
std::span<EntryMini> m_entries_current; void SetSearch(const std::string& term);
void OnLayoutChange();
Filter m_filter{Filter::Filter_All}; private:
SortType m_sort{SortType::SortType_Updated}; static constexpr inline const char* INI_SECTION = "appstore";
OrderType m_order{OrderType::OrderType_Decending};
std::size_t m_start{}; std::vector<Entry> m_entries{};
std::size_t m_index{}; // where i am in the array std::vector<EntryMini> m_entries_index[Filter_MAX]{};
LazyImage m_default_image; std::vector<EntryMini> m_entries_index_author{};
LazyImage m_update; std::vector<EntryMini> m_entries_index_search{};
LazyImage m_get; std::span<EntryMini> m_entries_current{};
LazyImage m_local;
LazyImage m_installed; option::OptionLong m_filter{INI_SECTION, "filter", Filter::Filter_All};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
s64 m_index{}; // where i am in the array
LazyImage m_default_image{};
LazyImage m_update{};
LazyImage m_get{};
LazyImage m_local{};
LazyImage m_installed{};
ImageDownloadState m_repo_download_state{ImageDownloadState::None}; ImageDownloadState m_repo_download_state{ImageDownloadState::None};
std::unique_ptr<List> m_list{};
std::string m_search_term; std::string m_search_term{};
std::string m_author_term; std::string m_author_term{};
u64 m_entry_search_jump_back{}; s64 m_entry_search_jump_back{};
u64 m_entry_author_jump_back{}; s64 m_entry_author_jump_back{};
bool m_is_search{}; bool m_is_search{};
bool m_is_author{}; bool m_is_author{};
bool m_dirty{}; // if set, does a sort bool m_dirty{}; // if set, does a sort

View File

@@ -10,21 +10,22 @@ struct Menu final : MenuBase {
Menu(const fs::FsPath& path); Menu(const fs::FsPath& path);
~Menu(); ~Menu();
auto GetShortTitle() const -> const char* override { return "File"; };
void Update(Controller* controller, TouchInfo* touch) override; void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override; void OnFocusGained() override;
private: private:
const fs::FsPath m_path; const fs::FsPath m_path;
fs::FsNativeSd m_fs; fs::FsNativeSd m_fs{};
FsFile m_file; FsFile m_file{};
s64 m_file_size{}; s64 m_file_size{};
s64 m_file_offset{}; s64 m_file_offset{};
std::unique_ptr<ScrollableText> m_scroll_text; std::unique_ptr<ScrollableText> m_scroll_text{};
std::size_t m_start{}; s64 m_start{};
std::size_t m_index{}; // where i am in the array s64 m_index{}; // where i am in the array
}; };
} // namespace sphaira::ui::menu::fileview } // namespace sphaira::ui::menu::fileview

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "ui/menus/menu_base.hpp" #include "ui/menus/menu_base.hpp"
#include "ui/list.hpp"
#include "nro.hpp" #include "nro.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "option.hpp" #include "option.hpp"
@@ -9,6 +10,12 @@
namespace sphaira::ui::menu::filebrowser { namespace sphaira::ui::menu::filebrowser {
enum class FsType {
Sd,
ImageNand,
ImageSd,
};
enum class SelectedType { enum class SelectedType {
None, None,
Copy, Copy,
@@ -22,7 +29,7 @@ enum SortType {
}; };
enum OrderType { enum OrderType {
OrderType_Decending, OrderType_Descending,
OrderType_Ascending, OrderType_Ascending,
}; };
@@ -79,21 +86,45 @@ struct FileEntry : FsDirectoryEntry {
struct FileAssocEntry { struct FileAssocEntry {
fs::FsPath path{}; // ini name fs::FsPath path{}; // ini name
std::string name; // ini name std::string name{}; // ini name
std::vector<std::string> ext; // list of ext std::vector<std::string> ext{}; // list of ext
std::vector<std::string> database; // list of systems std::vector<std::string> database{}; // list of systems
bool use_base_name{}; // if set, uses base name (rom.zip) otherwise uses internal name (rom.gba)
auto IsExtension(std::string_view extension, std::string_view internal_extension) const -> bool {
for (const auto& assoc_ext : ext) {
if (extension.length() == assoc_ext.length() && !strncasecmp(assoc_ext.data(), extension.data(), assoc_ext.length())) {
return true;
}
if (internal_extension.length() == assoc_ext.length() && !strncasecmp(assoc_ext.data(), internal_extension.data(), assoc_ext.length())) {
return true;
}
}
return false;
}
}; };
struct LastFile { struct LastFile {
fs::FsPath name; fs::FsPath name{};
u64 index; s64 index{};
u64 offset; float offset{};
u64 entries_count; s64 entries_count{};
}; };
struct FsDirCollection {
fs::FsPath path{};
fs::FsPath parent_name{};
std::vector<FsDirectoryEntry> files{};
std::vector<FsDirectoryEntry> dirs{};
};
using FsDirCollections = std::vector<FsDirCollection>;
struct Menu final : MenuBase { struct Menu final : MenuBase {
Menu(const std::vector<NroEntry>& nro_entries); Menu(const std::vector<NroEntry>& nro_entries);
~Menu(); ~Menu();
auto GetShortTitle() const -> const char* override { return "Files"; };
void Update(Controller* controller, TouchInfo* touch) override; void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override; void OnFocusGained() override;
@@ -103,8 +134,14 @@ struct Menu final : MenuBase {
} }
private: private:
void SetIndex(std::size_t index); void SetIndex(s64 index);
void InstallForwarder(); void InstallForwarder();
void InstallFiles();
void UnzipFiles(fs::FsPath folder);
void ZipFiles(fs::FsPath zip_path);
void UploadFiles();
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result; auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
void LoadAssocEntriesPath(const fs::FsPath& path); void LoadAssocEntriesPath(const fs::FsPath& path);
@@ -115,7 +152,7 @@ private:
return GetNewPath(m_path, entry.name); return GetNewPath(m_path, entry.name);
} }
auto GetNewPath(u64 index) const -> fs::FsPath { auto GetNewPath(s64 index) const -> fs::FsPath {
return GetNewPath(m_path, GetEntry(index).name); return GetNewPath(m_path, GetEntry(index).name);
} }
@@ -124,15 +161,15 @@ private:
} }
auto GetSelectedEntries() const -> std::vector<FileEntry> { auto GetSelectedEntries() const -> std::vector<FileEntry> {
if (!m_selected_count) {
return {};
}
std::vector<FileEntry> out; std::vector<FileEntry> out;
for (auto&e : m_entries) { if (!m_selected_count) {
if (e.IsSelected()) { out.emplace_back(GetEntry());
out.emplace_back(e); } else {
for (auto&e : m_entries) {
if (e.IsSelected()) {
out.emplace_back(e);
}
} }
} }
@@ -151,13 +188,6 @@ private:
m_selected_path = m_path; m_selected_path = m_path;
} }
void AddCurrentFileToSelection(SelectedType type) {
m_selected_files.emplace_back(GetEntry());
m_selected_count++;
m_selected_type = type;
m_selected_path = m_path;
}
void ResetSelection() { void ResetSelection() {
m_selected_files.clear(); m_selected_files.clear();
m_selected_count = 0; m_selected_count = 0;
@@ -203,46 +233,54 @@ private:
void OnDeleteCallback(); void OnDeleteCallback();
void OnPasteCallback(); void OnPasteCallback();
void OnRenameCallback(); void OnRenameCallback();
auto CheckIfUpdateFolder() -> Result;
auto get_collection(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out) -> Result;
void SetFs(const fs::FsPath& new_path, u32 new_type);
private: private:
static constexpr inline const char* INI_SECTION = "filebrowser"; static constexpr inline const char* INI_SECTION = "filebrowser";
const std::vector<NroEntry>& m_nro_entries; const std::vector<NroEntry>& m_nro_entries;
fs::FsPath m_path; std::unique_ptr<fs::FsNative> m_fs{};
std::vector<FileEntry> m_entries; FsType m_fs_type{};
std::vector<u32> m_entries_index; // files not including hidden fs::FsPath m_path{};
std::vector<u32> m_entries_index_hidden; // includes hidden files std::vector<FileEntry> m_entries{};
std::vector<u32> m_entries_index_search; // files found via search std::vector<u32> m_entries_index{}; // files not including hidden
std::span<u32> m_entries_current; std::vector<u32> m_entries_index_hidden{}; // includes hidden files
std::vector<u32> m_entries_index_search{}; // files found via search
std::span<u32> m_entries_current{};
std::unique_ptr<List> m_list{};
std::optional<fs::FsPath> m_daybreak_path{};
// search options // search options
// show files [X] // show files [X]
// show folders [X] // show folders [X]
// recursive (slow) [ ] // recursive (slow) [ ]
std::vector<FileAssocEntry> m_assoc_entries; std::vector<FileAssocEntry> m_assoc_entries{};
std::vector<FileEntry> m_selected_files; std::vector<FileEntry> m_selected_files{};
// this keeps track of the highlighted file before opening a folder // this keeps track of the highlighted file before opening a folder
// if the user presses B to go back to the previous dir // 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 // this vector is popped, then, that entry is checked if it still exists
// if it does, the index becomes that file. // if it does, the index becomes that file.
std::vector<LastFile> m_previous_highlighted_file; std::vector<LastFile> m_previous_highlighted_file{};
fs::FsPath m_selected_path; fs::FsPath m_selected_path{};
std::size_t m_index{}; s64 m_index{};
std::size_t m_index_offset{}; s64 m_selected_count{};
std::size_t m_selected_count{};
SelectedType m_selected_type{SelectedType::None}; SelectedType m_selected_type{SelectedType::None};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical}; option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending}; option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false}; option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false};
option::OptionBool m_folders_first{INI_SECTION, "folders_first", true}; option::OptionBool m_folders_first{INI_SECTION, "folders_first", true};
option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false}; option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false};
option::OptionBool m_ignore_read_only{INI_SECTION, "ignore_read_only", false};
option::OptionBool m_search_show_files{INI_SECTION, "search_show_files", true}; option::OptionLong m_mount{INI_SECTION, "mount", 0};
option::OptionBool m_search_show_folders{INI_SECTION, "search_show_folders", true};
option::OptionBool m_search_recursive{INI_SECTION, "search_recursive", false};
bool m_loaded_assoc_entries{}; bool m_loaded_assoc_entries{};
bool m_is_update_folder{}; bool m_is_update_folder{};

View File

@@ -0,0 +1,61 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "yati/source/stream.hpp"
namespace sphaira::ui::menu::ftp {
enum class State {
// not connected.
None,
// just connected, starts the transfer.
Connected,
// set whilst transfer is in progress.
Progress,
// set when the transfer is finished.
Done,
// failed to connect.
Failed,
};
struct StreamFtp final : yati::source::Stream {
StreamFtp(const fs::FsPath& path, std::stop_token token);
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
bool Push(const void* buf, s64 size);
void Disable();
// private:
fs::FsPath m_path{};
std::stop_token m_token{};
std::vector<u8> m_buffer{};
Mutex m_mutex{};
bool m_active{};
// bool m_push_exit{};
};
struct Menu final : MenuBase {
Menu();
~Menu();
auto GetShortTitle() const -> const char* override { return "FTP"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
// this should be private
// private:
std::shared_ptr<StreamFtp> m_source{};
Thread m_thread{};
Mutex m_mutex{};
// the below are shared across threads, lock with the above mutex!
State m_state{State::None};
const char* m_user{};
const char* m_pass{};
unsigned m_port{};
bool m_anon{};
bool m_was_ftp_enabled{};
};
} // namespace sphaira::ui::menu::ftp

View File

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

View File

@@ -0,0 +1,82 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "yati/container/base.hpp"
#include "yati/source/base.hpp"
#include "ui/list.hpp"
#include <span>
#include <memory>
namespace sphaira::ui::menu::gc {
struct GcCollection : yati::container::CollectionEntry {
GcCollection(const char* _name, s64 _size, u8 _type, u8 _id_offset) {
name = _name;
size = _size;
type = _type;
id_offset = _id_offset;
}
// NcmContentType
u8 type{};
u8 id_offset{};
};
using GcCollections = std::vector<GcCollection>;
struct ApplicationEntry {
u64 app_id{};
u32 version{};
u8 key_gen{};
std::vector<GcCollections> application{};
std::vector<GcCollections> patch{};
std::vector<GcCollections> add_on{};
std::vector<GcCollections> data_patch{};
yati::container::Collections tickets{};
auto GetSize() const -> s64;
auto GetSize(const std::vector<GcCollections>& entries) const -> s64;
};
struct Menu final : MenuBase {
Menu();
~Menu();
auto GetShortTitle() const -> const char* override { return "GC"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
private:
Result GcMount();
void GcUnmount();
Result GcPoll(bool* inserted);
Result GcOnEvent();
Result UpdateStorageSize();
void FreeImage();
void OnChangeIndex(s64 new_index);
private:
FsDeviceOperator m_dev_op{};
FsGameCardHandle m_handle{};
std::unique_ptr<fs::FsNativeGameCard> m_fs{};
FsEventNotifier m_event_notifier{};
Event m_event{};
std::vector<ApplicationEntry> m_entries{};
std::unique_ptr<List> m_list{};
s64 m_entry_index{};
s64 m_option_index{};
s64 m_size_free_sd{};
s64 m_size_total_sd{};
s64 m_size_free_nand{};
s64 m_size_total_nand{};
NacpLanguageEntry m_lang_entry{};
int m_icon{};
bool m_mounted{};
};
} // namespace sphaira::ui::menu::gc

View File

@@ -0,0 +1,75 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "ui/list.hpp"
#include "fs.hpp"
#include "option.hpp"
#include <vector>
#include <string>
namespace sphaira::ui::menu::gh {
struct AssetEntry {
std::string name{};
std::string path{};
std::string pre_install_message{};
std::string post_install_message{};
};
struct Entry {
fs::FsPath json_path{};
std::string url{};
std::string owner{};
std::string repo{};
std::string tag{};
std::string pre_install_message{};
std::string post_install_message{};
std::vector<AssetEntry> assets{};
};
struct GhApiAsset {
std::string name{};
std::string content_type{};
u64 size{};
u64 download_count{};
std::string browser_download_url{};
};
struct GhApiEntry {
std::string tag_name{};
std::string name{};
std::vector<GhApiAsset> assets{};
};
struct Menu final : MenuBase {
Menu();
~Menu();
auto GetShortTitle() const -> const char* override { return "GitHub"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
private:
void SetIndex(s64 index);
void Scan();
void LoadEntriesFromPath(const fs::FsPath& path);
auto GetEntry() -> Entry& {
return m_entries[m_index];
}
auto GetEntry() const -> const Entry& {
return m_entries[m_index];
}
void Sort();
void UpdateSubheading();
private:
std::vector<Entry> m_entries{};
s64 m_index{};
std::unique_ptr<List> m_list{};
};
} // namespace sphaira::ui::menu::gh

View File

@@ -0,0 +1,35 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "ui/scrolling_text.hpp"
#include "ui/list.hpp"
#include <string>
#include <memory>
namespace sphaira::ui::menu::grid {
enum LayoutType {
LayoutType_List,
LayoutType_Grid,
LayoutType_GridDetail,
};
struct Menu : MenuBase {
using MenuBase::MenuBase;
protected:
void OnLayoutChange(std::unique_ptr<List>& list, int layout);
void DrawEntry(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version);
// same as above but doesn't draw image and returns image dimension.
Vec4 DrawEntryNoImage(NVGcontext* vg, Theme* theme, int layout, const Vec4& v, bool selected, const char* name, const char* author, const char* version);
private:
Vec4 DrawEntry(NVGcontext* vg, Theme* theme, bool draw_image, int layout, const Vec4& v, bool selected, int image, const char* name, const char* author, const char* version);
private:
ScrollingText m_scroll_name{};
ScrollingText m_scroll_author{};
ScrollingText m_scroll_version{};
};
} // namespace sphaira::ui::menu::grid

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "ui/menus/menu_base.hpp" #include "ui/menus/grid_menu_base.hpp"
#include "ui/list.hpp"
#include "nro.hpp" #include "nro.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "option.hpp" #include "option.hpp"
@@ -17,44 +18,51 @@ enum SortType {
}; };
enum OrderType { enum OrderType {
OrderType_Decending, OrderType_Descending,
OrderType_Ascending, OrderType_Ascending,
}; };
struct Menu final : MenuBase { using LayoutType = grid::LayoutType;
struct Menu final : grid::Menu {
Menu(); Menu();
~Menu(); ~Menu();
auto GetShortTitle() const -> const char* override { return "Apps"; };
void Update(Controller* controller, TouchInfo* touch) override; void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override; void OnFocusGained() override;
void SetIndex(std::size_t index);
void InstallHomebrew();
void ScanHomebrew();
void Sort();
void SortAndFindLastFile();
auto GetHomebrewList() const -> const std::vector<NroEntry>& { auto GetHomebrewList() const -> const std::vector<NroEntry>& {
return m_entries; return m_entries;
} }
auto IsStarEnabled() -> bool {
return m_sort.Get() >= SortType_UpdatedStar;
}
static Result InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon); static Result InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon);
static Result InstallHomebrewFromPath(const fs::FsPath& path); static Result InstallHomebrewFromPath(const fs::FsPath& path);
private:
void SetIndex(s64 index);
void InstallHomebrew();
void ScanHomebrew();
void Sort();
void SortAndFindLastFile();
void FreeEntries();
void OnLayoutChange();
auto IsStarEnabled() -> bool {
return m_sort.Get() >= SortType_UpdatedStar;
}
private: private:
static constexpr inline const char* INI_SECTION = "homebrew"; static constexpr inline const char* INI_SECTION = "homebrew";
std::vector<NroEntry> m_entries; std::vector<NroEntry> m_entries{};
std::size_t m_start{}; s64 m_index{}; // where i am in the array
std::size_t m_index{}; // where i am in the array std::unique_ptr<List> m_list{};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar}; option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending}; option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false}; option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};
}; };

View File

@@ -30,16 +30,19 @@ struct Menu final : MenuBase {
Menu(); Menu();
~Menu(); ~Menu();
auto GetShortTitle() const -> const char* override { return "IRS"; };
void Update(Controller* controller, TouchInfo* touch) override; void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override; void OnFocusGained() override;
private:
void PollCameraStatus(bool statup = false); void PollCameraStatus(bool statup = false);
void LoadDefaultConfig(); void LoadDefaultConfig();
void UpdateConfig(const IrsImageTransferProcessorExConfig* config); void UpdateConfig(const IrsImageTransferProcessorExConfig* config);
void ResetImage(); void ResetImage();
void UpdateImage(); void UpdateImage();
void updateColourArray(); void updateColourArray();
auto GetEntryName(s64 i) -> std::string;
private: private:
Result m_init_rc{}; Result m_init_rc{};
@@ -61,7 +64,7 @@ private:
Rotation m_rotation{Rotation_90}; Rotation m_rotation{Rotation_90};
Colour m_colour{Colour_Grey}; Colour m_colour{Colour_Grey};
int m_image{}; int m_image{};
std::size_t m_index{}; s64 m_index{};
}; };
} // namespace sphaira::ui::menu::irs } // namespace sphaira::ui::menu::irs

View File

@@ -3,7 +3,6 @@
#include "ui/widget.hpp" #include "ui/widget.hpp"
#include "ui/menus/homebrew.hpp" #include "ui/menus/homebrew.hpp"
#include "ui/menus/filebrowser.hpp" #include "ui/menus/filebrowser.hpp"
#include "ui/menus/appstore.hpp"
namespace sphaira::ui::menu::main { namespace sphaira::ui::menu::main {
@@ -18,6 +17,32 @@ enum class UpdateState {
Error, Error,
}; };
using MiscMenuFunction = std::function<std::shared_ptr<ui::menu::MenuBase>(void)>;
enum MiscMenuFlag : u8 {
// can be set as the rightside menu.
MiscMenuFlag_Shortcut = 1 << 0,
// needs install option to be enabled.
MiscMenuFlag_Install = 1 << 1,
};
struct MiscMenuEntry {
const char* name;
const char* title;
MiscMenuFunction func;
u8 flag;
auto IsShortcut() const -> bool {
return flag & MiscMenuFlag_Shortcut;
}
auto IsInstall() const -> bool {
return flag & MiscMenuFlag_Install;
}
};
auto GetMiscMenuEntries() -> std::span<const MiscMenuEntry>;
// this holds 2 menus and allows for switching between them // this holds 2 menus and allows for switching between them
struct MainMenu final : Widget { struct MainMenu final : Widget {
MainMenu(); MainMenu();
@@ -28,15 +53,18 @@ struct MainMenu final : Widget {
void OnFocusGained() override; void OnFocusGained() override;
void OnFocusLost() override; void OnFocusLost() override;
auto IsMenu() const -> bool override {
return true;
}
private: private:
void OnLRPress(std::shared_ptr<MenuBase> menu, Button b); void OnLRPress(std::shared_ptr<MenuBase> menu, Button b);
void AddOnLPress(); void AddOnLRPress();
void AddOnRPress();
private: private:
std::shared_ptr<homebrew::Menu> m_homebrew_menu{}; std::shared_ptr<homebrew::Menu> m_homebrew_menu{};
std::shared_ptr<filebrowser::Menu> m_filebrowser_menu{}; std::shared_ptr<filebrowser::Menu> m_filebrowser_menu{};
std::shared_ptr<appstore::Menu> m_app_store_menu{}; std::shared_ptr<MenuBase> m_right_side_menu{};
std::shared_ptr<MenuBase> m_current_menu{}; std::shared_ptr<MenuBase> m_current_menu{};
std::string m_update_url{}; std::string m_update_url{};

View File

@@ -1,7 +1,6 @@
#pragma once #pragma once
#include "ui/widget.hpp" #include "ui/widget.hpp"
#include "nro.hpp"
#include <string> #include <string>
namespace sphaira::ui::menu { namespace sphaira::ui::menu {
@@ -10,17 +9,39 @@ struct MenuBase : Widget {
MenuBase(std::string title); MenuBase(std::string title);
virtual ~MenuBase(); virtual ~MenuBase();
virtual auto GetShortTitle() const -> const char* = 0;
virtual void Update(Controller* controller, TouchInfo* touch); virtual void Update(Controller* controller, TouchInfo* touch);
virtual void Draw(NVGcontext* vg, Theme* theme); virtual void Draw(NVGcontext* vg, Theme* theme);
auto IsMenu() const -> bool override {
return true;
}
void SetTitle(std::string title); void SetTitle(std::string title);
void SetTitleSubHeading(std::string sub_heading); void SetTitleSubHeading(std::string sub_heading);
void SetSubHeading(std::string sub_heading); void SetSubHeading(std::string sub_heading);
auto GetTitle() const {
return m_title;
}
private: private:
std::string m_title; void UpdateVars();
std::string m_title_sub_heading;
std::string m_sub_heading; private:
AppletType m_applet_type; std::string m_title{};
std::string m_title_sub_heading{};
std::string m_sub_heading{};
protected:
struct tm m_tm{};
TimeStamp m_poll_timestamp{};
u32 m_battery_percetange{};
PsmChargerType m_charger_type{};
NifmInternetConnectionType m_type{};
NifmInternetConnectionStatus m_status{};
u32 m_strength{};
u32 m_ip{};
}; };
} // namespace sphaira::ui::menu } // namespace sphaira::ui::menu

View File

@@ -2,6 +2,8 @@
#include "ui/menus/menu_base.hpp" #include "ui/menus/menu_base.hpp"
#include "ui/scrollable_text.hpp" #include "ui/scrollable_text.hpp"
#include "ui/scrolling_text.hpp"
#include "ui/list.hpp"
#include "option.hpp" #include "option.hpp"
#include <span> #include <span>
@@ -15,28 +17,14 @@ enum class ImageDownloadState {
}; };
struct LazyImage { struct LazyImage {
LazyImage() = default;
~LazyImage(); ~LazyImage();
int image{}; int image{};
int w{}, h{}; int w{}, h{};
bool tried_cache{};
bool cached{};
ImageDownloadState state{ImageDownloadState::None}; ImageDownloadState state{ImageDownloadState::None};
u8 first_pixel[4]{};
}; };
// "mutation setLike($type: String!, $id: String!, $value: Boolean!) {\n setLike(type: $type, id: $id, value: $value)\n}\n"
// https://api.themezer.net/?query=query($nsfw:Boolean,$target:String,$page:Int,$limit:Int,$sort:String,$order:String,$query:String){themeList(nsfw:$nsfw,target:$target,page:$page,limit:$limit,sort:$sort,order:$order,query:$query){id,creator{id,display_name},details{name,description},last_updated,dl_count,like_count,target,preview{original,thumb}}}&variables={"nsfw":false,"target":null,"page":1,"limit":10,"sort":"updated","order":"desc","query":null}
// https://api.themezer.net/?query=query($nsfw:Boolean,$page:Int,$limit:Int,$sort:String,$order:String,$query:String){packList(nsfw:$nsfw,page:$page,limit:$limit,sort:$sort,order:$order,query:$query){id,creator{id,display_name},details{name,description},last_updated,dl_count,like_count,themes{id,creator{display_name},details{name,description},last_updated,dl_count,like_count,target,preview{original,thumb}}}}&variables={"nsfw":false,"page":1,"limit":10,"sort":"updated","order":"desc","query":null}
// https://api.themezer.net/?query=query($id:String!){pack(id:$id){id,creator{display_name},details{name,description},last_updated,categories,dl_count,like_count,themes{id,details{name},layout{id,details{name}},categories,target,preview{original,thumb},last_updated,dl_count,like_count}}}&variables={"id":"16d"}
// https://api.themezer.net/?query=query{nxinstaller(id:"t9a6"){themes{filename,url,mimetype}}}
// https://api.themezer.net/?query=query{downloadTheme(id:"t9a6"){filename,url,mimetype}}
// https://api.themezer.net/?query=query{downloadPack(id:"t9a6"){filename,url,mimetype}}
// {"data":{"setLike":true}}
// https://api.themezer.net/?query=mutation{setLike(type:"packs",id:"5",value:true){data{setLike}}}
// https://api.themezer.net/?query=mutation($type:String!,$id:String!,$value:Boolean!){setLike(type:$type,id:$id,value:$value){data{setLike}}}&variables={"type":"packs","id":"5","value":true}
enum MenuState { enum MenuState {
MenuState_Normal, MenuState_Normal,
MenuState_Search, MenuState_Search,
@@ -56,73 +44,49 @@ enum class PageLoadState {
}; };
struct Creator { struct Creator {
std::string id; std::string id{};
std::string display_name; std::string display_name{};
}; };
struct Details { struct Details {
std::string name; std::string name{};
std::string description;
}; };
struct Preview { struct Preview {
std::string original; std::string thumb{};
std::string thumb; LazyImage lazy_image{};
LazyImage lazy_image;
}; };
struct DownloadPack { struct DownloadPack {
std::string filename; std::string filename{};
std::string url; std::string url{};
std::string mimetype; std::string mimetype{};
}; };
using DownloadTheme = DownloadPack; using DownloadTheme = DownloadPack;
struct ThemeEntry { struct ThemeEntry {
std::string id; std::string id{};
Creator creator; Preview preview{};
Details details;
std::string last_updated;
u64 dl_count;
u64 like_count;
std::vector<std::string> categories;
std::string target;
Preview preview;
}; };
// struct Pack {
// std::string id;
// Creator creator;
// Details details;
// std::string last_updated;
// std::vector<std::string> categories;
// u64 dl_count;
// u64 like_count;
// std::vector<ThemeEntry> themes;
// };
struct PackListEntry { struct PackListEntry {
std::string id; std::string id{};
Creator creator; Creator creator{};
Details details; Details details{};
std::string last_updated; std::vector<ThemeEntry> themes{};
std::vector<std::string> categories;
u64 dl_count;
u64 like_count;
std::vector<ThemeEntry> themes;
}; };
struct Pagination { struct Pagination {
u64 page; u64 page{};
u64 limit; u64 limit{};
u64 page_count; u64 page_count{};
u64 item_count; u64 item_count{};
}; };
struct PackList { struct PackList {
std::vector<PackListEntry> packList; std::vector<PackListEntry> packList{};
Pagination pagination; Pagination pagination{};
}; };
struct Config { struct Config {
@@ -131,10 +95,10 @@ struct Config {
u32 sort_index{}; u32 sort_index{};
u32 order_index{}; u32 order_index{};
// search query, if empty, its not used // search query, if empty, its not used
std::string query; std::string query{};
// this is actually an array of creator ids, but we don't support that feature // this is actually an array of creator ids, but we don't support that feature
// if empty, its not used // if empty, its not used
std::string creator; std::string creator{};
// defaults // defaults
u32 page{1}; u32 page{1};
u32 limit{18}; u32 limit{18};
@@ -160,7 +124,7 @@ struct Config {
struct Menu; // fwd struct Menu; // fwd
struct PageEntry { struct PageEntry {
std::vector<PackListEntry> m_packList; std::vector<PackListEntry> m_packList{};
Pagination m_pagination{}; Pagination m_pagination{};
PageLoadState m_ready{PageLoadState::None}; PageLoadState m_ready{PageLoadState::None};
}; };
@@ -169,17 +133,18 @@ struct Menu final : MenuBase {
Menu(); Menu();
~Menu(); ~Menu();
auto GetShortTitle() const -> const char* override { return "Themezer"; };
void Update(Controller* controller, TouchInfo* touch) override; void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override; void OnFocusGained() override;
void SetIndex(std::size_t index) { void SetIndex(s64 index) {
m_index = index; m_index = index;
if (!m_index) {
m_list->SetYoff(0);
}
} }
// void SetSearch(const std::string& term);
// void SetAuthor();
void InvalidateAllPages(); void InvalidateAllPages();
void PackListDownload(); void PackListDownload();
void OnPackListDownload(); void OnPackListDownload();
@@ -188,14 +153,17 @@ private:
static constexpr inline const char* INI_SECTION = "themezer"; static constexpr inline const char* INI_SECTION = "themezer";
static constexpr inline u32 MAX_ON_PAGE = 16; // same as website static constexpr inline u32 MAX_ON_PAGE = 16; // same as website
std::vector<PageEntry> m_pages; std::vector<PageEntry> m_pages{};
std::size_t m_page_index{}; s64 m_page_index{};
std::size_t m_page_index_max{1}; s64 m_page_index_max{1};
std::string m_search{}; std::string m_search{};
std::size_t m_start{}; s64 m_index{}; // where i am in the array
std::size_t m_index{}; // where i am in the array std::unique_ptr<List> m_list{};
ScrollingText m_scroll_name{};
ScrollingText m_scroll_author{};
// options // options
option::OptionLong m_sort{INI_SECTION, "sort", 0}; option::OptionLong m_sort{INI_SECTION, "sort", 0};

View File

@@ -0,0 +1,45 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "yati/source/usb.hpp"
namespace sphaira::ui::menu::usb {
enum class State {
// not connected.
None,
// just connected, waiting for file list.
Connected_WaitForFileList,
// just connected, starts the transfer.
Connected_StartingTransfer,
// set whilst transfer is in progress.
Progress,
// set when the transfer is finished.
Done,
// failed to connect.
Failed,
};
struct Menu final : MenuBase {
Menu();
~Menu();
auto GetShortTitle() const -> const char* override { return "USB"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
// this should be private
// private:
std::shared_ptr<yati::source::Usb> m_usb_source{};
bool m_was_mtp_enabled{};
Thread m_thread{};
Mutex m_mutex{};
// the below are shared across threads, lock with the above mutex!
State m_state{State::None};
std::vector<std::string> m_names{};
bool m_usb_has_connection{};
};
} // namespace sphaira::ui::menu::usb

View File

@@ -19,13 +19,12 @@ public:
auto IsDone() const noexcept { return m_count == 0; } auto IsDone() const noexcept { return m_count == 0; }
private: private:
void OnLayoutChange() override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
private: private:
std::string m_text; std::string m_text{};
std::size_t m_count{180}; // count down to zero std::size_t m_count{180}; // count down to zero
Side m_side; Side m_side{};
bool m_bounds_measured{}; bool m_bounds_measured{};
}; };
@@ -34,7 +33,6 @@ public:
NotifMananger() = default; NotifMananger() = default;
~NotifMananger() = default; ~NotifMananger() = default;
void OnLayoutChange() override;
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
void Push(const NotifEntry& entry); void Push(const NotifEntry& entry);
@@ -49,8 +47,8 @@ private:
void Draw(NVGcontext* vg, Theme* theme, Entries& entries); void Draw(NVGcontext* vg, Theme* theme, Entries& entries);
private: private:
Entries m_entries_left; Entries m_entries_left{};
Entries m_entries_right; Entries m_entries_right{};
Mutex m_mutex{}; Mutex m_mutex{};
}; };

View File

@@ -1,90 +1,45 @@
#pragma once #pragma once
#include "nanovg.h" #include "nanovg.h"
#include "ui/widget.hpp" #include "ui/types.hpp"
#include "ui/scrolling_text.hpp"
namespace sphaira::ui::gfx { namespace sphaira::ui::gfx {
enum class Colour { void drawImage(NVGcontext*, float x, float y, float w, float h, int texture, float rounded = 0.F);
BLACK, void drawImage(NVGcontext*, const Vec4& v, int texture, float rounded = 0.F);
LIGHT_BLACK,
SILVER,
DARK_GREY,
GREY,
WHITE,
CYAN,
TEAL,
BLUE,
LIGHT_BLUE,
YELLOW,
RED,
};
void drawImage(NVGcontext*, float x, float y, float w, float h, int texture);
void drawImage(NVGcontext*, Vec4 v, int texture);
void drawImageRounded(NVGcontext*, float x, float y, float w, float h, int texture);
void drawImageRounded(NVGcontext*, Vec4 v, int texture);
auto getColour(Colour c) -> NVGcolor;
void dimBackground(NVGcontext*); void dimBackground(NVGcontext*);
void drawRect(NVGcontext*, float x, float y, float w, float h, Colour c, bool rounded = false); void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor& c, float rounding = 0.F);
void drawRect(NVGcontext*, Vec4 vec, Colour c, bool rounded = false); void drawRect(NVGcontext*, const Vec4& v, const NVGcolor& c, float rounding = 0.F);
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor& c, bool rounded = false); void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint& p, float rounding = 0.F);
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor&& c, bool rounded = false); void drawRect(NVGcontext*, const Vec4& v, const NVGpaint& p, float rounding = 0.F);
void drawRect(NVGcontext*, Vec4 vec, const NVGcolor& c, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, const NVGcolor&& c, bool rounded = false);
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint& p, bool rounded = false);
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint&& p, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, const NVGpaint& p, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, const NVGpaint&& p, bool rounded = false);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, Colour c); void drawRectOutline(NVGcontext*, const Theme*, float size, float x, float y, float w, float h);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, Colour c); void drawRectOutline(NVGcontext*, const Theme*, float size, const Vec4& v);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGcolor& c);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGcolor&& c);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGcolor& c);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGcolor&& c);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGpaint& p);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGpaint&& p);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint& p);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint&& p);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, Colour c);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c); void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor&& c);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p); void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint&& p);
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, Colour c);
void drawText(NVGcontext*, float x, float y, float size, Colour c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, Vec2 vec, float size, const char* str, const char* end, int align, Colour c);
void drawText(NVGcontext*, Vec2 vec, float size, Colour c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c); void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c);
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor&& c);
void drawText(NVGcontext*, float x, float y, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr); void drawText(NVGcontext*, float x, float y, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, float x, float y, float size, const NVGcolor&& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr); void drawText(NVGcontext*, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c);
void drawText(NVGcontext*, Vec2 vec, float size, const char* str, const char* end, int align, const NVGcolor& c); void drawText(NVGcontext*, const Vec2& v, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, Vec2 vec, float size, const char* str, const char* end, int align, const NVGcolor&& c);
void drawText(NVGcontext*, Vec2 vec, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, Vec2 vec, float size, const NVGcolor&& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawTextArgs(NVGcontext*, float x, float y, float size, int align, Colour c, const char* str, ...) __attribute__ ((format (printf, 7, 8)));
void drawTextArgs(NVGcontext*, float x, float y, float size, int align, const NVGcolor& c, const char* str, ...) __attribute__ ((format (printf, 7, 8))); void drawTextArgs(NVGcontext*, float x, float y, float size, int align, const NVGcolor& c, const char* str, ...) __attribute__ ((format (printf, 7, 8)));
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr); 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 drawTextBox(NVGcontext*, float x, float y, float size, float bound, NVGcolor&& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, Colour 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, ...) __attribute__ ((format (printf, 5, 6)));
// void textBounds(NVGcontext*, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6)));
// void textBounds(NVGcontext*, float *bounds, const char* str);
auto getButton(Button button) -> const char*; auto getButton(Button button) -> const char*;
void drawButton(NVGcontext* vg, float x, float y, float size, Button button); void drawScrollbar(NVGcontext*, const Theme*, u32 index_off, u32 count, u32 max_per_page);
void drawButtons(NVGcontext* vg, const Widget::Actions& actions, const NVGcolor& c, float start_x = 1220.f); void drawScrollbar(NVGcontext*, const Theme*, float x, float y, float h, u32 index_off, u32 count, u32 max_per_page);
void drawDimBackground(NVGcontext* vg); void drawScrollbar2(NVGcontext*, const Theme*, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page);
void drawScrollbar2(NVGcontext*, const Theme*, s64 index_off, s64 count, s64 row, s64 page);
void drawAppLable(NVGcontext* vg, const Theme*, ScrollingText& st, float x, float y, float w, const char* name);
void updateHighlightAnimation(); void updateHighlightAnimation();
void getHighlightAnimation(float* gradientX, float* gradientY, float* color); void getHighlightAnimation(float* gradientX, float* gradientY, float* color);

View File

@@ -1,16 +1,17 @@
#pragma once #pragma once
#include "types.hpp" #include "types.hpp"
#include <stop_token>
namespace sphaira::ui { namespace sphaira::ui {
class Object { class Object {
public: public:
Object() = default; Object() = default;
virtual ~Object() = default; virtual ~Object() {
m_stop_source.request_stop();
}
// virtual auto OnLayoutChange() -> void = 0;
virtual auto OnLayoutChange() -> void {};
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void = 0; virtual auto Draw(NVGcontext* vg, Theme* theme) -> void = 0;
auto GetPos() const noexcept { auto GetPos() const noexcept {
@@ -73,8 +74,14 @@ public:
m_hidden = value; m_hidden = value;
} }
auto GetToken() const {
return m_stop_source.get_token();
}
protected: protected:
Vec4 m_pos{}; Vec4 m_pos{};
// used for lifetime management across threads.
std::stop_source m_stop_source{};
bool m_hidden{false}; bool m_hidden{false};
}; };

View File

@@ -12,14 +12,13 @@ public:
OptionBoxEntry(const std::string& text, Vec4 pos); OptionBoxEntry(const std::string& text, Vec4 pos);
auto Update(Controller* controller, TouchInfo* touch) -> void override {} auto Update(Controller* controller, TouchInfo* touch) -> void override {}
auto OnLayoutChange() -> void override {}
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto Selected(bool enable) -> void; auto Selected(bool enable) -> void;
private: private:
private: private:
std::string m_text; std::string m_text{};
Vec2 m_text_pos{}; Vec2 m_text_pos{};
bool m_selected{false}; bool m_selected{false};
}; };
@@ -28,32 +27,33 @@ private:
// todo: support upto 4 options. // todo: support upto 4 options.
class OptionBox final : public Widget { class OptionBox final : public Widget {
public: public:
using Callback = std::function<void(std::optional<std::size_t> index)>; using Callback = std::function<void(std::optional<s64> index)>;
using Option = std::string; using Option = std::string;
using Options = std::vector<Option>; using Options = std::vector<Option>;
public: public:
OptionBox(const std::string& message, const Option& a, Callback cb); // confirm 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); // yesno 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, std::size_t index, Callback cb); // 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 Option& b, const Option& c, Callback cb); // tri
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, std::size_t index, Callback cb); // tri
auto Update(Controller* controller, TouchInfo* touch) -> void override; auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
private: private:
auto Setup(std::size_t index) -> void; // common setup values auto Setup(s64 index) -> void; // common setup values
void SetIndex(s64 index);
private: private:
std::string m_message; std::string m_message{};
Callback m_callback; Callback m_callback{};
int m_image{};
Vec4 m_spacer_line{}; Vec4 m_spacer_line{};
std::size_t m_index{}; s64 m_index{};
std::vector<OptionBoxEntry> m_entries; std::vector<OptionBoxEntry> m_entries{};
}; };
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -1,27 +0,0 @@
#pragma once
#include "ui/widget.hpp"
#include <optional>
namespace sphaira::ui {
class OptionList final : public Widget {
public:
using Options = std::vector<std::pair<std::string, std::function<void()>>>;
public:
OptionList(Options _options);
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
protected:
Options m_options;
std::size_t m_index{};
private:
};
} // namespace sphaira::ui

View File

@@ -1,7 +1,8 @@
#pragma once #pragma once
#include "ui/widget.hpp" #include "ui/widget.hpp"
#include "ui/scrollbar.hpp" #include "ui/scrolling_text.hpp"
#include "ui/list.hpp"
#include <optional> #include <optional>
namespace sphaira::ui { namespace sphaira::ui {
@@ -9,18 +10,22 @@ namespace sphaira::ui {
class PopupList final : public Widget { class PopupList final : public Widget {
public: public:
using Items = std::vector<std::string>; using Items = std::vector<std::string>;
using Callback = std::function<void(std::optional<std::size_t>)>; using Callback = std::function<void(std::optional<s64>)>;
public: public:
explicit PopupList(std::string title, Items items, Callback cb, std::size_t index = 0); 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, Callback cb, std::string index);
PopupList(std::string title, Items items, std::string& index_str_ref, std::size_t& 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, std::string& index_ref);
PopupList(std::string title, Items items, std::size_t& index_ref); PopupList(std::string title, Items items, s64& index_ref);
auto Update(Controller* controller, TouchInfo* touch) -> void override; auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
private:
void SetIndex(s64 index);
private: private:
static constexpr Vec2 m_title_pos{70.f, 28.f}; static constexpr Vec2 m_title_pos{70.f, 28.f};
@@ -28,20 +33,18 @@ private:
static constexpr float m_text_xoffset{15.f}; static constexpr float m_text_xoffset{15.f};
static constexpr float m_line_width{1220.f}; static constexpr float m_line_width{1220.f};
std::string m_title; std::string m_title{};
Items m_items; Items m_items{};
Callback m_callback; Callback m_callback{};
std::size_t m_index; // index in list array s64 m_index{}; // index in list array
std::size_t m_index_offset{}; // drawing from array start s64 m_starting_index{};
// std::size_t& index_ref; std::unique_ptr<List> m_list{};
// std::string& index_str_ref; ScrollingText m_scroll_text{};
float m_selected_y{};
float m_yoff{}; float m_yoff{};
float m_line_top{}; float m_line_top{};
float m_line_bottom{}; float m_line_bottom{};
ScrollBar m_scrollbar;
}; };
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -3,6 +3,7 @@
#include "widget.hpp" #include "widget.hpp"
#include "fs.hpp" #include "fs.hpp"
#include <functional> #include <functional>
#include <span>
namespace sphaira::ui { namespace sphaira::ui {
@@ -12,17 +13,23 @@ using ProgressBoxDoneCallback = std::function<void(bool success)>;
struct ProgressBox final : Widget { struct ProgressBox final : Widget {
ProgressBox( ProgressBox(
int image,
const std::string& action,
const std::string& title, const std::string& title,
ProgressBoxCallback callback, ProgressBoxDoneCallback done = [](bool success){}, ProgressBoxCallback callback, ProgressBoxDoneCallback done = [](bool success){},
int cpuid = 1, int prio = 0x2C, int stack_size = 1024*1024 int cpuid = 1, int prio = 0x2C, int stack_size = 1024*128
); );
~ProgressBox(); ~ProgressBox();
auto Update(Controller* controller, TouchInfo* touch) -> void override; auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto SetTitle(const std::string& title) -> ProgressBox&;
auto NewTransfer(const std::string& transfer) -> ProgressBox&; auto NewTransfer(const std::string& transfer) -> ProgressBox&;
auto UpdateTransfer(u64 offset, u64 size) -> ProgressBox&; auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;
// not const in order to avoid copy by using std::swap
auto SetImageData(std::vector<u8>& data) -> ProgressBox&;
auto SetImageDataConst(std::span<const u8> data) -> ProgressBox&;
void RequestExit(); void RequestExit();
auto ShouldExit() -> bool; auto ShouldExit() -> bool;
@@ -30,24 +37,52 @@ struct ProgressBox final : Widget {
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result; auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result;
void Yield(); void Yield();
auto OnDownloadProgressCallback() {
return [this](s64 dltotal, s64 dlnow, s64 ultotal, s64 ulnow){
if (this->ShouldExit()) {
return false;
}
if (dltotal) {
this->UpdateTransfer(dlnow, dltotal);
} else {
this->UpdateTransfer(ulnow, ultotal);
}
return true;
};
}
private:
void FreeImage();
public: public:
struct ThreadData { struct ThreadData {
ProgressBox* pbox; ProgressBox* pbox{};
ProgressBoxCallback callback; ProgressBoxCallback callback{};
bool result; bool result{};
}; };
private: private:
Mutex m_mutex{}; Mutex m_mutex{};
Thread m_thread{}; Thread m_thread{};
ThreadData m_thread_data{}; ThreadData m_thread_data{};
ProgressBoxDoneCallback m_done{}; ProgressBoxDoneCallback m_done{};
// shared data start.
std::string m_action{};
std::string m_title{}; std::string m_title{};
std::string m_transfer{}; std::string m_transfer{};
u64 m_size{}; s64 m_size{};
u64 m_offset{}; s64 m_offset{};
bool m_exit_requested{}; s64 m_last_offset{};
s64 m_speed{};
TimeStamp m_timestamp{};
std::vector<u8> m_image_data{};
// shared data end.
int m_image{};
bool m_own_image{};
}; };
// this is a helper function that does many things. // this is a helper function that does many things.

View File

@@ -14,15 +14,15 @@ struct ScrollableText final : Widget {
// float m_y_off = m_y_off_base; // float m_y_off = m_y_off_base;
// static constexpr float m_clip_y = 250.0F; // static constexpr float m_clip_y = 250.0F;
static constexpr inline float m_step = 30;
const float m_font_size;
const float m_y_off_base; const float m_y_off_base;
float m_y_off;
const float m_clip_y; const float m_clip_y;
const float m_end_w; const float m_end_w;
static constexpr float m_step = 30;
int m_index = 0; float m_y_off{};
const float m_font_size; int m_index{};
float m_bounds[4]; float m_bounds[4]{};
}; };
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -1,34 +0,0 @@
#pragma once
#include "ui/widget.hpp"
namespace sphaira::ui {
class ScrollBar final : public Widget {
public:
enum class Direction { DOWN, UP };
public:
ScrollBar() = default;
ScrollBar(Vec4 bounds, float entry_height, std::size_t entries);
auto Update(Controller* controller, TouchInfo* touch) -> void override {}
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto Setup(Vec4 bounds, float entry_height, std::size_t entries) -> void;
auto Move(Direction direction) -> void;
private:
auto Setup() -> void;
private:
Vec4 m_bounds{};
std::size_t m_entries{};
std::size_t m_index{};
float m_entry_height{};
float m_step_size{};
bool m_should_draw{false};
};
} // namespace sphaira::ui

View File

@@ -0,0 +1,20 @@
#pragma once
#include "ui/widget.hpp"
#include <string>
namespace sphaira::ui {
struct ScrollingText final {
public:
void Draw(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const std::string& text_entry);
void DrawArgs(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const char* s, ...) __attribute__ ((format (printf, 10, 11)));
void Reset(const std::string& text_entry = "");
private:
std::string m_str;
s64 m_tick;
float m_text_xoff;
};
} // namespace sphaira::ui

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "ui/widget.hpp" #include "ui/widget.hpp"
#include "ui/list.hpp"
#include <memory> #include <memory>
namespace sphaira::ui { namespace sphaira::ui {
@@ -9,11 +10,9 @@ class SidebarEntryBase : public Widget {
public: public:
SidebarEntryBase(std::string&& title); SidebarEntryBase(std::string&& title);
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void override; virtual auto Draw(NVGcontext* vg, Theme* theme) -> void override;
virtual auto OnLayoutChange() -> void override {}
protected: protected:
std::string m_title; std::string m_title;
Vec2 m_offset{};
}; };
class SidebarEntryBool final : public SidebarEntryBase { class SidebarEntryBool final : public SidebarEntryBase {
@@ -24,9 +23,9 @@ 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, 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"); SidebarEntryBool(std::string title, bool& option, std::string true_str = "On", std::string false_str = "Off");
private:
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
private:
bool m_option; bool m_option;
Callback m_callback; Callback m_callback;
std::string m_true_str; std::string m_true_str;
@@ -50,20 +49,24 @@ class SidebarEntryArray final : public SidebarEntryBase {
public: public:
using Items = std::vector<std::string>; using Items = std::vector<std::string>;
using ListCallback = std::function<void()>; using ListCallback = std::function<void()>;
using Callback = std::function<void(std::size_t& index)>; using Callback = std::function<void(s64& index)>;
public: public:
explicit SidebarEntryArray(std::string title, Items items, Callback cb, std::size_t index = 0); 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, Callback cb, std::string index);
SidebarEntryArray(std::string title, Items items, std::string& index); SidebarEntryArray(std::string title, Items items, std::string& index);
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
private: private:
Items m_items; Items m_items;
ListCallback m_list_callback; ListCallback m_list_callback;
Callback m_callback; Callback m_callback;
std::size_t m_index; s64 m_index;
s64 m_tick{};
float m_text_yoff{};
}; };
template <typename T> template <typename T>
@@ -101,33 +104,30 @@ public:
Sidebar(std::string title, std::string sub, Side side); Sidebar(std::string title, std::string sub, Side side);
auto Update(Controller* controller, TouchInfo* touch) -> void override; auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override {}
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override; auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override; auto OnFocusLost() noexcept -> void override;
void Add(std::shared_ptr<SidebarEntryBase> entry); void Add(std::shared_ptr<SidebarEntryBase> entry);
void AddSpacer();
void AddHeader(std::string name);
private: private:
void SetIndex(std::size_t index); void SetIndex(s64 index);
void SetupButtons();
private: private:
std::string m_title; std::string m_title;
std::string m_sub; std::string m_sub;
Side m_side; Side m_side;
Items m_items; Items m_items;
std::size_t m_index{}; s64 m_index{};
std::size_t m_index_offset{};
std::unique_ptr<List> m_list;
Vec4 m_top_bar{}; Vec4 m_top_bar{};
Vec4 m_bottom_bar{}; Vec4 m_bottom_bar{};
Vec2 m_title_pos{}; Vec2 m_title_pos{};
Vec4 m_base_pos{}; Vec4 m_base_pos{};
float m_selected_y{};
static constexpr float m_title_size{28.f}; static constexpr float m_title_size{28.f};
// static constexpr Vec2 box_size{380.f, 70.f}; // static constexpr Vec2 box_size{380.f, 70.f};
static constexpr Vec2 m_box_size{400.f, 70.f}; static constexpr Vec2 m_box_size{400.f, 70.f};

View File

@@ -14,7 +14,7 @@ namespace sphaira {
#define SCREEN_WIDTH 1280.f #define SCREEN_WIDTH 1280.f
#define SCREEN_HEIGHT 720.f #define SCREEN_HEIGHT 720.f
struct [[nodiscard]] Vec2 { struct Vec2 {
constexpr Vec2() = default; constexpr Vec2() = default;
constexpr Vec2(float _x, float _y) : x{_x}, y{_y} {} constexpr Vec2(float _x, float _y) : x{_x}, y{_y} {}
@@ -53,7 +53,7 @@ struct [[nodiscard]] Vec2 {
float x{}, y{}; float x{}, y{};
}; };
struct [[nodiscard]] Vec4 { struct Vec4 {
constexpr Vec4() = default; constexpr Vec4() = default;
constexpr Vec4(float _x, float _y, float _w, float _h) : x{_x}, y{_y}, w{_w}, h{_h} {} 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(Vec2 vec0, Vec2 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.x}, h{vec1.y} {}
@@ -114,15 +114,34 @@ struct [[nodiscard]] Vec4 {
struct TimeStamp { struct TimeStamp {
TimeStamp() { TimeStamp() {
Update();
}
void Update() {
start = armGetSystemTick(); start = armGetSystemTick();
} }
auto GetNs() -> u64 { auto GetNs() const -> u64 {
const auto end_ticks = armGetSystemTick(); const auto end_ticks = armGetSystemTick();
return armTicksToNs(end_ticks) - armTicksToNs(start); return armTicksToNs(end_ticks) - armTicksToNs(start);
} }
auto GetSeconds() -> double { auto GetMs() const -> u64 {
const auto ns = GetNs();
return ns/1000/1000;
}
auto GetSeconds() const -> u64 {
const auto ns = GetNs();
return ns/1000/1000/1000;
}
auto GetMsD() const -> double {
const double ns = GetNs();
return ns/1000.0/1000.0;
}
auto GetSecondsD() const -> double {
const double ns = GetNs(); const double ns = GetNs();
return ns/1000.0/1000.0/1000.0; return ns/1000.0/1000.0/1000.0;
} }
@@ -143,21 +162,55 @@ struct ElementEntry {
}; };
enum ThemeEntryID { enum ThemeEntryID {
// colour of the background, can be an image.
ThemeEntryID_BACKGROUND, ThemeEntryID_BACKGROUND,
// colour of the grid background (homebrew, appstore), can be an image.
ThemeEntryID_GRID, ThemeEntryID_GRID,
ThemeEntryID_SELECTED, // background colour of a popup.
ThemeEntryID_SELECTED_OVERLAY, ThemeEntryID_POPUP,
ThemeEntryID_TEXT, // colour of the error text / button.
ThemeEntryID_TEXT_SELECTED, ThemeEntryID_ERROR,
// colour of all text.
ThemeEntryID_TEXT,
// colour of text info and subheaders.
ThemeEntryID_TEXT_INFO,
// colour of selected item text.
ThemeEntryID_TEXT_SELECTED,
// background colour of a selected item, can be an image (not recommended).
ThemeEntryID_SELECTED_BACKGROUND,
// colour of line separators in a list.
ThemeEntryID_LINE,
ThemeEntryID_LINE_SEPARATOR,
// colour of the sidebar backrgound.
ThemeEntryID_SIDEBAR,
// colour of the scrollbar (full portion).
ThemeEntryID_SCROLLBAR,
// colour of the scrollbar background (empty portion).
ThemeEntryID_SCROLLBAR_BACKGROUND,
// colour of the progressbar (full portion).
ThemeEntryID_PROGRESSBAR,
// colour of the progressbar background (empty portion).
ThemeEntryID_PROGRESSBAR_BACKGROUND,
// the colours of the pulsing effect, from 1 -> 2.
ThemeEntryID_HIGHLIGHT_1,
ThemeEntryID_HIGHLIGHT_2,
// changes the colours of the internal icons used below.
ThemeEntryID_ICON_COLOUR,
// images used in the filebrowser.
ThemeEntryID_ICON_AUDIO, ThemeEntryID_ICON_AUDIO,
ThemeEntryID_ICON_VIDEO, ThemeEntryID_ICON_VIDEO,
ThemeEntryID_ICON_IMAGE, ThemeEntryID_ICON_IMAGE,
ThemeEntryID_ICON_FILE, ThemeEntryID_ICON_FILE,
ThemeEntryID_ICON_FOLDER, ThemeEntryID_ICON_FOLDER,
ThemeEntryID_ICON_ZIP, ThemeEntryID_ICON_ZIP,
ThemeEntryID_ICON_GAME,
ThemeEntryID_ICON_NRO, ThemeEntryID_ICON_NRO,
ThemeEntryID_MAX, ThemeEntryID_MAX,
@@ -167,49 +220,42 @@ struct ThemeMeta {
std::string name; std::string name;
std::string author; std::string author;
std::string version; std::string version;
std::string ini_path; fs::FsPath inherit;
fs::FsPath ini_path;
}; };
struct Theme { struct Theme {
std::string name; ThemeMeta meta;
std::string author;
std::string version;
fs::FsPath path;
PLSR_BFSTM music;
ElementEntry elements[ThemeEntryID_MAX]; ElementEntry elements[ThemeEntryID_MAX];
// NVGcolor background; // bg auto GetColour(ThemeEntryID id) const {
// NVGcolor lines; // grid lines return elements[id].colour;
// NVGcolor spacer; // lines in popup box }
// NVGcolor text; // text colour
// NVGcolor text_info; // description text
NVGcolor selected; // selected colours
// NVGcolor overlay; // popup overlay colour
// void DrawElement(float x, float y, float w, float h, ThemeEntryID id);
}; };
enum class TouchState { // enum class TouchGesture {
Start, // set when touch has started // None,
Touching, // set when touch is held longer than 1 frame // Tap,
Stop, // set after touch is released // Scroll,
None, // set when there is no touch // };
};
struct TouchInfo { struct TouchInfo {
s32 initial_x; HidTouchState initial;
s32 initial_y; HidTouchState cur;
s32 cur_x; auto in_range(const Vec4& v) const -> bool {
s32 cur_y; return cur.x >= v.x && cur.x <= v.x + v.w && cur.y >= v.y && cur.y <= v.y + v.h;
}
s32 prev_x; auto in_range(s32 x, s32 y, s32 w, s32 h) const -> bool {
s32 prev_y; return in_range(Vec4(x, y, w, h));
}
u32 finger_id;
bool is_touching; bool is_touching;
bool is_tap; bool is_tap;
bool is_scroll;
bool is_clicked;
bool is_end;
}; };
enum class Button : u64 { enum class Button : u64 {
@@ -276,37 +322,36 @@ inline ActionType operator|(ActionType a, ActionType b) {
} }
struct Action final { struct Action final {
using CallbackEmpty = std::function<void()>;
using CallbackWithBool = std::function<void(bool)>;
using Callback = std::variant< using Callback = std::variant<
std::function<void()>, CallbackEmpty,
std::function<void(bool)> CallbackWithBool
>; >;
Action(Callback cb) : m_type{ActionType::DOWN}, m_hint{""}, m_callback{cb}, m_hidden{true} {} Action(Callback cb) : Action{ActionType::DOWN, "", cb} {}
Action(std::string hint, Callback cb) : m_type{ActionType::DOWN}, m_hint{hint}, m_callback{cb} {} Action(std::string hint, Callback cb) : Action{ActionType::DOWN, hint, cb} {}
Action(u8 type, Callback cb) : m_type{type}, m_hint{""}, m_callback{cb}, m_hidden{true} {} Action(u8 type, Callback cb) : Action{type, "", cb} {}
Action(u8 type, std::string hint, Callback cb) : m_type{type}, m_hint{hint}, m_callback{cb} {} Action(u8 type, std::string hint, Callback cb) : m_type{type}, m_callback{cb}, m_hint{hint} {}
auto IsHidden() const noexcept { return m_hidden; } auto IsHidden() const noexcept { return m_hint.empty(); }
auto Invoke(bool down) const { auto Invoke(bool down) const {
// todo: make this a visit std::visit([down](auto&& arg){
switch (m_callback.index()) { using T = std::decay_t<decltype(arg)>;
case 0: if constexpr(std::is_same_v<T, CallbackEmpty>) {
std::get<0>(m_callback)(); arg();
break; } else if constexpr(std::is_same_v<T, CallbackWithBool>) {
case 1: arg(down);
std::get<1>(m_callback)(down); } else {
break; static_assert(false, "non-exhaustive visitor!");
} }
// std::visit([down, this](auto& cb){ }, m_callback);
// cb(down);
// }), m_callback;
} }
u8 m_type; u8 m_type{};
std::string m_hint; // todo: make optional Callback m_callback{};
Callback m_callback; std::string m_hint{};
bool m_hidden{false}; // replace this optional text
}; };
struct Controller { struct Controller {
@@ -340,7 +385,7 @@ struct Controller {
m_kup = 0; m_kup = 0;
} }
void UpdateButtonHeld(HidNpadButton buttons) { void UpdateButtonHeld(u64 buttons) {
if (m_kdown & buttons) { if (m_kdown & buttons) {
m_step = 50; m_step = 50;
m_counter = 0; m_counter = 0;
@@ -348,7 +393,7 @@ struct Controller {
m_counter += m_step; m_counter += m_step;
if (m_counter >= m_MAX) { if (m_counter >= m_MAX) {
m_kdown |= buttons; m_kdown |= m_kheld & buttons;
m_counter = 0; m_counter = 0;
m_step = std::min(m_step + 50, m_MAX_STEP); m_step = std::min(m_step + 50, m_MAX_STEP);
} }

View File

@@ -8,7 +8,20 @@
namespace sphaira::ui { namespace sphaira::ui {
struct uiButton final : Object {
uiButton(Button button, Action action) : m_button{button}, m_action{action} {}
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
Button m_button;
Action m_action;
Vec4 m_button_pos{};
Vec4 m_hint_pos{};
};
struct Widget : public Object { struct Widget : public Object {
using Actions = std::map<Button, Action>;
using uiButtons = std::vector<uiButton>;
virtual ~Widget() = default; virtual ~Widget() = default;
virtual void Update(Controller* controller, TouchInfo* touch); virtual void Update(Controller* controller, TouchInfo* touch);
@@ -26,6 +39,10 @@ struct Widget : public Object {
return m_focus; return m_focus;
} }
virtual auto IsMenu() const -> bool {
return false;
}
auto HasAction(Button button) const -> bool; auto HasAction(Button button) const -> bool;
void SetAction(Button button, Action action); void SetAction(Button button, Action action);
void SetActions(std::same_as<std::pair<Button, Action>> auto ...args) { void SetActions(std::same_as<std::pair<Button, Action>> auto ...args) {
@@ -45,6 +62,8 @@ struct Widget : public Object {
m_actions.clear(); m_actions.clear();
} }
auto FireAction(Button button, u8 type = ActionType::DOWN) -> bool;
void SetPop(bool pop = true) { void SetPop(bool pop = true) {
m_pop = pop; m_pop = pop;
} }
@@ -53,9 +72,14 @@ struct Widget : public Object {
return m_pop; return m_pop;
} }
using Actions = std::map<Button, Action>; auto SetUiButtonPos(Vec2 pos) {
// using Actions = std::unordered_map<Button, Action>; m_button_pos = pos;
Actions m_actions; }
auto GetUiButtons() const -> uiButtons;
Actions m_actions{};
Vec2 m_button_pos{1220, 675};
bool m_focus{false}; bool m_focus{false};
bool m_pop{false}; bool m_pop{false};
}; };

View File

@@ -0,0 +1,101 @@
#pragma once
#include <vector>
#include <string>
#include <new>
#include <switch.h>
namespace sphaira::usb {
struct Base {
enum { USBModule = 523 };
enum : Result {
Result_Cancelled = MAKERESULT(USBModule, 100),
};
Base(u64 transfer_timeout);
// sets up usb.
virtual Result Init() = 0;
// returns 0 if usb is connected to a device.
virtual Result IsUsbConnected(u64 timeout) = 0;
// transfers a chunk of data, check out_size_transferred for how much was transferred.
Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout);
Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred) {
return TransferPacketImpl(read, page, size, out_size_transferred, m_transfer_timeout);
}
// transfers all data.
Result TransferAll(bool read, void *data, u32 size, u64 timeout);
Result TransferAll(bool read, void *data, u32 size) {
return TransferAll(read, data, size, m_transfer_timeout);
}
// returns the cancel event.
auto GetCancelEvent() {
return &m_uevent;
}
// cancels an in progress transfer.
void Cancel() {
ueventSignal(GetCancelEvent());
}
auto& GetTransferBuffer() {
return m_aligned;
}
auto GetTransferTimeout() const {
return m_transfer_timeout;
}
public:
// custom allocator for std::vector that respects alignment.
// https://en.cppreference.com/w/cpp/named_req/Allocator
template <typename T, std::size_t Align>
struct CustomVectorAllocator {
public:
// https://en.cppreference.com/w/cpp/memory/new/operator_new
auto allocate(std::size_t n) -> T* {
n = (n + (Align - 1)) &~ (Align - 1);
return new(align) T[n];
}
// https://en.cppreference.com/w/cpp/memory/new/operator_delete
auto deallocate(T* p, std::size_t n) noexcept -> void {
// ::operator delete[] (p, n, align);
::operator delete[] (p, align);
}
private:
static constexpr inline std::align_val_t align{Align};
};
template <typename T>
struct PageAllocator : CustomVectorAllocator<T, 0x1000> {
using value_type = T; // used by std::vector
};
using PageAlignedVector = std::vector<u8, PageAllocator<u8>>;
protected:
enum UsbSessionEndpoint {
UsbSessionEndpoint_In = 0,
UsbSessionEndpoint_Out = 1,
};
virtual Event *GetCompletionEvent(UsbSessionEndpoint ep) = 0;
virtual Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) = 0;
virtual Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_xfer_id) = 0;
virtual Result GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) = 0;
private:
u64 m_transfer_timeout{};
UEvent m_uevent{};
PageAlignedVector m_aligned{};
};
} // namespace sphaira::usb

View File

@@ -0,0 +1,47 @@
#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
};
struct TUSHeader {
u32 magic; // TUL0 (Tinfoil Usb List 0)
u32 nspListSize;
u64 padding;
};
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

View File

@@ -0,0 +1,47 @@
#pragma once
#include "usb/usbhs.hpp"
#include <string>
#include <memory>
#include <span>
#include <switch.h>
namespace sphaira::usb::upload {
struct Usb {
enum { USBModule = 523 };
enum : Result {
Result_BadMagic = MAKERESULT(USBModule, 0),
Result_Exit = MAKERESULT(USBModule, 1),
Result_BadCount = MAKERESULT(USBModule, 2),
Result_BadTransferSize = MAKERESULT(USBModule, 3),
Result_BadTotalSize = MAKERESULT(USBModule, 4),
Result_BadCommand = MAKERESULT(USBModule, 4),
};
Usb(u64 transfer_timeout);
virtual ~Usb();
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
Result IsUsbConnected(u64 timeout) {
return m_usb->IsUsbConnected(timeout);
}
// waits for connection and then sends file list.
Result WaitForConnection(u64 timeout, std::span<const std::string> names);
// polls for command, executes transfer if possible.
// will return Result_Exit if exit command is recieved.
Result PollCommands();
private:
Result FileRangeCmd(u64 data_size);
private:
std::unique_ptr<usb::UsbHs> m_usb;
};
} // namespace sphaira::usb::upload

View File

@@ -0,0 +1,27 @@
#pragma once
#include "base.hpp"
namespace sphaira::usb {
// Device Host
struct UsbDs final : Base {
using Base::Base;
~UsbDs();
Result Init() override;
Result IsUsbConnected(u64 timeout) override;
Result GetSpeed(UsbDeviceSpeed* out);
private:
Event *GetCompletionEvent(UsbSessionEndpoint ep) override;
Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) override;
Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) override;
Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) override;
private:
UsbDsInterface* m_interface{};
UsbDsEndpoint* m_endpoints[2]{};
};
} // namespace sphaira::usb

View File

@@ -0,0 +1,33 @@
#pragma once
#include "base.hpp"
namespace sphaira::usb {
struct UsbHs final : Base {
UsbHs(u8 index, const UsbHsInterfaceFilter& filter, u64 transfer_timeout);
~UsbHs();
Result Init() override;
Result IsUsbConnected(u64 timeout) override;
private:
Event *GetCompletionEvent(UsbSessionEndpoint ep) override;
Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) override;
Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_xfer_id) override;
Result GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) override;
Result Connect();
void Close();
private:
u8 m_index{};
UsbHsInterfaceFilter m_filter{};
UsbHsInterface m_interface{};
UsbHsClientIfSession m_s{};
UsbHsClientEpSession m_endpoints[2]{};
Event m_event{};
bool m_connected{};
};
} // namespace sphaira::usb

View File

@@ -1,13 +0,0 @@
#pragma once
#include <switch.h>
#include <string>
namespace sphaira {
// if show_error = true, it will display popup error box on
// faliure. set this to false if you want to handle errors
// from the caller.
auto WebShow(const std::string& url, bool show_error = true) -> Result;
} // namespace sphaira

View File

@@ -0,0 +1,43 @@
#pragma once
#include "yati/source/base.hpp"
#include <vector>
#include <string>
#include <memory>
#include <switch.h>
namespace sphaira::yati::container {
enum class CollectionType {
CollectionType_NCA,
CollectionType_NCZ,
CollectionType_TIK,
CollectionType_CERT,
};
struct CollectionEntry {
// collection name within file.
std::string name{};
// collection offset within file.
s64 offset{};
// collection size within file, may be compressed size.
s64 size{};
};
using Collections = std::vector<CollectionEntry>;
struct Base {
using Source = source::Base;
Base(std::shared_ptr<Source> source) : m_source{source} { }
virtual ~Base() = default;
virtual Result GetCollections(Collections& out) = 0;
auto GetSource() const {
return m_source;
}
protected:
std::shared_ptr<Source> m_source;
};
} // namespace sphaira::yati::container

View File

@@ -0,0 +1,17 @@
#pragma once
#include "base.hpp"
#include <switch.h>
#include <span>
namespace sphaira::yati::container {
struct Nsp final : Base {
using Base::Base;
Result GetCollections(Collections& out) override;
// builds nsp meta data and the size of the entier nsp.
static auto Build(std::span<CollectionEntry> collections, s64& size) -> std::vector<u8>;
};
} // namespace sphaira::yati::container

View File

@@ -0,0 +1,15 @@
#pragma once
#include "base.hpp"
#include <vector>
#include <memory>
#include <switch.h>
namespace sphaira::yati::container {
struct Xci final : Base {
using Base::Base;
Result GetCollections(Collections& out) override;
};
} // namespace sphaira::yati::container

View File

@@ -0,0 +1,57 @@
#pragma once
#include <switch.h>
namespace sphaira::crypto {
struct Aes128 {
Aes128(const void *key, bool is_encryptor) {
m_is_encryptor = is_encryptor;
aes128ContextCreate(&m_ctx, key, is_encryptor);
}
void Run(void *dst, const void *src) {
if (m_is_encryptor) {
aes128EncryptBlock(&m_ctx, dst, src);
} else {
aes128DecryptBlock(&m_ctx, dst, src);
}
}
private:
Aes128Context m_ctx;
bool m_is_encryptor;
};
struct Aes128Xts {
Aes128Xts(const u8 *key, bool is_encryptor) : Aes128Xts{key, key + 0x10, is_encryptor} { }
Aes128Xts(const void *key0, const void *key1, bool is_encryptor) {
m_is_encryptor = is_encryptor;
aes128XtsContextCreate(&m_ctx, key0, key1, is_encryptor);
}
void Run(void *dst, const void *src, u64 sector, u64 sector_size, u64 data_size) {
for (u64 pos = 0; pos < data_size; pos += sector_size) {
aes128XtsContextResetSector(&m_ctx, sector++, true);
if (m_is_encryptor) {
aes128XtsEncrypt(&m_ctx, static_cast<u8*>(dst) + pos, static_cast<const u8*>(src) + pos, sector_size);
} else {
aes128XtsDecrypt(&m_ctx, static_cast<u8*>(dst) + pos, static_cast<const u8*>(src) + pos, sector_size);
}
}
}
private:
Aes128XtsContext m_ctx;
bool m_is_encryptor;
};
static inline void cryptoAes128(const void *in, void *out, const void* key, bool is_encryptor) {
Aes128(key, is_encryptor).Run(out, in);
}
static inline void cryptoAes128Xts(const void* in, void* out, const u8* key, u64 sector, u64 sector_size, u64 data_size, bool is_encryptor) {
Aes128Xts(key, is_encryptor).Run(out, in, sector, sector_size, data_size);
}
} // namespace sphaira::crypto

View File

@@ -0,0 +1,101 @@
#pragma once
#include <switch.h>
#include <span>
#include "ncm.hpp"
#include "keys.hpp"
namespace sphaira::es {
enum { TicketModule = 522 };
enum : Result {
// found ticket has missmatching rights_id from it's name.
Result_InvalidTicketBadRightsId = MAKERESULT(TicketModule, 71),
Result_InvalidTicketVersion = MAKERESULT(TicketModule, 72),
Result_InvalidTicketKeyType = MAKERESULT(TicketModule, 73),
Result_InvalidTicketKeyRevision = MAKERESULT(TicketModule, 74),
};
enum TicketSigantureType {
TicketSigantureType_RSA_4096_SHA1 = 0x010000,
TicketSigantureType_RSA_2048_SHA1 = 0x010001,
TicketSigantureType_ECDSA_SHA1 = 0x010002,
TicketSigantureType_RSA_4096_SHA256 = 0x010003,
TicketSigantureType_RSA_2048_SHA256 = 0x010004,
TicketSigantureType_ECDSA_SHA256 = 0x010005,
TicketSigantureType_HMAC_SHA1_160 = 0x010006,
};
enum TicketTitleKeyType {
TicketTitleKeyType_Common = 0,
TicketTitleKeyType_Personalized = 1,
};
enum TicketPropertiesBitfield {
TicketPropertiesBitfield_None = 0,
// temporary ticket, removed on restart
TicketPropertiesBitfield_Temporary = 1 << 4,
};
struct TicketData {
u8 issuer[0x40];
u8 title_key_block[0x100];
u8 ticket_version1;
u8 title_key_type;
u16 ticket_version2;
u8 license_type;
u8 master_key_revision;
u16 properties_bitfield;
u8 _0x148[0x8];
u64 ticket_id;
u64 device_id;
FsRightsId rights_id;
u32 account_id;
u8 _0x174[0xC];
u8 _0x180[0x140];
};
static_assert(sizeof(TicketData) == 0x2C0);
struct EticketRsaDeviceKey {
u8 ctr[AES_128_KEY_SIZE];
u8 private_exponent[0x100];
u8 modulus[0x100];
u32 public_exponent; ///< Stored using big endian byte order. Must match ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT.
u8 padding[0x14];
u64 device_id;
u8 ghash[0x10];
};
static_assert(sizeof(EticketRsaDeviceKey) == 0x240);
// es functions.
Result Initialize();
void Exit();
Service* GetServiceSession();
// todo: find the ipc that gets personalised tickets.
// todo: if ipc doesn't exist, manually parse es personalised save.
// todo: add personalised -> common ticket conversion.
// todo: make the above an option for both dump and install.
Result ImportTicket(const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size);
Result CountCommonTicket(s32* count);
Result CountPersonalizedTicket(s32* count);
Result ListCommonTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count);
Result ListPersonalizedTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count);
Result ListMissingPersonalizedTicket(s32 *out_entries_written, FsRightsId* out_ids, s32 count); // untested
Result GetCommonTicketSize(u64 *size_out, const FsRightsId* rightsId);
Result GetCommonTicketData(u64 *size_out, void *tik_data, u64 tik_size, const FsRightsId* rightsId);
Result GetCommonTicketAndCertificateSize(u64 *tik_size_out, u64 *cert_size_out, const FsRightsId* rightsId); // [4.0.0+]
Result GetCommonTicketAndCertificateData(u64 *tik_size_out, u64 *cert_size_out, void* tik_buf, u64 tik_size, void* cert_buf, u64 cert_size, const FsRightsId* rightsId); // [4.0.0+]
// ticket functions.
Result GetTicketDataOffset(std::span<const u8> ticket, u64& out);
Result GetTicketData(std::span<const u8> ticket, es::TicketData* out);
Result SetTicketData(std::span<u8> ticket, const es::TicketData* in);
Result GetTitleKey(keys::KeyEntry& out, const TicketData& data, const keys::Keys& keys);
Result DecryptTitleKey(keys::KeyEntry& out, u8 key_gen, const keys::Keys& keys);
Result PatchTicket(std::span<u8> ticket, const keys::Keys& keys);
} // namespace sphaira::es

View File

@@ -0,0 +1,70 @@
#pragma once
#include <switch.h>
#include <array>
#include <cstring>
#include "defines.hpp"
namespace sphaira::keys {
struct KeyEntry {
u8 key[AES_128_KEY_SIZE]{};
auto IsValid() const -> bool {
const KeyEntry empty{};
return std::memcmp(key, &empty, sizeof(key));
}
};
using KeySection = std::array<KeyEntry, 0x20>;
struct Keys {
u8 header_key[0x20]{};
// the below are only found if read_from_file=true
KeySection key_area_key[0x3]{}; // index
KeySection titlekek{};
KeySection master_key{};
KeyEntry eticket_rsa_kek{};
SetCalRsa2048DeviceKey eticket_device_key{};
static auto FixKey(u8 key) -> u8 {
if (key) {
return key - 1;
}
return key;
}
auto HasNcaKeyArea(u8 key, u8 index) const -> bool {
return key_area_key[index][FixKey(key)].IsValid();
}
auto HasTitleKek(u8 key) const -> bool {
return titlekek[FixKey(key)].IsValid();
}
auto HasMasterKey(u8 key) const -> bool {
return master_key[FixKey(key)].IsValid();
}
auto GetNcaKeyArea(KeyEntry* out, u8 key, u8 index) const -> Result {
R_UNLESS(HasNcaKeyArea(key, index), 0x1);
*out = key_area_key[index][FixKey(key)];
R_SUCCEED();
}
auto GetTitleKek(KeyEntry* out, u8 key) const -> Result {
R_UNLESS(HasTitleKek(key), 0x1);
*out = titlekek[FixKey(key)];
R_SUCCEED();
}
auto GetMasterKey(KeyEntry* out, u8 key) const -> Result {
R_UNLESS(HasMasterKey(key), 0x1);
*out = master_key[FixKey(key)];
R_SUCCEED();
}
};
void parse_hex_key(void* key, const char* hex);
Result parse_keys(Keys& out, bool read_from_file);
} // namespace sphaira::keys

View File

@@ -0,0 +1,228 @@
#pragma once
#include "fs.hpp"
#include "keys.hpp"
#include "ncm.hpp"
#include <switch.h>
#include <vector>
namespace sphaira::nca {
#define NCA0_MAGIC 0x3041434E
#define NCA2_MAGIC 0x3241434E
#define NCA3_MAGIC 0x3341434E
#define NCA_SECTOR_SIZE 0x200
#define NCA_XTS_SECTION_SIZE 0xC00
#define NCA_SECTION_TOTAL 0x4
#define NCA_MEDIA_REAL(x)((x * 0x200))
#define NCA_PROGRAM_LOGO_OFFSET 0x8000
#define NCA_META_CNMT_OFFSET 0xC20
enum KeyGenerationOld {
KeyGenerationOld_100 = 0x0,
KeyGenerationOld_Unused = 0x1,
KeyGenerationOld_300 = 0x2,
};
enum KeyGeneration {
KeyGeneration_301 = 0x3,
KeyGeneration_400 = 0x4,
KeyGeneration_500 = 0x5,
KeyGeneration_600 = 0x6,
KeyGeneration_620 = 0x7,
KeyGeneration_700 = 0x8,
KeyGeneration_810 = 0x9,
KeyGeneration_900 = 0x0A,
KeyGeneration_910 = 0x0B,
KeyGeneration_1210 = 0x0C,
KeyGeneration_1300 = 0x0D,
KeyGeneration_1400 = 0x0E,
KeyGeneration_1500 = 0x0F,
KeyGeneration_1600 = 0x10,
KeyGeneration_1700 = 0x11,
KeyGeneration_1800 = 0x12,
KeyGeneration_1900 = 0x13,
KeyGeneration_2000 = 0x14,
KeyGeneration_Invalid = 0xFF,
};
enum KeyAreaEncryptionKeyIndex {
KeyAreaEncryptionKeyIndex_Application = 0x0,
KeyAreaEncryptionKeyIndex_Ocean = 0x1,
KeyAreaEncryptionKeyIndex_System = 0x2
};
enum DistributionType {
DistributionType_System = 0x0,
DistributionType_GameCard = 0x1
};
enum ContentType {
ContentType_Program = 0x0,
ContentType_Meta = 0x1,
ContentType_Control = 0x2,
ContentType_Manual = 0x3,
ContentType_Data = 0x4,
ContentType_PublicData = 0x5,
};
enum FileSystemType {
FileSystemType_RomFS = 0x0,
FileSystemType_PFS0 = 0x1
};
enum HashType {
HashType_Auto = 0x0,
HashType_HierarchicalSha256 = 0x2,
HashType_HierarchicalIntegrity = 0x3
};
enum EncryptionType {
EncryptionType_Auto = 0x0,
EncryptionType_None = 0x1,
EncryptionType_AesXts = 0x2,
EncryptionType_AesCtr = 0x3,
EncryptionType_AesCtrEx = 0x4,
EncryptionType_AesCtrSkipLayerHash = 0x5, // [14.0.0+]
EncryptionType_AesCtrExSkipLayerHash = 0x6, // [14.0.0+]
};
struct SectionTableEntry {
u32 media_start_offset; // divided by 0x200.
u32 media_end_offset; // divided by 0x200.
u8 _0x8[0x4]; // unknown.
u8 _0xC[0x4]; // unknown.
};
struct LayerRegion {
u64 offset;
u64 size;
};
struct HierarchicalSha256Data {
u8 master_hash[0x20];
u32 block_size;
u32 layer_count;
LayerRegion hash_layer;
LayerRegion pfs0_layer;
LayerRegion unused_layers[3];
u8 _0x78[0x80];
};
#pragma pack(push, 1)
struct HierarchicalIntegrityVerificationLevelInformation {
u64 logical_offset;
u64 hash_data_size;
u32 block_size; // log2
u32 _0x14; // reserved
};
#pragma pack(pop)
struct InfoLevelHash {
u32 max_layers;
HierarchicalIntegrityVerificationLevelInformation levels[6];
u8 signature_salt[0x20];
};
struct IntegrityMetaInfo {
u32 magic; // IVFC
u32 version;
u32 master_hash_size;
InfoLevelHash info_level_hash;
u8 master_hash[0x20];
u8 _0xE0[0x18];
};
static_assert(sizeof(HierarchicalSha256Data) == 0xF8);
static_assert(sizeof(IntegrityMetaInfo) == 0xF8);
static_assert(sizeof(HierarchicalSha256Data) == sizeof(IntegrityMetaInfo));
struct FsHeader {
u16 version; // always 2.
u8 fs_type; // see FileSystemType.
u8 hash_type; // see HashType.
u8 encryption_type; // see EncryptionType.
u8 metadata_hash_type;
u8 _0x6[0x2]; // empty.
union {
HierarchicalSha256Data hierarchical_sha256_data;
IntegrityMetaInfo integrity_meta_info; // used for romfs
} hash_data;
u8 patch_info[0x40];
u64 section_ctr;
u8 spares_info[0x30];
u8 compression_info[0x28];
u8 meta_data_hash_data_info[0x30];
u8 reserved[0x30];
};
static_assert(sizeof(FsHeader) == 0x200);
static_assert(sizeof(FsHeader::hash_data) == 0xF8);
struct SectionHeaderHash {
u8 sha256[0x20];
};
struct KeyArea {
u8 area[0x10];
};
struct Header {
u8 rsa_fixed_key[0x100];
u8 rsa_npdm[0x100]; // key from npdm.
u32 magic;
u8 distribution_type; // see DistributionType.
u8 content_type; // see ContentType.
u8 old_key_gen; // see KeyGenerationOld.
u8 kaek_index; // see KeyAreaEncryptionKeyIndex.
u64 size;
u64 program_id;
u32 context_id;
u32 sdk_version;
u8 key_gen; // see KeyGeneration.
u8 sig_key_gen;
u8 _0x222[0xE]; // empty.
FsRightsId rights_id;
SectionTableEntry fs_table[NCA_SECTION_TOTAL];
SectionHeaderHash fs_header_hash[NCA_SECTION_TOTAL];
KeyArea key_area[NCA_SECTION_TOTAL];
u8 _0x340[0xC0]; // empty.
FsHeader fs_header[NCA_SECTION_TOTAL];
auto GetKeyGeneration() const -> u8 {
if (old_key_gen < key_gen) {
return key_gen;
} else {
return old_key_gen;
}
}
void SetKeyGeneration(u8 key_generation) {
if (key_generation <= 0x2) {
old_key_gen = key_generation;
key_gen = 0;
} else {
old_key_gen = 0x2;
key_gen = key_generation;
}
}
};
static_assert(sizeof(Header) == 0xC00);
Result DecryptKeak(const keys::Keys& keys, Header& header);
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation);
Result VerifyFixedKey(const Header& header);
// helpers that parse an nca.
Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos);
Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out = nullptr, s64 nacp_size = 0, std::vector<u8>* icon_out = nullptr);
auto GetKeyGenStr(u8 key_gen) -> const char*;
} // namespace sphaira::nca

View File

@@ -0,0 +1,47 @@
#pragma once
#include <switch.h>
namespace sphaira::ncm {
struct PackagedContentMeta {
u64 title_id;
u32 title_version;
u8 meta_type; // NcmContentMetaType
u8 content_meta_platform; // [17.0.0+]
NcmContentMetaHeader meta_header;
u8 install_type; // NcmContentInstallType
u8 _0x17;
u32 required_sys_version;
u8 _0x1C[0x4];
};
static_assert(sizeof(PackagedContentMeta) == 0x20);
struct ContentStorageRecord {
NcmContentMetaKey key;
u8 storage_id; // NcmStorageId
u8 padding[0x7];
};
union ExtendedHeader {
NcmApplicationMetaExtendedHeader application;
NcmPatchMetaExtendedHeader patch;
NcmAddOnContentMetaExtendedHeader addon;
NcmLegacyAddOnContentMetaExtendedHeader addon_legacy;
NcmDataPatchMetaExtendedHeader data_patch;
};
auto GetMetaTypeStr(u8 meta_type) -> const char*;
auto GetStorageIdStr(u8 storage_id) -> const char*;
auto GetMetaTypeShortStr(u8 meta_type) -> const char*;
auto GetAppId(u8 meta_type, u64 id) -> u64;
auto GetAppId(const NcmContentMetaKey& key) -> u64;
auto GetAppId(const PackagedContentMeta& meta) -> u64;
auto GetContentIdFromStr(const char* str) -> NcmContentId;
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id);
Result Register(NcmContentStorage* cs, const NcmContentId *content_id, const NcmPlaceHolderId *placeholder_id);
} // namespace sphaira::ncm

View File

@@ -0,0 +1,54 @@
#pragma once
#include <switch.h>
namespace sphaira::ncz {
#define NCZ_SECTION_MAGIC 0x4E544345535A434EUL
// todo: byteswap this
#define NCZ_BLOCK_MAGIC std::byteswap(0x4E435A424C4F434BUL)
#define NCZ_SECTION_OFFSET (0x4000 + sizeof(ncz::Header))
struct Header {
u64 magic; // NCZ_SECTION_MAGIC
u64 total_sections;
};
struct BlockHeader {
u64 magic; // NCZ_BLOCK_MAGIC
u8 version;
u8 type;
u8 padding;
u8 block_size_exponent;
u32 total_blocks;
u64 decompressed_size;
};
struct Block {
u32 size;
};
struct BlockInfo {
u64 offset; // compressed offset.
u64 size; // compressed size.
auto InRange(u64 off) const -> bool {
return off < offset + size && off >= offset;
}
};
struct Section {
u64 offset;
u64 size;
u64 crypto_type;
u64 padding;
u8 key[0x10];
u8 counter[0x10];
auto InRange(u64 off) const -> bool {
return off < offset + size && off >= offset;
}
};
} // namespace sphaira::ncz

View File

@@ -0,0 +1,62 @@
#pragma once
#include <switch.h>
namespace sphaira::npdm {
struct Meta {
u32 magic; // "META"
u32 signature_key_generation; // +9.0.0
u32 _0x8;
u8 flags;
u8 _0xD;
u8 main_thread_priority;
u8 main_thread_core_num;
u32 _0x10;
u32 sys_resource_size; // +3.0.0
u32 version;
u32 main_thread_stack_size;
char title_name[0x10];
char product_code[0x10];
u8 _0x40[0x30];
u32 aci0_offset;
u32 aci0_size;
u32 acid_offset;
u32 acid_size;
};
struct Acid {
u8 rsa_sig[0x100];
u8 rsa_pub[0x100];
u32 magic; // "ACID"
u32 size;
u8 version;
u8 _0x209[0x1];
u8 _0x20A[0x2];
u32 flags;
u64 program_id_min;
u64 program_id_max;
u32 fac_offset;
u32 fac_size;
u32 sac_offset;
u32 sac_size;
u32 kac_offset;
u32 kac_size;
u8 _0x238[0x8];
};
struct Aci0 {
u32 magic; // "ACI0"
u8 _0x4[0xC];
u64 program_id;
u8 _0x18[0x8];
u32 fac_offset;
u32 fac_size;
u32 sac_offset;
u32 sac_size;
u32 kac_offset;
u32 kac_size;
u8 _0x38[0x8];
};
} // namespace sphaira::npdm

View File

@@ -0,0 +1,22 @@
#pragma once
#include <switch.h>
#include "ncm.hpp"
namespace sphaira::ns {
enum ApplicationRecordType {
// installed
ApplicationRecordType_Installed = 0x3,
// application is gamecard, but gamecard isn't insterted
ApplicationRecordType_GamecardMissing = 0x5,
// archived
ApplicationRecordType_Archived = 0xB,
};
Result PushApplicationRecord(Service* srv, u64 tid, const ncm::ContentStorageRecord* records, u32 count);
Result ListApplicationRecordContentMeta(Service* srv, u64 offset, u64 tid, ncm::ContentStorageRecord* out_records, u32 count, s32* entries_read);
Result DeleteApplicationRecord(Service* srv, u64 tid);
Result InvalidateApplicationControlCache(Service* srv, u64 tid);
} // namespace sphaira::ns

View File

@@ -0,0 +1,62 @@
/*
* rsa.c
*
* Copyright (c) 2018-2019, SciresM.
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nxdumptool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef __RSA_H__
#define __RSA_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#define RSA2048_BYTES 0x100
#define RSA2048_BITS (RSA2048_BYTES * 8)
#define RSA2048_SIG_SIZE RSA2048_BYTES
#define RSA2048_PUBKEY_SIZE RSA2048_BYTES
/// Verifies a RSA-2048-PSS with SHA-256 signature.
/// Suitable for NCA and NPDM signatures.
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size);
/// Verifies a RSA-2048-PKCS#1 v1.5 with SHA-256 signature.
/// Suitable for ticket and certificate chain signatures.
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
bool rsa2048VerifySha256BasedPkcs1v15Signature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size);
/// Performs RSA-2048-OAEP decryption.
/// Suitable to decrypt the titlekey block from personalized tickets.
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
/// 'label' and 'label_size' arguments are optional -- if not needed, these may be set to NULL and 0, respectively.
bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \
size_t private_exponent_size, const void *label, size_t label_size, size_t *out_size);
#ifdef __cplusplus
}
#endif
#endif /* __RSA_H__ */

View File

@@ -0,0 +1,56 @@
#pragma once
#include <switch/types.h>
#include <switch/result.h>
#include <switch/kernel/mutex.h>
#include <switch/sf/service.h>
#include <switch/services/sm.h>
typedef struct ServiceGuard {
Mutex mutex;
u32 refCount;
} ServiceGuard;
NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g)
{
mutexLock(&g->mutex);
return (g->refCount++) == 0;
}
NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void))
{
if (R_FAILED(rc)) {
cleanupFunc();
--g->refCount;
}
mutexUnlock(&g->mutex);
return rc;
}
NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
{
mutexLock(&g->mutex);
if (g->refCount && (--g->refCount) == 0)
cleanupFunc();
mutexUnlock(&g->mutex);
}
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
\
static ServiceGuard g_##name##Guard; \
NX_INLINE Result _##name##Initialize _paramdecl; \
static void _##name##Cleanup(void); \
\
Result name##Initialize _paramdecl \
{ \
Result rc = 0; \
if (serviceGuardBeginInit(&g_##name##Guard)) \
rc = _##name##Initialize _parampass; \
return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \
} \
\
void name##Exit(void) \
{ \
serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \
}
#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ())

View File

@@ -0,0 +1,29 @@
#pragma once
#include <vector>
#include <switch.h>
namespace sphaira::yati::source {
struct Base {
virtual ~Base() = default;
// virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) = 0;
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) = 0;
virtual bool IsStream() const {
return false;
}
virtual void SignalCancel() {
}
Result GetOpenResult() const {
return m_open_result;
}
protected:
Result m_open_result{};
};
} // namespace sphaira::yati::source

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