Compare commits

...

98 Commits

Author SHA1 Message Date
J-D-K
c094030c52 Minor code revisions in preparation for cache system on 20.0+ 2025-05-28 18:33:58 -04:00
JK
291d2fd82f
Merge pull request #250 from impeeza/impeeza-ES-419-Update
ES-419 update
2024-11-05 11:36:59 -05:00
impeeza
df03aac552
ES-419 update
Seems the update of this file get lost in between commits.
2024-11-05 11:22:58 -05:00
J-D-K
24aafcbf63 Update build date 2024-11-05 10:10:20 -05:00
J-D-K
1e06516317 Settings Readme: Fix more typos I missed. 2024-10-31 21:14:59 -04:00
J-D-K
da121677fb Update Settings Readme and fix a few typos 2024-10-31 17:08:25 -04:00
J-D-K
361b862bf3 Update settings readme with note. 2024-10-31 14:29:29 -04:00
J-D-K
790e4bebdf Add settings readme 2024-10-31 14:25:52 -04:00
J-D-K
9ac5f39476 Tweak: Make config save in a few more places. 2024-10-31 13:42:44 -04:00
J-D-K
44a466fef0 Update Makefile build date 2024-10-31 11:04:34 -04:00
J-D-K
04ce90c7dd Change default config settings, make config save on toggle. 2024-10-31 10:35:21 -04:00
JK
ddbf601819
Merge pull request #212 from karasuhebi/master-1
Fixing typo
2024-10-16 15:46:10 -04:00
JK
9b8c852b5c
Merge pull request #244 from DDinghoya/patch-2
Update ko.txt
2024-10-16 13:07:57 -04:00
JK
ddcebdcff6
Merge pull request #240 from impeeza/patch-1
Update REMOTE_INSTRUCTIONS.MD
2024-10-16 13:07:27 -04:00
JK
cf39346d69
Merge pull request #191 from Pf-16/patch-3
Update it.txt
2024-10-16 13:07:11 -04:00
JK
7949582fd8
Merge pull request #182 from qazrfv1234/qazrfv1234-patch-4
Update zh-TW.txt
2024-10-16 13:06:55 -04:00
DDinghoya
1bda18d98f
Update ko.txt 2024-09-22 00:32:07 +09:00
impeeza
3f39769b52
Update REMOTE_INSTRUCTIONS.MD
Small clarification about the use of `basepath`, thanks to @nimaskji on https://github.com/J-D-K/JKSV/issues/239
2024-08-09 20:23:16 -05:00
JK
8c3c58cadc
Merge pull request #236 from rado0x54/bugfix/fix-safeTitle-on-remote-fs
Fix: Only use safeTitle for remote directory name.
2024-08-06 05:26:35 -04:00
JK
e1634556ca
Merge pull request #234 from rado0x54/bugfix/fix-no-itemname
Fix empty [R] name on certain webdav servers.
2024-08-06 05:26:20 -04:00
Martin Riedel
c2cf998d10 fix: use safeTitle instead of title for directory name on remote filesystem. This is a breaking change. Prior names with unsafe titlenames need to be moved manually. 2024-07-27 11:28:34 -04:00
Martin Riedel
f9668895eb fix: Provide fallback for item.name from href if webdav server does not provide "displayname" property. 2024-07-26 07:17:03 -04:00
Jaime J. Denizard
2cf5a22062
Merge branch 'J-D-K:master' into master-1 2024-03-08 23:31:01 -05:00
JK
df89d8b116
Merge pull request #207 from rado0x54/feature/webdav
Webdav Support
2024-02-06 18:48:25 -05:00
Jaime J. Denizard
24644bb743
Fixing typo 2023-08-23 02:51:31 -04:00
Martin Riedel
1aca15ae6e fix: Fixed href assignment for servers without origin. 2023-07-12 09:52:10 -04:00
Martin Riedel
6b8c3da184 fix: Webdav href contains absolute URI the id were not set correctly. 2023-07-11 22:22:03 -04:00
Martin Riedel
ef633b1752 fix: updated Makefile to prioritize local over global imports (gd.h error) 2023-07-10 16:47:41 -04:00
Martin Riedel
f342bf15af feat: PR-related cleanup
- updated build instructions
- added user-agent header to webdav calls
- Generalized popDriveNotActive to popRemoteNotActive
- Updated version identifier presented in app
- Removed some commented code
2023-07-10 15:01:44 -04:00
Martin Riedel
938c52b603 feat: Webdav support for remote storage
- Created common interface that GDrive and Webdav implement (rfs::IRemoteFS)
- Moved shared functionality into shared interface/implementation
- drive.h/.cpp was replaced by remote.h/.cpp
- fld.cpp now gets a copy of RfsItem (former gdIems), because the implementation is not required to retain these (e.g. Webdav does not)
- UI presentation changed from [GD] to [R] (Remote)
2023-07-09 23:13:01 -04:00
Martin Riedel
a893d3fa56 chore: aligned all SDL2 imports to use <SDL2/*> 2023-07-09 23:04:44 -04:00
Martin Riedel
69901a93be feat: Added CMakeLists.txt to support Jetbrains IDE (not for building) 2023-07-09 23:04:44 -04:00
Martin Riedel
aee8d777fc fixed root cause of GDrive config parsing crash 2023-07-07 16:30:02 -04:00
Martin Riedel
96cdb7f488 Ignore dot (.) files
When reading the drive config ignore all files starting with a .
2023-07-05 11:00:27 -04:00
J-D-K
1ac23d2750 Fixes + Revisions to last commit 2023-06-04 20:15:27 -04:00
J-D-K
85bca2d66e Add textureMgr class I wrote at work and prepare a little for auto upload option 2023-05-22 17:13:49 -04:00
J-D-K
6f8393483f Applet mode warning + disable drive/browser to prevent crash 2023-05-17 17:00:18 -04:00
Pf-16
14a2debccd
Update it.txt
Felt like it would be a more accurate translation
2023-05-05 16:09:45 +02:00
J-D-K
83228a2911 Libnx update 2023-04-15 05:37:19 -04:00
qazrfv1234
07b210b0bd
Update zh-TW.txt 2023-03-08 20:57:10 +08:00
J-D-K
09f610b3c9 Attempt to fix GD instructions broken by ancient HTML 2023-02-23 17:10:41 -05:00
JK
d4f16ab540
Merge pull request #176 from SciresM/master
Fix font services type for 16.0.0
2023-02-23 17:00:16 -05:00
Michael Scire
dd3dfa08ae Fix font services type for 16.0.0 2023-02-22 18:37:32 -07:00
J-D-K
d693f8afbb Remove logging from debugging 2023-02-12 09:16:37 -05:00
J-D-K
3e16d635dc Update Drive instructions, load client_id and client_secret from Google JSON instead 2023-02-12 09:14:45 -05:00
JK
3fad76ecaa
Merge pull request #167 from bitest/master
Update zh-CN.txt
2022-11-20 20:14:17 -05:00
bitest
52976d613f
Update zh-CN.txt 2022-11-18 04:40:01 +08:00
J-D-K
f955093746 Temp fixes for non-ASCII system users and drive crash if login canceled. 2022-11-17 14:58:02 -05:00
JK
5049a52529
Merge pull request #166 from qazrfv1234/qazrfv1234-patch-3
Update zh-TW.txt
2022-11-17 13:23:41 -05:00
qazrfv1234
df100e8e32
Update zh-TW.txt 2022-11-17 20:26:10 +08:00
JK
e16e1db34b
Merge pull request #153 from yyoossk/master
jpn update
2022-11-16 18:56:01 -05:00
JK
c6ad8f3bb0
Merge pull request #154 from DDinghoya/patch-1
Update ko.txt
2022-11-16 18:47:19 -05:00
DDinghoya
42dff0b887
Update ko.txt 2022-03-26 14:33:54 +09:00
yyoossk
2b640eed52
jpn update 2022-03-16 21:01:11 +09:00
J-D-K
1f0868de78 Fix drive patch size mismatch 2021-12-21 19:18:58 -05:00
JK
501658cfb9
Merge pull request #145 from SimoCasa/patch-2
Update it.txt
2021-12-21 19:14:12 -05:00
Simo
9a89682ec0
Update it.txt
Update based on new variables in French translations
2021-12-16 23:16:11 +01:00
J-D-K
8d988055d7 A few small fixes and tweaks 2021-12-15 23:29:13 -05:00
J-D-K
2f34ddd3ba Update Drive instructions, readme 2021-12-15 20:21:33 -05:00
J-D-K
b2be2a26fb Remove cfg::driveAuthCode, login on switch instead 2021-12-15 20:04:47 -05:00
J-D-K
00dd0ee6af Updated French 2021-12-07 20:34:34 -05:00
JK
e3d54bed23
Merge pull request #134 from SimoCasa/master
Update it.txt
2021-12-02 20:46:42 -05:00
JK
d1d0564d5b
Merge branch 'master' into master 2021-12-02 20:45:45 -05:00
J-D-K
ca24954079 One last one 2021-12-01 17:55:33 -05:00
J-D-K
f92f8c01ee Try to fix readme with ancient web dev knowledge 2021-12-01 17:52:54 -05:00
J-D-K
72795af2f5 Add Google Drive instructions. Maybe fix them later. 2021-12-01 17:32:50 -05:00
J-D-K
b212d53247 Remove friends only function 2021-12-01 15:01:14 -05:00
J-D-K
df453ee667 Tweak file mode start point, finish restore from drive code 2021-12-01 14:57:06 -05:00
J-D-K
411452658c Update translation files, might need a second look 2021-11-30 15:59:13 -05:00
J-D-K
c29688ea97 Fix config loading, early drive code, threaded file read/write 2021-11-30 15:58:05 -05:00
J-D-K
2fbfe5b62e First(?) readme update, thread race fix 2021-11-15 17:47:12 -05:00
Simo
6ff97ad765
Update it.txt 2021-11-03 23:35:44 +01:00
Simo
6cbf56840a
Update it.txt 2021-11-03 23:32:13 +01:00
Simo
ba07b988b8
Update it.txt 2021-11-03 23:31:09 +01:00
Simo
0ad0dfb598
Delete it.txt 2021-11-03 23:04:14 +01:00
Simo
e49e7bea84
Create it.txt 2021-11-03 23:03:56 +01:00
Simo
09cf058f1b
Delete it.txt 2021-11-03 23:02:55 +01:00
Simo
2015ef490d
Create it.txt 2021-11-03 23:02:22 +01:00
Simo
97789951c3
Update it.txt 2021-11-03 22:48:13 +01:00
J-D-K
093b1bc178 Fix file mode copy file 2021-10-13 17:20:42 -04:00
J-D-K
9ca4c6ff52 Move stuff, fix stuff. 2021-10-12 23:14:04 -04:00
J-D-K
bf15660c2d Code Revisions + Auto extend saves 2021-10-12 21:36:33 -04:00
J-D-K
94b0c800ca Upload new French translation 2021-10-01 21:12:57 -04:00
JK
27e3a26334
Merge pull request #129 from qazrfv1234/qazrfv1234-patch-2
Update zh-TW.txt
2021-09-27 19:19:02 -04:00
JK
5b4a920def
Merge branch 'master' into qazrfv1234-patch-2 2021-09-27 19:17:29 -04:00
qazrfv1234
9392fef706
Update zh-TW.txt 2021-09-26 18:24:07 +08:00
J-D-K
815cdaf513 Revised translation loading, exporting 2021-09-26 02:09:37 -04:00
JK
757c2da90b
Merge pull request #124 from zdm65477730/master
full support for Lang translation in UI
2021-09-25 23:42:08 -04:00
Damien Zhao
18cb1e0742
adjust translations in UI
1. Remove error message translations.
2. Using format string in json file instead of using C++ strings.
3. More accurate naming for translation entries
4. Added the initial strings for newly added translation items in json.
2021-09-21 16:14:08 +08:00
damien zhao
37e8f10aa8
zh-CN.txt mini update 2021-09-05 11:34:05 +08:00
damien zhao
c6c95eb498
min zh-CN.txt update 2021-09-05 11:18:11 +08:00
damien zhao
af210d2f65
full UI with translation 2021-09-05 11:11:26 +08:00
damien zhao
f718f5ac4e
fix lang bug 2021-09-05 01:12:43 +08:00
damien zhao
a60471e888
full ui Lang in lang txt 2021-09-04 23:15:34 +08:00
damien zhao
f3bd18e3df
addSimplified Chinese support 2021-09-04 19:34:57 +08:00
J-D-K
b0a5bd1e7e Add French, code for JP + FR, fix edited menu text not scrolling. 2021-09-03 17:54:50 -04:00
JK
0cea708fb0
Merge pull request #121 from yyoossk/master
jpn
2021-09-03 17:33:55 -04:00
yyoossk
8fe8d788fc
jpn 2021-09-02 08:07:57 +09:00
81 changed files with 8782 additions and 3207 deletions

136
.clang-format Normal file
View File

@ -0,0 +1,136 @@
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
- Regex: '.*'
Priority: 1
SortPriority: 0
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: true
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 1000
PointerAlignment: Right
ReflowComments: false
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Latest
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: Never
...

9
.clang-tidy Normal file
View File

@ -0,0 +1,9 @@
---
Checks: 'clang-analyzer-*,cppcoreguidelines-*,modernize-*,bugprone-*,performance-*,readability-*,readability-non-const-parameter,misc-const-correctness,misc-use-anonymous-namespace,google-explicit-constructor,-modernize-use-trailing-return-type,-bugprone-exception-escape,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-avoid-magic-numbers,-bugprone-easily-swappable-parameters,-cppcoreguidelines-non-private-member-variables-in-classes'
WarningsAsErrors: ''
HeaderFilterRegex: ''
CheckOptions:
- key: readability-magic-numbers.IgnoredFloatingPointValues
value: '0.0;1.0;100.0;'
- key: readability-magic-numbers.IgnoredIntegerValues
value: '0;1;2;3;4;5;6;7;8;9;'

11
.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
# Set the default behavior for all files.
* text=auto eol=lf
# Normalized and converts to native line endings on checkout.
*.c text
*.cc text
*.cxx
*.cpp text
*.h text
*.hxx text
*.hpp text

15
.gitignore vendored
View File

@ -1,3 +1,16 @@
build/
.vscode/
*.cbp
*.layout
*.layout
.editorconfig
.idea/
cmake-build-debug/
# build artifacts
JKSV.elf
JKSV.lst
JKSV.nacp
JKSV.nro
JKSV.nso
JKSV.pfs0

50
CMakeLists.txt Normal file
View File

@ -0,0 +1,50 @@
# This is mainly for IDE Support (CLION), not for building (use Makefile directly).
cmake_minimum_required(VERSION 3.8)
project(JKSV)
set(CMAKE_CXX_STANDARD 17)
set(SOURCE_FILES
src/cfg.cpp
src/curlfuncs.cpp
src/data.cpp
src/fs.cpp
src/gd.cpp
src/gfx.cpp
src/main.cpp
src/rfs.cpp
src/type.cpp
src/ui.cpp
src/util.cpp
src/webdav.cpp
src/fs/dir.cpp
src/fs/remote.cpp
src/fs/file.cpp
src/fs/fsfile.c
src/fs/zip.cpp
src/gfx/textureMgr.cpp
src/ui/ext.cpp
src/ui/fld.cpp
src/ui/fm.cpp
src/ui/miscui.cpp
src/ui/sett.cpp
src/ui/sldpanel.cpp
src/ui/thrdProc.cpp
src/ui/ttl.cpp
src/ui/ttlview.cpp
src/ui/uistr.cpp
src/ui/usr.cpp)
# Specify external includes here
include_directories(./inc)
include_directories(./inc/fs)
include_directories(./inc/gfx)
include_directories(./inc/ui)
include_directories($ENV{DEVKITPRO}/devkitA64/aarch64-none-elf/include)
include_directories($ENV{DEVKITPRO}/devkitA64/lib/gcc/aarch64-none-elf/10.1.0/include)
include_directories($ENV{DEVKITPRO}/libnx/include)
include_directories($ENV{DEVKITPRO}/portlibs/switch/include)
include_directories($ENV{DEVKITPRO}/portlibs/switch/include/freetype2)
add_executable(JKSV ${SOURCE_FILES})

42
JKSV_Settings_ENG.MD Normal file
View File

@ -0,0 +1,42 @@
# JKSV's Settings menu options and what they do:
1. **Empty Trash Bin**: Empties the trash folder of all backups moved to it. Running this now and then is important if you choose to use the trash bin option. It is enabled by default.
2. **Check for Updates**: Checks JKSV's Github release page for any new updates and downloads it for you.
3. **Set JKSV Output Folder**: Changes the folder in which JKSV stores your save backups. Remember to end the path with a slash so this works properly.
4. **Edit Blacklisted Titles**: Opens a menu that allows you to select and remove titles that are hidden in JKSV's title selection menu.
5. **Delete All Save Backups**: Wipes JKSV's folder clean for you to start fresh.
6. **Include Device Saves With Users**: Includes device type saves (Animal Crossing, for example) with users so finding them is easier and also makes it easier to differentiate and have multiple saves for different users on the same console.
7. **Auto Backup On Restore**: Makes JKSV automatically create a backup before restoring another backup just in case.
8. **Auto Name backups**: Automatically names backups and skips the keyboard entirely.
9. **Overclock/CPU Boost**: Overclocks the CPU when exporting ZIP archives. This has little effect on the speed of compression so it's recommended to leave it off. It's being removed in the rewrite anyway for having almost no impact.
10. **Hold to Delete**: Whether or not you would like JKSV to force you to hold A to delete a backup.
11. **Hold to Restore**: Whether or not you would like JKSV to force you to hold A to restore a backup.
12. **Hold to Overwrite**: Whether or not you would like JKSV to force you to hold A to overwrite a backup on your SD card.
13. **Force Mount**: This controls whether or not JKSV displays everything it finds on your Switch or only the saves it can successfully open and mount on boot.
14. **Account System Saves**: This controls whether or not JKSV displays system save data that has an account user ID associated with it or not. This is different than normal system save data because that does not have an account user ID associated with it.
15. **Enable Writing to System Saves**: This enables writing and restoring to system save data and writing to the BIS partitions in the file browser mode. This is **dangerous** and should normally be left off. **Any damage incurred with this option on is your own fault.**
16. **Use FS Commands Directly**: Uses the Switch's FS file functions instead of LibNX's fs_dev and stdio. This can _sometimes_ resolve odd issues.
17. **Export Saves to Zip**: Uses ZIP files for backing up saves instead of unpacked files in folders. This has the benefit of higher compatibility with various edge-case saves with non-ASCII filenames which the SD card can't handle.
18. **Force English to be Used**: Overrides the detected language and uses the default English strings.
19. **Enable Trash Bin**: Enables moving backups deleted to the `_TRASH_` folder instead of deleting them permanently.
20. **Title Sorting Type**: Changes the way titles are sorted and displayed.
22. **Animation Scale**: Changes the transition speed for animated parts of the UI. One being instant, 8.0 being the slowest _I normally allow_.

View File

@ -32,13 +32,13 @@ include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
TARGET := JKSV
BUILD := build
SOURCES := src src/ui
SOURCES := src src/ui src/fs src/gfx
DATA := data
INCLUDES := inc inc/ui
INCLUDES := inc inc/ui inc/fs inc/gfx
EXEFS_SRC := exefs_src
APP_TITLE := JKSV
APP_AUTHOR := JK
APP_VERSION := 09.01.2021
APP_VERSION := 11.5.2024
ROMFS := romfs
ICON := icon.jpg
@ -47,17 +47,16 @@ ICON := icon.jpg
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
override CFLAGS += `sdl2-config --cflags` -g -Wall -O2 -ffunction-sections -ffast-math \
$(ARCH) $(DEFINES)
override CFLAGS += $(INCLUDE) -D__SWITCH__ `freetype-config --cflags` `curl-config --cflags`
override CFLAGS += $(INCLUDE) -D__SWITCH__
override CFLAGS += `sdl2-config --cflags` `freetype-config --cflags` `curl-config --cflags`
override CFLAGS += -g -Wall -O2 -ffunction-sections -ffast-math $(ARCH) $(DEFINES)
CXXFLAGS:= $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := `sdl2-config --libs` `freetype-config --libs` `curl-config --libs` -lSDL2_image -lwebp -lpng -ljpeg -lz -lminizip -ljson-c -lnx
LIBS := `sdl2-config --libs` `freetype-config --libs` `curl-config --libs` -lSDL2_image -lwebp -lpng -ljpeg -lz -lminizip -ljson-c -ltinyxml2 -lnx
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing

View File

@ -1,54 +1,70 @@
# JKSV
Data Dump/Restore tool for Switch.
JK's Save Manager Switch Edition.
<img src="https://i.imgur.com/yLcTPzt.jpg"/>
## Info
JKSV on Switch started as a small project/port to test some things and get familiar with libnx. A list of what it currently can do:
1. Dump and restore save data.
* This includes the ability to dump and restore to/from any location on SD by pressing minus and using the Advanced Mode.
* Save Data can be dumped and restored directly to and from ZIP archives by enabling `Export To ZIP` in the options menus. Folders will still be detected and usable even with this enabled.
2. Dump system save data
This started as a simple, straight port of my 3DS save manager I publicly released in 2016. Despite not originally wanting to take it too far, I continued working on it for fun when I can. Currently it can:
1. Dump and restore account save data.
2. Dump and restore device saves shared by all users (Games such as Animal Crossing).
3. Dump and restore BCAT Data.
4. Dump and restore cache Saves.
5. Dump system save data.
* Dumping this data is always enabled, but writing back needs to be enabled from the options menu. Writing to this can be very dangerous.
3. Open and explore bis storage partitions via the Extras menu
* Processes can be terminated from the Extras menu allowing you to open even more of these and explore more.
6. Export save data to folders like the orignal or compressed zip files to save space.
7. Upload and download save backups to [Google Drive](./REMOTE_INSTRUCTIONS.MD#gdrive) if it is configured.
8. Upload and download save backups to [WebDav](./REMOTE_INSTRUCTIONS.MD#webdav) if it is configured.
9. Create save data so the user no longer needs to boot games to import saves.
* Titles can be rescanned from the Extras menu. For example, if you insert a game card while JKSV is open, rescanning will load and add it to the save creation menu(s).
10. Export and use SVI files to create save data for titles not installed on your system. For games that detect other game saves to unlock content.
* SVI files are simply the title ID, NACP, and icon packed into a file. Placing them in `JKSV/svi` will load them as if they are any other game on your switch. They will appear in the save creation menus with the rest.
11. Extend save data containers to any size the user wants or automatically if the save cannot fit into the current one.
12. Delete save data from the system.
13. Reset save data as if the game was never launched.
14. Display stats and information about the game/save: Play time, launch count, title ID (TID), save ID(SID)/name of save file on user nand partition, etc.
15. Open and explore bis storage partitions via the Extras menu
* BIS Storage is opened inside a basic filebrowser. The partition's listing is on the left. Your SD is on the right.
* Only copying to SD and file properties work on BIS partitions. Writing to and deleting are disabled unless enabled like system save data.
4. Misc Extras:
* Ability to remove downloaded firmware updates from NAND. This is located in the extras menu (ZR on User selection)
16. Misc Extras:
* Ability to remove downloaded firmware updates from NAND. This is located under Extras.
* Terminating processes by [ID](https://switchbrew.org/wiki/Title_list#System_Modules). Allowing you to dump normally unopenable system archives.
* Mount by System Save [ID](https://switchbrew.org/wiki/Flash_Filesystem#System_Savegames). Normally used when the terminated process makes JKSV unable to rescan titles without the Switch crashing.
* Mount and open RomFS of process the homebrew menu takes over (if launched as NRO).
* Hold R while opening a game or applet with Atmosphere so the homebrew menu loads. Open JKSV and press minus and select **Mount Process RomFS**. The romfs of the app should appear in the browser along with your SD on the right.
* Mount and open RomFS of process the homebrew menu takes over (if launched as NRO).
* Hold R while opening a game or applet with Atmosphere so the homebrew menu loads. Open JKSV and press minus and select **Mount Process RomFS**. The romfs of the app should appear in the browser along with your SD on the right.
**NOTE: Some features may require building JKSV from source. I am extremely picky and only release when I am satisfied and sure things work 100% as expected.**
## Quick Guide
**A custom path can be defined by creating "sdmc:/switch/jksv_dir.txt" and typing the path you want to use. For example, `sdmc:/switch/JKSV/` will force JKSV to create its folder in the switch homebrew folder.**
1. Main/User Menu
* A Selects the currently highlighted user and allows you to browse their titles.
* Y Dumps the save data for all users.
* X opens the sub menu of options for the currently highlighted user:
* Dump All for [X] dumps all of the saves for the highlighted user.
* Create save data opens a list of games found on your switch and will allow to create save data for them without needing to start the games.
* SVI files located in `JKSV/svi` will also be loaded and added to this list. These are to create save data for games not currently on the system. This is for games that search for other saves to unlock extra content.
* Cache saves require a cache index number to be created. This information can be found under information for a cache save when being exported.
* Create All Save Data creates save data for every title on your system for the selected user.
* Delete All Save Data deletes all save data for the selected user. This is permanent.
* Settings and Extras below.
**Custom "safe titles" can be set by creating a file named `titleDefs.txt` in your JKSV directory. These are the folders on SD in which game saves are stored. For example, creating this file and adding the line `0100BFE00E9CA000 = "The Witcher 3 CE"` will set The Witcher 3 to use `[pathto]/JKSV/The Witcher 3 CE/` directory instead of its title id. These need to be ASCII or they will fail to work.**
1. User Select
* A opens the selected user's save files.
* Y Dumps __all__ save data from __all users__, device save data, and BCAT save data. System save data is not included in this.
* X Changes the UI mode to a text menu based one for people that prefer 3DS JKSM style text menus instead.
* Minus Opens the Options menu.
* ZR opens a small menu of extras.
2. Title Select
* A Opens the title for backup and restore.
* L and R change the current user.
* Y Dumps all saves for the currently selected user.
* X adds the selected title to a list of favorites that are pushed to the top of the title list/icons.
* Minus adds the selected title to a list of ignored titles.
* ZR __ERASES__ The selected title's save from the system. This is the same as going into settings and deleting via data management. __THIS DOES NOT DELETE JKSV's SAVE FOLDERS__.
3. Backup/Folder Menu
* Minus opens file mode. File mode is a basic file browser for moving individual files and folders. This also adds the ability to restore saves from any location on your SD card.
* Holding L or R while selecting new with A will automatically name the backup for you without opening the keyboard.
* A creates a new backup
* JKSV adds some suggestions to the keyboard's dictionary such as: date strings, the current user's name **if it's safe**, and the current title or a generated abbreviation.
* Y Restores save data from the selected folder.
* X Deletes the selected folder.
* ZR __ERASES__ the current save data for the title from __your system__, but leaves the archive on NAND.
2. Title/Game Select
* A selects and opens the backup menu.
* Adding `.zip` to the end of your file name will force zip regardless of whether it's enabled or not.
* Y Favorites a title and pushes it to the top of your games.
* L and R jump forward down your games.
* X opens the title options menu:
* Information displays stats about the game for the current user.
* Blacklist adds the title to a list that is not displayed.
* Change Output Folder changes the folder to which the game's saves are written.
* Open in File Mode opens the save in a basic file browser.
* Delete All Save Backups deletes all of the backups for the current title.
* Reset Save Data wipes the save clean as if it was never run.
* Delete Save Data deletes the save data for the title from the system. This is the same as doing it from the Switch's data management setting.
* Extend Save Data extends the container for the current title. This is also done automatically if the save being imported is too large for the container.
* Different games have different limits. Most games do not need this at all. A few will take advantage of a larger container, others extend theirs at times and will need larger containers than created by default.
* Export SVI exports the data needed to create save data for the current title to `JKSV/ncap/[TID].svi`. This is just the title ID, NACP, and icon of the title. These can be used to create save data for games not installed on your system.
4. File Mode
* A opens directories.
@ -60,6 +76,13 @@ JKSV on Switch started as a small project/port to test some things and get famil
* Make Dir creates a folder.
* Properties gets file size and directory size.
* ZL or ZR Change the controlled menu.
5. Settings
* Empty Trash Bin deletes all backups inside the `_TRASH_` folder. The trash bin feature can be disabled further down.
* Check For Updates checks github for updates to JKSV. This currently only updates the NRO release. Maybe NSP later.
* Set JKSV Save Output Folder allows you to set where JKSV's working directory is. Files and folders should be relocated for you.
* Edit Blacklisted Titles allows you to removed titles blacklisted from being shown.
* Delete All Save Backups wipes JKSV's folder of all save backups.
5. Extras
* SD To SD Browser opens the filebrowser with your SD open in both panels
@ -75,7 +98,7 @@ JKSV on Switch started as a small project/port to test some things and get famil
## Building:
1. Requires [devkitPro](https://devkitpro.org/) and [libnx](https://github.com/switchbrew/libnx)
2. Requires switch-freetype, libpng, zlib, libjpeg-turbo, lib-curl, and lib-json-c
2. `dkp-pacman -S switch-curl switch-freetype switch-libjpeg-turbo switch-tinyxml2 switch-libjson-c switch-libpng switch-libwebp switch-sdl2 switch-sdl2_gfx switch-sdl2_image switch-zlib`
## Credits and Thanks:
* [shared-font](https://github.com/switchbrew/switch-portlibs-examples) example by yellows8 for loading system font with Freetype. All other font handling code (converting to SDL2, resizing on the fly, checking for glyphs, cache, etc) is my own.

58
REMOTE_INSTRUCTIONS.MD Normal file
View File

@ -0,0 +1,58 @@
# Remote Storage
⚠️ **WARNING** ⚠️: Breaking change with Version 07.27.2024. See [here](#remote-changelog)
## <a name="gdrive"></a><center> How to use Google Drive with JKSV </center>
**USING GOOGLE DRIVE WITH JKSV CURRENTLY REQUIRES BUILDING IT YOURSELF. I AM VERY BUSY LATELY AND THINGS WILL ONLY GET FINISHED WHEN I HAVE TIME. Thanks, sorry for yelling.**
**NOTE: As of Feb 2023, JKSV now uses the JSON downloaded from Google directly instead of editing JKSV's configuration file.**
**Google only allows unverified apps to have up to 100 test users. Not only would this limit be filled within minutes, but each user has to manually added. People have been asking for some kind of cloud support since I wrote JKSM on 3DS and this is my way to support it while getting around Google's restrictions.**
**Note: Due to Google's restrictions on unverified apps, you will be required to login once every seven days. There is nothing I can do about this at the moment.**
1. Go to https://console.cloud.google.com/cloud-resource-manager, if this is your first time, accept the terms and you should now have the dashboard in front of you.
2. Click `CREATE PROJECT` on the next screen.<br><center><img src="https://i.imgur.com/9SDS2e0.png" /></center>
3. On the next screen name your project JKSV. Organization is not required. Click create.
4. Give it a few seconds and the project should be created.
5. In the top left corner of your screen, click the navigation menu to the left of where it says **Google Cloud**. Find **Enabled APIs and services**. You may need to refresh the page if it doesn't update automatically to continue. <br><center><img src="https://i.imgur.com/JhqOpgc.png" /></center>
6. **Double check at this point to make 100% sure JKSV is the active project just in case it is not.** <br><center><img src="https://i.imgur.com/U49aIcb.png" /></center>
7. Once the dashboard loads, click **+ENABLE APIS AND SERVICES**. <br><center><img src="https://i.imgur.com/qaIhjID.png" /></center>
8. Scroll down a little and find Google Drive API under Google Workspace.<br><center><img src="https://i.imgur.com/cAC7h1r.png" /></center>
9. Click on it and click Enable on the next screen.
10. On the next screen, Google should be informing you that you need to create credentials in order to use Drive. Click Create Credentials.<br><center><img src="https://i.imgur.com/CRhFXQ4.png" /></center>
11. Under **Which API are you using?**, find **Cloud Storage API**. Under **What data will you be accessing?**, select **User data**. Click **Next**. <br><center><img src="https://i.imgur.com/fiulRpn.png" /></center>
12. Fill out the following screen. **Save and continue**.
13. Click **Add or Remove Scopes**.
14. Find **.../auth/drive** in the API list, select it, and click update. **Save and Continue**.
15. At this point you should be at a section named **OAuth Client ID**. Select **Desktop app**, name it **JKSV** and click **Create**.
16. Download the credentials saved in JSON format for later. Click **Done**.
17. Next, open the navigation menu in the top left again. Go down to **APIs and Services** and click on **OAuth consent screen**.<br><center><img src="https://i.imgur.com/OrMtG1x.png" /></center>
18. Scroll down to the section named **Test users**. Add yourself as a test user. This concludes the Google side of things.<br><center><img src="https://i.imgur.com/RTV2LMZ.png" /></center>
19. Next, find the JSON file you downloaded earlier. Copy it or send it via FTP to the following folder on your SD Card: `SD:/config/JKSV/`
20. The next time you start JKSV on your Switch, you should be prompted to login to Google via the Switch's web browser. Ta-da!
## <a name="webdav"></a><center> How to use WebDav with JKSV </center>
**NOTE: If you have [GDrive](#gdrive) configured (via JSON), it takes preference over WebDav**
1. Create a file `webdav.json` with the following content:
```json
{
"origin": "https://your-webdav-server",
"basepath": "optional-base-path",
"username": "testuser",
"password": "testpassword"
}
```
- `origin` (mandatory): protocol + serveraddress + (optional port), e.g. `https://your-webdav-server` or `http://localhost:8080` - **No trailing slash**
- `basepath` (optional): e.g. `dir`, `dir/subdir` must exist beforehand - **No leading AND trailing slash** - if your path uses special characters or spaces, they must be on URI encoding, by example `Game Saves` should be stored as `Game%20Saves`.
- `username` (optional): username, if server uses credentials
- `password` (optional): username, if server uses credentials
2. Copy file to following folder on your card `SD:/config/JKSV/`
3. The next time you start JKSV on your Switch, you should get a popup about the Webdav status
4. If problems arise, check the log at `SD:/JKSV/log.txt`
## Remote Changelog
- **07.27.2024**: **Breaking Change**. "Unsafe" characters were removed from titlename on the remote directory. That means,
that if you had existing safes under a title with unsafe characters, you will need to move them manually.

View File

@ -1,8 +1,9 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_map>
#include <vector>
namespace cfg
{
@ -17,21 +18,23 @@ namespace cfg
void loadConfig();
void saveConfig();
bool isBlacklisted(const uint64_t& tid);
bool isBlacklisted(uint64_t tid);
void addTitleToBlacklist(void *a);
void removeTitleFromBlacklist(const uint64_t& tid);
void removeTitleFromBlacklist(uint64_t tid);
bool isFavorite(const uint64_t& tid);
void addTitleToFavorites(const uint64_t& tid);
bool isFavorite(uint64_t tid);
void addTitleToFavorites(uint64_t tid);
bool isDefined(const uint64_t& tid);
void pathDefAdd(const uint64_t& tid, const std::string& newPath);
std::string getPathDefinition(const uint64_t& tid);
bool isDefined(uint64_t tid);
void pathDefAdd(uint64_t tid, const std::string &newPath);
std::string getPathDefinition(uint64_t tid);
void addPathToFilter(const uint64_t& tid, const std::string& _p);
void addPathToFilter(uint64_t tid, const std::string &_p);
extern std::unordered_map<std::string, bool> config;
extern std::vector<uint64_t> blacklist;
extern std::vector<uint64_t> favorites;
extern uint8_t sortType;
}
extern std::string driveClientID, driveClientSecret, driveRefreshToken;
extern std::string webdavOrigin, webdavBasePath, webdavUser, webdavPassword;
} // namespace cfg

View File

@ -1,10 +1,35 @@
#ifndef CURLFUNCS_H
#define CURLFUNCS_H
#pragma once
#include <string>
#include <vector>
std::string getJSONURL(std::vector<std::string> *headers, const std::string& _url);
bool getBinURL(std::vector<uint8_t> *out, const std::string& _url);
#define HEADER_ERROR "ERROR"
#endif // CURLFUNCS_H
namespace curlFuncs
{
typedef struct
{
FILE *f;
uint64_t *o;
} curlUpArgs;
typedef struct
{
std::string path;
unsigned int size;
uint64_t *o;
} curlDlArgs;
size_t writeDataString(const char *buff, size_t sz, size_t cnt, void *u);
size_t writeHeaders(const char *buff, size_t sz, size_t cnt, void *u);
size_t readDataFile(char *buff, size_t sz, size_t cnt, void *u);
size_t readDataBuffer(char *buff, size_t sz, size_t cnt, void *u);
size_t writeDataFile(const char *buff, size_t sz, size_t cnt, void *u);
size_t writeDataBuffer(const char *buff, size_t sz, size_t cnt, void *u);
std::string getHeader(const std::string& _name, std::vector<std::string> *h);
//Shortcuts/legacy
std::string getJSONURL(std::vector<std::string> *headers, const std::string& url);
bool getBinURL(std::vector<uint8_t> *out, const std::string& url);
}

View File

@ -1,89 +1,115 @@
#pragma once
#include <switch.h>
#include <vector>
#include <string>
#include <unordered_map>
#include <vector>
#include "gfx.h"
#define BLD_MON 9
#define BLD_DAY 1
#define BLD_YEAR 2021
#define BLD_MON 5
#define BLD_DAY 28
#define BLD_YEAR 2025
namespace data
{
extern bool forceMount;
//Loads user + title info
// Loads user + title info
void init();
void exit();
bool loadUsersTitles(bool clearUsers);
void sortUserTitles();
//Draws some stats to the upper left corner
// Draws some stats to the upper left corner
void dispStats();
//Global stuff for all titles/saves
// Global stuff for all titles/saves
typedef struct
{
NacpStruct nacp;
std::string title, safeTitle, author;//Shortcuts sorta.
SDL_Texture *icon;
bool fav;
/// @brief Control data.
NsApplicationControlData data;
/// @brief Saves whether or not the title has valid control data.
bool hasControlData = false;
std::string title, safeTitle, author;
SDL_Texture *icon = NULL;
bool fav;
} titleInfo;
//Holds stuff specific to user's titles/saves
// Holds stuff specific to user's titles/saves
typedef struct
{
//Makes it easier to grab id
uint64_t tid;
FsSaveDataInfo saveInfo;
PdmPlayStatistics playStats;
// Makes it easier to grab id
uint64_t tid;
FsSaveDataInfo saveInfo;
PdmPlayStatistics playStats;
} userTitleInfo;
//Class to store user info + titles
// Class to store user info + titles
class user
{
public:
user() = default;
user(const AccountUid& _id, const std::string& _backupName);
user(const AccountUid& _id, const std::string& _backupName, SDL_Texture *img);
user(AccountUid _id, const std::string &_backupName, const std::string &_safeBackupName);
user(AccountUid _id, const std::string &_backupName, const std::string &_safeBackupName, SDL_Texture *img);
//Sets ID
void setUID(const AccountUid& _id);
// Sets ID
void setUID(AccountUid _id);
//Assigns icon
void assignIcon(SDL_Texture *_icn) { userIcon = _icn; }
// Assigns icon
void assignIcon(SDL_Texture *_icn)
{
userIcon = _icn;
}
//Returns user ID
AccountUid getUID() const { return userID; }
u128 getUID128() const { return uID128; }
// Returns user ID
AccountUid getUID() const
{
return userID;
}
u128 getUID128() const
{
return uID128;
}
//Returns username
std::string getUsername() const { return username; }
std::string getUsernameSafe() const { return userSafe; }
// Returns username
std::string getUsername() const
{
return username;
}
std::string getUsernameSafe() const
{
return userSafe;
}
SDL_Texture *getUserIcon(){ return userIcon; }
void delIcon(){ SDL_DestroyTexture(userIcon); }
SDL_Texture *getUserIcon()
{
return userIcon;
}
void delIcon()
{
SDL_DestroyTexture(userIcon);
}
std::vector<data::userTitleInfo> titleInfo;
void addUserTitleInfo(const uint64_t& _tid, const FsSaveDataInfo *_saveInfo, const PdmPlayStatistics *_stats);
void addUserTitleInfo(const uint64_t &_tid,
const FsSaveDataInfo *_saveInfo,
const PdmPlayStatistics *_stats);
private:
AccountUid userID;
u128 uID128;
std::string username, userSafe;
//User icon
// User icon
SDL_Texture *userIcon;
};
//User vector
// User vector
extern std::vector<user> users;
//Title data/info map
// Title data/info map
extern std::unordered_map<uint64_t, data::titleInfo> titles;
//Sets/Retrieves current user/title
// Sets/Retrieves current user/title
void setUserIndex(unsigned _sUser);
data::user *getCurrentUser();
unsigned getCurrentUserIndex();
@ -92,13 +118,14 @@ namespace data
data::userTitleInfo *getCurrentUserTitleInfo();
unsigned getCurrentUserTitleInfoIndex();
//Gets pointer to info that also has title + nacp
data::titleInfo *getTitleInfoByTID(const uint64_t& tid);
// Gets pointer to info that also has title + nacp
data::titleInfo *getTitleInfoByTID(const uint64_t &tid);
//More shortcut functions
std::string getTitleNameByTID(const uint64_t& tid);
std::string getTitleSafeNameByTID(const uint64_t& tid);
SDL_Texture *getTitleIconByTID(const uint64_t& tid);
int getTitleIndexInUser(const data::user& u, const uint64_t& tid);
// More shortcut functions
std::string getTitleNameByTID(const uint64_t &tid);
std::string getTitleSafeNameByTID(const uint64_t &tid);
SDL_Texture *getTitleIconByTID(const uint64_t &tid);
int getTitleIndexInUser(const data::user &u, const uint64_t &tid);
extern SetLanguage sysLang;
}
} // namespace data

View File

@ -1,186 +0,0 @@
#pragma once
#include <string>
#include <cstdio>
#include <vector>
#include <switch.h>
#include <dirent.h>
#include <minizip/zip.h>
#include <minizip/unzip.h>
#include "fsfile.h"
#include "fsthrd.h"
#include "data.h"
#include "miscui.h"
#define BUFF_SIZE 0xC0000
namespace fs
{
void init();
void exit();
//Mounts usr's save data for open. Returns false it fails
bool mountSave(const FsSaveDataInfo& _m);
inline bool unmountSave() { return fsdevUnmountDevice("sv") == 0; }
bool commitToDevice(const std::string& dev);
void createSaveData(FsSaveDataType _type, uint64_t _tid, AccountUid _userID);
void copyFile(const std::string& from, const std::string& to);
void copyFileCommit(const std::string& from, const std::string& to, const std::string& dev);
//Recursively copies 'from' to 'to'
void copyDirToDir(const std::string& from, const std::string& to);
//Copies from to zipFile to
void copyDirToZip(const std::string& from, zipFile to);
//Same as above, but commits data to 'dev' after every file is closed
void copyDirToDirCommit(const std::string& from, const std::string& to, const std::string& dev);
//Copies unzfile to 'to'
void copyZipToDir(unzFile unz, const std::string& to, const std::string& dev);
bool dirNotEmpty(const std::string& _dir);
bool zipNotEmpty(unzFile unzip);
void mkDir(const std::string& _p);
void mkDirRec(const std::string& _p);
//deletes file
void delfile(const std::string& path);
//Recursively deletes 'path'
void delDir(const std::string& path);
//Loads paths to filter from backup/deletion
void loadPathFilters(const uint64_t& tid);
bool pathIsFiltered(const std::string& _path);
void freePathFilters();
void wipeSave();
//Dumps all titles for current user
void dumpAllUserSaves();
void getShowFileProps(const std::string& _path);
void getShowDirProps(const std::string& _path);
bool fileExists(const std::string& _path);
//Returns file size
size_t fsize(const std::string& _f);
bool isDir(const std::string& _path);
std::string getWorkDir();
void setWorkDir(const std::string& _w);
class dirItem
{
public:
dirItem(const std::string& pathTo, const std::string& sItem);
std::string getItm() const { return itm; }
std::string getName() const;
std::string getExt() const;
bool isDir() const { return dir; }
private:
std::string itm;
bool dir = false;
};
//Just retrieves a listing for _path and stores it in item vector
class dirList
{
public:
dirList() = default;
dirList(const std::string& _path);
void reassign(const std::string& _path);
void rescan();
std::string getItem(int index) const { return item[index].getItm(); }
std::string getItemExt(int index) const { return item[index].getExt(); }
bool isDir(int index) const { return item[index].isDir(); }
unsigned getCount() const { return item.size(); }
private:
DIR *d;
struct dirent *ent;
std::string path;
std::vector<dirItem> item;
};
class dataFile
{
public:
dataFile(const std::string& _path);
~dataFile();
void close(){ fclose(f); }
bool isOpen() const { return opened; }
bool readNextLine(bool proc);
//Finds where variable name ends. When a '(' or '=' is hit. Strips spaces
void procLine();
std::string getLine() const { return line; }
std::string getName() const { return name; }
//Reads until ';', ',', or '\n' is hit and returns as string.
std::string getNextValueStr();
int getNextValueInt();
private:
FILE *f;
std::string line, name;
size_t lPos = 0;
bool opened = false;
};
//Structs to send data to threads
typedef struct
{
ui::menu *m;
fs::dirList *d;
} backupArgs;
typedef struct
{
std::string to, from, dev;
zipFile z;
unzFile unz;
bool cleanup = false, trimZipPath = false;
uint8_t trimZipPlaces = 0;
uint64_t offset = 0;
ui::progBar *prog;
threadStatus *thrdStatus;
Mutex arglck = 0;
void argLock() { mutexLock(&arglck); }
void argUnlock() { mutexUnlock(&arglck); }
} copyArgs;
typedef struct
{
FsSaveDataType type;
uint64_t tid;
AccountUid account;
uint16_t index;
} svCreateArgs;
typedef struct
{
std::string path;
bool origin = false;
unsigned dirCount = 0;
unsigned fileCount = 0;
uint64_t totalSize = 0;
} dirCountArgs;
copyArgs *copyArgsCreate(const std::string& from, const std::string& to, const std::string& dev, zipFile z, unzFile unz, bool _cleanup, bool _trimZipPath, uint8_t _trimPlaces);
void copyArgsDestroy(copyArgs *c);
//Take a pointer to backupArgs^
void createNewBackup(void *a);
void overwriteBackup(void *a);
void restoreBackup(void *a);
void deleteBackup(void *a);
void logOpen();
void logWrite(const char *fmt, ...);
void logClose();
}

65
inc/fs.h Normal file
View File

@ -0,0 +1,65 @@
#pragma once
#include <minizip/unzip.h>
#include <minizip/zip.h>
#include "fs/dir.h"
#include "fs/file.h"
#include "fs/fsfile.h"
#include "fs/fstype.h"
#include "fs/remote.h"
#include "fs/zip.h"
#include "ui/miscui.h"
#define BUFF_SIZE 0x4000
#define ZIP_BUFF_SIZE 0x20000
#define TRANSFER_BUFFER_LIMIT 0xC00000
namespace fs
{
copyArgs *copyArgsCreate(const std::string &src,
const std::string &dst,
const std::string &dev,
zipFile z,
unzFile unz,
bool _cleanup,
bool _trimZipPath,
uint8_t _trimPlaces);
void copyArgsDestroy(copyArgs *c);
void init();
bool mountSave(const FsSaveDataInfo &_m);
inline bool unmountSave()
{
return fsdevUnmountDevice("sv") == 0;
}
bool commitToDevice(const std::string &dev);
std::string getWorkDir();
void setWorkDir(const std::string &_w);
//Loads paths to filter from backup/deletion
void loadPathFilters(uint64_t tid);
bool pathIsFiltered(const std::string &_path);
void freePathFilters();
void createSaveData(FsSaveDataType _type, uint64_t _tid, AccountUid _uid, threadInfo *t);
void createSaveDataThreaded(FsSaveDataType _type, uint64_t _tid, AccountUid _uid);
bool extendSaveData(const data::userTitleInfo *tinfo, uint64_t extSize, threadInfo *t);
void extendSaveDataThreaded(const data::userTitleInfo *tinfo, uint64_t extSize);
uint64_t getJournalSize(const data::userTitleInfo *tinfo);
uint64_t getJournalSizeMax(const data::userTitleInfo *tinfo);
//Always threaded
void wipeSave();
void createNewBackup(void *a);
void overwriteBackup(void *a);
void restoreBackup(void *a);
void deleteBackup(void *a);
void dumpAllUserSaves(void *a);
void dumpAllUsersAllSaves(void *a);
void logOpen();
void logWrite(const char *fmt, ...);
} // namespace fs

83
inc/fs/dir.h Normal file
View File

@ -0,0 +1,83 @@
#pragma once
#include "type.h"
#include <string>
#include <vector>
namespace fs
{
void mkDir(const std::string &_p);
void mkDirRec(const std::string &_p);
void delDir(const std::string &_p);
bool dirNotEmpty(const std::string &_dir);
bool isDir(const std::string &_path);
//threadInfo is optional. Only for updating task status.
void copyDirToDir(const std::string &src, const std::string &dst, threadInfo *t);
void copyDirToDirThreaded(const std::string &src, const std::string &dst);
void copyDirToDirCommit(const std::string &src, const std::string &dst, const std::string &dev, threadInfo *t);
void copyDirToDirCommitThreaded(const std::string &src, const std::string &dst, const std::string &dev);
void getDirProps(const std::string &path, unsigned &dirCount, unsigned &fileCount, uint64_t &totalSize);
class dirItem
{
public:
dirItem(const std::string &pathTo, const std::string &sItem);
std::string getItm() const
{
return itm;
}
std::string getName() const;
std::string getExt() const;
bool isDir() const
{
return dir;
}
private:
std::string itm;
bool dir = false;
};
//Just retrieves a listing for _path and stores it in item vector
class dirList
{
public:
dirList() = default;
dirList(const std::string &_path, bool ignoreDotFiles = false);
void reassign(const std::string &_path);
void rescan();
std::string getItem(int index) const
{
return item[index].getItm();
}
std::string getItemExt(int index) const
{
return item[index].getExt();
}
bool isDir(int index) const
{
return item[index].isDir();
}
unsigned getCount() const
{
return item.size();
}
fs::dirItem *getDirItemAt(unsigned int _ind)
{
return &item[_ind];
}
private:
std::string path;
std::vector<dirItem> item;
};
} // namespace fs

65
inc/fs/file.h Normal file
View File

@ -0,0 +1,65 @@
#pragma once
#include <string>
#include <cstdio>
#include <vector>
#include <switch.h>
#include <dirent.h>
#include <minizip/zip.h>
#include <minizip/unzip.h>
#include "fs.h"
#include "data.h"
#include "ui.h"
namespace fs
{
//Copy args are optional and only used if passed and threaded
void copyFile(const std::string& src, const std::string& dst, threadInfo *t);
void copyFileThreaded(const std::string& src, const std::string& dst);
void copyFileCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t);
void copyFileCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev);
void fileDrawFunc(void *a);
//deletes file
void delfile(const std::string& _p);
//Dumps all titles for current user
void dumpAllUserSaves();
void getShowFileProps(const std::string& _path);
void getShowDirProps(const std::string& _path);
bool fileExists(const std::string& _path);
//Returns file size
size_t fsize(const std::string& _f);
class dataFile
{
public:
dataFile(const std::string& _path);
~dataFile();
void close(){ fclose(f); }
bool isOpen() const { return opened; }
bool readNextLine(bool proc);
//Finds where variable name ends. When a '(' or '=' is hit. Strips spaces
void procLine();
std::string getLine() const { return line; }
std::string getName() const { return name; }
//Reads until ';', ',', or '\n' is hit and returns as string.
std::string getNextValueStr();
int getNextValueInt();
private:
FILE *f;
std::string line, name;
size_t lPos = 0;
bool opened = false;
};
void logOpen();
void logWrite(const char *fmt, ...);
void logClose();
}

46
inc/fs/fstype.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include "data.h"
#include "miscui.h"
#include "type.h"
namespace fs
{
typedef struct
{
std::string src, dst, dev;
zipFile z;
unzFile unz;
bool cleanup = false, trimZipPath = false;
uint8_t trimZipPlaces = 0;
uint64_t offset = 0;
ui::progBar *prog;
threadStatus *thrdStatus;
Mutex arglck = 0;
void argLock() { mutexLock(&arglck); }
void argUnlock() { mutexUnlock(&arglck); }
} copyArgs;
typedef struct
{
FsSaveDataType type;
uint64_t tid;
AccountUid account;
uint16_t index;
} svCreateArgs;
typedef struct
{
const data::userTitleInfo *tinfo;
uint64_t extSize;
} svExtendArgs;
typedef struct
{
std::string path;
bool origin = false;
unsigned dirCount = 0;
unsigned fileCount = 0;
uint64_t totalSize = 0;
} dirCountArgs;
}

21
inc/fs/remote.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "../rfs.h"
#define JKSV_DRIVE_FOLDER "JKSV"
namespace fs
{
extern rfs::IRemoteFS *rfs;
extern std::string rfsRootID;
void remoteInit();
void remoteExit();
// Google Drive
void driveInit();
std::string driveSignInGetAuthCode();
// Webdav
void webDavInit();
}

18
inc/fs/zip.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <minizip/zip.h>
#include <minizip/unzip.h>
#include "type.h"
namespace fs
{
//threadInfo is optional and only used when threaded versions are used
void copyDirToZip(const std::string& src, zipFile dst, bool trimPath, int trimPlaces, threadInfo *t);
void copyDirToZipThreaded(const std::string& src, zipFile dst, bool trimPath, int trimPlaces);
void copyZipToDir(unzFile src, const std::string& dst, const std::string& dev, threadInfo *t);
void copyZipToDirThreaded(unzFile src, const std::string& dst, const std::string& dev);
uint64_t getZipTotalSize(unzFile unz);
bool zipNotEmpty(unzFile unz);
}

View File

@ -1,21 +0,0 @@
#pragma once
/*Threaded functions for copying files.
file.cpp has wrapper functions for these.
No need to call directly.*/
namespace fs
{
void _fileDrawFunc(void *a);
void createSaveData_t(void *a);
void copyFile_t(void *a);
void copyFileCommit_t(void *a);
void copyDirToDir_t(void *a);
void copyDirToDirCommit_t(void *a);
void copyDirToZip_t(void *a);
void copyZipToDir_t(void *a);
void wipesave_t(void *a);
void closeZip_t(void *a);
void backupUserSaves_t(void *a);
void getShowDirProps_t(void *a);
}

66
inc/gd.h Normal file
View File

@ -0,0 +1,66 @@
#pragma once
#include <curl/curl.h>
#include <json-c/json.h>
#include <string>
#include <vector>
#include <unordered_map>
#include "curlfuncs.h"
#include "rfs.h"
#define HEADER_CONTENT_TYPE_APP_JSON "Content-Type: application/json; charset=UTF-8"
#define HEADER_AUTHORIZATION "Authorization: Bearer "
#define MIMETYPE_FOLDER "application/vnd.google-apps.folder"
namespace drive
{
class gd : public rfs::IRemoteFS
{
public:
void setClientID(const std::string& _clientID) { clientID = _clientID; }
void setClientSecret(const std::string& _clientSecret) { secretID = _clientSecret; }
void setRefreshToken(const std::string& _refreshToken) { rToken = _refreshToken; }
bool exhangeAuthCode(const std::string& _authCode);
bool hasToken() { return token.empty() == false; }
bool refreshToken();
bool tokenIsValid();
void clearDriveList() { driveList.clear(); }
// TODO: This also gets files that do not belong to JKSV
void driveListInit(const std::string& _q);
void driveListAppend(const std::string& _q);
std::vector<rfs::RfsItem> getListWithParent(const std::string& _parent);
void debugWriteList();
bool createDir(const std::string& _dirName, const std::string& _parent);
// TODO: This is problematic, because multiple files could share the same name without a parent.
bool dirExists(const std::string& _dirName);
bool dirExists(const std::string& _dirName, const std::string& _parent);
bool fileExists(const std::string& _filename, const std::string& _parent);
void uploadFile(const std::string& _filename, const std::string& _parent, curlFuncs::curlUpArgs *_upload);
void updateFile(const std::string& _fileID, curlFuncs::curlUpArgs *_upload);
void downloadFile(const std::string& _fileID, curlFuncs::curlDlArgs *_download);
void deleteFile(const std::string& _fileID);
std::string getClientID() const { return clientID; }
std::string getClientSecret() const { return secretID; }
std::string getRefreshToken() const { return rToken; }
std::string getFileID(const std::string& _name, const std::string& _parent);
// TODO: This is problematic, because multiple files could share the same name without a parent.
std::string getDirID(const std::string& _name);
std::string getDirID(const std::string& _name, const std::string& _parent);
size_t getDriveListCount() const { return driveList.size(); }
rfs::RfsItem *getItemAt(unsigned int _ind) { return &driveList[_ind]; }
private:
std::vector<rfs::RfsItem> driveList;
std::string clientID, secretID, token, rToken;
};
}

View File

@ -1,18 +1,17 @@
#pragma once
#include <SDL.h>
#include <SDL2/SDL.h>
#include "textureMgr.h"
namespace gfx
{
extern SDL_Renderer *render;
extern gfx::textureMgr *texMgr;
void init();
void exit();
void present();
SDL_Texture *loadJPEGMem(const void *jpegData, size_t jpegSize);
SDL_Texture *loadImageFile(const char *file);
void drawTextf(SDL_Texture *target, int fontSize, int x, int y, const SDL_Color *c, const char *fmt, ...);
void drawTextfWrap(SDL_Texture *target, int fontSize, int x, int y, int maxWidth, const SDL_Color* c, const char *fmt, ...);
size_t getTextWidth(const char *str, int fontSize);

33
inc/gfx/textureMgr.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <vector>
#include <SDL2/SDL.h>
/*Texture manager class
Keeps pointers to ALL textures so it is not possible to forget to free them. Also keeps code a little easier to read. A little.*/
typedef enum
{
IMG_FMT_PNG,
IMG_FMT_JPG,
IMG_FMT_BMP
} imgTypes;
namespace gfx
{
class textureMgr
{
public:
textureMgr() = default;
~textureMgr();
void textureAdd(SDL_Texture *_tex);
SDL_Texture *textureCreate(int _w, int _h);
SDL_Texture *textureLoadFromFile(const char *_path);
SDL_Texture *textureLoadFromMem(imgTypes _type, const void *_dat, size_t _datSize);
void textureResize(SDL_Texture **_tex, int _w, int _h);
private:
std::vector<SDL_Texture *> textures;
};
}

53
inc/rfs.h Normal file
View File

@ -0,0 +1,53 @@
#pragma once
#include <string>
#include "curlfuncs.h"
#include <mutex>
#define UPLOAD_BUFFER_SIZE 0x8000
#define DOWNLOAD_BUFFER_SIZE 0xC00000
#define USER_AGENT "JKSV"
namespace rfs {
typedef struct
{
std::string name, id, parent;
bool isDir = false;
unsigned int size;
} RfsItem;
class IRemoteFS
{
public:
virtual ~IRemoteFS() {} // Virtual destructor to allow correct deletion through the base class pointer
virtual bool createDir(const std::string& _dirName, const std::string& _parent) = 0;
virtual bool dirExists(const std::string& _dirName, const std::string& _parent) = 0;
virtual bool fileExists(const std::string& _filename, const std::string& _parent) = 0;
virtual void uploadFile(const std::string& _filename, const std::string& _parent, curlFuncs::curlUpArgs *_upload) = 0;
virtual void updateFile(const std::string& _fileID, curlFuncs::curlUpArgs *_upload) = 0;
virtual void downloadFile(const std::string& _fileID, curlFuncs::curlDlArgs *_download) = 0;
virtual void deleteFile(const std::string& _fileID) = 0;
virtual std::string getFileID(const std::string& _name, const std::string& _parent) = 0;
virtual std::string getDirID(const std::string& _name, const std::string& _parent) = 0;
virtual std::vector<RfsItem> getListWithParent(const std::string& _parent) = 0;
};
// Shared multi-threading definitions
typedef struct
{
curlFuncs::curlDlArgs *cfa;
std::mutex dataLock;
std::condition_variable cond;
std::vector<uint8_t> sharedBuffer;
bool bufferFull = false;
unsigned int downloaded = 0;
} dlWriteThreadStruct;
extern std::vector<uint8_t> downloadBuffer;
void writeThread_t(void *a);
size_t writeDataBufferThreaded(uint8_t *buff, size_t sz, size_t cnt, void *u);
}

View File

@ -15,6 +15,7 @@
#include "ui/sldpanel.h"
#include "ui/usr.h"
#include "ui/ttl.h"
#include "ui/fld.h"
#include "ui/sett.h"
#include "ui/ext.h"
#include "ui/fm.h"
@ -47,7 +48,6 @@ namespace ui
inline uint64_t padKeysDown() { return padGetButtonsDown(&pad); }
inline uint64_t padKeysHeld() { return padGetButtons(&pad); }
inline uint64_t padKeysUp() { return padGetButtonsUp(&pad); }
inline void touchGetXY(uint32_t *x, uint32_t *y) { *x = touchState.touches[0].x; *y = touchState.touches[0].y; }
inline void changeState(int newState)
{
@ -86,6 +86,9 @@ namespace ui
void init();
void exit();
//Inits HID
void hidInit();
//Adds a panel pointer to a vector since they need to be drawn over everything else
int registerMenu(ui::menu *m);
int registerPanel(ui::slideOutPanel *sop);

19
inc/ui/fld.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include "ui.h"
#include "dir.h"
namespace ui
{
//extern ui::menu *fldMenu;
extern ui::slideOutPanel *fldPanel;
//extern fs::dirList *fldList;
void fldInit();
void fldExit();
void fldUpdate();
//Populate to open menu, refresh for updating after actions
void fldPopulateMenu();
void fldRefreshMenu();
}

View File

@ -4,7 +4,7 @@ namespace ui
{
void fmInit();
void fmExit();
void fmPrep(const FsSaveDataType& _type, const std::string& _dev, bool _commit);
void fmPrep(FsSaveDataType _type, const std::string &_dev, const std::string &_baseSDMC, bool _commit);
void fmUpdate();
void fmDraw(SDL_Texture *target);
}
} // namespace ui

View File

@ -1,10 +1,10 @@
#pragma once
#include <SDL2/SDL.h>
#include <vector>
#include <SDL.h>
#include "type.h"
#include "gfx.h"
#include "type.h"
#define POP_FRAME_DEFAULT 130
@ -26,55 +26,60 @@ namespace ui
{
typedef struct
{
std::string text;
bool hold;
bool cleanup;
funcPtr func;
void *args;
//Stuff needed to keep track
bool sel = 1;//1 = YES
unsigned lgFrame = 0, frameCount = 0;//To count frames cause I don't have time and am lazy
std::string text;
bool hold;
funcPtr confFunc, cancelFunc;
void *args;
unsigned lgFrame = 0, frameCount = 0; //To count frames cause I don't have time and am lazy
} confirmArgs;
typedef struct
{
funcPtr func = NULL;
void *args = NULL;
HidNpadButton button = (HidNpadButton)0;
funcPtr func = NULL;
void *args = NULL;
HidNpadButton button = (HidNpadButton)0;
} menuOptEvent;
typedef struct
{
SDL_Texture *icn = NULL;
std::string txt;
int txtWidth;
std::vector<menuOptEvent> events;
SDL_Texture *icn = NULL;
std::string txt;
int txtWidth;
std::vector<menuOptEvent> events;
} menuOpt;
class menu
{
public:
menu() = default;
menu(const int& _x, const int& _y, const int& _rW, const int& _fS, const int& _mL);
menu(int _x, int _y, int _rW, int _fS, int _mL);
void editParam(int _param, int newVal);
//Gets executed when menu changes at all
void setOnChangeFunc(funcPtr func) { onChange = func; }
void setOnChangeFunc(funcPtr func)
{
onChange = func;
}
//executed when .update() is called.
void setCallback(funcPtr _callback, void *args) { callback = _callback; callbackArgs = args; }
void setCallback(funcPtr _callback, void *args)
{
callback = _callback;
callbackArgs = args;
}
//Adds option.
int addOpt(SDL_Texture *_icn, const std::string& add);
int addOpt(SDL_Texture *_icn, const std::string &add);
//Adds an function to be executed on pressing button specified
void optAddButtonEvent(unsigned _ind, HidNpadButton _button, funcPtr _func, void *args);
//Changes opt text
void editOpt(int ind, SDL_Texture *_icn, const std::string& ch);
size_t getOptCount() { return opt.size(); }
int getOptPos(const std::string& txt);
void editOpt(int ind, SDL_Texture *_icn, const std::string &ch);
size_t getOptCount()
{
return opt.size();
}
int getOptPos(const std::string &txt);
//Clears menu stuff
~menu();
@ -83,10 +88,16 @@ namespace ui
void update();
//Returns selected option
int getSelected() { return selected; }
int getSelected()
{
return selected;
}
//Returns menu option count
int getCount() { return opt.size(); }
int getCount()
{
return opt.size();
}
//Draws the menu at x and y. rectWidth is the width of the rectangle drawn under the selected
void draw(SDL_Texture *target, const SDL_Color *textClr, bool drawText);
@ -95,11 +106,17 @@ namespace ui
void reset();
//Resets selected + start
void resetSel() { selected = 0; }
void resetSel()
{
selected = 0;
}
//Enables control/disables drawing select box
void setActive(bool _set);
bool getActive() { return isActive; }
bool getActive()
{
return isActive;
}
private:
//drawing x and y + rectangle width/height. Height is calc'd with font size
@ -123,15 +140,18 @@ namespace ui
public:
//Constructor. _max is the maximum value
progBar() = default;
progBar(const uint64_t& _max) { max = _max; }
progBar(uint64_t _max) : max(_max) {};
void setMax(const uint64_t& _max) { max = _max; };
void setMax(uint64_t _max)
{
max = _max;
}
//Updates progress
void update(const uint64_t& _prog);
void update(uint64_t _prog);
//Draws with text at top
void draw(const std::string& text);
void draw(const std::string &text);
private:
uint64_t max = 0, prog = 0;
@ -140,8 +160,8 @@ namespace ui
typedef struct
{
std::string message;
int rectWidth = 0, frames = 0, y = 720;
std::string message;
int rectWidth = 0, frames = 0, y = 720;
} popMessage;
class popMessageMngr
@ -151,20 +171,26 @@ namespace ui
void update();
void popMessageAdd(const std::string& mess, int frameTime);
void popMessageAdd(const std::string &mess, int frameTime);
void draw();
private:
std::vector<popMessage> popQueue;//All graphics need to be on main thread. Directly adding will cause text issues
std::vector<popMessage>
popQueue; //All graphics need to be on main thread. Directly adding will cause text issues
std::vector<popMessage> message;
};
//General use
ui::confirmArgs *confirmArgsCreate(bool _hold, funcPtr _func, void *_funcArgs, bool _cleanup, const char *fmt, ...);
ui::confirmArgs *confirmArgsCreate(bool _hold,
funcPtr _confFunc,
funcPtr _cancelFunc,
void *_funcArgs,
const char *fmt,
...);
void confirm(void *a);
void showMessage(const char *fmt, ...);
bool confirmTransfer(const std::string& f, const std::string& t);
bool confirmDelete(const std::string& p);
bool confirmTransfer(const std::string &f, const std::string &t);
bool confirmDelete(const std::string &p);
void drawBoundBox(SDL_Texture *target, int x, int y, int w, int h, uint8_t clrSh);
void drawTextbox(SDL_Texture *target, int x, int y, int w, int h);
}
} // namespace ui

View File

@ -1,6 +1,6 @@
#pragma once
#include <SDL.h>
#include <SDL2/SDL.h>
namespace ui
{

View File

@ -14,7 +14,6 @@ namespace ui
{
public:
slideOutPanel(int _w, int _h, int _y, slidePanelOrientation _side, funcPtr _draw);
~slideOutPanel();
void resizePanel(int _w, int _h, int _y);
void update();

View File

@ -4,9 +4,8 @@ namespace ui
{
void ttlInit();
void ttlExit();
void ttlSetActive(int usr);
void ttlSetActive(int usr, bool _set, bool _showSel);
void ttlRefresh();
void populateFldMenu();
//JIC for func ptr
void ttlReset();

View File

@ -1,6 +1,6 @@
#pragma once
#include <SDL.h>
#include <SDL2/SDL.h>
#include "type.h"
#include "data.h"

View File

@ -7,7 +7,7 @@ namespace ui
{
void initStrings();
void loadTrans();
void saveTranslationFile(void *a);
void saveTranslationFiles(void *a);
extern std::map<std::pair<std::string, int>, std::string> strings;
inline std::string getUIString(const std::string& _name, int ind){ return strings[std::make_pair(_name, ind)]; }

View File

@ -1,10 +1,9 @@
#ifndef UTIL_H
#define UTIL_H
#pragma once
#include "data.h"
#include "ui.h"
#include "file.h"
#include "gfx.h"
#include "ui.h"
namespace util
{
@ -37,41 +36,74 @@ namespace util
CPU_SPEED_1785MHz = 1785000000
} cpuSpds;
typedef enum
{
GPU_SPEED_0MHz = 0,
GPU_SPEED_76MHz = 76800000,
GPU_SPEED_153MHz = 153600000,
GPU_SPEED_203MHz = 230400000,
GPU_SPEED_307MHz = 307200000, //Handheld 1
GPU_SPEED_384MHz = 384000000, //Handheld 2
GPU_SPEED_460MHz = 460800000,
GPU_SPEED_537MHz = 537600000,
GPU_SPEED_614MHz = 614400000,
GPU_SPEED_768MHz = 768000000, //Docked
GPU_SPEED_844MHz = 844800000,
GPU_SPEED_921MHZ = 921600000
} gpuSpds;
typedef enum
{
RAM_SPEED_0MHz = 0,
RAM_SPEED_40MHz = 40800000,
RAM_SPEED_68MHz = 68000000,
RAM_SPEED_102MHz = 102000000,
RAM_SPEED_204MHz = 204000000,
RAM_SPEED_408MHz = 408000000,
RAM_SPEED_665MHz = 665600000,
RAM_SPEED_800MHz = 800000000,
RAM_SPEED_1065MHz = 1065600000,
RAM_SPEED_1331MHz = 1331200000,
RAM_SPEED_1600MHz = 1600000000
} ramSpds;
//Returns string with date S+ time
std::string getDateTime(int fmt);
//Copys dir list to a menu with 'D: ' + 'F: '
void copyDirListToMenu(const fs::dirList& d, ui::menu& m);
void copyDirListToMenu(const fs::dirList &d, ui::menu &m);
//Removes last folder from '_path'
void removeLastFolderFromString(std::string& _path);
size_t getTotalPlacesInPath(const std::string& _path);
void trimPath(std::string& _path, uint8_t _places);
void removeLastFolderFromString(std::string &_path);
size_t getTotalPlacesInPath(const std::string &_path);
void trimPath(std::string &_path, uint8_t _places);
std::string safeString(const std::string& s);
std::string getInfoString(data::user& u, const uint64_t& tid);
std::string getStringInput(SwkbdType _type, const std::string& def, const std::string& head, size_t maxLength, unsigned dictCnt, const std::string dictWords[]);
inline std::string getExtensionFromString(const std::string& get)
inline bool isASCII(uint32_t t)
{
size_t ext = get.find_last_of('.');
if(ext != get.npos)
return get.substr(ext + 1, get.npos);
else
return "";
return t > 30 && t < 127;
}
std::string generateAbbrev(const uint64_t& tid);
std::string safeString(const std::string &s);
std::string getStringInput(SwkbdType _type,
const std::string &def,
const std::string &head,
size_t maxLength,
unsigned dictCnt,
const std::string dictWords[]);
std::string getExtensionFromString(const std::string &get);
std::string getFilenameFromPath(const std::string &get);
std::string generateAbbrev(uint64_t tid);
//removes char from C++ string
void stripChar(char _c, std::string& _s);
void stripChar(char _c, std::string &_s);
void replaceStr(std::string& _str, const std::string& _find, const std::string& _rep);
void replaceStr(std::string &_str, const std::string &_find, const std::string &_rep);
//For future external translation support. Replaces [button] with button chars
void replaceButtonsInString(std::string& rep);
void replaceButtonsInString(std::string &rep);
//Creates a basic generic icon for stuff without one
SDL_Texture *createIconGeneric(const char *txt, int fontSize, bool clearBack);
@ -89,36 +121,41 @@ namespace util
return ret;
}
inline std::string getIDStr(const uint64_t& _id)
inline std::string getIDStr(uint64_t _id)
{
char tmp[18];
sprintf(tmp, "%016lX", _id);
return std::string(tmp);
}
inline std::string getIDStrLower(const uint64_t& _id)
inline std::string getIDStrLower(uint64_t _id)
{
char tmp[18];
sprintf(tmp, "%08X", (uint32_t)_id);
return std::string(tmp);
}
inline std::string generatePathByTID(const uint64_t& tid)
inline std::string generatePathByTID(uint64_t tid)
{
return fs::getWorkDir() + data::getTitleSafeNameByTID(tid) + "/";
}
std::string getSizeString(const uint64_t& _size);
std::string getSizeString(uint64_t _size);
inline void createTitleDirectoryByTID(const uint64_t& tid)
inline void createTitleDirectoryByTID(uint64_t tid)
{
std::string makePath = fs::getWorkDir() + data::getTitleSafeNameByTID(tid);
mkdir(makePath.c_str(), 777);
}
Result accountDeleteUser(AccountUid *uid);
void sysBoost();
void sysNormal();
inline bool isApplet()
{
AppletType type = appletGetAppletType();
return type == AppletType_LibraryApplet;
}
void setCPU(uint32_t hz);
void checkForUpdate(void *a);
}
#endif // UTIL_H
} // namespace util

53
inc/webdav.h Normal file
View File

@ -0,0 +1,53 @@
#pragma once
#include <curl/curl.h>
#include <string>
#include <tinyxml2.h>
#include "rfs.h"
namespace rfs {
// Note: Everything declared an "id" is the full path component from the origin to the resource starting with a "/".
// Note: Directories ALWAYS have a trailing / while files NEVER have a trailing /
// e.g. /<basePath>/JKSV/
// e.g. /<basePath>/JKSV/<title-id>/
// e.g. /<basePath>/JKSV/<title-id>/<file>
// e.g. /
// other string arguments never have any leading or trailing "/"
class WebDav : public IRemoteFS {
private:
CURL* curl;
std::string origin;
std::string basePath;
std::string username;
std::string password;
std::vector<RfsItem> parseXMLResponse(const std::string& xml);
bool resourceExists(const std::string& id);
std::string appendResourceToParentId(const std::string& resourceName, const std::string& parentId, bool isDir);
std::string getNamespacePrefix(tinyxml2::XMLElement* root, const std::string& nsURI);
public:
WebDav(const std::string& origin,
const std::string& username,
const std::string& password);
~WebDav();
bool createDir(const std::string& dirName, const std::string& parentId);
bool dirExists(const std::string& dirName, const std::string& parentId);
bool fileExists(const std::string& filename, const std::string& parentId);
void uploadFile(const std::string& filename, const std::string& parentId, curlFuncs::curlUpArgs *_upload);
void updateFile(const std::string& fileID, curlFuncs::curlUpArgs *_upload);
void downloadFile(const std::string& fileID, curlFuncs::curlDlArgs *_download);
void deleteFile(const std::string& fileID);
std::string getFileID(const std::string& name, const std::string& parentId);
std::string getDirID(const std::string& dirName, const std::string& parentId);
std::vector<RfsItem> getListWithParent(const std::string& _parent);
std::string getDisplayNameFromURL(const std::string &url);
};
}

View File

@ -1,2 +1,171 @@
#Deutsch translation file
#Copy en-US.txt here and use as a guide.
author = 0, "NULL"
confirmBlacklist = 0, "Are you sure you want to add #%s# to your blacklist?"
confirmCopy = 0, "Are you sure you want to copy #%s# to #%s#?"
confirmCreateAllSaveData = 0, "Are you sure you would like to create all save data on this system for #%s#? This can take a while depending on how many titles are found."
confirmDelete = 0, "Are you sure you want to delete #%s#? *This is permanent*!"
confirmDeleteBackupsAll = 0, "Are you sure you would like to delete *all* of your save backups for all of your games?"
confirmDeleteBackupsTitle = 0, "Are you sure you would like to delete all save backups for #%s#?"
confirmDeleteSaveData = 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"
confirmDriveOverwrite = 0, "Downloading this backup from drive will overwrite the one on your SD card. Continue?"
confirmOverwrite = 0, "Are you sure you want to overwrite #%s#?"
confirmResetSaveData = 0, "*WARNING*: This *will* reset the save data for this game as if it was never ran before. Are you sure you want to do this?"
confirmRestore = 0, "Are you sure you want to restore #%s#?"
debugStatus = 0, "User Count: "
debugStatus = 1, "Current User: "
debugStatus = 2, "Current Title: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "Sort Type: "
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Yes [A]"
extrasMenu = 0, "SD to SD Browser"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Remove Pending Update"
extrasMenu = 6, "Terminate Process"
extrasMenu = 7, "Mount System Save"
extrasMenu = 8, "Rescan Titles"
extrasMenu = 9, "Mount Process RomFS"
extrasMenu = 10, "Backup JKSV Folder"
extrasMenu = 11, "*[DEV]* Output en-US"
fileModeFileProperties = 0, "Path: %s\nSize: %s"
fileModeFolderProperties = 0, "Path: %s\nSub Folders: %u\nFile Count: %u\nTotal Size: %s"
fileModeMenu = 0, "Copy To "
fileModeMenu = 1, "Delete"
fileModeMenu = 2, "Rename"
fileModeMenu = 3, "Make Dir"
fileModeMenu = 4, "Properties"
fileModeMenu = 5, "Close"
fileModeMenu = 6, "Add to Path Filters"
fileModeMenuMkDir = 0, "New"
folderMenuNew = 0, "New Backup"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close"
helpSettings = 0, "[A] Toggle [X] Defaults [B] Back"
helpTitle = 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back"
helpUser = 0, "[A] Select [Y] Dump All Saves [X] User Options"
holdingText = 0, "(Hold) "
holdingText = 1, "(Keep Holding) "
holdingText = 2, "(Almost There!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Play Time: %02d:%02d"
infoStatus = 3, "Total Launches: %u"
infoStatus = 4, "Publisher: %s"
infoStatus = 5, "Save Type: %s"
infoStatus = 6, "Cache Index: %u"
infoStatus = 7, "User: %s"
infoStatus = 9, ""
loadingStartPage = 0, "Loading..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Settings"
onlineErrorConnecting = 0, "Error Connecting!"
onlineNoUpdates = 0, "No Updates Available."
popAddedToPathFilter = 0, "'#%s#' added to path filters."
popCPUBoostEnabled = 0, "CPU Boost Enabled for ZIP."
popChangeOutputError = 0, "#%s# contains illegal or non-ASCII characters."
popChangeOutputFolder = 0, "#%s# changed to #%s#"
popDriveFailed = 0, "Failed to start Google Drive."
popRemoteNotActive = 0, "Remote is not available"
popDriveStarted = 0, "Google Drive started successfully."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Error committing file to save!"
popFolderIsEmpty = 0, "Folder is empty!"
popProcessShutdown = 0, "#%s# successfully shutdown."
popSVIExported = 0, "SVI Exported."
popSaveIsEmpty = 0, "Save data is empty!"
popTrashEmptied = 0, "Trash emptied"
popZipIsEmpty = 0, "ZIP file is empty!"
saveDataBackupDeleted = 0, "#%s# has been deleted."
saveDataBackupMovedToTrash = 0, "#%s# has been moved to trash."
saveDataCreatedForUser = 0, "Save data created for %s!"
saveDataCreationFailed = 0, "Save data creation failed!"
saveDataDeleteAllUser = 0, "*ARE YOU SURE YOU WANT TO DELETE ALL SAVE DATA FOR %s?*"
saveDataDeleteSuccess = 0, "Save data for #%s# deleted!"
saveDataExtendFailed = 0, "Failed to extend save data."
saveDataExtendSuccess = 0, "Save data for #%s# extended!"
saveDataNoneFound = 0, "No saves found for #%s#!"
saveDataResetSuccess = 0, "Save for #%s# reset!"
saveDataTypeText = 0, "System"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Device"
saveDataTypeText = 4, "Temporary"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Empty Trash Bin"
settingsMenu = 1, "Check for Updates"
settingsMenu = 2, "Set JKSV Save Output Folder"
settingsMenu = 3, "Edit Blacklisted Titles"
settingsMenu = 4, "Delete All Save Backups"
settingsMenu = 5, "Include Device Saves With Users: "
settingsMenu = 6, "Auto Backup On Restore: "
settingsMenu = 7, "Auto-Name Backups: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Hold To Delete: "
settingsMenu = 10, "Hold To Restore: "
settingsMenu = 11, "Hold To Overwrite: "
settingsMenu = 12, "Force Mount: "
settingsMenu = 13, "Account System Saves: "
settingsMenu = 14, "Enable Writing to System Saves: "
settingsMenu = 15, "Use FS Commands Directly: "
settingsMenu = 16, "Export Saves to ZIP: "
settingsMenu = 17, "Force English To Be Used: "
settingsMenu = 18, "Enable Trash Bin: "
settingsMenu = 19, "Title Sorting Type: "
settingsMenu = 20, "Animation Scale: "
settingsOff = 0, "Off"
settingsOn = 0, ">On>"
sortType = 0, "Alphabetical"
sortType = 1, "Time Played"
sortType = 2, "Last Played"
swkbdEnterName = 0, "Enter a new name"
swkbdExpandSize = 0, "Enter New Size in MB"
swkbdMkDir = 0, "Enter a folder name"
swkbdNewSafeTitle = 0, "Input New Output Folder"
swkbdProcessID = 0, "Enter Process ID"
swkbdRename = 0, "Enter a new name for item"
swkbdSaveIndex = 0, "Enter Cache Index"
swkbdSetWorkDir = 0, "Enter a new Output Path"
swkbdSysSavID = 0, "Enter System Save ID"
threadStatusAddingFileToZip = 0, "Adding '#%s#' to ZIP..."
threadStatusCalculatingSaveSize = 0, "Calculating save data size..."
threadStatusCheckingForUpdate = 0, "Checking for updates..."
threadStatusCompressingSaveForUpload = 0, "Compressing #%s# for upload..."
threadStatusCopyingFile = 0, "Copying '#%s#'..."
threadStatusCreatingSaveData = 0, "Creating Save Data for #%s#..."
threadStatusDecompressingFile = 0, "Decompressing '#%s#'..."
threadStatusDeletingFile = 0, "Deleting..."
threadStatusDeletingSaveData = 0, "Deleting Save Data for #%s#..."
threadStatusDeletingUpdate = 0, "Deleting pending update..."
threadStatusDownloadingFile = 0, "Downloading #%s#..."
threadStatusDownloadingUpdate = 0, "Downloading update..."
threadStatusExtendingSaveData = 0, "Extending Save Data for #%s#..."
threadStatusGetDirProps = 0, "Getting Folder Properties..."
threadStatusOpeningFolder = 0, "Opening '#%s#'..."
threadStatusPackingJKSV = 0, "Writing JKSV folder contents to ZIP..."
threadStatusResettingSaveData = 0, "Resetting save data..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "Uploading #%s#..."
titleOptions = 0, "Information"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Change Output Folder"
titleOptions = 3, "Open in File Mode"
titleOptions = 4, "Delete All Save Backups"
titleOptions = 5, "Reset Save Data"
titleOptions = 6, "Delete Save Data"
titleOptions = 7, "Extend Save Data"
titleOptions = 8, "Export SVI"
translationMainPage = 0, "Translation: "
userOptions = 0, "Dump All For "
userOptions = 1, "Create Save Data"
userOptions = 2, "Create All Save Data"
userOptions = 3, "Delete All User Saves"

View File

@ -1,2 +1,171 @@
#British English translation file
#Copy en-US.txt here and add a bunch of u's where they don't belong.
author = 0, "NULL"
confirmBlacklist = 0, "Are you sure you want to add #%s# to your blacklist?"
confirmCopy = 0, "Are you sure you want to copy #%s# to #%s#?"
confirmCreateAllSaveData = 0, "Are you sure you would like to create all save data on this system for #%s#? This can take a while depending on how many titles are found."
confirmDelete = 0, "Are you sure you want to delete #%s#? *This is permanent*!"
confirmDeleteBackupsAll = 0, "Are you sure you would like to delete *all* of your save backups for all of your games?"
confirmDeleteBackupsTitle = 0, "Are you sure you would like to delete all save backups for #%s#?"
confirmDeleteSaveData = 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"
confirmDriveOverwrite = 0, "Downloading this backup from drive will overwrite the one on your SD card. Continue?"
confirmOverwrite = 0, "Are you sure you want to overwrite #%s#?"
confirmResetSaveData = 0, "*WARNING*: This *will* reset the save data for this game as if it was never ran before. Are you sure you want to do this?"
confirmRestore = 0, "Are you sure you want to restore #%s#?"
debugStatus = 0, "User Count: "
debugStatus = 1, "Current User: "
debugStatus = 2, "Current Title: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "Sort Type: "
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Yes [A]"
extrasMenu = 0, "SD to SD Browser"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Remove Pending Update"
extrasMenu = 6, "Terminate Process"
extrasMenu = 7, "Mount System Save"
extrasMenu = 8, "Rescan Titles"
extrasMenu = 9, "Mount Process RomFS"
extrasMenu = 10, "Backup JKSV Folder"
extrasMenu = 11, "*[DEV]* Output en-US"
fileModeFileProperties = 0, "Path: %s\nSize: %s"
fileModeFolderProperties = 0, "Path: %s\nSub Folders: %u\nFile Count: %u\nTotal Size: %s"
fileModeMenu = 0, "Copy To "
fileModeMenu = 1, "Delete"
fileModeMenu = 2, "Rename"
fileModeMenu = 3, "Make Dir"
fileModeMenu = 4, "Properties"
fileModeMenu = 5, "Close"
fileModeMenu = 6, "Add to Path Filters"
fileModeMenuMkDir = 0, "New"
folderMenuNew = 0, "New Backup"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close"
helpSettings = 0, "[A] Toggle [X] Defaults [B] Back"
helpTitle = 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back"
helpUser = 0, "[A] Select [Y] Dump All Saves [X] User Options"
holdingText = 0, "(Hold) "
holdingText = 1, "(Keep Holding) "
holdingText = 2, "(Almost There!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Play Time: %02d:%02d"
infoStatus = 3, "Total Launches: %u"
infoStatus = 4, "Publisher: %s"
infoStatus = 5, "Save Type: %s"
infoStatus = 6, "Cache Index: %u"
infoStatus = 7, "User: %s"
infoStatus = 9, ""
loadingStartPage = 0, "Loading..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Settings"
onlineErrorConnecting = 0, "Error Connecting!"
onlineNoUpdates = 0, "No Updates Available."
popAddedToPathFilter = 0, "'#%s#' added to path filters."
popCPUBoostEnabled = 0, "CPU Boost Enabled for ZIP."
popChangeOutputError = 0, "#%s# contains illegal or non-ASCII characters."
popChangeOutputFolder = 0, "#%s# changed to #%s#"
popDriveFailed = 0, "Failed to start Google Drive."
popRemoteNotActive = 0, "Remote is not available"
popDriveStarted = 0, "Google Drive started successfully."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Error committing file to save!"
popFolderIsEmpty = 0, "Folder is empty!"
popProcessShutdown = 0, "#%s# successfully shutdown."
popSVIExported = 0, "SVI Exported."
popSaveIsEmpty = 0, "Save data is empty!"
popTrashEmptied = 0, "Trash emptied"
popZipIsEmpty = 0, "ZIP file is empty!"
saveDataBackupDeleted = 0, "#%s# has been deleted."
saveDataBackupMovedToTrash = 0, "#%s# has been moved to trash."
saveDataCreatedForUser = 0, "Save data created for %s!"
saveDataCreationFailed = 0, "Save data creation failed!"
saveDataDeleteAllUser = 0, "*ARE YOU SURE YOU WANT TO DELETE ALL SAVE DATA FOR %s?*"
saveDataDeleteSuccess = 0, "Save data for #%s# deleted!"
saveDataExtendFailed = 0, "Failed to extend save data."
saveDataExtendSuccess = 0, "Save data for #%s# extended!"
saveDataNoneFound = 0, "No saves found for #%s#!"
saveDataResetSuccess = 0, "Save for #%s# reset!"
saveDataTypeText = 0, "System"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Device"
saveDataTypeText = 4, "Temporary"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Empty Trash Bin"
settingsMenu = 1, "Check for Updates"
settingsMenu = 2, "Set JKSV Save Output Folder"
settingsMenu = 3, "Edit Blacklisted Titles"
settingsMenu = 4, "Delete All Save Backups"
settingsMenu = 5, "Include Device Saves With Users: "
settingsMenu = 6, "Auto Backup On Restore: "
settingsMenu = 7, "Auto-Name Backups: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Hold To Delete: "
settingsMenu = 10, "Hold To Restore: "
settingsMenu = 11, "Hold To Overwrite: "
settingsMenu = 12, "Force Mount: "
settingsMenu = 13, "Account System Saves: "
settingsMenu = 14, "Enable Writing to System Saves: "
settingsMenu = 15, "Use FS Commands Directly: "
settingsMenu = 16, "Export Saves to ZIP: "
settingsMenu = 17, "Force English To Be Used: "
settingsMenu = 18, "Enable Trash Bin: "
settingsMenu = 19, "Title Sorting Type: "
settingsMenu = 20, "Animation Scale: "
settingsOff = 0, "Off"
settingsOn = 0, ">On>"
sortType = 0, "Alphabetical"
sortType = 1, "Time Played"
sortType = 2, "Last Played"
swkbdEnterName = 0, "Enter a new name"
swkbdExpandSize = 0, "Enter New Size in MB"
swkbdMkDir = 0, "Enter a folder name"
swkbdNewSafeTitle = 0, "Input New Output Folder"
swkbdProcessID = 0, "Enter Process ID"
swkbdRename = 0, "Enter a new name for item"
swkbdSaveIndex = 0, "Enter Cache Index"
swkbdSetWorkDir = 0, "Enter a new Output Path"
swkbdSysSavID = 0, "Enter System Save ID"
threadStatusAddingFileToZip = 0, "Adding '#%s#' to ZIP..."
threadStatusCalculatingSaveSize = 0, "Calculating save data size..."
threadStatusCheckingForUpdate = 0, "Checking for updates..."
threadStatusCompressingSaveForUpload = 0, "Compressing #%s# for upload..."
threadStatusCopyingFile = 0, "Copying '#%s#'..."
threadStatusCreatingSaveData = 0, "Creating Save Data for #%s#..."
threadStatusDecompressingFile = 0, "Decompressing '#%s#'..."
threadStatusDeletingFile = 0, "Deleting..."
threadStatusDeletingSaveData = 0, "Deleting Save Data for #%s#..."
threadStatusDeletingUpdate = 0, "Deleting pending update..."
threadStatusDownloadingFile = 0, "Downloading #%s#..."
threadStatusDownloadingUpdate = 0, "Downloading update..."
threadStatusExtendingSaveData = 0, "Extending Save Data for #%s#..."
threadStatusGetDirProps = 0, "Getting Folder Properties..."
threadStatusOpeningFolder = 0, "Opening '#%s#'..."
threadStatusPackingJKSV = 0, "Writing JKSV folder contents to ZIP..."
threadStatusResettingSaveData = 0, "Resetting save data..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "Uploading #%s#..."
titleOptions = 0, "Information"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Change Output Folder"
titleOptions = 3, "Open in File Mode"
titleOptions = 4, "Delete All Save Backups"
titleOptions = 5, "Reset Save Data"
titleOptions = 6, "Delete Save Data"
titleOptions = 7, "Extend Save Data"
titleOptions = 8, "Export SVI"
translationMainPage = 0, "Translation: "
userOptions = 0, "Dump All For "
userOptions = 1, "Create Save Data"
userOptions = 2, "Create All Save Data"
userOptions = 3, "Delete All User Saves"

View File

@ -1,13 +1,3 @@
#JKSV Translation File
#File can be placed and named "/JKSV/trans.txt" for testing without building
#Format is: stringName = index, "string"
#You only need to translate "string", there is no need to touch the other two parameters.
#'%s' is replaced with game titles and paths. '#', '*', '>', etc change text color. [x] are replaced with button glyphs. '\n' can be used to break lines
#This is exported from JKSV itself to make adding strings to this easier.
#It can also export already made translations with new strings if added, cutting down work for everyone.
#If there are issues with spacing, line breaking, untranslatable English text, etc let me know in a git issue or your pull request.
#If author is not "NULL", your name will be displayed in the UI
author = 0, "NULL"
confirmBlacklist = 0, "Are you sure you want to add #%s# to your blacklist?"
confirmCopy = 0, "Are you sure you want to copy #%s# to #%s#?"
@ -16,9 +6,15 @@ confirmDelete = 0, "Are you sure you want to delete #%s#? *This is permanent*!"
confirmDeleteBackupsAll = 0, "Are you sure you would like to delete *all* of your save backups for all of your games?"
confirmDeleteBackupsTitle = 0, "Are you sure you would like to delete all save backups for #%s#?"
confirmDeleteSaveData = 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"
confirmDriveOverwrite = 0, "Downloading this backup from drive will overwrite the one on your SD card. Continue?"
confirmOverwrite = 0, "Are you sure you want to overwrite #%s#?"
confirmResetSaveData = 0, "*WARNING*: This *will* reset the save data for this game as if it was never ran before. Are you sure you want to do this?"
confirmRestore = 0, "Are you sure you want to restore #%s#?"
debugStatus = 0, "User Count: "
debugStatus = 1, "Current User: "
debugStatus = 2, "Current Title: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "Sort Type: "
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Yes [A]"
@ -43,21 +39,44 @@ fileModeMenu = 3, "Make Dir"
fileModeMenu = 4, "Properties"
fileModeMenu = 5, "Close"
fileModeMenu = 6, "Add to Path Filters"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [B] Close"
fileModeMenuMkDir = 0, "New"
folderMenuNew = 0, "New Backup"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close"
helpSettings = 0, "[A] Toggle [X] Defaults [B] Back"
helpTitle = 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back"
helpUser = 0, "[A] Select [X] User Options"
helpUser = 0, "[A] Select [Y] Dump All Saves [X] User Options"
holdingText = 0, "(Hold) "
holdingText = 1, "(Keep Holding) "
holdingText = 2, "(Almost There!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Play Time: %02d:%02d"
infoStatus = 3, "Total Launches: %u"
infoStatus = 4, "Publisher: %s"
infoStatus = 5, "Save Type: %s"
infoStatus = 6, "Cache Index: %u"
infoStatus = 7, "User: %s"
infoStatus = 9, ""
loadingStartPage = 0, "Loading..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Settings"
onlineErrorConnecting = 0, "Error Connecting!"
onlineNoUpdates = 0, "No Updates Available."
popAddedToPathFilter = 0, "'#%s#' added to path filters."
popCPUBoostEnabled = 0, "CPU Boost Enabled for ZIP."
popChangeOutputError = 0, "#%s# contains illegal or non-ASCII characters."
popChangeOutputFolder = 0, "#%s# changed to #%s#"
popDriveFailed = 0, "Failed to start Google Drive."
popRemoteNotActive = 0, "Remote is not available"
popDriveStarted = 0, "Google Drive started successfully."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Error committing file to save!"
popFolderIsEmpty = 0, "Folder is empty!"
popProcessShutdown = 0, "#%s# successfully shutdown."
popSVIExported = 0, "SVI Exported."
popSaveIsEmpty = 0, "Save data is empty!"
popTrashEmptied = 0, "Trash emptied"
popZipIsEmpty = 0, "ZIP file is empty!"
saveDataBackupDeleted = 0, "#%s# has been deleted."
saveDataBackupMovedToTrash = 0, "#%s# has been moved to trash."
@ -69,6 +88,19 @@ saveDataExtendFailed = 0, "Failed to extend save data."
saveDataExtendSuccess = 0, "Save data for #%s# extended!"
saveDataNoneFound = 0, "No saves found for #%s#!"
saveDataResetSuccess = 0, "Save for #%s# reset!"
saveDataTypeText = 0, "System"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Device"
saveDataTypeText = 4, "Temporary"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Empty Trash Bin"
settingsMenu = 1, "Check for Updates"
settingsMenu = 2, "Set JKSV Save Output Folder"
@ -96,26 +128,33 @@ sortType = 0, "Alphabetical"
sortType = 1, "Time Played"
sortType = 2, "Last Played"
swkbdEnterName = 0, "Enter a new name"
swkbdExpandSize = 0, "Enter New Size in MB"
swkbdMkDir = 0, "Enter a folder name"
swkbdNewSafeTitle = 0, "Input New Output Folder"
swkbdProcessID = 0, "Enter Process ID"
swkbdRename = 0, "Enter a new name for item"
swkbdSaveIndex = 0, "Enter Cache Index"
swkbdSetWorkDir = 0, "Enter a new Output Path"
swkbdSysSavID = 0, "Enter System Save ID"
threadStatusAddingFileToZip = 0, "Adding '#%s#' to ZIP..."
threadStatusCalculatingSaveSize = 0, "Calculating save data size..."
threadStatusCheckingForUpdate = 0, "Checking for updates..."
threadStatusCompressingSaveForUpload = 0, "Compressing #%s# for upload..."
threadStatusCopyingFile = 0, "Copying '#%s#'..."
threadStatusCreatingSaveData = 0, "Creating Save Data for #%s#..."
threadStatusDecompressingFile = 0, "Decompressing '#%s#'..."
threadStatusDeletingFile = 0, "Deleting..."
threadStatusDeletingSaveData = 0, "Deleting Save Data for #%s#..."
threadStatusDeletingUpdate = 0, "Deleting pending update..."
threadStatusDownloadingFile = 0, "Downloading #%s#..."
threadStatusDownloadingUpdate = 0, "Downloading update..."
threadStatusExtendingSaveData = 0, "Extending Save Data for #%s#..."
threadStatusGetDirProps = 0, "Getting Folder Properties..."
threadStatusOpeningFolder = 0, "Opening '#%s#'..."
threadStatusPackingJKSV = 0, "Writing JKSV folder contents to ZIP..."
threadStatusResettingSaveData = 0, "Resetting save data..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "Uploading #%s#..."
titleOptions = 0, "Information"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Change Output Folder"
@ -124,6 +163,8 @@ titleOptions = 4, "Delete All Save Backups"
titleOptions = 5, "Reset Save Data"
titleOptions = 6, "Delete Save Data"
titleOptions = 7, "Extend Save Data"
titleOptions = 8, "Export SVI"
translationMainPage = 0, "Translation: "
userOptions = 0, "Dump All For "
userOptions = 1, "Create Save Data"
userOptions = 2, "Create All Save Data"

View File

@ -1,2 +1,218 @@
#Latin American Spanish translation file
#Copy en-US.txt here and use as a guide.
appletModeWarning = 0, "*ADVERTENCIA*: JKSV se está ejecutando en modo 'applet'. Algunas características no funcionarán."
author = 0, "Impeeza"
confirmBlacklist = 0, "¿Estás seguro que deseas adicionar #%s# a la lista negra?"
confirmCopy = 0, "¿Seguro que desea copiar #%s# a #%s#?"
confirmCreateAllSaveData = 0, "¿Seguro que desea crear todos los Datos de Partidas Guardadas en este sistema para #%s#? ¡Puede tomar un tiempo dependiendo de la cantidad de títulos sean encontrados!"
confirmDelete = 0, "¿Seguro que desea borrar #%s#? *¡Es permanente!*"
confirmDeleteBackupsAll = 0, "¿Seguro que desea borrar *TODOS* los respaldos de partidas guardadas para todos sus juegos?"
confirmDeleteBackupsTitle = 0, "¿Seguro que desea borrar *TODOS* los respaldos de partidas guardadas para #%s#?"
confirmDeleteSaveData = 0, "*ADVERTENCIA*: Ésto eliminará la partida guardada para #%s# *DE SU SISTEMA*. ¿Está seguro que así lo desea?"
#CHANGED=============================================>
confirmDriveOverwrite = 0, "Al descargar este respaldo desde el servidor remoto se reemplazará el que actualmente está en la tarjeta SD, ¿Continuar?"
#<====================================================
confirmOverwrite = 0, "¿Sobrescribir #%s#?"
confirmResetSaveData = 0, "*ADVERTENCIA*: Ésto restablecerá la partida guardada para este juego, como si nunca se hubiera ejecutado. ¿Está seguro que es lo que desea?"
confirmRestore = 0, "¿Seguro que desea restaurar #%s#?"
#CHANGED=============================================>
debugStatus = 0, "Número de Usuarios: "
debugStatus = 1, "Usuario Actual: "
debugStatus = 2, "Título Actual: "
debugStatus = 3, "Título Seguro: "
debugStatus = 4, "Tipo órden: "
#<====================================================
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Si [A]"
extrasMenu = 0, "Navegador de SD a SD"
extrasMenu = 1, "Navegar 'ProdInfo'"
extrasMenu = 2, "Navegar 'Safe'"
extrasMenu = 3, "Navegar 'System'"
extrasMenu = 4, "Navegar 'User'"
extrasMenu = 5, "Eliminar actualizaciones pendientes"
extrasMenu = 6, "Detener proceso"
extrasMenu = 7, "Montar 'System Save'"
extrasMenu = 8, "Recargar Lista de Títulos"
extrasMenu = 9, "Navegar 'Process RomFS'"
extrasMenu = 10, "Realizar respaldo de la carpeta de JKSV"
extrasMenu = 11, "*[DEV]* Extraer Archivos de Lenguaje actuales"
fileModeFileProperties = 0, "Ruta: %s\nTamaño: %s"
fileModeFolderProperties = 0, "Ruta: %s\nSubCarpetas: %u\nCantidad Archivos: %u\nTamaño Total: %s"
fileModeMenu = 0, "Copiar a "
fileModeMenu = 1, "Borrar"
fileModeMenu = 2, "Renombrar"
fileModeMenu = 3, "Crear Carpeta"
fileModeMenu = 4, "Propiedades"
fileModeMenu = 5, "Cerrar"
fileModeMenu = 6, "Adicionar a filtros de Carpetas"
fileModeMenuMkDir = 0, "Nuevo"
folderMenuNew = 0, "Nuevo Respaldo"
#CHANGED=============================================>
helpFolder = 0, "[A] Selecionar [Y] Restaurar [X] Borrar [ZR] Subir [B] Cerrar"
#<====================================================
helpSettings = 0, "[A] Cambiar [X] Predeterminados [B] Atrás"
helpTitle = 0, "[A] Seleccionar [L][R] Cambiar Página [Y] Favoritos [X] Opciones de Títulos [B] Atrás"
helpUser = 0, "[A] Seleccionar [Y] Extraer Todos [X] Opciones de Usuario"
holdingText = 0, "(Sostenga) "
holdingText = 1, "(Siga Sosteniendo) "
holdingText = 2, "(¡Ya Casi!) "
#CHANGED=============================================>
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Tiempo Jugado: %02d:%02d"
infoStatus = 3, "Veces Ejecutado: %u"
infoStatus = 4, "Editor: %s"
infoStatus = 5, "Tipo Partida Guardada: %s"
infoStatus = 6, "Índice de Caché: %u"
infoStatus = 7, "Usuario: %s"
infoStatus = 9, ""
#<=====================================================
loadingStartPage = 0, "Cargando..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Configs."
onlineErrorConnecting = 0, "¡Error Conectando!"
onlineNoUpdates = 0, "No hay actualizaciones disponibles."
popAddedToPathFilter = 0, "Se adicionó '#%s#' a filtros de carpeta."
popCPUBoostEnabled = 0, "Aumento de CPU para ZIP."
popChangeOutputError = 0, "#%s# contiene carácteres ilegales o no-ASCII."
popChangeOutputFolder = 0, "Se cambia #%s# por #%s#"
#CHANGED=============================================>
popDriveFailed = 0, "Falla al iniciar Google Drive."
popRemoteNotActive = 0, "Servidor Remoto no está disponible."
popDriveStarted = 0, "Se ha iniciado Google Drive correctamente."
popWebdavStarted = 0, "Se ha iniciado Webdav Correctamente."
popWebdavFailed = 0, "Falla al iniciar Webdav."
#<====================================================
popErrorCommittingFile = 0, "¡Error guardando archivo!"
popFolderIsEmpty = 0, "¡La carpeta está vacía!"
popProcessShutdown = 0, "#%s# Apagado satisfactorio."
#CHANGED=============================================>
popSVIExported = 0, "SVI Exportado."
#<====================================================
popSaveIsEmpty = 0, "¡Partida Guardada está vacía!"
popTrashEmptied = 0, "Papelera Vaciada"
popZipIsEmpty = 0, "¡Archivo ZIP está vacío!"
saveDataBackupDeleted = 0, "#%s# ha sido borrado."
saveDataBackupMovedToTrash = 0, "#%s# ha sido movido a papelera."
saveDataCreatedForUser = 0, "Se creó Partida Guardada para %s!"
saveDataCreationFailed = 0, "¡Error en creación de Partida Guardada!"
saveDataDeleteAllUser = 0, "*¿SEGURO QUE DESEA BORRAR TODAS LAS PARTIDAS GUARDADAS DE %s?*"
saveDataDeleteSuccess = 0, "¡Se borró la Partida Guardada de #%s#!"
saveDataExtendFailed = 0, "Error al expandir la partida guardada."
saveDataExtendSuccess = 0, "¡Partida Guardada para #%s# expandida con éxito!"
saveDataIndexText = 0, "Guardar Índice: "
saveDataNoneFound = 0, "¡No se encontraron partidas guardadas para #%s#!"
saveDataResetSuccess = 0, "¡Partida Guardada para #%s# restablecida!"
saveDataTypeText = 0, "Datos de Sistema"
saveDataTypeText = 1, "Partida Guardada"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Datos de Dispositivo"
saveDataTypeText = 4, "Almacenamiento Temporal"
saveDataTypeText = 5, "Almacenamiento de Caché"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Caché"
saveTypeMainMenu = 3, "Sistema"
saveTypeMainMenu = 4, "BCAT de Sistema"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Vaciar Papelera"
settingsMenu = 1, "Buscar Actualizaciones"
settingsMenu = 2, "Configurar carpeta de salida para JKSV"
settingsMenu = 3, "Editar lista negra de Títulos"
settingsMenu = 4, "Eliminar todos los respaldos"
settingsMenu = 5, "Incluír Datos de Dispositivo con los Usuarios: "
settingsMenu = 6, "Crear respaldo automático al restaurar: "
settingsMenu = 7, "Nombrar Automáticamente los respaldos: "
settingsMenu = 8, "Overclock/Aumento de CPU: "
settingsMenu = 9, "Sostener para Borrar: "
settingsMenu = 10, "Sostener para Restaurar: "
settingsMenu = 11, "Sostener para Sobreescribir: "
settingsMenu = 12, "Montar forzado: "
settingsMenu = 13, "Datos de la cuenta de Sistema: "
settingsMenu = 14, "Habilitar escritura para datos de Sistema: "
settingsMenu = 15, "Usar comandos FS directamente: "
settingsMenu = 16, "Exportar partidas guardadas a archivos ZIP: "
settingsMenu = 17, "Forzar el uso de idioma Inglés: "
settingsMenu = 18, "Habilitar papelera: "
settingsMenu = 19, "Ordenar Títulos: "
settingsMenu = 20, "Escala de Animación: "
settingsMenu = 21, "Subir automáticamente a GDrive/Webdav: "
settingsOff = 0, "Apagado"
settingsOn = 0, ">Encendido>"
sortType = 0, "Alfabéticamente"
sortType = 1, "Por Duración Juegos"
sortType = 2, "Recientemente Jugado"
swkbdEnterName = 0, "Ingrese un nuevo nombre"
swkbdExpandSize = 0, "Ingrese nuevo tamaño en MB"
swkbdMkDir = 0, "Ingrese nombre de carpeta"
swkbdNewSafeTitle = 0, "Ingrese Nueva Carpeta de Salida"
swkbdProcessID = 0, "Ingrese ID de proceso"
swkbdRename = 0, "Ingrese un nuevo nombre para el elemento"
swkbdSaveIndex = 0, "Ingrese Índice de Caché"
swkbdSetWorkDir = 0, "Ingrese Nueva Carpeta de Salida"
swkbdSysSavID = 0, "Ingrese ID de Datos de Sistema"
threadStatusAddingFileToZip = 0, "Agregando '#%s#' a archivo ZIP..."
#CHANGED=============================================>
threadStatusCalculatingSaveSize = 0, "Calculando tamaño de partida guardada..."
#<====================================================
threadStatusCheckingForUpdate = 0, "Verificando actualizaciones..."
#CHANGED=============================================>
threadStatusCompressingSaveForUpload = 0, "Comprimiendo #%s# para subirlo..."
#<====================================================
threadStatusCopyingFile = 0, "Copiando '#%s#'..."
threadStatusCreatingSaveData = 0, "Creando Partida Guardada para #%s#..."
threadStatusDecompressingFile = 0, "Descomprimiendo '#%s#'..."
threadStatusDeletingFile = 0, "Borrando..."
threadStatusDeletingSaveData = 0, "Borrando Partida Guardada para #%s#..."
threadStatusDeletingUpdate = 0, "Borrando Actualización Pendiente..."
#CHANGED=============================================>
threadStatusDownloadingFile = 0, "Descargando #%s#..."
#<====================================================
threadStatusDownloadingUpdate = 0, "Borrando Actualización..."
threadStatusExtendingSaveData = 0, "Expandiendo Datos Guardados para #%s#..."
threadStatusGetDirProps = 0, "Obteniendo propiedades de Carpeta..."
threadStatusOpeningFolder = 0, "Abriendo '#%s#'..."
threadStatusPackingJKSV = 0, "Guardando contenidos de carpeta de JKSV en archivo ZIP..."
threadStatusResettingSaveData = 0, "Restableciendo datos de partida..."
threadStatusSavingTranslations = 0, "Guardando archivo maestro..."
#CHANGED=============================================>
threadStatusUploadingFile = 0, "Subiendo #%s#..."
#<====================================================
titleOptions = 0, "Información"
titleOptions = 1, "Lista Negra"
titleOptions = 2, "Cambiar Carpeta de Salida"
titleOptions = 3, "Abrir en Modo Archivo"
titleOptions = 4, "Borrar todos los respaldos"
titleOptions = 5, "Restablecer Datos de Partida"
titleOptions = 6, "Borrar Datos de Partida"
titleOptions = 7, "Expandir Datos de Partida"
#CHANGED=============================================>
titleOptions = 8, "Exportar SVI"
#<====================================================
translationMainPage = 0, "Traducción: "
userOptions = 0, "Extrar Todo para "
userOptions = 1, "Crear Datos de Partida"
userOptions = 2, "Crear Todos los Datos de Partida"
userOptions = 3, "Borrar Datos de Partida para todos los Usuarios"

View File

@ -1,2 +1,171 @@
#Spanish(Spain) translation file
#Copy en-US.txt here and use as a guide.
author = 0, "NULL"
confirmBlacklist = 0, "Are you sure you want to add #%s# to your blacklist?"
confirmCopy = 0, "Are you sure you want to copy #%s# to #%s#?"
confirmCreateAllSaveData = 0, "Are you sure you would like to create all save data on this system for #%s#? This can take a while depending on how many titles are found."
confirmDelete = 0, "Are you sure you want to delete #%s#? *This is permanent*!"
confirmDeleteBackupsAll = 0, "Are you sure you would like to delete *all* of your save backups for all of your games?"
confirmDeleteBackupsTitle = 0, "Are you sure you would like to delete all save backups for #%s#?"
confirmDeleteSaveData = 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"
confirmDriveOverwrite = 0, "Downloading this backup from drive will overwrite the one on your SD card. Continue?"
confirmOverwrite = 0, "Are you sure you want to overwrite #%s#?"
confirmResetSaveData = 0, "*WARNING*: This *will* reset the save data for this game as if it was never ran before. Are you sure you want to do this?"
confirmRestore = 0, "Are you sure you want to restore #%s#?"
debugStatus = 0, "User Count: "
debugStatus = 1, "Current User: "
debugStatus = 2, "Current Title: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "Sort Type: "
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Yes [A]"
extrasMenu = 0, "SD to SD Browser"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Remove Pending Update"
extrasMenu = 6, "Terminate Process"
extrasMenu = 7, "Mount System Save"
extrasMenu = 8, "Rescan Titles"
extrasMenu = 9, "Mount Process RomFS"
extrasMenu = 10, "Backup JKSV Folder"
extrasMenu = 11, "*[DEV]* Output en-US"
fileModeFileProperties = 0, "Path: %s\nSize: %s"
fileModeFolderProperties = 0, "Path: %s\nSub Folders: %u\nFile Count: %u\nTotal Size: %s"
fileModeMenu = 0, "Copy To "
fileModeMenu = 1, "Delete"
fileModeMenu = 2, "Rename"
fileModeMenu = 3, "Make Dir"
fileModeMenu = 4, "Properties"
fileModeMenu = 5, "Close"
fileModeMenu = 6, "Add to Path Filters"
fileModeMenuMkDir = 0, "New"
folderMenuNew = 0, "New Backup"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close"
helpSettings = 0, "[A] Toggle [X] Defaults [B] Back"
helpTitle = 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back"
helpUser = 0, "[A] Select [Y] Dump All Saves [X] User Options"
holdingText = 0, "(Hold) "
holdingText = 1, "(Keep Holding) "
holdingText = 2, "(Almost There!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Play Time: %02d:%02d"
infoStatus = 3, "Total Launches: %u"
infoStatus = 4, "Publisher: %s"
infoStatus = 5, "Save Type: %s"
infoStatus = 6, "Cache Index: %u"
infoStatus = 7, "User: %s"
infoStatus = 9, ""
loadingStartPage = 0, "Loading..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Settings"
onlineErrorConnecting = 0, "Error Connecting!"
onlineNoUpdates = 0, "No Updates Available."
popAddedToPathFilter = 0, "'#%s#' added to path filters."
popCPUBoostEnabled = 0, "CPU Boost Enabled for ZIP."
popChangeOutputError = 0, "#%s# contains illegal or non-ASCII characters."
popChangeOutputFolder = 0, "#%s# changed to #%s#"
popDriveFailed = 0, "Failed to start Google Drive."
popRemoteNotActive = 0, "Remote is not available"
popDriveStarted = 0, "Google Drive started successfully."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Error committing file to save!"
popFolderIsEmpty = 0, "Folder is empty!"
popProcessShutdown = 0, "#%s# successfully shutdown."
popSVIExported = 0, "SVI Exported."
popSaveIsEmpty = 0, "Save data is empty!"
popTrashEmptied = 0, "Trash emptied"
popZipIsEmpty = 0, "ZIP file is empty!"
saveDataBackupDeleted = 0, "#%s# has been deleted."
saveDataBackupMovedToTrash = 0, "#%s# has been moved to trash."
saveDataCreatedForUser = 0, "Save data created for %s!"
saveDataCreationFailed = 0, "Save data creation failed!"
saveDataDeleteAllUser = 0, "*ARE YOU SURE YOU WANT TO DELETE ALL SAVE DATA FOR %s?*"
saveDataDeleteSuccess = 0, "Save data for #%s# deleted!"
saveDataExtendFailed = 0, "Failed to extend save data."
saveDataExtendSuccess = 0, "Save data for #%s# extended!"
saveDataNoneFound = 0, "No saves found for #%s#!"
saveDataResetSuccess = 0, "Save for #%s# reset!"
saveDataTypeText = 0, "System"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Device"
saveDataTypeText = 4, "Temporary"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Empty Trash Bin"
settingsMenu = 1, "Check for Updates"
settingsMenu = 2, "Set JKSV Save Output Folder"
settingsMenu = 3, "Edit Blacklisted Titles"
settingsMenu = 4, "Delete All Save Backups"
settingsMenu = 5, "Include Device Saves With Users: "
settingsMenu = 6, "Auto Backup On Restore: "
settingsMenu = 7, "Auto-Name Backups: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Hold To Delete: "
settingsMenu = 10, "Hold To Restore: "
settingsMenu = 11, "Hold To Overwrite: "
settingsMenu = 12, "Force Mount: "
settingsMenu = 13, "Account System Saves: "
settingsMenu = 14, "Enable Writing to System Saves: "
settingsMenu = 15, "Use FS Commands Directly: "
settingsMenu = 16, "Export Saves to ZIP: "
settingsMenu = 17, "Force English To Be Used: "
settingsMenu = 18, "Enable Trash Bin: "
settingsMenu = 19, "Title Sorting Type: "
settingsMenu = 20, "Animation Scale: "
settingsOff = 0, "Off"
settingsOn = 0, ">On>"
sortType = 0, "Alphabetical"
sortType = 1, "Time Played"
sortType = 2, "Last Played"
swkbdEnterName = 0, "Enter a new name"
swkbdExpandSize = 0, "Enter New Size in MB"
swkbdMkDir = 0, "Enter a folder name"
swkbdNewSafeTitle = 0, "Input New Output Folder"
swkbdProcessID = 0, "Enter Process ID"
swkbdRename = 0, "Enter a new name for item"
swkbdSaveIndex = 0, "Enter Cache Index"
swkbdSetWorkDir = 0, "Enter a new Output Path"
swkbdSysSavID = 0, "Enter System Save ID"
threadStatusAddingFileToZip = 0, "Adding '#%s#' to ZIP..."
threadStatusCalculatingSaveSize = 0, "Calculating save data size..."
threadStatusCheckingForUpdate = 0, "Checking for updates..."
threadStatusCompressingSaveForUpload = 0, "Compressing #%s# for upload..."
threadStatusCopyingFile = 0, "Copying '#%s#'..."
threadStatusCreatingSaveData = 0, "Creating Save Data for #%s#..."
threadStatusDecompressingFile = 0, "Decompressing '#%s#'..."
threadStatusDeletingFile = 0, "Deleting..."
threadStatusDeletingSaveData = 0, "Deleting Save Data for #%s#..."
threadStatusDeletingUpdate = 0, "Deleting pending update..."
threadStatusDownloadingFile = 0, "Downloading #%s#..."
threadStatusDownloadingUpdate = 0, "Downloading update..."
threadStatusExtendingSaveData = 0, "Extending Save Data for #%s#..."
threadStatusGetDirProps = 0, "Getting Folder Properties..."
threadStatusOpeningFolder = 0, "Opening '#%s#'..."
threadStatusPackingJKSV = 0, "Writing JKSV folder contents to ZIP..."
threadStatusResettingSaveData = 0, "Resetting save data..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "Uploading #%s#..."
titleOptions = 0, "Information"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Change Output Folder"
titleOptions = 3, "Open in File Mode"
titleOptions = 4, "Delete All Save Backups"
titleOptions = 5, "Reset Save Data"
titleOptions = 6, "Delete Save Data"
titleOptions = 7, "Extend Save Data"
titleOptions = 8, "Export SVI"
translationMainPage = 0, "Translation: "
userOptions = 0, "Dump All For "
userOptions = 1, "Create Save Data"
userOptions = 2, "Create All Save Data"
userOptions = 3, "Delete All User Saves"

View File

@ -1,2 +1,171 @@
#Canadian French translation file
#Copy en-US.txt here and use as a guide.
author = 0, "NULL"
confirmBlacklist = 0, "Are you sure you want to add #%s# to your blacklist?"
confirmCopy = 0, "Are you sure you want to copy #%s# to #%s#?"
confirmCreateAllSaveData = 0, "Are you sure you would like to create all save data on this system for #%s#? This can take a while depending on how many titles are found."
confirmDelete = 0, "Are you sure you want to delete #%s#? *This is permanent*!"
confirmDeleteBackupsAll = 0, "Are you sure you would like to delete *all* of your save backups for all of your games?"
confirmDeleteBackupsTitle = 0, "Are you sure you would like to delete all save backups for #%s#?"
confirmDeleteSaveData = 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"
confirmDriveOverwrite = 0, "Downloading this backup from drive will overwrite the one on your SD card. Continue?"
confirmOverwrite = 0, "Are you sure you want to overwrite #%s#?"
confirmResetSaveData = 0, "*WARNING*: This *will* reset the save data for this game as if it was never ran before. Are you sure you want to do this?"
confirmRestore = 0, "Are you sure you want to restore #%s#?"
debugStatus = 0, "User Count: "
debugStatus = 1, "Current User: "
debugStatus = 2, "Current Title: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "Sort Type: "
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Yes [A]"
extrasMenu = 0, "SD to SD Browser"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Remove Pending Update"
extrasMenu = 6, "Terminate Process"
extrasMenu = 7, "Mount System Save"
extrasMenu = 8, "Rescan Titles"
extrasMenu = 9, "Mount Process RomFS"
extrasMenu = 10, "Backup JKSV Folder"
extrasMenu = 11, "*[DEV]* Output en-US"
fileModeFileProperties = 0, "Path: %s\nSize: %s"
fileModeFolderProperties = 0, "Path: %s\nSub Folders: %u\nFile Count: %u\nTotal Size: %s"
fileModeMenu = 0, "Copy To "
fileModeMenu = 1, "Delete"
fileModeMenu = 2, "Rename"
fileModeMenu = 3, "Make Dir"
fileModeMenu = 4, "Properties"
fileModeMenu = 5, "Close"
fileModeMenu = 6, "Add to Path Filters"
fileModeMenuMkDir = 0, "New"
folderMenuNew = 0, "New Backup"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close"
helpSettings = 0, "[A] Toggle [X] Defaults [B] Back"
helpTitle = 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back"
helpUser = 0, "[A] Select [Y] Dump All Saves [X] User Options"
holdingText = 0, "(Hold) "
holdingText = 1, "(Keep Holding) "
holdingText = 2, "(Almost There!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Play Time: %02d:%02d"
infoStatus = 3, "Total Launches: %u"
infoStatus = 4, "Publisher: %s"
infoStatus = 5, "Save Type: %s"
infoStatus = 6, "Cache Index: %u"
infoStatus = 7, "User: %s"
infoStatus = 9, ""
loadingStartPage = 0, "Loading..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Settings"
onlineErrorConnecting = 0, "Error Connecting!"
onlineNoUpdates = 0, "No Updates Available."
popAddedToPathFilter = 0, "'#%s#' added to path filters."
popCPUBoostEnabled = 0, "CPU Boost Enabled for ZIP."
popChangeOutputError = 0, "#%s# contains illegal or non-ASCII characters."
popChangeOutputFolder = 0, "#%s# changed to #%s#"
popDriveFailed = 0, "Failed to start Google Drive."
popRemoteNotActive = 0, "Remote is not available"
popDriveStarted = 0, "Google Drive started successfully."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Error committing file to save!"
popFolderIsEmpty = 0, "Folder is empty!"
popProcessShutdown = 0, "#%s# successfully shutdown."
popSVIExported = 0, "SVI Exported."
popSaveIsEmpty = 0, "Save data is empty!"
popTrashEmptied = 0, "Trash emptied"
popZipIsEmpty = 0, "ZIP file is empty!"
saveDataBackupDeleted = 0, "#%s# has been deleted."
saveDataBackupMovedToTrash = 0, "#%s# has been moved to trash."
saveDataCreatedForUser = 0, "Save data created for %s!"
saveDataCreationFailed = 0, "Save data creation failed!"
saveDataDeleteAllUser = 0, "*ARE YOU SURE YOU WANT TO DELETE ALL SAVE DATA FOR %s?*"
saveDataDeleteSuccess = 0, "Save data for #%s# deleted!"
saveDataExtendFailed = 0, "Failed to extend save data."
saveDataExtendSuccess = 0, "Save data for #%s# extended!"
saveDataNoneFound = 0, "No saves found for #%s#!"
saveDataResetSuccess = 0, "Save for #%s# reset!"
saveDataTypeText = 0, "System"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Device"
saveDataTypeText = 4, "Temporary"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Empty Trash Bin"
settingsMenu = 1, "Check for Updates"
settingsMenu = 2, "Set JKSV Save Output Folder"
settingsMenu = 3, "Edit Blacklisted Titles"
settingsMenu = 4, "Delete All Save Backups"
settingsMenu = 5, "Include Device Saves With Users: "
settingsMenu = 6, "Auto Backup On Restore: "
settingsMenu = 7, "Auto-Name Backups: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Hold To Delete: "
settingsMenu = 10, "Hold To Restore: "
settingsMenu = 11, "Hold To Overwrite: "
settingsMenu = 12, "Force Mount: "
settingsMenu = 13, "Account System Saves: "
settingsMenu = 14, "Enable Writing to System Saves: "
settingsMenu = 15, "Use FS Commands Directly: "
settingsMenu = 16, "Export Saves to ZIP: "
settingsMenu = 17, "Force English To Be Used: "
settingsMenu = 18, "Enable Trash Bin: "
settingsMenu = 19, "Title Sorting Type: "
settingsMenu = 20, "Animation Scale: "
settingsOff = 0, "Off"
settingsOn = 0, ">On>"
sortType = 0, "Alphabetical"
sortType = 1, "Time Played"
sortType = 2, "Last Played"
swkbdEnterName = 0, "Enter a new name"
swkbdExpandSize = 0, "Enter New Size in MB"
swkbdMkDir = 0, "Enter a folder name"
swkbdNewSafeTitle = 0, "Input New Output Folder"
swkbdProcessID = 0, "Enter Process ID"
swkbdRename = 0, "Enter a new name for item"
swkbdSaveIndex = 0, "Enter Cache Index"
swkbdSetWorkDir = 0, "Enter a new Output Path"
swkbdSysSavID = 0, "Enter System Save ID"
threadStatusAddingFileToZip = 0, "Adding '#%s#' to ZIP..."
threadStatusCalculatingSaveSize = 0, "Calculating save data size..."
threadStatusCheckingForUpdate = 0, "Checking for updates..."
threadStatusCompressingSaveForUpload = 0, "Compressing #%s# for upload..."
threadStatusCopyingFile = 0, "Copying '#%s#'..."
threadStatusCreatingSaveData = 0, "Creating Save Data for #%s#..."
threadStatusDecompressingFile = 0, "Decompressing '#%s#'..."
threadStatusDeletingFile = 0, "Deleting..."
threadStatusDeletingSaveData = 0, "Deleting Save Data for #%s#..."
threadStatusDeletingUpdate = 0, "Deleting pending update..."
threadStatusDownloadingFile = 0, "Downloading #%s#..."
threadStatusDownloadingUpdate = 0, "Downloading update..."
threadStatusExtendingSaveData = 0, "Extending Save Data for #%s#..."
threadStatusGetDirProps = 0, "Getting Folder Properties..."
threadStatusOpeningFolder = 0, "Opening '#%s#'..."
threadStatusPackingJKSV = 0, "Writing JKSV folder contents to ZIP..."
threadStatusResettingSaveData = 0, "Resetting save data..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "Uploading #%s#..."
titleOptions = 0, "Information"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Change Output Folder"
titleOptions = 3, "Open in File Mode"
titleOptions = 4, "Delete All Save Backups"
titleOptions = 5, "Reset Save Data"
titleOptions = 6, "Delete Save Data"
titleOptions = 7, "Extend Save Data"
titleOptions = 8, "Export SVI"
translationMainPage = 0, "Translation: "
userOptions = 0, "Dump All For "
userOptions = 1, "Create Save Data"
userOptions = 2, "Create All Save Data"
userOptions = 3, "Delete All User Saves"

View File

@ -1,2 +1,171 @@
#French(France) French translation file
#Copy en-US.txt here and use as a guide.
author = 0, "Shadow2560"
confirmBlacklist = 0, "Souhaitez-vous vraiment ajouter #%s# à votre liste noire?"
confirmCopy = 0, "Souhaitez-vous vraiment copier #%s# vers #%s#?"
confirmCreateAllSaveData = 0, "Souhaitez-vous vraiment créer toutes les données de sauvegarde de ce système pour #%s# ? Cela peut prendre un certain temps en fonction du nombre de titres trouvés."
confirmDelete = 0, "Souhaitez-vous vraiment supprimer #%s# ? *Ceci sera permanent* !"
confirmDeleteBackupsAll = 0, "Êtes-vous sûr de vouloir supprimer *toutes* vos sauvegardes pour tous vos jeux ?"
confirmDeleteBackupsTitle = 0, "Souhaitez-vous vraiment supprimer toutes les sauvegardes sauvegardées pour #%s#?"
confirmDeleteSaveData = 0, "*ATTENTION* : Ceci *effacera* les données sauvegardées pour #%s# *de votre système*. Êtes-vous sûr de vouloir faire cela ?"
confirmDriveOverwrite = 0, "Le téléchargement de cette sauvegarde depuis le disque dur écrasera celle qui se trouve sur votre carte SD. Continuer?"
confirmOverwrite = 0, "Souhaitez-vous vraiment écraser #%s#?"
confirmResetSaveData = 0, "*ATTENTION* : Cela *réinitialisera* les données de sauvegarde de ce jeu comme s'il n'avait jamais été exécuté auparavant. Êtes-vous sûr de vouloir faire cela ?"
confirmRestore = 0, "Souhaitez-vous vraiment restaurer #%s#?"
debugStatus = 0, "Nombre d'utilisateurs: "
debugStatus = 1, "Utilisateur actuel: "
debugStatus = 2, "Titre actuel: "
debugStatus = 3, "Titre sauvegardé: "
debugStatus = 4, "Type de tri: "
dialogNo = 0, "Non [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Oui [A]"
extrasMenu = 0, "Explorateur SD vers SD"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Supprimer la mise à jour en attente"
extrasMenu = 6, "Terminer le processus"
extrasMenu = 7, "Monter la sauvegarde système"
extrasMenu = 8, "Re-scanner les titres"
extrasMenu = 9, "Monter le processus RomFS"
extrasMenu = 10, "Sauvegarder le répertoire de JKSV"
extrasMenu = 11, "*[DEV]* Forcer la langue en en-US"
fileModeFileProperties = 0, "Chemin: %s\nTaille: %s"
fileModeFolderProperties = 0, "Chemin: %s\nSous-répertoires: %u\nNombre de fichiers: %u\nTaille totale: %s"
fileModeMenu = 0, "Copier vers "
fileModeMenu = 1, "Supprimer"
fileModeMenu = 2, "Renommer"
fileModeMenu = 3, "Créer un nouveau dossier"
fileModeMenu = 4, "Propriétés"
fileModeMenu = 5, "Fermer"
fileModeMenu = 6, "Ajouter aux filtres de chemins"
fileModeMenuMkDir = 0, "Nouveau"
folderMenuNew = 0, "Nouvelle sauvegarde"
helpFolder = 0, "[A] Sélectionner [Y] Restorer [X] Suprimer [ZR] Upload [B] Fermer"
helpSettings = 0, "[A] Basculer [X] Défauts [B] Retour"
helpTitle = 0, "[A] Sélectionner [L][R] Saut de page [Y] Favorie [X] Options du titre [B] Retour"
helpUser = 0, "[A] Sélectionner [X] Options pour l'utilisateur"
holdingText = 0, "(Maintenir) "
holdingText = 1, "(Garder maintenu) "
holdingText = 2, "(Presque fini!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Temps de jeu: %02d:%02d"
infoStatus = 3, "Nombre de fois lancé: %u"
infoStatus = 4, "Auteur: %s"
infoStatus = 5, "Type de sauvegarde: %s"
infoStatus = 6, "Index du Cache: %u"
infoStatus = 7, "Utilisateur: %s"
loadingStartPage = 0, "Chargement..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Paramètres"
onlineErrorConnecting = 0, "Erreur de connection!"
onlineNoUpdates = 0, "Aucune mise à jour disponible."
popAddedToPathFilter = 0, "'#%s#' ajouté aux filtres de chemins."
popCPUBoostEnabled = 0, "CPU Boost activé pour les ZIP."
popChangeOutputError = 0, "#%s# contient des caractères incorrects ou non-ASCII."
popChangeOutputFolder = 0, "#%s# modifié en #%s#"
popDriveFailed = 0, "Echec du démarrage de Google Drive."
popRemoteNotActive = 0, "Remote n'est pas utilisable"
popDriveStarted = 0, "Google Drive lancé avec succès."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Erreur d'ajout du fichier à la sauvegarde!"
popFolderIsEmpty = 0, "Le dossier est vide!"
popProcessShutdown = 0, "#%s# terminé avec succès."
popSVIExported = 0, "SVI Exporté."
popSaveIsEmpty = 0, "Les données de la sauvegarde sont vides!"
popTrashEmptied = 0, "Trash emptied"
popZipIsEmpty = 0, "Le fichier ZIP est vide!"
saveDataBackupDeleted = 0, "#%s# a été supprimé."
saveDataBackupMovedToTrash = 0, "#%s# a été déplacé dans la corbeille."
saveDataCreatedForUser = 0, "Données de sauvegarde créée pour %s!"
saveDataCreationFailed = 0, "Echec de la création des données de sauvegarde!"
saveDataDeleteAllUser = 0, "*Souhaitez-vous vraiment supprimer toutes les données de sauvegarde pour %s?*"
saveDataDeleteSuccess = 0, "Données de la sauvegarde pour #%s# Supprimée!"
saveDataExtendFailed = 0, "Echec de l'extention des données de la sauvegarde."
saveDataExtendSuccess = 0, "Données de la sauvegarde pour #%s# étendue!"
saveDataIndexText = 0, "Index de la sauvegarde: "
saveDataNoneFound = 0, "Aucune sauvegarde trouvée pour #%s#!"
saveDataResetSuccess = 0, "Sauvegarde pour #%s# réinitialisée!"
saveDataTypeText = 0, "Sauvegarde système"
saveDataTypeText = 1, "Données de sauvegarde"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Sauvegarde de périphérique"
saveDataTypeText = 4, "Stockage temporaire"
saveDataTypeText = 5, "Stockage cache"
saveDataTypeText = 6, "BCAT système"
saveTypeMainMenu = 0, "Périphérique"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "Système"
saveTypeMainMenu = 4, "BCAT système"
saveTypeMainMenu = 5, "Stockage SysTemp"
settingsMenu = 0, "Vider la corbeille"
settingsMenu = 1, "Vérifier les mises à jour"
settingsMenu = 2, "Définir le dossier de sortie de JKSV"
settingsMenu = 3, "Modifier les titres de la liste noire"
settingsMenu = 4, "Supprimer toutes les sauvegardes sauvegardées"
settingsMenu = 5, "Inclure les sauvegardes du matériel avec les utilisateurs: "
settingsMenu = 6, "Sauvegarder automatiquement avant la restauration: "
settingsMenu = 7, "Nom automatique des sauvegardes: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Maintenir pour supprimer: "
settingsMenu = 10, "Maintenir pour restaurer: "
settingsMenu = 11, "Maintenir pour écraser: "
settingsMenu = 12, "Forcer le montage: "
settingsMenu = 13, "Compte des sauvegardes système: "
settingsMenu = 14, "Activer l'écriture pour les sauvegardes système: "
settingsMenu = 15, "Utiliser directement les commandes FS : "
settingsMenu = 16, "Exporter les sauvegardes en ZIP: "
settingsMenu = 17, "Forcer l'utilisation de l'anglais: "
settingsMenu = 18, "Activer la corbeille: "
settingsMenu = 19, "Type de tri des titres: "
settingsMenu = 20, "Échelle d'animation: "
settingsOff = 0, "Désactivé"
settingsOn = 0, ">Activé>"
sortType = 0, "Alphabétique"
sortType = 1, "Temps de jeu"
sortType = 2, "Joué dernièrement"
swkbdEnterName = 0, "Entrez un nouveau nom"
swkbdExpandSize = 0, "Entrez la nouvelle taille en MB"
swkbdMkDir = 0, "Entrez un nom de dossier"
swkbdNewSafeTitle = 0, "Entrez un nouveau nom de dossier"
swkbdProcessID = 0, "Entrez l'ID du processus"
swkbdRename = 0, "Entrez un nouveau nom pour l'objet"
swkbdSaveIndex = 0, "Entrez l'index du cache"
swkbdSetWorkDir = 0, "Entrez un nouveau chemin de sortie"
swkbdSysSavID = 0, "Entrez l'ID de la sauvegarde système"
threadStatusAddingFileToZip = 0, "Ajout de '#%s#' au ZIP..."
threadStatusCalculatingSaveSize = 0, "Calcul de la taille de la sauvegarde..."
threadStatusCheckingForUpdate = 0, "Vérification de mises à jour..."
threadStatusCompressingSaveForUpload = 0, "Compression de #%s# pour l'upload..."
threadStatusCopyingFile = 0, "Copie de '#%s#'..."
threadStatusCreatingSaveData = 0, "Création de la sauvegarde pour #%s#..."
threadStatusDecompressingFile = 0, "Décompression de '#%s#'..."
threadStatusDeletingFile = 0, "Suppression..."
threadStatusDeletingSaveData = 0, "Supression de la sauvegarde pour #%s#..."
threadStatusDeletingUpdate = 0, "Suppression de la mise à jour en attente..."
threadStatusDownloadingFile = 0, "Téléchargement de #%s#..."
threadStatusDownloadingUpdate = 0, "Téléchargement de la mise à jour..."
threadStatusExtendingSaveData = 0, "Extention de la sauvegarde pour #%s#..."
threadStatusGetDirProps = 0, "Obtention des propriétés du dossier..."
threadStatusOpeningFolder = 0, "Ouverture de '#%s#'..."
threadStatusPackingJKSV = 0, "Ecriture du contenu du répertoire JKSV dans le ZIP..."
threadStatusResettingSaveData = 0, "Réinitialisation de la sauvegarde..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "Upload de #%s#..."
titleOptions = 0, "Information"
titleOptions = 1, "Liste noir"
titleOptions = 2, "Changer le dossier de sortie"
titleOptions = 3, "Ouvrir en mode fichier"
titleOptions = 4, "Supprimer toutes les sauvegardes de la sauvegarde"
titleOptions = 5, "Réinitialiser la sauvegarde"
titleOptions = 6, "Supprimer la sauvegarde"
titleOptions = 7, "Etendre la sauvegarde"
titleOptions = 8, "Exporter SVI"
translationMainPage = 0, "Traduction: "
userOptions = 0, "Dumper tout pour"
userOptions = 1, "Sauvegarder la sauvegarde"
userOptions = 2, "Sauvegarder toutes les sauvegardes"
userOptions = 3, "Supprimer toutes les sauvegardes de l'utilisateur"

View File

@ -1,2 +1,171 @@
#Italian translation file
#Copy en-US.txt here and use as a guide.
author = 0, "SimoCasa"
confirmBlacklist = 0, "Sei sicuro di voler aggiungere #%s# alla tua blacklist?"
confirmCopy = 0, "Sei sicuro di voler copiare #%s# in #%s#?"
confirmCreateAllSaveData = 0, "Sei sicuro di voler creare tutti i dati di salvataggio su questo sistema per #%s#? Può metterci un po' di tempo in base ai titoli trovati."
confirmDelete = 0, "Sei sicuro di voler cancellare #%s#? *È permanente*!"
confirmDeleteBackupsAll = 0, "Sei sicuro di voler cancellare *tutti* i tuoi backup dei salvataggi di gioco?"
confirmDeleteBackupsTitle = 0, "Sei sicuro di voler cancellare tutti i salvataggi di backup di #%s#?"
confirmDeleteSaveData = 0, "*ATTENZIONE*: Questo *cancellerà* il salvataggio di #%s# *dal tuo sistema*. Sei sicuro di volerlo fare?"
confirmDriveOverwrite = 0, "Scaricando questo backup da Drive sovrascriverai quello nella tua scheda SD. Vuoi continuare?"
confirmOverwrite = 0, "Sei sicuro di voler sovrascrivere #%s#?"
confirmResetSaveData = 0, "*ATTENZIONE*: Questo *resetterà* i dati di salvataggio per questo gioco come se non fosse mai stato eseguito prima. Sei sicuro di volerlo fare?"
confirmRestore = 0, "Sei sicuro di voler ripristinare #%s#?"
debugStatus = 0, "Numero Utenti: "
debugStatus = 1, "Utente Corrente: "
debugStatus = 2, "Titolo Corrente: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "Tipo Ordinamento: "
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Si [A]"
extrasMenu = 0, "Browser da SD a SD"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Rimuovi Aggiornamento in attesa"
extrasMenu = 6, "Termina Processo"
extrasMenu = 7, "Monta Salvataggio di Sistema"
extrasMenu = 8, "Riscansiona Titoli"
extrasMenu = 9, "Monta Processo RomFS"
extrasMenu = 10, "Backup cartella JKSV"
extrasMenu = 11, "*[DEV]* Output en-US"
fileModeFileProperties = 0, "Percorso: %s\nDimensione: %s"
fileModeFolderProperties = 0, "Percorso: %s\nSottocartelle: %u\nNumero File: %u\nDimensione totale: %s"
fileModeMenu = 0, "Copia in "
fileModeMenu = 1, "Elimina"
fileModeMenu = 2, "Rinomina"
fileModeMenu = 3, "Crea Cartella"
fileModeMenu = 4, "Proprietà"
fileModeMenu = 5, "Chiudi"
fileModeMenu = 6, "Aggiungi ai Filtri del Percorso"
fileModeMenuMkDir = 0, "Nuovo"
folderMenuNew = 0, "Nuovo Backup"
helpFolder = 0, "[A] Seleziona [Y] Ripristina [X] Cancella [ZR] Upload [B] Chiudi"
helpSettings = 0, "[A] Attiva/Disattiva [X] Predefinite [B] Indietro"
helpTitle = 0, "[A] Seleziona [L][R] Jump [Y] Preferiti [X] Opzioni Titolo [B] Indietro"
helpUser = 0, "[A] Seleziona [Y] Dump di tutti i Salvataggi [X] Opzioni Utente"
holdingText = 0, "(Tieni Premuto) "
holdingText = 1, "(Continua a Premere) "
holdingText = 2, "(Ci sei quasi!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Tempo di Gioco: %02d:%02d"
infoStatus = 3, "Avvii Totali: %u"
infoStatus = 4, "Casa Editrice: %s"
infoStatus = 5, "Tipo Salvataggio: %s"
infoStatus = 6, "Indice della Cache: %u"
infoStatus = 7, "Utente: %s"
loadingStartPage = 0, "Caricamento..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Impost."
onlineErrorConnecting = 0, "Errore di Connessione!"
onlineNoUpdates = 0, "Nessun Aggiornamento Disponibile."
popAddedToPathFilter = 0, "'#%s#' aggiunto ai filtri del percorso."
popCPUBoostEnabled = 0, "CPU Boost Abilitato per ZIP."
popChangeOutputError = 0, "#%s# contiene caratteri illeciti o non-ASCII."
popChangeOutputFolder = 0, "#%s# cambiato in #%s#"
popDriveFailed = 0, "Impossibile avviare Google Drive."
popRemoteNotActive = 0, "Remote non è Disponibile"
popDriveStarted = 0, "Google Drive avviato con successo."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Errore durante il commiting del file da salvare!"
popFolderIsEmpty = 0, "La cartella è vuota!"
popProcessShutdown = 0, "#%s# spento con successo."
popSVIExported = 0, "SVI Esportato."
popSaveIsEmpty = 0, "Il salvataggio è vuoto!"
popTrashEmptied = 0, "Cestino Svuotato"
popZipIsEmpty = 0, "Il file ZIP è vuoto!"
saveDataBackupDeleted = 0, "#%s# è stato cancellato."
saveDataBackupMovedToTrash = 0, "#%s# è stato spostato nel cestino."
saveDataCreatedForUser = 0, "Salvataggio creato per %s!"
saveDataCreationFailed = 0, "Creazione salvataggio fallita!"
saveDataDeleteAllUser = 0, "*SEI SICURO DI VOLER CANCELLARE TUTTI I SALVATAGGI DI %s?*"
saveDataDeleteSuccess = 0, "Salvataggio di #%s# cancellato!"
saveDataExtendFailed = 0, "Estensione salvataggio fallita."
saveDataExtendSuccess = 0, "Salvataggio di #%s# esteso!"
saveDataIndexText = 0, "Indice di backup: "
saveDataNoneFound = 0, "Nessun Salvataggio trovato per #%s#!"
saveDataResetSuccess = 0, "Salvataggio di #%s# resettato!"
saveDataTypeText = 0, "Sistema"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Dispositivo"
saveDataTypeText = 4, "Temporanea"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Disposit."
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "Sistema"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Svuota Cestino"
settingsMenu = 1, "Controllo Aggiornamenti"
settingsMenu = 2, "Imposta cartella JKSV di Output"
settingsMenu = 3, "Modifica titoli in Blacklist"
settingsMenu = 4, "Cancella tutti i Salvataggi di Backup"
settingsMenu = 5, "Includi Salvataggi Dispositivo con Utenti: "
settingsMenu = 6, "Backup Automatico nel Ripristino: "
settingsMenu = 7, "Auto-Rinominazione Backups: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Tieni premuto per eliminare: "
settingsMenu = 10, "Tieni premuto per ripristinare: "
settingsMenu = 11, "Tieni premuto per sovrascrivere: "
settingsMenu = 12, "Forza Montaggio: "
settingsMenu = 13, "Account System Saves: "
settingsMenu = 14, "Abilita scrittura su Salvataggi Sistema: "
settingsMenu = 15, "Usa comandi FS direttamente: "
settingsMenu = 16, "Esporta salvataggio in ZIP: "
settingsMenu = 17, "Forza utilizzo Inglese: "
settingsMenu = 18, "Abilita Cestino: "
settingsMenu = 19, "Tipo di ordinamento del Titolo: "
settingsMenu = 20, "Scala di Animazione: "
settingsOff = 0, "Off"
settingsOn = 0, ">On>"
sortType = 0, "Alfabetico"
sortType = 1, "Tempo di Gioco"
sortType = 2, "Ultima Partita"
swkbdEnterName = 0, "Inserisci nuovo nome"
swkbdExpandSize = 0, "Inserisci nuova dimensione in MB"
swkbdMkDir = 0, "Inserisci nome cartella"
swkbdNewSafeTitle = 0, "Inserisci una nuova cartella di output"
swkbdProcessID = 0, "Inserisci Process ID"
swkbdRename = 0, "Inserisci un nuovo nome per elemento"
swkbdSaveIndex = 0, "Inserisci l'Indice della Cache"
swkbdSetWorkDir = 0, "Inserisci un nuovo Percorso Output"
swkbdSysSavID = 0, "Inserisci System Save ID"
threadStatusAddingFileToZip = 0, "Aggiungi '#%s#' a ZIP..."
threadStatusCalculatingSaveSize = 0, "Calcolo dimensione salvataggio..."
threadStatusCheckingForUpdate = 0, "Controllo Aggiornamenti..."
threadStatusCompressingSaveForUpload = 0, "Compressione di #%s# per l'upload..."
threadStatusCopyingFile = 0, "Copiando '#%s#'..."
threadStatusCreatingSaveData = 0, "Creazione salvataggio per #%s#..."
threadStatusDecompressingFile = 0, "Decompressione '#%s#'..."
threadStatusDeletingFile = 0, "Cancellando..."
threadStatusDeletingSaveData = 0, "Cancellazione salvataggio di #%s#..."
threadStatusDeletingUpdate = 0, "Cancellazione dell'aggiornamento in sospeso..."
threadStatusDownloadingFile = 0, "Download di #%s#..."
threadStatusDownloadingUpdate = 0, "Download aggiornamento..."
threadStatusExtendingSaveData = 0, "Estensione Salvataggio per #%s#..."
threadStatusGetDirProps = 0, "Ottenimento le proprietà della cartella..."
threadStatusOpeningFolder = 0, "Apertura '#%s#'..."
threadStatusPackingJKSV = 0, "Scrittura del contenuto della cartella JKSV su ZIP..."
threadStatusResettingSaveData = 0, "Reset salvataggio..."
threadStatusSavingTranslations = 0, "Salvataggio del file master..."
threadStatusUploadingFile = 0, "Upload di #%s#..."
titleOptions = 0, "Informazioni"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Cambia Cartella Output"
titleOptions = 3, "Apri in Modalità File"
titleOptions = 4, "Elimina tutti i Salvataggi di Backup"
titleOptions = 5, "Resetta Salvataggio"
titleOptions = 6, "Elimina Salvataggio"
titleOptions = 7, "Estendi Salvataggio"
titleOptions = 8, "Esporta SVI"
translationMainPage = 0, "Traduzione: "
userOptions = 0, "Dump di tutto per "
userOptions = 1, "Crea salvataggio"
userOptions = 2, "Crea tutti salvataggi"
userOptions = 3, "Cancella tutti salvataggi Utente"

View File

@ -1,2 +1,215 @@
#Japanese translation file
#Copy en-US.txt here and use as a guide.
author = 0, "NULL"
confirmBlacklist = 0, "ブラックリストに#%s#を追加してもよろしいですか?"
confirmCopy = 0, "#%s#を#%s#にコピーしてもよろしいですか?"
confirmCreateAllSaveData = 0, "このシステムで#%s#のすべてのセーブデータを作成してもよろしいですか?見つかったタイトルの数によっては、時間がかかる場合があります。"
confirmDelete = 0, "#%s#を削除してもよろしいですか? これは永続的です!"
confirmDeleteBackupsAll = 0, "全てのゲームのセーブバックアップを全て削除してもよろしいですか?"
confirmDeleteBackupsTitle = 0, "#%s#のすべてのセーブデータのバックアップを削除してもよろしいですか?"
confirmDeleteSaveData = 0, "警告: これにより、#%s#のセーブデータがシステムから消去されます。 これを実行してもよろしいですか?"
#CHANGED=============================================>
confirmDriveOverwrite = 0, "このバックアップをドライブからダウンロードすると、SDカードにあるバックアップが上書きされます。続行しますか"
#<====================================================
confirmOverwrite = 0, "#%s#を上書きしてもよろしいですか?"
confirmResetSaveData = 0, "警告: これにより、このゲームのセーブデータが、以前に実行されたことがないかのようにリセットされます。これを実行してもよろしいですか?"
confirmRestore = 0, "#%s#を復元してもよろしいですか?"
#CHANGED=============================================>
debugStatus = 0, "ユーザー数: "
debugStatus = 1, "現在のユーザー: "
debugStatus = 2, "現在のタイトル: "
debugStatus = 3, "安全なタイトル: "
debugStatus = 4, "ソートの種類: "
#<====================================================
dialogNo = 0, "いいえ [B]"
dialogOK = 0, "了解 [A]"
dialogYes = 0, "はい [A]"
extrasMenu = 0, "SDからSDブラウザ"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "保留中のアップデータを削除する"
extrasMenu = 6, "プロセスを終了します"
extrasMenu = 7, "マウントシステムSave"
extrasMenu = 8, "タイトルの再スキャン"
extrasMenu = 9, "マウントプロセスRomFS"
extrasMenu = 10, "JKSVフォルダをバックアップ"
extrasMenu = 11, "*[DEV]* 出力en-US"
fileModeFileProperties = 0, "パス: %s\n容量: %s"
fileModeFolderProperties = 0, "パス: %s\nサブフォルダ: %u\nファイル数: %u\n全体の大きさ: %s"
fileModeMenu = 0, "コピー先 "
fileModeMenu = 1, "削除"
fileModeMenu = 2, "リネーム"
fileModeMenu = 3, "ディレクトリ作成"
fileModeMenu = 4, "プロパティ"
fileModeMenu = 5, "閉じる"
fileModeMenu = 6, "パスフィルターに追加"
fileModeMenuMkDir = 0, "新規"
folderMenuNew = 0, "新規バックアップ"
#CHANGED=============================================>
helpFolder = 0, "[A] 選択 [Y] リストア [X] 削除 [ZR] アップロード [B] 閉じる"
#<====================================================
helpSettings = 0, "[A] トグル [X] デフォルト [B] 戻る"
helpTitle = 0, "[A] 選択 [L][R] ジャンプ [Y] お気に入り [X] タイトルオプション [B] 戻る"
helpUser = 0, "[A] 選択 [X] ユーザー設定"
holdingText = 0, "(所有) "
holdingText = 1, "(保持し続けます) "
holdingText = 2, "(もうすぐです!) "
#CHANGED=============================================>
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "プレイ時間: %02d:%02d"
infoStatus = 3, "起動回数: %u"
infoStatus = 4, "出版社: %s"
infoStatus = 5, "セーブの種類: %s"
infoStatus = 6, "キャッシュインデックス: %u"
infoStatus = 7, "ユーザー: %s"
#<=====================================================
loadingStartPage = 0, "読み込み中..."
mainMenuExtras = 0, "エクストラ"
mainMenuSettings = 0, "設定"
onlineErrorConnecting = 0, "接続エラー!"
onlineNoUpdates = 0, "利用可能なアップデートはありません。"
popAddedToPathFilter = 0, "'#%s#' パスフィルターに追加されました。"
popCPUBoostEnabled = 0, "ZIPでCPUブーストが有効になっています。"
popChangeOutputError = 0, "#%s# は、不正な文字または非ASCII文字が含まれています。"
popChangeOutputFolder = 0, "#%s# から #%s# に変更"
#CHANGED=============================================>
popDriveFailed = 0, "Google Driveの起動に失敗しました。"
popRemoteNotActive = 0, "Remoteドライブは使用できません"
popDriveStarted = 0, "Google Driveが正常に起動しました。"
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
#<====================================================
popErrorCommittingFile = 0, "保存するファイルのコミット中にエラーが発生しました!"
popFolderIsEmpty = 0, "フォルダは空です!"
popProcessShutdown = 0, "#%s# 正常にシャットダウンしました。"
#CHANGED=============================================>
popSVIExported = 0, "SVIをエクスポートしました。"
#<====================================================
popSaveIsEmpty = 0, "セーブデータは空です!"
popTrashEmptied = 0, "ゴミ箱を空にする"
popZipIsEmpty = 0, "ZIPファイルは空です!"
saveDataBackupDeleted = 0, "#%s#が削除されました。"
saveDataBackupMovedToTrash = 0, "#%s#はゴミ箱に移動されました"
saveDataCreatedForUser = 0, "#%s#用に作成されたデータを保存する!"
saveDataCreationFailed = 0, "セーブデータの作成に失敗しました!"
saveDataDeleteAllUser = 0, "#%s#のすべての保存データを削除してもよろしいですか?*"
saveDataDeleteSuccess = 0, "#%s#のセーブデータは削除されました!"
saveDataExtendFailed = 0, "セーブデータの拡張に失敗しました。"
saveDataExtendSuccess = 0, "#%s#のセーブデータを拡張!"
saveDataIndexText = 0, "存檔索引號:"
saveDataNoneFound = 0, "#%s#のセーブデータが見つかりません!"
saveDataResetSuccess = 0, "#%s#のセーブデータをリセット!"
saveDataTypeText = 0, "システム\n"
saveDataTypeText = 1, "アカウント\n"
saveDataTypeText = 2, "BCAT\n"
saveDataTypeText = 3, "デバイス\n"
saveDataTypeText = 4, "一時的なもの\n"
saveDataTypeText = 5, "キャッシュ\n"
saveDataTypeText = 6, "システムBCAT\n"
saveTypeMainMenu = 0, "デバイス"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "キャッシュ"
saveTypeMainMenu = 3, "システム"
saveTypeMainMenu = 4, "システム BCAT"
saveTypeMainMenu = 5, "システマティックストレージ機器"
settingsMenu = 0, "ゴミ箱を空にする"
settingsMenu = 1, "アップデートを確認する"
settingsMenu = 2, "JKSVセーブデータ保存出力フォルダを設定する"
settingsMenu = 3, "ブラックリストに載っているタイトルを編集する"
settingsMenu = 4, "すべての保存バックアップを削除する"
settingsMenu = 5, "ユーザーと一緒にデバイスのセーブデータを含める: "
settingsMenu = 6, "復元時の自動バックアップ: "
settingsMenu = 7, "自動名前バックアップ: "
settingsMenu = 8, "オーバークロック/CPUブースト: "
settingsMenu = 9, "削除するために保持: "
settingsMenu = 10, "保持して復元: "
settingsMenu = 11, "上書きするために保持: "
settingsMenu = 12, "フォースマウント: "
settingsMenu = 13, "アカウントシステムセーブデータ: "
settingsMenu = 14, "システムセーブデータへの書き込みを有効にする: "
settingsMenu = 15, "FSコマンドを直接使用する: "
settingsMenu = 16, "セーブデータをZIPにエクスポート: "
settingsMenu = 17, "英語を強制的に使用する: "
settingsMenu = 18, "ごみ箱を有効にする: "
settingsMenu = 19, "タイトルの並べ替えタイプ: "
settingsMenu = 20, "アニメーションスケール: "
settingsOff = 0, "オフ"
settingsOn = 0, ">オン>"
sortType = 0, "アルファベット順"
sortType = 1, "プレイ時間"
sortType = 2, "最後にプレイ"
swkbdEnterName = 0, "新しい名前を入力してください"
swkbdExpandSize = 0, "新しいサイズをMBで入力"
swkbdMkDir = 0, "フォルダ名を入力してください"
swkbdNewSafeTitle = 0, "新規出力先フォルダを入力してください"
swkbdProcessID = 0, "プロセスIDを入力してください"
swkbdRename = 0, "アイテムの新しい名前を入力してください"
swkbdSaveIndex = 0, "キャッシュインデックスを入力してください"
swkbdSetWorkDir = 0, "新しい出力パスを入力してください"
swkbdSysSavID = 0, "システムセーブIDを入力してください"
threadStatusAddingFileToZip = 0, "#%s#をZIPに追加中..."
#CHANGED=============================================>
threadStatusCalculatingSaveSize = 0, "保存データサイズの算出中..."
#<====================================================
threadStatusCheckingForUpdate = 0, "アップデートの確認中..."
#CHANGED=============================================>
threadStatusCompressingSaveForUpload = 0, "アップロードのために #%s# を圧縮中..."
#<====================================================
threadStatusCopyingFile = 0, "#%s#をコピーしています..."
threadStatusCreatingSaveData = 0, "#%s#のセームデータの作成中..."
threadStatusDecompressingFile = 0, "#%s#の解凍中..."
threadStatusDeletingFile = 0, "削除中..."
threadStatusDeletingSaveData = 0, "#%s#のセーブデータを削除中..."
threadStatusDeletingUpdate = 0, "保留中のアップデータを削除中..."
#CHANGED=============================================>
threadStatusDownloadingFile = 0, "ダウンロード中 #%s#..."
#<====================================================
threadStatusDownloadingUpdate = 0, "アップデータをダウンロード中..."
threadStatusExtendingSaveData = 0, "#%s#のセーブデータを拡張中..."
threadStatusGetDirProps = 0, "フォルダプロパティの取得中..."
threadStatusOpeningFolder = 0, "#%s#を開き中..."
threadStatusPackingJKSV = 0, "JKSVフォルダの内容をZIPに書き込み中..."
threadStatusResettingSaveData = 0, "セーブデータをリストアしています..."
threadStatusSavingTranslations = 0, "ファイルマスタの保存中..."
#CHANGED=============================================>
threadStatusUploadingFile = 0, "アップロード中 #%s#..."
#<====================================================
titleOptions = 0, "インフォメーション"
titleOptions = 1, "ブラックリスト"
titleOptions = 2, "出力フォルダを変更する"
titleOptions = 3, "ファイルモードで開く"
titleOptions = 4, "すべてのセーブデータバックアップを削除する"
titleOptions = 5, "セーブデータをリセット"
titleOptions = 6, "セーブデータを削除"
titleOptions = 7, "セーブデータを拡張"
#CHANGED=============================================>
titleOptions = 8, "エクスポートSVI"
#<====================================================
translationMainPage = 0, "翻訳: "
userOptions = 0, "すべてをダンプする "
userOptions = 1, "セーブデータを作成"
userOptions = 2, "全てのセーブデータを作成"
userOptions = 3, "全てのユーザーセーブデータを削除"

View File

@ -1,2 +1,171 @@
#Korean translation file
#Copy en-US.txt here and use as a guide.
author = 0, "NULL"
confirmBlacklist = 0, "블랙리스트에 #%s#을(를) 추가하겠습니까?"
confirmCopy = 0, "#%s#을(를) #%s#에 복사하겠습니까?"
confirmCreateAllSaveData = 0, "이 시스템에서 #%s#에 대한 모든 저장 데이터를 생성하겠습니까? 검색된 타이틀 수에 따라 시간이 걸릴 수 있습니다."
confirmDelete = 0, "#%s#을(를) 삭제하겠습니까? *영구적입니다*!"
confirmDeleteBackupsAll = 0, "모든 게임에 대한 *모든* 저장 백업을 삭제하겠습니까?"
confirmDeleteBackupsTitle = 0, "#%s#에 대한 모든 백업 백업을 삭제하겠습니까?"
confirmDeleteSaveData = 0, "경고*: 이렇게 하면 #%s#에 대한 저장 데이터가 *시스템에서* 지워집니다. 정말 하겠습니까?"
confirmDriveOverwrite = 0, "드라이브에서 이 백업을 다운로드하면 SD 카드에 있는 백업을 덮어씁니다. 계속합니까?"
confirmOverwrite = 0, "#%s#을(를) 덮어쓰시겠습니까?"
confirmResetSaveData = 0, "경고*: 이렇게 하면 이 게임의 저장 데이터가 이전에 실행되지 않은 것처럼 *리셋됩니다*. 정말 하겠습니까?"
confirmRestore = 0, "#%s#을(를) 복원하겠습니까?"
debugStatus = 0, "사용자 수: "
debugStatus = 1, "현자 사용자: "
debugStatus = 2, "현재 제목: "
debugStatus = 3, "안전 제목: "
debugStatus = 4, "정렬 유형: "
dialogNo = 0, "아니오 [B]"
dialogOK = 0, "확인 [A]"
dialogYes = 0, "예 [A]"
extrasMenu = 0, "SD에서 SD 브라우저로"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: 안전"
extrasMenu = 3, "BIS: 시스템"
extrasMenu = 4, "BIS: 사용자"
extrasMenu = 5, "보류 중인 업데이트 제거"
extrasMenu = 6, "프로세스 종료"
extrasMenu = 7, "시스템 저장 인식"
extrasMenu = 8, "제목 다시 검색"
extrasMenu = 9, "프로세스 RomFS 인식"
extrasMenu = 10, "JKSV 폴더 백업"
extrasMenu = 11, "*[DEV]* 출력 en-US"
fileModeFileProperties = 0, "경로: %s\n크기: %s"
fileModeFolderProperties = 0, "경로: %s\n하위 폴더: %u\n파일 수: %u\n전체 크기: %s"
fileModeMenu = 0, "에게 복사 "
fileModeMenu = 1, "삭제"
fileModeMenu = 2, "이름 바꾸기"
fileModeMenu = 3, "폴더 만들기"
fileModeMenu = 4, "속성"
fileModeMenu = 5, "닫기"
fileModeMenu = 6, "경로 필터에 추가"
fileModeMenuMkDir = 0, "새로 만들기"
folderMenuNew = 0, "새 백업"
helpFolder = 0, "[A] 선택 [Y] 복원 [X] 삭제 [ZR] 업로드 [B] 닫기"
helpSettings = 0, "[A] 전환 [X] 기본 [B] 뒤로가기"
helpTitle = 0, "[A] 선택 [L][R] 점프 [Y] 즐겨찾기 [X] 제목 옵션 [B] 뒤로가기"
helpUser = 0, "[A] 선택 [Y] 모든 세이브 덤프 [X] 사용자 옵션"
holdingText = 0, "(길게 누르기) "
holdingText = 1, "(계속 누르기) "
holdingText = 2, "(거의 완료!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "즐긴 시간: %02d:%02d"
infoStatus = 3, "총 실행 수: %u"
infoStatus = 4, "발행자: %s"
infoStatus = 5, "저장 유형: %s"
infoStatus = 6, "캐시 인덱스: %u"
infoStatus = 7, "사용자: %s"
infoStatus = 9, ""
loadingStartPage = 0, "로드 중..."
mainMenuExtras = 0, "추가"
mainMenuSettings = 0, "설정"
onlineErrorConnecting = 0, "연결 오류!"
onlineNoUpdates = 0, "사용 가능한 업데이트가 없습니다."
popAddedToPathFilter = 0, "'#%s#'이(가) 경로 필터에 추가되었습니다."
popCPUBoostEnabled = 0, "ZIP를 위한 CPU 부스트가 활성화되었습니다."
popChangeOutputError = 0, "#%s#에 잘못된 또는 ASCII가 아닌 문자가 포함되어 있습니다."
popChangeOutputFolder = 0, "#%s#이(가) #%s#로 변경되었습니다."
popDriveFailed = 0, "구글 드라이브 시작에 실패했습니다."
popRemoteNotActive = 0, "구글 드라이브를 사용할 수 없습니다."
popDriveStarted = 0, "구글 드라이브가 성공적으로 시작되었습니다."
popWebdavStarted = 0, "Webdav가 성공적으로 시작되었습니다."
popWebdavFailed =, 0, "Webdav를 시작하지 못했습니다."
popErrorCommittingFile = 0, "저장할 파일을 커밋하는 동안 오류가 발생했습니다!"
popFolderIsEmpty = 0, "폴더가 비어 있습니다!"
popProcessShutdown = 0, "#%s#이(가) 성공적으로 종료되었습니다."
popSVIExported = 0, "SVI를 내보냈습니다."
popSaveIsEmpty = 0, "저장 데이터가 비어 있습니다!"
popTrashEmptied = 0, "휴지통이 비었습니다."
popZipIsEmpty = 0, "ZIP 파일이 비어 있습니다!"
saveDataBackupDeleted = 0, "#%s#이(가) 삭제되었습니다."
saveDataBackupMovedToTrash = 0, "#%s#이(가) 휴지통으로 이동되었습니다."
saveDataCreatedForUser = 0, "%s에 대해 생성된 데이터 저장!"
saveDataCreationFailed = 0, "저장 데이터 생성 실패!"
saveDataDeleteAllUser = 0, "*%s에 대한 모든 저장 데이터를 삭제하겠습니까?*"
saveDataDeleteSuccess = 0, "#%s#에 대한 저장 데이터가 삭제되었습니다!"
saveDataExtendFailed = 0, "저장 데이터 확장에 실패했습니다."
saveDataExtendSuccess = 0, "#%s#에 대한 데이터 저장이 확장되었습니다!"
saveDataNoneFound = 0, "#%s#에 대한 저장을 찾을 수 없습니다!"
saveDataResetSuccess = 0, "#%s# 재설정을 위해 저장합니다!"
saveDataTypeText = 0, "시스템"
saveDataTypeText = 1, "계정"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "장치"
saveDataTypeText = 4, "임시"
saveDataTypeText = 5, "캐시"
saveDataTypeText = 6, "시스템 BCAT"
saveTypeMainMenu = 0, "장치"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "캐시"
saveTypeMainMenu = 3, "시스템"
saveTypeMainMenu = 4, "시스템 BCAT"
saveTypeMainMenu = 5, "SysTemp 스토리지템"
settingsMenu = 0, "휴지통 비우기"
settingsMenu = 1, "업데이트 확인"
settingsMenu = 2, "JKSV 저장 출력 폴더 설정"
settingsMenu = 3, "블랙리스트에 있는 제목 편집"
settingsMenu = 4, "모든 저장 백업 삭제"
settingsMenu = 5, "사용자와 함께 장치 저장 포함: "
settingsMenu = 6, "복원 시 자동 백업: "
settingsMenu = 7, "자동 이름 백업: "
settingsMenu = 8, "오버클럭/CPU 부스트: "
settingsMenu = 9, "삭제하려면 길게 누르기: "
settingsMenu = 10, "복원하려면 길게 누르기: "
settingsMenu = 11, "덮어쓰려면 길게 누르기: "
settingsMenu = 12, "강제 인식: "
settingsMenu = 13, "계정 시스템 저장: "
settingsMenu = 14, "시스템 저장에 쓰기 활성화: "
settingsMenu = 15, "FS 명령을 직접 사용: "
settingsMenu = 16, "저장된 파일을 ZIP으로 내보내기: "
settingsMenu = 17, "강제 영어 사용: "
settingsMenu = 18, "휴지통 활성화: "
settingsMenu = 19, "제목 정렬 유형: "
settingsMenu = 20, "애니메이션 스케일: "
settingsOff = 0, "끔"
settingsOn = 0, ">켬>"
sortType = 0, "알파벳"
sortType = 1, "즐긴 시간"
sortType = 2, "마지막 실행"
swkbdEnterName = 0, "새 이름 입력"
swkbdExpandSize = 0, "새 MB 크기 입력"
swkbdMkDir = 0, "폴더 이름 입력"
swkbdNewSafeTitle = 0, "새 출력 폴더 입력"
swkbdProcessID = 0, "프로세스 ID 입력"
swkbdRename = 0, "항목의 새 이름을 입력"
swkbdSaveIndex = 0, "캐시 인덱스 입력"
swkbdSetWorkDir = 0, "새 출력 경로 입력"
swkbdSysSavID = 0, "시스템 저장 ID 입력"
threadStatusAddingFileToZip = 0, "'#%s#'을(를) ZIP에 추가하는 중..."
threadStatusCalculatingSaveSize = 0, "저장 데이터 크기 계산 중..."
threadStatusCheckingForUpdate = 0, "업데이트 확인 중..."
threadStatusCompressingSaveForUpload = 0, "업로드를 위해 #%s# 압축 중..."
threadStatusCopyingFile = 0, "'#%s#' 복사 중..."
threadStatusCreatingSaveData = 0, "#%s#에 대한 저장 데이터 생성 중..."
threadStatusDecompressingFile = 0, "'#%s#' 압축 해제 중..."
threadStatusDeletingFile = 0, "삭제 중..."
threadStatusDeletingSaveData = 0, "#%s#에 대한 저장 데이터 삭제 중..."
threadStatusDeletingUpdate = 0, "대기 중인 업데이트 삭제 중..."
threadStatusDownloadingFile = 0, "#%s# 다운로드 중..."
threadStatusDownloadingUpdate = 0, "업데이트 다운로드 중..."
threadStatusExtendingSaveData = 0, "#%s#에 대한 저장 데이터 확장 중..."
threadStatusGetDirProps = 0, "폴더 속성 가져오기..."
threadStatusOpeningFolder = 0, "'#%s#'을(를) 여는 중..."
threadStatusPackingJKSV = 0, "JKSV 폴더 내용을 ZIP에 쓰는 중..."
threadStatusResettingSaveData = 0, "저장 데이터 재설정 중..."
threadStatusSavingTranslations = 0, "마스터 파일 저장 중..."
threadStatusUploadingFile = 0, "#%s# 업로드 중..."
titleOptions = 0, "정보"
titleOptions = 1, "블랙리스트"
titleOptions = 2, "출력 폴더 변경"
titleOptions = 3, "파일 모드에서 열기"
titleOptions = 4, "모든 저장 백업 삭제"
titleOptions = 5, "저장 데이터 재설정"
titleOptions = 6, "저장 데이터 삭제"
titleOptions = 7, "저장 데이터 확장"
titleOptions = 8, "SVI 내보내기"
translationMainPage = 0, "번역: "
userOptions = 0, "에 대해 모두 덤프 "
userOptions = 1, "저장 데이터 생성"
userOptions = 2, "모든 저장 데이터 생성"
userOptions = 3, "모든 사용자 저장 삭제"

View File

@ -1,2 +1,171 @@
#Dutch translation file
#Copy en-US.txt here and use as a guide.
author = 0, "NULL"
confirmBlacklist = 0, "Are you sure you want to add #%s# to your blacklist?"
confirmCopy = 0, "Are you sure you want to copy #%s# to #%s#?"
confirmCreateAllSaveData = 0, "Are you sure you would like to create all save data on this system for #%s#? This can take a while depending on how many titles are found."
confirmDelete = 0, "Are you sure you want to delete #%s#? *This is permanent*!"
confirmDeleteBackupsAll = 0, "Are you sure you would like to delete *all* of your save backups for all of your games?"
confirmDeleteBackupsTitle = 0, "Are you sure you would like to delete all save backups for #%s#?"
confirmDeleteSaveData = 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"
confirmDriveOverwrite = 0, "Downloading this backup from drive will overwrite the one on your SD card. Continue?"
confirmOverwrite = 0, "Are you sure you want to overwrite #%s#?"
confirmResetSaveData = 0, "*WARNING*: This *will* reset the save data for this game as if it was never ran before. Are you sure you want to do this?"
confirmRestore = 0, "Are you sure you want to restore #%s#?"
debugStatus = 0, "User Count: "
debugStatus = 1, "Current User: "
debugStatus = 2, "Current Title: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "Sort Type: "
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Yes [A]"
extrasMenu = 0, "SD to SD Browser"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Remove Pending Update"
extrasMenu = 6, "Terminate Process"
extrasMenu = 7, "Mount System Save"
extrasMenu = 8, "Rescan Titles"
extrasMenu = 9, "Mount Process RomFS"
extrasMenu = 10, "Backup JKSV Folder"
extrasMenu = 11, "*[DEV]* Output en-US"
fileModeFileProperties = 0, "Path: %s\nSize: %s"
fileModeFolderProperties = 0, "Path: %s\nSub Folders: %u\nFile Count: %u\nTotal Size: %s"
fileModeMenu = 0, "Copy To "
fileModeMenu = 1, "Delete"
fileModeMenu = 2, "Rename"
fileModeMenu = 3, "Make Dir"
fileModeMenu = 4, "Properties"
fileModeMenu = 5, "Close"
fileModeMenu = 6, "Add to Path Filters"
fileModeMenuMkDir = 0, "New"
folderMenuNew = 0, "New Backup"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close"
helpSettings = 0, "[A] Toggle [X] Defaults [B] Back"
helpTitle = 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back"
helpUser = 0, "[A] Select [Y] Dump All Saves [X] User Options"
holdingText = 0, "(Hold) "
holdingText = 1, "(Keep Holding) "
holdingText = 2, "(Almost There!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Play Time: %02d:%02d"
infoStatus = 3, "Total Launches: %u"
infoStatus = 4, "Publisher: %s"
infoStatus = 5, "Save Type: %s"
infoStatus = 6, "Cache Index: %u"
infoStatus = 7, "User: %s"
infoStatus = 9, ""
loadingStartPage = 0, "Loading..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Settings"
onlineErrorConnecting = 0, "Error Connecting!"
onlineNoUpdates = 0, "No Updates Available."
popAddedToPathFilter = 0, "'#%s#' added to path filters."
popCPUBoostEnabled = 0, "CPU Boost Enabled for ZIP."
popChangeOutputError = 0, "#%s# contains illegal or non-ASCII characters."
popChangeOutputFolder = 0, "#%s# changed to #%s#"
popDriveFailed = 0, "Failed to start Google Drive."
popRemoteNotActive = 0, "Remote is not available"
popDriveStarted = 0, "Google Drive started successfully."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Error committing file to save!"
popFolderIsEmpty = 0, "Folder is empty!"
popProcessShutdown = 0, "#%s# successfully shutdown."
popSVIExported = 0, "SVI Exported."
popSaveIsEmpty = 0, "Save data is empty!"
popTrashEmptied = 0, "Trash emptied"
popZipIsEmpty = 0, "ZIP file is empty!"
saveDataBackupDeleted = 0, "#%s# has been deleted."
saveDataBackupMovedToTrash = 0, "#%s# has been moved to trash."
saveDataCreatedForUser = 0, "Save data created for %s!"
saveDataCreationFailed = 0, "Save data creation failed!"
saveDataDeleteAllUser = 0, "*ARE YOU SURE YOU WANT TO DELETE ALL SAVE DATA FOR %s?*"
saveDataDeleteSuccess = 0, "Save data for #%s# deleted!"
saveDataExtendFailed = 0, "Failed to extend save data."
saveDataExtendSuccess = 0, "Save data for #%s# extended!"
saveDataNoneFound = 0, "No saves found for #%s#!"
saveDataResetSuccess = 0, "Save for #%s# reset!"
saveDataTypeText = 0, "System"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Device"
saveDataTypeText = 4, "Temporary"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Empty Trash Bin"
settingsMenu = 1, "Check for Updates"
settingsMenu = 2, "Set JKSV Save Output Folder"
settingsMenu = 3, "Edit Blacklisted Titles"
settingsMenu = 4, "Delete All Save Backups"
settingsMenu = 5, "Include Device Saves With Users: "
settingsMenu = 6, "Auto Backup On Restore: "
settingsMenu = 7, "Auto-Name Backups: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Hold To Delete: "
settingsMenu = 10, "Hold To Restore: "
settingsMenu = 11, "Hold To Overwrite: "
settingsMenu = 12, "Force Mount: "
settingsMenu = 13, "Account System Saves: "
settingsMenu = 14, "Enable Writing to System Saves: "
settingsMenu = 15, "Use FS Commands Directly: "
settingsMenu = 16, "Export Saves to ZIP: "
settingsMenu = 17, "Force English To Be Used: "
settingsMenu = 18, "Enable Trash Bin: "
settingsMenu = 19, "Title Sorting Type: "
settingsMenu = 20, "Animation Scale: "
settingsOff = 0, "Off"
settingsOn = 0, ">On>"
sortType = 0, "Alphabetical"
sortType = 1, "Time Played"
sortType = 2, "Last Played"
swkbdEnterName = 0, "Enter a new name"
swkbdExpandSize = 0, "Enter New Size in MB"
swkbdMkDir = 0, "Enter a folder name"
swkbdNewSafeTitle = 0, "Input New Output Folder"
swkbdProcessID = 0, "Enter Process ID"
swkbdRename = 0, "Enter a new name for item"
swkbdSaveIndex = 0, "Enter Cache Index"
swkbdSetWorkDir = 0, "Enter a new Output Path"
swkbdSysSavID = 0, "Enter System Save ID"
threadStatusAddingFileToZip = 0, "Adding '#%s#' to ZIP..."
threadStatusCalculatingSaveSize = 0, "Calculating save data size..."
threadStatusCheckingForUpdate = 0, "Checking for updates..."
threadStatusCompressingSaveForUpload = 0, "Compressing #%s# for upload..."
threadStatusCopyingFile = 0, "Copying '#%s#'..."
threadStatusCreatingSaveData = 0, "Creating Save Data for #%s#..."
threadStatusDecompressingFile = 0, "Decompressing '#%s#'..."
threadStatusDeletingFile = 0, "Deleting..."
threadStatusDeletingSaveData = 0, "Deleting Save Data for #%s#..."
threadStatusDeletingUpdate = 0, "Deleting pending update..."
threadStatusDownloadingFile = 0, "Downloading #%s#..."
threadStatusDownloadingUpdate = 0, "Downloading update..."
threadStatusExtendingSaveData = 0, "Extending Save Data for #%s#..."
threadStatusGetDirProps = 0, "Getting Folder Properties..."
threadStatusOpeningFolder = 0, "Opening '#%s#'..."
threadStatusPackingJKSV = 0, "Writing JKSV folder contents to ZIP..."
threadStatusResettingSaveData = 0, "Resetting save data..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "Uploading #%s#..."
titleOptions = 0, "Information"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Change Output Folder"
titleOptions = 3, "Open in File Mode"
titleOptions = 4, "Delete All Save Backups"
titleOptions = 5, "Reset Save Data"
titleOptions = 6, "Delete Save Data"
titleOptions = 7, "Extend Save Data"
titleOptions = 8, "Export SVI"
translationMainPage = 0, "Translation: "
userOptions = 0, "Dump All For "
userOptions = 1, "Create Save Data"
userOptions = 2, "Create All Save Data"
userOptions = 3, "Delete All User Saves"

156
romfs/lang/pt-BR.txt Normal file
View File

@ -0,0 +1,156 @@
author = 0, "NULL"
confirmBlacklist = 0, "Are you sure you want to add #%s# to your blacklist?"
confirmCopy = 0, "Are you sure you want to copy #%s# to #%s#?"
confirmCreateAllSaveData = 0, "Are you sure you would like to create all save data on this system for #%s#? This can take a while depending on how many titles are found."
confirmDelete = 0, "Are you sure you want to delete #%s#? *This is permanent*!"
confirmDeleteBackupsAll = 0, "Are you sure you would like to delete *all* of your save backups for all of your games?"
confirmDeleteBackupsTitle = 0, "Are you sure you would like to delete all save backups for #%s#?"
confirmDeleteSaveData = 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"
confirmOverwrite = 0, "Are you sure you want to overwrite #%s#?"
confirmResetSaveData = 0, "*WARNING*: This *will* reset the save data for this game as if it was never ran before. Are you sure you want to do this?"
confirmRestore = 0, "Are you sure you want to restore #%s#?"
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Yes [A]"
extrasMenu = 0, "SD to SD Browser"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Remove Pending Update"
extrasMenu = 6, "Terminate Process"
extrasMenu = 7, "Mount System Save"
extrasMenu = 8, "Rescan Titles"
extrasMenu = 9, "Mount Process RomFS"
extrasMenu = 10, "Backup JKSV Folder"
extrasMenu = 11, "*[DEV]* Output en-US"
fileModeFileProperties = 0, "Path: %s\nSize: %s"
fileModeFolderProperties = 0, "Path: %s\nSub Folders: %u\nFile Count: %u\nTotal Size: %s"
fileModeMenu = 0, "Copy To "
fileModeMenu = 1, "Delete"
fileModeMenu = 2, "Rename"
fileModeMenu = 3, "Make Dir"
fileModeMenu = 4, "Properties"
fileModeMenu = 5, "Close"
fileModeMenu = 6, "Add to Path Filters"
fileModeMenuMkDir = 0, "New"
folderMenuNew = 0, "New Backup"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [B] Close"
helpSettings = 0, "[A] Toggle [X] Defaults [B] Back"
helpTitle = 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back"
helpUser = 0, "[A] Select [X] User Options"
holdingText = 0, "(Hold) "
holdingText = 1, "(Keep Holding) "
holdingText = 2, "(Almost There!) "
infoStatus = 0, "TID: "
infoStatus = 1, "SID: "
infoStatus = 2, "Play Time: "
infoStatus = 3, "Total Launches: "
infoStatus = 4, "User Count: "
infoStatus = 5, "Current User: "
infoStatus = 6, "Current Title: "
infoStatus = 7, "Safe Title: "
infoStatus = 8, "Sort Type: "
infoStatus = 9, "Saving the file master..."
infoStatus = 10, "Deleting..."
infoStatus = 11, "Trash emptied."
loadingStartPage = 0, "Loading..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Settings"
onlineErrorConnecting = 0, "Error Connecting!"
onlineNoUpdates = 0, "No Updates Available."
popAddedToPathFilter = 0, "'#%s#' added to path filters."
popCPUBoostEnabled = 0, "CPU Boost Enabled for ZIP."
popChangeOutputError = 0, "#%s# contains illegal or non-ASCII characters."
popChangeOutputFolder = 0, "#%s# changed to #%s#"
popErrorCommittingFile = 0, "Error committing file to save!"
popFolderIsEmpty = 0, "Folder is empty!"
popProcessShutdown = 0, "#%s# successfully shutdown."
popSaveIsEmpty = 0, "Save data is empty!"
popZipIsEmpty = 0, "ZIP file is empty!"
saveDataBackupDeleted = 0, "#%s# has been deleted."
saveDataBackupMovedToTrash = 0, "#%s# has been moved to trash."
saveDataCreatedForUser = 0, "Save data created for %s!"
saveDataCreationFailed = 0, "Save data creation failed!"
saveDataDeleteAllUser = 0, "*ARE YOU SURE YOU WANT TO DELETE ALL SAVE DATA FOR %s?*"
saveDataDeleteSuccess = 0, "Save data for #%s# deleted!"
saveDataExtendFailed = 0, "Failed to extend save data."
saveDataExtendSuccess = 0, "Save data for #%s# extended!"
saveDataIndexText = 0, "存檔索引號:"
saveDataNoneFound = 0, "No saves found for #%s#!"
saveDataResetSuccess = 0, "Save for #%s# reset!"
saveDataTypeText = 0, "系統存檔\n"
saveDataTypeText = 1, "用戶存檔\n"
saveDataTypeText = 2, "BCAT存檔\n"
saveDataTypeText = 3, "設備存檔\n"
saveDataTypeText = 4, "臨時存檔\n"
saveDataTypeText = 5, "緩存存檔\n"
saveDataTypeText = 6, "系統BCAT存檔\n"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Empty Trash Bin"
settingsMenu = 1, "Check for Updates"
settingsMenu = 2, "Set JKSV Save Output Folder"
settingsMenu = 3, "Edit Blacklisted Titles"
settingsMenu = 4, "Delete All Save Backups"
settingsMenu = 5, "Include Device Saves With Users: "
settingsMenu = 6, "Auto Backup On Restore: "
settingsMenu = 7, "Auto-Name Backups: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Hold To Delete: "
settingsMenu = 10, "Hold To Restore: "
settingsMenu = 11, "Hold To Overwrite: "
settingsMenu = 12, "Force Mount: "
settingsMenu = 13, "Account System Saves: "
settingsMenu = 14, "Enable Writing to System Saves: "
settingsMenu = 15, "Use FS Commands Directly: "
settingsMenu = 16, "Export Saves to ZIP: "
settingsMenu = 17, "Force English To Be Used: "
settingsMenu = 18, "Enable Trash Bin: "
settingsMenu = 19, "Title Sorting Type: "
settingsMenu = 20, "Animation Scale: "
settingsOff = 0, "Off"
settingsOn = 0, ">On>"
sortType = 0, "Alphabetical"
sortType = 1, "Time Played"
sortType = 2, "Last Played"
swkbdEnterName = 0, "Enter a new name"
swkbdExpandSize = 0, "Enter New Size in MB"
swkbdMkDir = 0, "Enter a folder name"
swkbdNewSafeTitle = 0, "Input New Output Folder"
swkbdProcessID = 0, "Enter Process ID"
swkbdRename = 0, "Enter a new name for item"
swkbdSaveIndex = 0, "Enter Cache Index"
swkbdSetWorkDir = 0, "Enter a new Output Path"
swkbdSysSavID = 0, "Enter System Save ID"
threadStatusAddingFileToZip = 0, "Adding '#%s#' to ZIP..."
threadStatusCheckingForUpdate = 0, "Checking for updates..."
threadStatusCopyingFile = 0, "Copying '#%s#'..."
threadStatusCreatingSaveData = 0, "Creating Save Data for #%s#..."
threadStatusDecompressingFile = 0, "Decompressing '#%s#'..."
threadStatusDeletingFile = 0, "Deleting..."
threadStatusDeletingSaveData = 0, "Deleting Save Data for #%s#..."
threadStatusDeletingUpdate = 0, "Deleting pending update..."
threadStatusDownloadingUpdate = 0, "Downloading update..."
threadStatusExtendingSaveData = 0, "Extending Save Data for #%s#..."
threadStatusGetDirProps = 0, "Getting Folder Properties..."
threadStatusOpeningFolder = 0, "Opening '#%s#'..."
threadStatusPackingJKSV = 0, "Writing JKSV folder contents to ZIP..."
threadStatusResettingSaveData = 0, "Resetting save data..."
titleOptions = 0, "Information"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Change Output Folder"
titleOptions = 3, "Open in File Mode"
titleOptions = 4, "Delete All Save Backups"
titleOptions = 5, "Reset Save Data"
titleOptions = 6, "Delete Save Data"
titleOptions = 7, "Extend Save Data"
translationMainPage = 0, "Translation: "
userOptions = 0, "Dump All For "
userOptions = 1, "Create Save Data"
userOptions = 2, "Create All Save Data"
userOptions = 3, "Delete All User Saves"

View File

@ -1,2 +1,171 @@
#Portugeuse translation file
#Copy en-US.txt here and use as a guide.
author = 0, "NULL"
confirmBlacklist = 0, "Are you sure you want to add #%s# to your blacklist?"
confirmCopy = 0, "Are you sure you want to copy #%s# to #%s#?"
confirmCreateAllSaveData = 0, "Are you sure you would like to create all save data on this system for #%s#? This can take a while depending on how many titles are found."
confirmDelete = 0, "Are you sure you want to delete #%s#? *This is permanent*!"
confirmDeleteBackupsAll = 0, "Are you sure you would like to delete *all* of your save backups for all of your games?"
confirmDeleteBackupsTitle = 0, "Are you sure you would like to delete all save backups for #%s#?"
confirmDeleteSaveData = 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"
confirmDriveOverwrite = 0, "Downloading this backup from drive will overwrite the one on your SD card. Continue?"
confirmOverwrite = 0, "Are you sure you want to overwrite #%s#?"
confirmResetSaveData = 0, "*WARNING*: This *will* reset the save data for this game as if it was never ran before. Are you sure you want to do this?"
confirmRestore = 0, "Are you sure you want to restore #%s#?"
debugStatus = 0, "User Count: "
debugStatus = 1, "Current User: "
debugStatus = 2, "Current Title: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "Sort Type: "
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Yes [A]"
extrasMenu = 0, "SD to SD Browser"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Remove Pending Update"
extrasMenu = 6, "Terminate Process"
extrasMenu = 7, "Mount System Save"
extrasMenu = 8, "Rescan Titles"
extrasMenu = 9, "Mount Process RomFS"
extrasMenu = 10, "Backup JKSV Folder"
extrasMenu = 11, "*[DEV]* Output en-US"
fileModeFileProperties = 0, "Path: %s\nSize: %s"
fileModeFolderProperties = 0, "Path: %s\nSub Folders: %u\nFile Count: %u\nTotal Size: %s"
fileModeMenu = 0, "Copy To "
fileModeMenu = 1, "Delete"
fileModeMenu = 2, "Rename"
fileModeMenu = 3, "Make Dir"
fileModeMenu = 4, "Properties"
fileModeMenu = 5, "Close"
fileModeMenu = 6, "Add to Path Filters"
fileModeMenuMkDir = 0, "New"
folderMenuNew = 0, "New Backup"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close"
helpSettings = 0, "[A] Toggle [X] Defaults [B] Back"
helpTitle = 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back"
helpUser = 0, "[A] Select [Y] Dump All Saves [X] User Options"
holdingText = 0, "(Hold) "
holdingText = 1, "(Keep Holding) "
holdingText = 2, "(Almost There!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Play Time: %02d:%02d"
infoStatus = 3, "Total Launches: %u"
infoStatus = 4, "Publisher: %s"
infoStatus = 5, "Save Type: %s"
infoStatus = 6, "Cache Index: %u"
infoStatus = 7, "User: %s"
infoStatus = 9, ""
loadingStartPage = 0, "Loading..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Settings"
onlineErrorConnecting = 0, "Error Connecting!"
onlineNoUpdates = 0, "No Updates Available."
popAddedToPathFilter = 0, "'#%s#' added to path filters."
popCPUBoostEnabled = 0, "CPU Boost Enabled for ZIP."
popChangeOutputError = 0, "#%s# contains illegal or non-ASCII characters."
popChangeOutputFolder = 0, "#%s# changed to #%s#"
popDriveFailed = 0, "Failed to start Google Drive."
popRemoteNotActive = 0, "Remote is not available"
popDriveStarted = 0, "Google Drive started successfully."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Error committing file to save!"
popFolderIsEmpty = 0, "Folder is empty!"
popProcessShutdown = 0, "#%s# successfully shutdown."
popSVIExported = 0, "SVI Exported."
popSaveIsEmpty = 0, "Save data is empty!"
popTrashEmptied = 0, "Trash emptied"
popZipIsEmpty = 0, "ZIP file is empty!"
saveDataBackupDeleted = 0, "#%s# has been deleted."
saveDataBackupMovedToTrash = 0, "#%s# has been moved to trash."
saveDataCreatedForUser = 0, "Save data created for %s!"
saveDataCreationFailed = 0, "Save data creation failed!"
saveDataDeleteAllUser = 0, "*ARE YOU SURE YOU WANT TO DELETE ALL SAVE DATA FOR %s?*"
saveDataDeleteSuccess = 0, "Save data for #%s# deleted!"
saveDataExtendFailed = 0, "Failed to extend save data."
saveDataExtendSuccess = 0, "Save data for #%s# extended!"
saveDataNoneFound = 0, "No saves found for #%s#!"
saveDataResetSuccess = 0, "Save for #%s# reset!"
saveDataTypeText = 0, "System"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Device"
saveDataTypeText = 4, "Temporary"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Empty Trash Bin"
settingsMenu = 1, "Check for Updates"
settingsMenu = 2, "Set JKSV Save Output Folder"
settingsMenu = 3, "Edit Blacklisted Titles"
settingsMenu = 4, "Delete All Save Backups"
settingsMenu = 5, "Include Device Saves With Users: "
settingsMenu = 6, "Auto Backup On Restore: "
settingsMenu = 7, "Auto-Name Backups: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Hold To Delete: "
settingsMenu = 10, "Hold To Restore: "
settingsMenu = 11, "Hold To Overwrite: "
settingsMenu = 12, "Force Mount: "
settingsMenu = 13, "Account System Saves: "
settingsMenu = 14, "Enable Writing to System Saves: "
settingsMenu = 15, "Use FS Commands Directly: "
settingsMenu = 16, "Export Saves to ZIP: "
settingsMenu = 17, "Force English To Be Used: "
settingsMenu = 18, "Enable Trash Bin: "
settingsMenu = 19, "Title Sorting Type: "
settingsMenu = 20, "Animation Scale: "
settingsOff = 0, "Off"
settingsOn = 0, ">On>"
sortType = 0, "Alphabetical"
sortType = 1, "Time Played"
sortType = 2, "Last Played"
swkbdEnterName = 0, "Enter a new name"
swkbdExpandSize = 0, "Enter New Size in MB"
swkbdMkDir = 0, "Enter a folder name"
swkbdNewSafeTitle = 0, "Input New Output Folder"
swkbdProcessID = 0, "Enter Process ID"
swkbdRename = 0, "Enter a new name for item"
swkbdSaveIndex = 0, "Enter Cache Index"
swkbdSetWorkDir = 0, "Enter a new Output Path"
swkbdSysSavID = 0, "Enter System Save ID"
threadStatusAddingFileToZip = 0, "Adding '#%s#' to ZIP..."
threadStatusCalculatingSaveSize = 0, "Calculating save data size..."
threadStatusCheckingForUpdate = 0, "Checking for updates..."
threadStatusCompressingSaveForUpload = 0, "Compressing #%s# for upload..."
threadStatusCopyingFile = 0, "Copying '#%s#'..."
threadStatusCreatingSaveData = 0, "Creating Save Data for #%s#..."
threadStatusDecompressingFile = 0, "Decompressing '#%s#'..."
threadStatusDeletingFile = 0, "Deleting..."
threadStatusDeletingSaveData = 0, "Deleting Save Data for #%s#..."
threadStatusDeletingUpdate = 0, "Deleting pending update..."
threadStatusDownloadingFile = 0, "Downloading #%s#..."
threadStatusDownloadingUpdate = 0, "Downloading update..."
threadStatusExtendingSaveData = 0, "Extending Save Data for #%s#..."
threadStatusGetDirProps = 0, "Getting Folder Properties..."
threadStatusOpeningFolder = 0, "Opening '#%s#'..."
threadStatusPackingJKSV = 0, "Writing JKSV folder contents to ZIP..."
threadStatusResettingSaveData = 0, "Resetting save data..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "Uploading #%s#..."
titleOptions = 0, "Information"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Change Output Folder"
titleOptions = 3, "Open in File Mode"
titleOptions = 4, "Delete All Save Backups"
titleOptions = 5, "Reset Save Data"
titleOptions = 6, "Delete Save Data"
titleOptions = 7, "Extend Save Data"
titleOptions = 8, "Export SVI"
translationMainPage = 0, "Translation: "
userOptions = 0, "Dump All For "
userOptions = 1, "Create Save Data"
userOptions = 2, "Create All Save Data"
userOptions = 3, "Delete All User Saves"

View File

@ -1,2 +1,171 @@
#Russian translation file
#Copy en-US.txt here and use as a guide.
author = 0, "NULL"
confirmBlacklist = 0, "Are you sure you want to add #%s# to your blacklist?"
confirmCopy = 0, "Are you sure you want to copy #%s# to #%s#?"
confirmCreateAllSaveData = 0, "Are you sure you would like to create all save data on this system for #%s#? This can take a while depending on how many titles are found."
confirmDelete = 0, "Are you sure you want to delete #%s#? *This is permanent*!"
confirmDeleteBackupsAll = 0, "Are you sure you would like to delete *all* of your save backups for all of your games?"
confirmDeleteBackupsTitle = 0, "Are you sure you would like to delete all save backups for #%s#?"
confirmDeleteSaveData = 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"
confirmDriveOverwrite = 0, "Downloading this backup from drive will overwrite the one on your SD card. Continue?"
confirmOverwrite = 0, "Are you sure you want to overwrite #%s#?"
confirmResetSaveData = 0, "*WARNING*: This *will* reset the save data for this game as if it was never ran before. Are you sure you want to do this?"
confirmRestore = 0, "Are you sure you want to restore #%s#?"
debugStatus = 0, "User Count: "
debugStatus = 1, "Current User: "
debugStatus = 2, "Current Title: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "Sort Type: "
dialogNo = 0, "No [B]"
dialogOK = 0, "OK [A]"
dialogYes = 0, "Yes [A]"
extrasMenu = 0, "SD to SD Browser"
extrasMenu = 1, "BIS: ProdInfoF"
extrasMenu = 2, "BIS: Safe"
extrasMenu = 3, "BIS: System"
extrasMenu = 4, "BIS: User"
extrasMenu = 5, "Remove Pending Update"
extrasMenu = 6, "Terminate Process"
extrasMenu = 7, "Mount System Save"
extrasMenu = 8, "Rescan Titles"
extrasMenu = 9, "Mount Process RomFS"
extrasMenu = 10, "Backup JKSV Folder"
extrasMenu = 11, "*[DEV]* Output en-US"
fileModeFileProperties = 0, "Path: %s\nSize: %s"
fileModeFolderProperties = 0, "Path: %s\nSub Folders: %u\nFile Count: %u\nTotal Size: %s"
fileModeMenu = 0, "Copy To "
fileModeMenu = 1, "Delete"
fileModeMenu = 2, "Rename"
fileModeMenu = 3, "Make Dir"
fileModeMenu = 4, "Properties"
fileModeMenu = 5, "Close"
fileModeMenu = 6, "Add to Path Filters"
fileModeMenuMkDir = 0, "New"
folderMenuNew = 0, "New Backup"
helpFolder = 0, "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close"
helpSettings = 0, "[A] Toggle [X] Defaults [B] Back"
helpTitle = 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back"
helpUser = 0, "[A] Select [Y] Dump All Saves [X] User Options"
holdingText = 0, "(Hold) "
holdingText = 1, "(Keep Holding) "
holdingText = 2, "(Almost There!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "Play Time: %02d:%02d"
infoStatus = 3, "Total Launches: %u"
infoStatus = 4, "Publisher: %s"
infoStatus = 5, "Save Type: %s"
infoStatus = 6, "Cache Index: %u"
infoStatus = 7, "User: %s"
infoStatus = 9, ""
loadingStartPage = 0, "Loading..."
mainMenuExtras = 0, "Extras"
mainMenuSettings = 0, "Settings"
onlineErrorConnecting = 0, "Error Connecting!"
onlineNoUpdates = 0, "No Updates Available."
popAddedToPathFilter = 0, "'#%s#' added to path filters."
popCPUBoostEnabled = 0, "CPU Boost Enabled for ZIP."
popChangeOutputError = 0, "#%s# contains illegal or non-ASCII characters."
popChangeOutputFolder = 0, "#%s# changed to #%s#"
popDriveFailed = 0, "Failed to start Google Drive."
popRemoteNotActive = 0, "Remote is not available"
popDriveStarted = 0, "Google Drive started successfully."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "Error committing file to save!"
popFolderIsEmpty = 0, "Folder is empty!"
popProcessShutdown = 0, "#%s# successfully shutdown."
popSVIExported = 0, "SVI Exported."
popSaveIsEmpty = 0, "Save data is empty!"
popTrashEmptied = 0, "Trash emptied"
popZipIsEmpty = 0, "ZIP file is empty!"
saveDataBackupDeleted = 0, "#%s# has been deleted."
saveDataBackupMovedToTrash = 0, "#%s# has been moved to trash."
saveDataCreatedForUser = 0, "Save data created for %s!"
saveDataCreationFailed = 0, "Save data creation failed!"
saveDataDeleteAllUser = 0, "*ARE YOU SURE YOU WANT TO DELETE ALL SAVE DATA FOR %s?*"
saveDataDeleteSuccess = 0, "Save data for #%s# deleted!"
saveDataExtendFailed = 0, "Failed to extend save data."
saveDataExtendSuccess = 0, "Save data for #%s# extended!"
saveDataNoneFound = 0, "No saves found for #%s#!"
saveDataResetSuccess = 0, "Save for #%s# reset!"
saveDataTypeText = 0, "System"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Device"
saveDataTypeText = 4, "Temporary"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "Empty Trash Bin"
settingsMenu = 1, "Check for Updates"
settingsMenu = 2, "Set JKSV Save Output Folder"
settingsMenu = 3, "Edit Blacklisted Titles"
settingsMenu = 4, "Delete All Save Backups"
settingsMenu = 5, "Include Device Saves With Users: "
settingsMenu = 6, "Auto Backup On Restore: "
settingsMenu = 7, "Auto-Name Backups: "
settingsMenu = 8, "Overclock/CPU Boost: "
settingsMenu = 9, "Hold To Delete: "
settingsMenu = 10, "Hold To Restore: "
settingsMenu = 11, "Hold To Overwrite: "
settingsMenu = 12, "Force Mount: "
settingsMenu = 13, "Account System Saves: "
settingsMenu = 14, "Enable Writing to System Saves: "
settingsMenu = 15, "Use FS Commands Directly: "
settingsMenu = 16, "Export Saves to ZIP: "
settingsMenu = 17, "Force English To Be Used: "
settingsMenu = 18, "Enable Trash Bin: "
settingsMenu = 19, "Title Sorting Type: "
settingsMenu = 20, "Animation Scale: "
settingsOff = 0, "Off"
settingsOn = 0, ">On>"
sortType = 0, "Alphabetical"
sortType = 1, "Time Played"
sortType = 2, "Last Played"
swkbdEnterName = 0, "Enter a new name"
swkbdExpandSize = 0, "Enter New Size in MB"
swkbdMkDir = 0, "Enter a folder name"
swkbdNewSafeTitle = 0, "Input New Output Folder"
swkbdProcessID = 0, "Enter Process ID"
swkbdRename = 0, "Enter a new name for item"
swkbdSaveIndex = 0, "Enter Cache Index"
swkbdSetWorkDir = 0, "Enter a new Output Path"
swkbdSysSavID = 0, "Enter System Save ID"
threadStatusAddingFileToZip = 0, "Adding '#%s#' to ZIP..."
threadStatusCalculatingSaveSize = 0, "Calculating save data size..."
threadStatusCheckingForUpdate = 0, "Checking for updates..."
threadStatusCompressingSaveForUpload = 0, "Compressing #%s# for upload..."
threadStatusCopyingFile = 0, "Copying '#%s#'..."
threadStatusCreatingSaveData = 0, "Creating Save Data for #%s#..."
threadStatusDecompressingFile = 0, "Decompressing '#%s#'..."
threadStatusDeletingFile = 0, "Deleting..."
threadStatusDeletingSaveData = 0, "Deleting Save Data for #%s#..."
threadStatusDeletingUpdate = 0, "Deleting pending update..."
threadStatusDownloadingFile = 0, "Downloading #%s#..."
threadStatusDownloadingUpdate = 0, "Downloading update..."
threadStatusExtendingSaveData = 0, "Extending Save Data for #%s#..."
threadStatusGetDirProps = 0, "Getting Folder Properties..."
threadStatusOpeningFolder = 0, "Opening '#%s#'..."
threadStatusPackingJKSV = 0, "Writing JKSV folder contents to ZIP..."
threadStatusResettingSaveData = 0, "Resetting save data..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "Uploading #%s#..."
titleOptions = 0, "Information"
titleOptions = 1, "Blacklist"
titleOptions = 2, "Change Output Folder"
titleOptions = 3, "Open in File Mode"
titleOptions = 4, "Delete All Save Backups"
titleOptions = 5, "Reset Save Data"
titleOptions = 6, "Delete Save Data"
titleOptions = 7, "Extend Save Data"
titleOptions = 8, "Export SVI"
translationMainPage = 0, "Translation: "
userOptions = 0, "Dump All For "
userOptions = 1, "Create Save Data"
userOptions = 2, "Create All Save Data"
userOptions = 3, "Delete All User Saves"

View File

@ -1,126 +1,170 @@
#Chinese translation file
#JKSV Switch translation guide
#Button strings [X] are replaced by JKSV at boot.
#string = set string to this.
#Translation author. Displayed in UI. NULL is ignored and not displayed.
author = "JamePeng"
#Help/Guide strings on bottom
#User Select
userHelp = "[A] 选择 [Y] 导出全部 [X] 模式切换 [-] 选项 [ZR] 附加选项"
#Title Select
titleHelp = "[A] 选择 [L][R] 更换用户 [Y] 导出全部 [X] 收藏 [-] 黑名单 [ZR] 擦除 [B] 返回"
#Folder Select
folderHelp = "[-] 文件模式 [L]/[R]+[A] 自动命名 [A] 备份 [Y] 还原 [X] 删除文件夹 [ZR] 擦除 [B] 返回"
#Options Menu
optHelp = "[A] 切换 [X] 恢复默认值 [B] 返回"
#Button prompts.
yt = "是 [A]"
nt = "否 [B]"
#On/Off for options. '>' is used to change color to green.
on = ">开>"
off = "关"
#Strings for messages/confirmation. '*' and '#' are used in strings to change color. %s is replaced with titles and names with sprintf.
#Text shown at the top of confirmation boxes
confirmHead = "确认窗口"
#Text shown at top of progress dialog when copying files
copyHead = "正在复制文件..."
#Confirm blacklist.
confirmBlacklist = "您是否确认添加 #%s# 到黑名单?"
#Confirm overwriting folder
confirmOverwrite = "您是否确认覆写 #%s#?"
#Confirm restoring save
confirmRestore = "您是否确认恢复 #%s#?"
#Confirm deleting.
confirmDelete = "您是否确认删除 #%s#? *该操作为永久性操作*"
#Confirm file copy in file mode
confirmCopy = "您是否确认拷贝 #%s# 到 #%s#?"
#Warning for erasing save data from system in title menu
confirmEraseNand = "*警告*: *这将会从主机系统擦除* #%s# *的存档数据*. *该操作与系统*#数据管理#*操作相同*! 您是否确认继续擦除操作?"
#Warning for deleting save data in folder menu
confirmEraseFolder = "*警告*: *这将会从主机系统删除* #%s# *的当前存档数据*! 您是否确认继续删除操作?"
#Error displayed in pop-up if no titles are found for user. Rarely happens, but can for Device and BCAT
noSavesFound = "沒有找到 #%s# 的存档数据!"
#Text displayed when holding is required. Should have a trailing space
holdingText = 0, "(需要按住) "
holdingText = 1, "(保持按住) "
holdingText = 2, "(差不多了!) "
#Menu options are held in arrays of strings.
#The number is the option to set, string is what to set to.
#Only the string after the comma needs to be edited.
#Adv/file mode menu
#'Copy to ' NEEDS a trailing space
advMenu = 0, "复制到"
advMenu = 1, "删除"
advMenu = 2, "重命名"
advMenu = 3, "新建文件夹"
advMenu = 4, "属性"
advMenu = 5, "关闭"
#Extras Menu
extMenu = 0, "存储文件管理器"
extMenu = 1, "查看BIS: PRODINFOF"
extMenu = 2, "查看BIS: SAFE"
extMenu = 3, "查看BIS: SYSTEM"
extMenu = 4, "查看BIS: USER"
extMenu = 5, "移除固件更新文件"
extMenu = 6, "终止指定ID进程"
extMenu = 7, "挂载系统存档"
extMenu = 8, "重新扫描Titles"
extMenu = 9, "挂载RomFS进程"
extMenu = 10, "备份JKSV目录"
#Options menu
#All options need a trailing space. It is the most likely to have things added to it.
optMenu = 0, "全部存档数据: "
optMenu = 1, "自动备份: "
optMenu = 2, "系统超频: "
optMenu = 3, "长按确认删除: "
optMenu = 4, "长按确认恢复: "
optMenu = 5, "长按确认覆写: "
optMenu = 6, "强制挂载: "
optMenu = 7, "关联用户的系统存档: "
optMenu = 8, "是否开启系统存档覆写: "
optMenu = 9, "默认使用文字列表模式: "
optMenu = 10, "使用系统FS复制指令: "
optMenu = 11, "跳过用户选择: "
optMenu = 12, "导出ZIP压缩包: "
optMenu = 13, "排序: "
#Explanations of what options do.
optMenuExp = 0, "包括设备关联账户中的所有存档数据"
optMenuExp = 1, "还原存档前,自动创建原存档的备份."
optMenuExp = 2, "在启动时对CPU进行小幅度超频至1224Mhz. 这与开发人员单元的运行速度相同."
optMenuExp = 3, "执行删除文件和文件夹的时候,是否提示长按[A]确认操作"
optMenuExp = 4, "执行恢复存档操作的时候,是否提示长按[A]确认操作"
optMenuExp = 5, "执行覆写存档数据操作的时候,是否提示长按[A]确认操作"
optMenuExp = 6, "开启后JKSV仅显示具有开放权限的存档数据. 关闭后将显示所有存档数据."
optMenuExp = 7, "开启后JKSV将显示与账户关联的系统存档数据."
optMenuExp = 8, "控制系统存档数据和分区是否可以写入和删除文件和数据. *如果您在不熟悉功能的情况下操作,可能会非常危险!*"
optMenuExp = 9, "切换浏览界面至文字列表模式类似3DS主机上JKSM的界面"
optMenuExp = 10, "直接使用Switch系统FS指令取代原有的标准输入输出方式"
optMenuExp = 11, "跳过用户选择界面直接使用首位用户的资料数据"
optMenuExp = 12, "导出存档到zip文件"
optMenuExp = 13, "更改标题排列排序方式"
#Sort Types
sortType = 0, "字母顺序"
sortType = 1, "游戏时间"
sortType = 2, "最后游戏"
author = 0, "NULL"
confirmBlacklist = 0, "是否确定要将#%s#添加到您的黑名单中?"
confirmCopy = 0, "是否确定要将#%s#复制到#%s#"
confirmCreateAllSaveData = 0, "是否确定要在此系统上为#%s#创建所有存档数据这可能需要一些时间具体取决于找到的Title的数量。"
confirmDelete = 0, "是否确定要删除#%s#*这是永久的*"
confirmDeleteBackupsAll = 0, "是否确定要删除您所有游戏的*所有*存档备份?"
confirmDeleteBackupsTitle = 0, "是否确定要删除#%s#的所有存档备份?"
confirmDeleteSaveData = 0, "*警告*:这*将会*从*系统*中删除#%s#的存档数据。您确定要这样做吗?"
confirmDriveOverwrite = 0, "下载这个备份将会覆盖您的SD卡上的备份。是否继续"
confirmOverwrite = 0, "是否确定要覆盖#%s#?"
confirmResetSaveData = 0, "*警告*:这*将会*重置此游戏的存档数据,就像以前从未运行过一样。您确定要这样做吗?"
confirmRestore = 0, "是否确定要还原#%s#"
debugStatus = 0, "用户数量: "
debugStatus = 1, "当前用户: "
debugStatus = 2, "当前Title: "
debugStatus = 3, "安全Title: "
debugStatus = 4, "排序方式: "
dialogNo = 0, "否 [B]"
dialogOK = 0, "确定 [A]"
dialogYes = 0, "是 [A]"
extrasMenu = 0, "文件管理器"
extrasMenu = 1, "BIS分区ProdInfoF"
extrasMenu = 2, "BIS分区Safe"
extrasMenu = 3, "BIS分区System"
extrasMenu = 4, "BIS分区User"
extrasMenu = 5, "删除挂起的更新"
extrasMenu = 6, "终止进程"
extrasMenu = 7, "挂载系统存档"
extrasMenu = 8, "重新扫描Title"
extrasMenu = 9, "挂载进程RomFS"
extrasMenu = 10, "备份JKSV文件夹"
extrasMenu = 11, "*[开发者]* 输出en-US"
fileModeFileProperties = 0, "路径:%s\n大小%s"
fileModeFolderProperties = 0, "路径:%s\n子目录%u\n文件数量%u\n总大小%s"
fileModeMenu = 0, "复制到"
fileModeMenu = 1, "删除"
fileModeMenu = 2, "重命名"
fileModeMenu = 3, "新建文件夹"
fileModeMenu = 4, "属性"
fileModeMenu = 5, "关闭"
fileModeMenu = 6, "添加到路径过滤器"
fileModeMenuMkDir = 0, "新建"
folderMenuNew = 0, "新建备份"
helpFolder = 0, "[A] 选择 [Y] 还原 [X] 删除 [ZR] 上传 [B] 关闭"
helpSettings = 0, "[A] 切换 [X] 恢复默认 [B] 返回"
helpTitle = 0, "[A] 选择 [L][R] 翻页 [Y] 收藏 [X] Title选项 [B] 返回"
helpUser = 0, "[A] 选择 [Y] 转储所有存档 [X] 用户选项"
holdingText = 0, "(按住)"
holdingText = 1, "(继续按住)"
holdingText = 2, "(快好了!)"
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "游玩时间: %02d:%02d"
infoStatus = 3, "启动次数: %u"
infoStatus = 4, "发行商: %s"
infoStatus = 5, "存档类型: %s"
infoStatus = 6, "缓存索引: %u"
infoStatus = 7, "用户: %s"
loadingStartPage = 0, "加载中..."
mainMenuExtras = 0, "附加功能"
mainMenuSettings = 0, "系统设置"
onlineErrorConnecting = 0, "连接错误!"
onlineNoUpdates = 0, "没有可用的更新。"
popAddedToPathFilter = 0, "'#%s#'已添加到路径过滤器。"
popCPUBoostEnabled = 0, "为ZIP压缩启用CPU超频。"
popChangeOutputError = 0, "#%s#包含非法或者非ASCII的字符。"
popChangeOutputFolder = 0, "#%s#更改到#%s#"
popDriveFailed = 0, "Google Drive启动失败。"
popRemoteNotActive = 0, "Remote不可用。"
popDriveStarted = 0, "Google Drive启动成功。"
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "提交要保存的文件时出错!"
popFolderIsEmpty = 0, "文件夹为空!"
popProcessShutdown = 0, "#%s#已成功关闭。"
popSVIExported = 0, "SVI文件已导出"
popSaveIsEmpty = 0, "存档数据为空!"
popTrashEmptied = 0, "回收站已清空"
popZipIsEmpty = 0, "ZIP文件为空"
saveDataBackupDeleted = 0, "#%s#已被删除。"
saveDataBackupMovedToTrash = 0, "#%s#已被移到回收站。"
saveDataCreatedForUser = 0, "%s的存档数据已创建"
saveDataCreationFailed = 0, "存档数据创建失败!"
saveDataDeleteAllUser = 0, "*确定要删除%s的所有存档数据吗*"
saveDataDeleteSuccess = 0, "#%s#的存档数据已删除!"
saveDataExtendFailed = 0, "扩展存档数据失败。"
saveDataExtendSuccess = 0, "#%s#的存档数据已扩展!"
saveDataNoneFound = 0, "没有找到#%s#的存档!"
saveDataResetSuccess = 0, "#%s#的存档已重置!"
saveDataTypeText = 0, "系统存档"
saveDataTypeText = 1, "用户存档"
saveDataTypeText = 2, "BCAT存档"
saveDataTypeText = 3, "设备存档"
saveDataTypeText = 4, "临时存档"
saveDataTypeText = 5, "缓存存档"
saveDataTypeText = 6, "系统BCAT存档"
saveTypeMainMenu = 0, "设备"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "缓存"
saveTypeMainMenu = 3, "系统"
saveTypeMainMenu = 4, "系统BCAT"
saveTypeMainMenu = 5, "临时"
settingsMenu = 0, "清空回收站"
settingsMenu = 1, "检查更新"
settingsMenu = 2, "设置JKSV的存档输出文件夹"
settingsMenu = 3, "编辑Title黑名单"
settingsMenu = 4, "删除所有存档备份"
settingsMenu = 5, "用户存档中包含设备存档:"
settingsMenu = 6, "恢复时自动备份:"
settingsMenu = 7, "自动命名备份:"
settingsMenu = 8, "CPU超频"
settingsMenu = 9, "按住以删除:"
settingsMenu = 10, "按住以恢复:"
settingsMenu = 11, "按住以覆盖:"
settingsMenu = 12, "强制挂载:"
settingsMenu = 13, "用户系统存档:"
settingsMenu = 14, "允许写入系统存档:"
settingsMenu = 15, "直接使用FS命令"
settingsMenu = 16, "导出存档到ZIP文件"
settingsMenu = 17, "强制使用英语:"
settingsMenu = 18, "启用回收站:"
settingsMenu = 19, "Title排序方式"
settingsMenu = 20, "动画比例:"
settingsOff = 0, "关"
settingsOn = 0, ">开>"
sortType = 0, "按软件名"
sortType = 1, "按游玩时间"
sortType = 2, "按最近游玩"
swkbdEnterName = 0, "请输入新的名称"
swkbdExpandSize = 0, "请输入新的大小单位MB"
swkbdMkDir = 0, "请输入文件夹名称"
swkbdNewSafeTitle = 0, "请输入新的输出文件夹"
swkbdProcessID = 0, "请输入进程ID"
swkbdRename = 0, "请输入新的项目名称"
swkbdSaveIndex = 0, "请输入缓存索引"
swkbdSetWorkDir = 0, "请输入新的输出路径"
swkbdSysSavID = 0, "请输入系统存档ID"
threadStatusAddingFileToZip = 0, "正在添加'#%s#'到ZIP文件..."
threadStatusCalculatingSaveSize = 0, "正在计算存档数据大小..."
threadStatusCheckingForUpdate = 0, "正在检查更新..."
threadStatusCompressingSaveForUpload = 0, "正在压缩#%s#以便上传..."
threadStatusCopyingFile = 0, "正在复制'#%s#'..."
threadStatusCreatingSaveData = 0, "正在创建#%s#的存档数据..."
threadStatusDecompressingFile = 0, "正在解压'#%s#'..."
threadStatusDeletingFile = 0, "正在删除..."
threadStatusDeletingSaveData = 0, "正在删除#%s#的存档数据..."
threadStatusDeletingUpdate = 0, "正在删除挂起的更新..."
threadStatusDownloadingFile = 0, "正在下载#%s#..."
threadStatusDownloadingUpdate = 0, "正在下载更新..."
threadStatusExtendingSaveData = 0, "正在扩展#%s#的存档数据..."
threadStatusGetDirProps = 0, "正在获取文件夹属性..."
threadStatusOpeningFolder = 0, "正在打开'#%s#'..."
threadStatusPackingJKSV = 0, "正在写入JKSV文件夹的内容到ZIP文件..."
threadStatusResettingSaveData = 0, "正在重置存档数据..."
threadStatusSavingTranslations = 0, "Saving the file master..."
threadStatusUploadingFile = 0, "正在上传#%s#..."
titleOptions = 0, "信息"
titleOptions = 1, "黑名单"
titleOptions = 2, "更改输出文件夹"
titleOptions = 3, "在文件管理器中打开"
titleOptions = 4, "删除所有存档备份"
titleOptions = 5, "重置存档数据"
titleOptions = 6, "删除存档数据"
titleOptions = 7, "扩展存档数据"
titleOptions = 8, "导出SVI文件"
translationMainPage = 0, "翻译:"
userOptions = 0, "转储所有存档:"
userOptions = 1, "创建存档数据"
userOptions = 2, "创建所有游戏的存档数据"
userOptions = 3, "删除所有用户存档"

View File

@ -1,13 +1,3 @@
#JKSV Translation File
#File can be placed and named "/JKSV/trans.txt" for testing without building
#Format is: stringName = index, "string"
#You only need to translate "string", there is no need to touch the other two parameters.
#'%s' is replaced with game titles and paths. '#', '*', '>', etc change text color. [x] are replaced with button glyphs. '\n' can be used to break lines
#This is exported from JKSV itself to make adding strings to this easier.
#It can also export already made translations with new strings if added, cutting down work for everyone.
#If there are issues with spacing, line breaking, untranslatable English text, etc let me know in a git issue or your pull request.
#If author is not "NULL", your name will be displayed in the UI
author = 0, "Leo"
confirmBlacklist = 0, "是否確定要將#%s#加入黑名單?"
confirmCopy = 0, "是否確定要將#%s#複製到#%s#?"
@ -16,11 +6,17 @@ confirmDelete = 0, "是否確定要刪除#%s#? *此為永久性刪除!*"
confirmDeleteBackupsAll = 0, "是否確定要刪除你備份所有遊戲的 *全部* 存檔進度?"
confirmDeleteBackupsTitle = 0, "是否確定要刪除#%s#的全部存檔進度?"
confirmDeleteSaveData = 0, "*注意*: 此操作 *將會* 完整刪除#%s# *在主機系統內* 的存檔進度. 請再次確認是否要繼續進行?"
confirmDriveOverwrite = 0, "準備將主機的儲存進度備份至記憶卡,將會覆蓋記憶卡上原有的備份檔案. 請再次確認是否要繼續進行?"
confirmOverwrite = 0, "是否確定要覆寫#%s#?"
confirmResetSaveData = 0, "*注意*: 此操作 *將會* 重置歸零此遊戲的存檔進度,像是從未執行過. 請再次確認是否要繼續進行?"
confirmRestore = 0, "是否確定要還原#%s#?"
debugStatus = 0, "使用者數量: "
debugStatus = 1, "目前使用者: "
debugStatus = 2, "目前的Title: "
debugStatus = 3, "Safe Title: "
debugStatus = 4, "排序準則: "
dialogNo = 0, "否 [B]"
dialogOK = 0, "好 [A]"
dialogOK = 0, "確定 [A]"
dialogYes = 0, "是 [A]"
extrasMenu = 0, "檔案管理視窗"
extrasMenu = 1, "BIS: ProdInfoF"
@ -43,21 +39,43 @@ fileModeMenu = 3, "新建資料夾"
fileModeMenu = 4, "屬性"
fileModeMenu = 5, "關閉"
fileModeMenu = 6, "新增至路徑篩選器"
helpFolder = 0, "[A] 選定 [Y] 還原 [X] 刪除 [B] 關閉"
fileModeMenuMkDir = 0, "新建"
folderMenuNew = 0, "新建目錄"
helpFolder = 0, "[A] 選定 [Y] 還原 [X] 刪除 [ZR] 上傳 [B] 返回"
helpSettings = 0, "[A] 切換 [X] 恢復預設值 [B] 返回"
helpTitle = 0, "[A] 選定 [L][R] 翻頁 [Y] 最愛 [X] Title選項 [B] 返回"
helpUser = 0, "[A] 選定 [X] 使用者選項"
holdingText = 0, "(請按住) "
holdingText = 1, "(繼續按住) "
holdingText = 2, "(確認執行!) "
infoStatus = 0, "TID: %016lX"
infoStatus = 1, "SID: %016lX"
infoStatus = 2, "遊玩時間: %02d:%02d"
infoStatus = 3, "啟動次數: %u"
infoStatus = 4, "發行商: %s"
infoStatus = 5, "存檔類型: %s"
infoStatus = 6, "快取索引: %u"
infoStatus = 7, "使用者: %s"
loadingStartPage = 0, "加載中…"
mainMenuExtras = 0, "附加設定"
mainMenuSettings = 0, "系統設定"
onlineErrorConnecting = 0, "連線失敗!"
onlineNoUpdates = 0, "目前沒有可用的更新版本!"
popAddedToPathFilter = 0, "'#%s#' 已新增至路徑篩選器."
popCPUBoostEnabled = 0, "壓縮ZIP時將啟用CPU超頻."
popChangeOutputError = 0, "#%s# 包含非法或者非ASCII字元."
popChangeOutputFolder = 0, "#%s# 更改到 #%s#"
popDriveFailed = 0, "無法啟用Google雲端硬碟."
popRemoteNotActive = 0, "沒有可用的Remote雲端硬碟."
popDriveStarted = 0, "Google雲端硬碟已正確啟用."
popWebdavStarted = 0, "Webdav started successfully."
popWebdavFailed =, 0, "Failed to start Webdav."
popErrorCommittingFile = 0, "提交檔案進行儲存時發生錯誤!"
popFolderIsEmpty = 0, "資料夾內沒有檔案!"
popProcessShutdown = 0, "#%s# 程序已關閉."
popSVIExported = 0, "已匯出SVI."
popSaveIsEmpty = 0, "沒有找到存檔進度!"
popTrashEmptied = 0, "資源回收筒已清空"
popZipIsEmpty = 0, "ZIP壓縮檔內沒有檔案!"
saveDataBackupDeleted = 0, "#%s#已被刪除."
saveDataBackupMovedToTrash = 0, "#%s#已被移動至資源回收筒."
@ -67,8 +85,22 @@ saveDataDeleteAllUser = 0, "*是否確認要刪除%s使用者的所有遊戲進
saveDataDeleteSuccess = 0, "#%s#的進度存檔已刪除!"
saveDataExtendFailed = 0, "無法擴充此遊戲的進度儲存空間."
saveDataExtendSuccess = 0, "#%s#的進度存檔儲存空間已擴充!"
saveDataIndexText = 0, "存檔索引:"
saveDataNoneFound = 0, "沒有#%s#的進度存檔!"
saveDataResetSuccess = 0, "#%s#的進度存檔已重置!"
saveDataTypeText = 0, "System"
saveDataTypeText = 1, "Account"
saveDataTypeText = 2, "BCAT"
saveDataTypeText = 3, "Device"
saveDataTypeText = 4, "Temporary"
saveDataTypeText = 5, "Cache"
saveDataTypeText = 6, "System BCAT"
saveTypeMainMenu = 0, "Device"
saveTypeMainMenu = 1, "BCAT"
saveTypeMainMenu = 2, "Cache"
saveTypeMainMenu = 3, "System"
saveTypeMainMenu = 4, "System BCAT"
saveTypeMainMenu = 5, "SysTemp Storagetem"
settingsMenu = 0, "清空資源回收筒"
settingsMenu = 1, "檢查程式更新"
settingsMenu = 2, "設定JKSV進度存檔匯出的資料夾"
@ -96,26 +128,33 @@ sortType = 0, "依名稱字母順序"
sortType = 1, "依遊玩時間長度"
sortType = 2, "依最後遊玩時間"
swkbdEnterName = 0, "請輸入新的名稱"
swkbdExpandSize = 0, "輸入新的大小(MB)"
swkbdMkDir = 0, "請輸入資料夾名稱"
swkbdNewSafeTitle = 0, "輸入新的匯出目錄名稱"
swkbdProcessID = 0, "請輸入程序ID"
swkbdRename = 0, "請輸入item的新名稱"
swkbdSaveIndex = 0, "請輸入快取索引"
swkbdSetWorkDir = 0, "請輸入新的匯出路徑"
swkbdSysSavID = 0, "請輸入系統存檔ID"
threadStatusAddingFileToZip = 0, "正在將'#%s#'的存檔壓縮為ZIP..."
threadStatusCalculatingSaveSize = 0, "正在計算進度檔案大小..."
threadStatusCheckingForUpdate = 0, "檢查是否有可用更新..."
threadStatusCompressingSaveForUpload = 0, "正在壓縮#%s#準備上傳..."
threadStatusCopyingFile = 0, "正在複製'#%s#'..."
threadStatusCreatingSaveData = 0, "正在新增#%s#的存檔進度..."
threadStatusDecompressingFile = 0, "'#%s#'存檔正在解壓縮..."
threadStatusDeletingFile = 0, "刪除中..."
threadStatusDeletingSaveData = 0, "正在刪除#%s#的存檔進度.."
threadStatusDeletingUpdate = 0, "正在刪除系統更新..."
threadStatusDownloadingFile = 0, "正在下載#%s#..."
threadStatusDownloadingUpdate = 0, "正在下載更新版本..."
threadStatusExtendingSaveData = 0, "正在擴充#%s#的存檔空間..."
threadStatusGetDirProps = 0, "正在取得資料夾屬性資訊..."
threadStatusOpeningFolder = 0, "正在開啟'#%s#'資料夾..."
threadStatusPackingJKSV = 0, "正在將JKSV資料夾壓縮為ZIP..."
threadStatusResettingSaveData = 0, "正在重置遊戲存檔進度..."
threadStatusSavingTranslations = 0, "正在儲存file master..."
threadStatusUploadingFile = 0, "正在上傳#%s#..."
titleOptions = 0, "詳細資訊"
titleOptions = 1, "設為黑名單"
titleOptions = 2, "變更匯出檔案資料夾"
@ -124,6 +163,8 @@ titleOptions = 4, "刪除所有進度存檔"
titleOptions = 5, "重置進度存檔"
titleOptions = 6, "刪除進度存檔"
titleOptions = 7, "擴充存檔空間"
titleOptions = 8, "匯出SVI"
translationMainPage = 0, "翻譯:"
userOptions = 0, "備份使用者全部存檔:"
userOptions = 1, "新增遊戲存檔進度"
userOptions = 2, "新增所有遊戲存檔進度"

View File

@ -1,34 +1,56 @@
#include <switch.h>
#include <json-c/json.h>
#include <string>
#include <switch.h>
#include <unordered_map>
#include "cfg.h"
#include "data.h"
#include "file.h"
#include "type.h"
#include "ui.h"
#include "util.h"
#include "type.h"
std::unordered_map<std::string, bool> cfg::config;
std::vector<uint64_t> cfg::blacklist;
std::vector<uint64_t> cfg::favorites;
static std::unordered_map<uint64_t, std::string> pathDefs;
uint8_t cfg::sortType;
const char *cfgPath = "sdmc:/config/JKSV/JKSV.cfg", *titleDefPath = "sdmc:/config/JKSV/titleDefs.txt", *workDirLegacy = "sdmc:/switch/jksv_dir.txt";
static std::unordered_map<std::string, unsigned> cfgStrings =
{
{"workDir", 0}, {"includeDeviceSaves", 1}, {"autoBackup", 2}, {"overclock", 3}, {"holdToDelete", 4}, {"holdToRestore", 5},
{"holdToOverwrite", 6}, {"forceMount", 7}, {"accountSystemSaves", 8}, {"allowSystemSaveWrite", 9}, {"directFSCommands", 10},
{"exportToZIP", 11}, {"languageOverride", 12}, {"enableTrashBin", 13}, {"titleSortType", 14}, {"animationScale", 15},
{"favorite", 16}, {"blacklist", 17}, {"autoName", 18}
std::string cfg::driveClientID, cfg::driveClientSecret, cfg::driveRefreshToken;
std::string cfg::webdavOrigin, cfg::webdavBasePath, cfg::webdavUser, cfg::webdavPassword;
const char *cfgPath = "sdmc:/config/JKSV/JKSV.cfg", *titleDefPath = "sdmc:/config/JKSV/titleDefs.txt",
*workDirLegacy = "sdmc:/switch/jksv_dir.txt";
static std::unordered_map<std::string, unsigned> cfgStrings = {
{"workDir", 0},
{"includeDeviceSaves", 1},
{"autoBackup", 2},
{"overclock", 3},
{"holdToDelete", 4},
{"holdToRestore", 5},
{"holdToOverwrite", 6},
{"forceMount", 7},
{"accountSystemSaves", 8},
{"allowSystemSaveWrite", 9},
{"directFSCommands", 10},
{"exportToZIP", 11},
{"languageOverride", 12},
{"enableTrashBin", 13},
{"titleSortType", 14},
{"animationScale", 15},
{"favorite", 16},
{"blacklist", 17},
{"autoName", 18},
{"driveRefreshToken", 19},
};
const std::string _true_ = "true", _false_ = "false";
bool cfg::isBlacklisted(const uint64_t& tid)
bool cfg::isBlacklisted(uint64_t tid)
{
for(uint64_t& bid : cfg::blacklist)
if(tid == bid) return true;
for (uint64_t &bid : cfg::blacklist)
if (tid == bid)
return true;
return false;
}
@ -40,47 +62,51 @@ void cfg::addTitleToBlacklist(void *a)
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
uint64_t tid = d->tid;
cfg::blacklist.push_back(tid);
for(data::user& u : data::users)
for (data::user &u : data::users)
{
for(unsigned i = 0; i < u.titleInfo.size(); i++)
if(u.titleInfo[i].tid == tid) u.titleInfo.erase(u.titleInfo.begin() + i);
for (unsigned i = 0; i < u.titleInfo.size(); i++)
if (u.titleInfo[i].tid == tid)
u.titleInfo.erase(u.titleInfo.begin() + i);
}
ui::ttlRefresh();
cfg::saveConfig();
t->finished = true;
}
void cfg::removeTitleFromBlacklist(const uint64_t& tid)
void cfg::removeTitleFromBlacklist(uint64_t tid)
{
for(unsigned i = 0; i < cfg::blacklist.size(); i++)
for (unsigned i = 0; i < cfg::blacklist.size(); i++)
{
if(cfg::blacklist[i] == tid)
if (cfg::blacklist[i] == tid)
cfg::blacklist.erase(cfg::blacklist.begin() + i);
}
data::loadUsersTitles(false);
ui::ttlRefresh();
cfg::saveConfig();
}
bool cfg::isFavorite(const uint64_t& tid)
bool cfg::isFavorite(uint64_t tid)
{
for(uint64_t& fid : cfg::favorites)
if(tid == fid) return true;
for (uint64_t &fid : cfg::favorites)
if (tid == fid)
return true;
return false;
}
static int getFavoriteIndex(const uint64_t& tid)
static int getFavoriteIndex(uint64_t tid)
{
for(unsigned i = 0; i < cfg::favorites.size(); i++)
for (unsigned i = 0; i < cfg::favorites.size(); i++)
{
if(tid == cfg::favorites[i])
if (tid == cfg::favorites[i])
return i;
}
return -1;
}
void cfg::addTitleToFavorites(const uint64_t& tid)
void cfg::addTitleToFavorites(uint64_t tid)
{
if(cfg::isFavorite(tid))
if (cfg::isFavorite(tid))
{
int rem = getFavoriteIndex(tid);
cfg::favorites.erase(cfg::favorites.begin() + rem);
@ -90,41 +116,47 @@ void cfg::addTitleToFavorites(const uint64_t& tid)
data::sortUserTitles();
ui::ttlRefresh();
cfg::saveConfig();
}
bool cfg::isDefined(const uint64_t& tid)
bool cfg::isDefined(uint64_t tid)
{
for(auto& def : pathDefs)
if(def.first == tid) return true;
for (auto &def : pathDefs)
if (def.first == tid)
return true;
return false;
}
void cfg::pathDefAdd(const uint64_t& tid, const std::string& newPath)
void cfg::pathDefAdd(uint64_t tid, const std::string &newPath)
{
std::string oldSafe = data::titles[tid].safeTitle;
data::titleInfo *t = data::getTitleInfoByTID(tid);
std::string oldSafe = t->safeTitle;
std::string tmp = util::safeString(newPath);
if(!tmp.empty())
if (!tmp.empty())
{
pathDefs[tid] = tmp;
data::titles[tid].safeTitle = tmp;
t->safeTitle = tmp;
std::string oldOutput = fs::getWorkDir() + oldSafe;
std::string newOutput = fs::getWorkDir() + tmp;
rename(oldOutput.c_str(), newOutput.c_str());
ui::showPopMessage(POP_FRAME_DEFAULT, "'%s' changed to '%s'", oldSafe.c_str(), tmp.c_str());
ui::showPopMessage(POP_FRAME_DEFAULT,
ui::getUICString("popChangeOutputFolder", 0),
oldSafe.c_str(),
tmp.c_str());
}
else
ui::showPopMessage(POP_FRAME_DEFAULT, "'%s' contains illegal or non-ASCII characters.", newPath.c_str());
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popChangeOutputError", 0), newPath.c_str());
}
std::string cfg::getPathDefinition(const uint64_t& tid)
std::string cfg::getPathDefinition(uint64_t tid)
{
return pathDefs[tid];
}
void cfg::addPathToFilter(const uint64_t& tid, const std::string& _p)
void cfg::addPathToFilter(uint64_t tid, const std::string &_p)
{
char outpath[128];
sprintf(outpath, "sdmc:/config/JKSV/0x%016lX_filter.txt", tid);
@ -145,15 +177,16 @@ void cfg::resetConfig()
cfg::config["accSysSave"] = false;
cfg::config["sysSaveWrite"] = false;
cfg::config["directFsCmd"] = false;
cfg::config["zip"] = false;
cfg::config["zip"] = true;
cfg::config["langOverride"] = false;
cfg::config["trashBin"] = true;
cfg::config["autoName"] = false;
cfg::sortType = cfg::ALPHA;
ui::animScale = 3.0f;
cfg::config["autoUpload"] = false;
}
static inline bool textToBool(const std::string& _txt)
static inline bool textToBool(const std::string &_txt)
{
return _txt == _true_ ? true : false;
}
@ -165,7 +198,7 @@ static inline std::string boolToText(bool _b)
static inline std::string sortTypeText()
{
switch(cfg::sortType)
switch (cfg::sortType)
{
case cfg::ALPHA:
return "ALPHA";
@ -184,7 +217,7 @@ static inline std::string sortTypeText()
static void loadWorkDirLegacy()
{
if(fs::fileExists(workDirLegacy))
if (fs::fileExists(workDirLegacy))
{
char tmp[256];
memset(tmp, 0, 256);
@ -203,14 +236,14 @@ static void loadWorkDirLegacy()
static void loadConfigLegacy()
{
std::string legacyCfgPath = fs::getWorkDir() + "cfg.bin";
if(fs::fileExists(legacyCfgPath))
if (fs::fileExists(legacyCfgPath))
{
FILE *oldCfg = fopen(legacyCfgPath.c_str(), "rb");
uint64_t cfgIn = 0;
fread(&cfgIn, sizeof(uint64_t), 1, oldCfg);
fread(&cfg::sortType, 1, 1, oldCfg);
fread(&ui::animScale, sizeof(float), 1, oldCfg);
if(ui::animScale == 0)
if (ui::animScale == 0)
ui::animScale = 3.0f;
fclose(oldCfg);
@ -234,10 +267,10 @@ static void loadConfigLegacy()
static void loadFavoritesLegacy()
{
std::string legacyFavPath = fs::getWorkDir() + "favorites.txt";
if(fs::fileExists(legacyFavPath))
if (fs::fileExists(legacyFavPath))
{
fs::dataFile fav(legacyFavPath);
while(fav.readNextLine(false))
while (fav.readNextLine(false))
cfg::favorites.push_back(strtoul(fav.getLine().c_str(), NULL, 16));
fav.close();
fs::delfile(legacyFavPath);
@ -247,10 +280,10 @@ static void loadFavoritesLegacy()
static void loadBlacklistLegacy()
{
std::string legacyBlPath = fs::getWorkDir() + "blacklist.txt";
if(fs::fileExists(legacyBlPath))
if (fs::fileExists(legacyBlPath))
{
fs::dataFile bl(legacyBlPath);
while(bl.readNextLine(false))
while (bl.readNextLine(false))
cfg::blacklist.push_back(strtoul(bl.getLine().c_str(), NULL, 16));
bl.close();
fs::delfile(legacyBlPath);
@ -260,10 +293,10 @@ static void loadBlacklistLegacy()
static void loadTitleDefsLegacy()
{
std::string titleDefLegacy = fs::getWorkDir() + "titleDefs.txt";
if(fs::fileExists(titleDefLegacy))
if (fs::fileExists(titleDefLegacy))
{
fs::dataFile getPaths(titleDefLegacy);
while(getPaths.readNextLine(true))
while (getPaths.readNextLine(true))
{
uint64_t tid = strtoul(getPaths.getName().c_str(), NULL, 16);
pathDefs[tid] = getPaths.getNextValueStr();
@ -276,10 +309,10 @@ static void loadTitleDefsLegacy()
//Oops
static void loadTitleDefs()
{
if(fs::fileExists(titleDefPath))
if (fs::fileExists(titleDefPath))
{
fs::dataFile getPaths(titleDefPath);
while(getPaths.readNextLine(true))
while (getPaths.readNextLine(true))
{
uint64_t tid = strtoul(getPaths.getName().c_str(), NULL, 16);
pathDefs[tid] = getPaths.getNextValueStr();
@ -287,8 +320,68 @@ static void loadTitleDefs()
}
}
static void loadDriveConfig()
{
// Start Google Drive
fs::dirList cfgList("/config/JKSV/", true);
std::string clientSecretPath;
for (unsigned i = 0; i < cfgList.getCount(); i++)
{
std::string itemName = cfgList.getItem(i);
if (itemName.find("client_secret") != itemName.npos)
{
clientSecretPath = "/config/JKSV/" + cfgList.getItem(i);
break;
}
}
if (!clientSecretPath.empty())
{
json_object *installed, *clientID, *clientSecret, *driveJSON = json_object_from_file(clientSecretPath.c_str());
if (driveJSON)
{
if (json_object_object_get_ex(driveJSON, "installed", &installed))
{
if (json_object_object_get_ex(installed, "client_id", &clientID) &&
json_object_object_get_ex(installed, "client_secret", &clientSecret))
{
cfg::driveClientID = json_object_get_string(clientID);
cfg::driveClientSecret = json_object_get_string(clientSecret);
}
}
json_object_put(driveJSON);
}
}
// End Google Drive
// Webdav
json_object *webdavJSON = json_object_from_file("/config/JKSV/webdav.json");
json_object *origin, *basepath, *username, *password;
if (webdavJSON)
{
if (json_object_object_get_ex(webdavJSON, "origin", &origin))
{
cfg::webdavOrigin = json_object_get_string(origin);
}
if (json_object_object_get_ex(webdavJSON, "basepath", &basepath))
{
cfg::webdavBasePath = json_object_get_string(basepath);
}
if (json_object_object_get_ex(webdavJSON, "username", &username))
{
cfg::webdavUser = json_object_get_string(username);
}
if (json_object_object_get_ex(webdavJSON, "password", &password))
{
cfg::webdavPassword = json_object_get_string(password);
}
json_object_put(webdavJSON);
}
}
void cfg::loadConfig()
{
cfg::resetConfig();
loadWorkDirLegacy();
loadConfigLegacy();
loadFavoritesLegacy();
@ -296,117 +389,130 @@ void cfg::loadConfig()
loadTitleDefsLegacy();
loadTitleDefs();
if(fs::fileExists(cfgPath))
if (fs::fileExists(cfgPath))
{
fs::dataFile cfgRead(cfgPath);
while(cfgRead.readNextLine(true))
while (cfgRead.readNextLine(true))
{
switch(cfgStrings[cfgRead.getName()])
std::string varName = cfgRead.getName();
if (cfgStrings.find(varName) != cfgStrings.end())
{
case 0:
fs::setWorkDir(cfgRead.getNextValueStr());
break;
switch (cfgStrings[varName])
{
case 0:
fs::setWorkDir(cfgRead.getNextValueStr());
break;
case 1:
cfg::config["incDev"] = textToBool(cfgRead.getNextValueStr());
break;
case 1:
cfg::config["incDev"] = textToBool(cfgRead.getNextValueStr());
break;
case 2:
cfg::config["autoBack"] = textToBool(cfgRead.getNextValueStr());
break;
case 2:
cfg::config["autoBack"] = textToBool(cfgRead.getNextValueStr());
break;
case 3:
cfg::config["ovrClk"] = textToBool(cfgRead.getNextValueStr());
break;
case 3:
cfg::config["ovrClk"] = textToBool(cfgRead.getNextValueStr());
break;
case 4:
cfg::config["holdDel"] = textToBool(cfgRead.getNextValueStr());
break;
case 4:
cfg::config["holdDel"] = textToBool(cfgRead.getNextValueStr());
break;
case 5:
cfg::config["holdRest"] = textToBool(cfgRead.getNextValueStr());
break;
case 5:
cfg::config["holdRest"] = textToBool(cfgRead.getNextValueStr());
break;
case 6:
cfg::config["holdOver"] = textToBool(cfgRead.getNextValueStr());
break;
case 6:
cfg::config["holdOver"] = textToBool(cfgRead.getNextValueStr());
break;
case 7:
cfg::config["forceMount"] = textToBool(cfgRead.getNextValueStr());
break;
case 7:
cfg::config["forceMount"] = textToBool(cfgRead.getNextValueStr());
break;
case 8:
cfg::config["accSysSave"] = textToBool(cfgRead.getNextValueStr());
break;
case 8:
cfg::config["accSysSave"] = textToBool(cfgRead.getNextValueStr());
break;
case 9:
cfg::config["sysSaveWrite"] = textToBool(cfgRead.getNextValueStr());
break;
case 9:
cfg::config["sysSaveWrite"] = textToBool(cfgRead.getNextValueStr());
break;
case 10:
cfg::config["directFsCmd"] = textToBool(cfgRead.getNextValueStr());
break;
case 10:
cfg::config["directFsCmd"] = textToBool(cfgRead.getNextValueStr());
break;
case 11:
cfg::config["zip"] = textToBool(cfgRead.getNextValueStr());
break;
case 11:
cfg::config["zip"] = textToBool(cfgRead.getNextValueStr());
break;
case 12:
cfg::config["langOverride"] = textToBool(cfgRead.getNextValueStr());
break;
case 12:
cfg::config["langOverride"] = textToBool(cfgRead.getNextValueStr());
break;
case 13:
cfg::config["trashBin"] = textToBool(cfgRead.getNextValueStr());
break;
case 13:
cfg::config["trashBin"] = textToBool(cfgRead.getNextValueStr());
break;
case 14:
case 14:
{
std::string getSort = cfgRead.getNextValueStr();
if(getSort == "ALPHA")
if (getSort == "ALPHA")
cfg::sortType = cfg::ALPHA;
else if(getSort == "MOST_PLAYED")
else if (getSort == "MOST_PLAYED")
cfg::sortType = cfg::MOST_PLAYED;
else
cfg::sortType = cfg::LAST_PLAYED;
}
break;
case 15:
case 15:
{
std::string animFloat = cfgRead.getNextValueStr();
ui::animScale = strtof(animFloat.c_str(), NULL);
}
break;
case 16:
case 16:
{
std::string tid = cfgRead.getNextValueStr();
cfg::favorites.push_back(strtoul(tid.c_str(), NULL, 16));
}
break;
case 17:
case 17:
{
std::string tid = cfgRead.getNextValueStr();
cfg::blacklist.push_back(strtoul(tid.c_str(), NULL, 16));
}
break;
case 18:
cfg::config["autoName"] = textToBool(cfgRead.getNextValueStr());
break;
case 18:
cfg::config["autoName"] = textToBool(cfgRead.getNextValueStr());
break;
default:
break;
case 19:
cfg::driveRefreshToken = cfgRead.getNextValueStr();
break;
case 20:
cfg::config["autoUpload"] = textToBool(cfgRead.getNextValueStr());
break;
default:
break;
}
}
}
}
loadDriveConfig();
}
static void savePathDefs()
{
FILE *pathOut = fopen(titleDefPath, "w");
for(auto& p : pathDefs)
for (auto &p : pathDefs)
fprintf(pathOut, "0x%016lX = \"%s\"\n", p.first, p.second.c_str());
fclose(pathOut);
}
@ -432,21 +538,24 @@ void cfg::saveConfig()
fprintf(cfgOut, "titleSortType = %s\n", sortTypeText().c_str());
fprintf(cfgOut, "animationScale = %f\n", ui::animScale);
if(!cfg::favorites.empty())
if (!cfg::driveRefreshToken.empty())
fprintf(cfgOut, "driveRefreshToken = %s\n", cfg::driveRefreshToken.c_str());
if (!cfg::favorites.empty())
{
fprintf(cfgOut, "\n#favorites\n");
for(uint64_t& f : cfg::favorites)
for (uint64_t &f : cfg::favorites)
fprintf(cfgOut, "favorite = 0x%016lX\n", f);
}
if(!cfg::blacklist.empty())
if (!cfg::blacklist.empty())
{
fprintf(cfgOut, "\n#blacklist\n");
for(uint64_t& b : cfg::blacklist)
for (uint64_t &b : cfg::blacklist)
fprintf(cfgOut, "blacklist = 0x%016lX\n", b);
}
fclose(cfgOut);
if(!pathDefs.empty())
if (!pathDefs.empty())
savePathDefs();
}

View File

@ -2,21 +2,54 @@
#include <vector>
#include <curl/curl.h>
size_t writeDataString(const char *buff, size_t sz, size_t cnt, void *u)
#include "curlfuncs.h"
#include "util.h"
size_t curlFuncs::writeDataString(const char *buff, size_t sz, size_t cnt, void *u)
{
std::string *str = (std::string *)u;
str->append(buff, 0, sz * cnt);
return sz * cnt;
}
size_t writeDataHead(const char *buff, size_t sz, size_t cnt, void *u)
size_t curlFuncs::writeHeaders(const char *buff, size_t sz, size_t cnt, void *u)
{
std::vector<std::string> *headers = (std::vector<std::string> *)u;
headers->push_back(buff);
return sz * cnt;
}
std::string getJSONURL(std::vector<std::string> *headers, const std::string& _url)
size_t curlFuncs::readDataFile(char *buff, size_t sz, size_t cnt, void *u)
{
curlFuncs::curlUpArgs*in = (curlFuncs::curlUpArgs *)u;
size_t ret = fread(buff, sz, cnt, in->f);
if(in->o)
*in->o = ftell(in->f);
return ret;
}
std::string curlFuncs::getHeader(const std::string& name, std::vector<std::string> *h)
{
std::string ret = HEADER_ERROR;
for (unsigned i = 0; i < h->size(); i++)
{
std::string curHeader = h->at(i);
size_t colonPos = curHeader.find_first_of(':');
if (curHeader.substr(0, colonPos) == name)
{
ret = curHeader.substr(colonPos + 2);
break;
}
}
util::stripChar('\n', ret);
util::stripChar('\r', ret);
return ret;
}
std::string curlFuncs::getJSONURL(std::vector<std::string> *headers, const std::string& _url)
{
std::string ret;
CURL *handle = curl_easy_init();
@ -29,7 +62,7 @@ std::string getJSONURL(std::vector<std::string> *headers, const std::string& _ur
curl_easy_setopt(handle, CURLOPT_TIMEOUT, 15);
if(headers)
{
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, writeDataHead);
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, writeHeaders);
curl_easy_setopt(handle, CURLOPT_HEADERDATA, headers);
}
@ -49,7 +82,7 @@ size_t writeDataBin(uint8_t *buff, size_t sz, size_t cnt, void *u)
return sizeIn;
}
bool getBinURL(std::vector<uint8_t> *out, const std::string& _url)
bool curlFuncs::getBinURL(std::vector<uint8_t> *out, const std::string& _url)
{
bool ret = false;
CURL *handle = curl_easy_init();

View File

@ -1,55 +1,76 @@
#include <vector>
#include <unordered_map>
#include <string>
#include <cstring>
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <memory>
#include <string>
#include <string_view>
#include <switch.h>
#include <unordered_map>
#include <vector>
#include "cfg.h"
#include "data.h"
#include "file.h"
#include "util.h"
#include "type.h"
#include "cfg.h"
#include "util.h"
//FsSaveDataSpaceId_All doesn't work for SD
static const unsigned saveOrder [] = { 0, 1, 2, 3, 4, 100, 101 };
namespace
{
/// @brief This is the string used for when a publisher isn't found in the NACP.
constexpr std::string_view STRING_UNKOWN_AUTHOR = "Someone?";
/// @brief This array holds the order in which to scan save data space IDs since the all pseudo value from libnx doesn't seem to work.
constexpr std::array<FsSaveDataSpaceId, 7> ORDER_SAVE_DATA_SPACES = {FsSaveDataSpaceId_System,
FsSaveDataSpaceId_User,
FsSaveDataSpaceId_SdSystem,
FsSaveDataSpaceId_Temporary,
FsSaveDataSpaceId_SdUser,
FsSaveDataSpaceId_ProperSystem,
FsSaveDataSpaceId_SafeMode};
/// @brief This saves whether or not the System BCAT user was pushed.
bool s_systemBCATPushed = false;
/// @brief This saves whether or not the Temporary save user was pushed.
bool s_temporaryPushed = false;
} // namespace
/// @brief External variables I used to track selected user/title.
int selUser = 0, selData = 0;
//User vector
/// @brief This vector holds the user instances.
std::vector<data::user> data::users;
//System language
/// @brief System language. I don't know why this is here in data?
SetLanguage data::sysLang;
//For other save types
static bool sysBCATPushed = false, tempPushed = false;
/// @brief This is the map of titleInfo.
std::unordered_map<uint64_t, data::titleInfo> data::titles;
static SDL_Texture *iconMask;
//Sorts titles by sortType
static struct
{
bool operator()(const data::userTitleInfo& a, const data::userTitleInfo& b)
{
//Favorites override EVERYTHING
if(cfg::isFavorite(a.tid) != cfg::isFavorite(b.tid)) return cfg::isFavorite(a.tid);
switch(cfg::sortType)
bool operator()(const data::userTitleInfo &a, const data::userTitleInfo &b)
{
case cfg::ALPHA:
//Favorites override EVERYTHING
if (cfg::isFavorite(a.tid) != cfg::isFavorite(b.tid))
return cfg::isFavorite(a.tid);
switch (cfg::sortType)
{
case cfg::ALPHA:
{
std::string titleA = data::getTitleNameByTID(a.tid);
std::string titleB = data::getTitleNameByTID(b.tid);
uint32_t pointA, pointB;
for(unsigned i = 0, j = 0; i < titleA.length(); )
for (unsigned i = 0, j = 0; i < titleA.length();)
{
ssize_t aCnt = decode_utf8(&pointA, (const uint8_t *)&titleA.data()[i]);
ssize_t bCnt = decode_utf8(&pointB, (const uint8_t *)&titleB.data()[j]);
pointA = tolower(pointA), pointB = tolower(pointB);
if(pointA != pointB)
if (pointA != pointB)
return pointA < pointB;
i += aCnt;
@ -58,112 +79,185 @@ static struct
}
break;
case cfg::MOST_PLAYED:
return a.playStats.playtimeMinutes > b.playStats.playtimeMinutes;
break;
case cfg::MOST_PLAYED:
return a.playStats.playtime > b.playStats.playtime;
break;
case cfg::LAST_PLAYED:
return a.playStats.last_timestampUser > b.playStats.last_timestampUser;
break;
case cfg::LAST_PLAYED:
return a.playStats.last_timestamp_user > b.playStats.last_timestamp_user;
break;
}
return false;
}
return false;
}
} sortTitles;
//Returns -1 for new
static int getUserIndex(const AccountUid& id)
static int getUserIndex(AccountUid id)
{
u128 nId = util::accountUIDToU128(id);
for(unsigned i = 0; i < data::users.size(); i++)
if(data::users[i].getUID128() == nId) return i;
for (unsigned i = 0; i < data::users.size(); i++)
if (data::users[i].getUID128() == nId)
return i;
return -1;
}
static inline bool accountSystemSaveCheck(const FsSaveDataInfo& _inf)
static inline bool accountSystemSaveCheck(const FsSaveDataInfo &_inf)
{
if(_inf.save_data_type == FsSaveDataType_System && util::accountUIDToU128(_inf.uid) != 0 && !cfg::config["accSysSave"])
if (_inf.save_data_type == FsSaveDataType_System && util::accountUIDToU128(_inf.uid) != 0 &&
!cfg::config["accSysSave"])
return false;
return true;
}
//Minimal init/test to avoid loading and creating things I don't need
static bool testMount(const FsSaveDataInfo& _inf)
static bool testMount(const FsSaveDataInfo &_inf)
{
bool ret = false;
if(!cfg::config["forceMount"])
if (!cfg::config["forceMount"])
return true;
if((ret = fs::mountSave(_inf)))
if ((ret = fs::mountSave(_inf)))
fs::unmountSave();
return ret;
}
static inline void addTitleToList(const uint64_t& tid)
/// @brief This version of the function uses the title/application ID to pull the data from the system.
/// @param titleID Title/Application ID of the title to add.
static void add_title_to_list(uint64_t titleID)
{
uint64_t outSize = 0;
NsApplicationControlData *ctrlData = new NsApplicationControlData;
NacpLanguageEntry *ent;
Result ctrlRes = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, ctrlData, sizeof(NsApplicationControlData), &outSize);
Result nacpRes = nacpGetLanguageEntry(&ctrlData->nacp, &ent);
size_t iconSize = outSize - sizeof(ctrlData->nacp);
// This is the output size of the control data.
uint64_t dataSize = 0;
// This is the size of the icon.
size_t iconSize = 0;
// Nacp language entry pointer.
NacpLanguageEntry *entry = nullptr;
if(R_SUCCEEDED(ctrlRes) && !(outSize < sizeof(ctrlData->nacp)) && R_SUCCEEDED(nacpRes) && iconSize > 0)
// Try to read the control data directly into the map.
Result nsError = nsGetApplicationControlData(NsApplicationControlSource_Storage,
titleID,
&data::titles[titleID].data,
sizeof(NsApplicationControlData),
&dataSize);
// At this point, regardless, the title should exist in the map. Using a reference from here on out.
data::titleInfo &info = data::titles[titleID];
// Calc icon size. Not sure this is really needed?
iconSize = dataSize - sizeof(NacpStruct);
// If that failed, run the backup routine and bail.
if (R_FAILED(nsError) || dataSize < sizeof(info.data.nacp) || iconSize <= 0 ||
R_FAILED(nacpGetLanguageEntry(&info.data.nacp, &entry)))
{
//Copy nacp
memcpy(&data::titles[tid].nacp, &ctrlData->nacp, sizeof(NacpStruct));
// Clear the NACP.
std::memset(&info.data.nacp, 0x00, sizeof(NacpStruct));
//Setup 'shortcuts' to strings
NacpLanguageEntry *ent;
nacpGetLanguageEntry(&data::titles[tid].nacp, &ent);
data::titles[tid].title = ent->name;
data::titles[tid].author = ent->author;
if(cfg::isDefined(tid))
data::titles[tid].safeTitle = cfg::getPathDefinition(tid);
else if((data::titles[tid].safeTitle = util::safeString(ent->name)) == "")
data::titles[tid].safeTitle = util::getIDStr(tid);
// Set the title and publisher.
info.title = util::getIDStr(titleID);
info.author = STRING_UNKOWN_AUTHOR;
if(cfg::isFavorite(tid))
data::titles[tid].fav = true;
// Check if the title has a path defined in the config.
if (cfg::isDefined(titleID))
{
info.safeTitle = cfg::getPathDefinition(titleID);
}
else
{
info.safeTitle = util::getIDStr(titleID);
}
data::titles[tid].icon = gfx::loadJPEGMem(ctrlData->icon, iconSize);
if(!data::titles[tid].icon)
data::titles[tid].icon = util::createIconGeneric(util::getIDStrLower(tid).c_str(), 32, true);
// Create the icon.
std::string idHex = util::getIDStrLower(titleID);
info.icon = util::createIconGeneric(idHex.c_str(), 32, true);
// Bail. Do not continue. We're done.
return;
}
else
{
memset(&data::titles[tid].nacp, 0, sizeof(NacpStruct));
data::titles[tid].title = util::getIDStr(tid);
data::titles[tid].author = "Someone?";
if(cfg::isDefined(tid))
data::titles[tid].safeTitle = cfg::getPathDefinition(tid);
// Check to make sure the title isn't blank. If it is, use English.
if (std::char_traits<char>::length(entry->name) == 0)
{
info.title = info.data.nacp.lang[SetLanguage_ENUS].name;
}
else
data::titles[tid].safeTitle = util::getIDStr(tid);
{
info.title = entry->name;
}
data::titles[tid].icon = util::createIconGeneric(util::getIDStrLower(tid).c_str(), 32, true);
// Check the same for author/publisher.
if (std::char_traits<char>::length(entry->author) == 0)
{
info.author = info.data.nacp.lang[SetLanguage_ENUS].author;
}
else
{
info.author = entry->author;
}
// Load the icon from the control data.
info.icon = gfx::texMgr->textureLoadFromMem(IMG_FMT_JPG, info.data.icon, iconSize);
// Make sure to save that the title has valid control data.
info.hasControlData = true;
}
delete ctrlData;
}
static inline bool titleIsLoaded(const uint64_t& tid)
{
auto findTid = data::titles.find(tid);
return findTid == data::titles.end() ? false : true;
}
static void loadUserAccounts()
{
s32 total = 0;
AccountUid *uids = new AccountUid[8];
if(R_SUCCEEDED(accountListAllUsers(uids, 8, &total)))
// The following is universal whether the title has control data or not.
// Set the safe path title. Note: I hate the second else if statement, but screw it. Rewrite avoids this kind of stuff.
if (cfg::isDefined(titleID))
{
for(int i = 0; i < total; i++)
data::users.emplace_back(uids[i], "");
info.safeTitle = cfg::getPathDefinition(titleID);
}
else if ((info.safeTitle = util::safeString(entry->name)) == "")
{
info.safeTitle = util::getIDStr(titleID);
}
// Favorite check.
if (cfg::isFavorite(titleID))
{
info.fav = true;
}
}
/// @brief Adds a title to the map using the application ID and cached control data.
/// @param titleID Application ID of the title.
/// @param data Reference to the data to use.
static void add_title_to_list(uint64_t titleID, NsApplicationControlData &data)
{
// Start by memcpy'ing the data over.
std::memcpy(&data::titles[titleID].data, &data, sizeof(NsApplicationControlData));
// Since this function is always being fed cached data, we're going to assume everything else is good.
}
static inline bool titleIsLoaded(uint64_t titleID)
{
return data::titles.find(titleID) != data::titles.end();
}
static void loadUserAccounts(void)
{
// Total accounts listed.
int total = 0;
// Switch has eight accounts max.
AccountUid accounts[8] = {0};
if (R_FAILED(accountListAllUsers(accounts, 8, &total)))
{
// Rewrite logs this.
return;
}
for (int i = 0; i < total; i++)
{
// Maybe to do: This looks stupid. Overload constructors?
data::users.emplace_back(accounts[i], "", "");
}
delete[] uids;
}
//This can load titles installed without having save data
@ -171,10 +265,96 @@ static void loadTitlesFromRecords()
{
NsApplicationRecord nsRecord;
int32_t entryCount = 0, recordOffset = 0;
while(R_SUCCEEDED(nsListApplicationRecord(&nsRecord, 1, recordOffset++, &entryCount)) && entryCount > 0)
while (R_SUCCEEDED(nsListApplicationRecord(&nsRecord, 1, recordOffset++, &entryCount)) && entryCount > 0)
{
if(!titleIsLoaded(nsRecord.application_id))
addTitleToList(nsRecord.application_id);
if (!titleIsLoaded(nsRecord.application_id))
{
add_title_to_list(nsRecord.application_id);
}
}
}
static void import_svi_files(void)
{
// This is the path used. JKSV master was written before fslib.
std::string sviPath = fs::getWorkDir() + "svi";
// Get the listing and check to make sure there was actually something in it.
fs::dirList sviList(sviPath);
if (sviList.getCount() <= 0)
{
// Just return and don't do anything.
return;
}
// Loop through the listing and load them all.
for (unsigned int i = 0; i < sviList.getCount(); i++)
{
// Full path to the file.
std::string fullPath = sviPath + "/" + sviList.getItem(i);
// Grab the size of the SVI before continuing.
size_t sviSize = fs::fsize(fullPath);
// Application ID
uint64_t applicationID = 0;
// Language entry.
NacpLanguageEntry *entry = nullptr;
// Icon size.
size_t iconSize = 0;
// Open the file and read the application ID.
std::FILE *sviFile = std::fopen(sviPath.c_str(), "rb");
std::fread(&applicationID, 1, sizeof(uint64_t), sviFile);
// If it already exists in the map, just continue.
if (titleIsLoaded(applicationID))
{
std::fclose(sviFile);
continue;
}
// Read the NACP data into the map directly.
std::fread(&data::titles[applicationID].data.nacp, 1, sizeof(NacpStruct), sviFile);
// Calculate the icon size and read that too. The ControlData has memory set aside for the icon anyway.
iconSize = fs::fsize(fullPath) - (sizeof(uint64_t) + sizeof(NacpStruct));
std::fread(&data::titles[applicationID].data.icon, 1, iconSize, sviFile);
// Think I should be safe to use a reference now?
data::titleInfo &info = data::titles[applicationID];
// Setup the title stuff.
if (R_FAILED(nacpGetLanguageEntry(&info.data.nacp, &entry)))
{
// Default to English.
info.title = info.data.nacp.lang[SetLanguage_ENUS].name;
info.author = info.data.nacp.lang[SetLanguage_ENUS].author;
}
else
{
info.title = entry->name;
info.author = entry->author;
}
// Safe path.
if (cfg::isDefined(applicationID))
{
info.safeTitle = cfg::getPathDefinition(applicationID);
}
else if ((info.safeTitle = util::safeString(info.title)).empty())
{
info.safeTitle = util::getIDStr(applicationID);
}
// Favorite
if (cfg::isFavorite(applicationID))
{
info.fav = true;
}
// Just going to assume this is good.
info.icon = gfx::texMgr->textureLoadFromMem(IMG_FMT_JPG, info.data.icon, iconSize);
}
}
@ -186,47 +366,55 @@ bool data::loadUsersTitles(bool clearUsers)
s64 total = 0;
loadTitlesFromRecords();
import_svi_files();
//Clear titles
for(data::user& u : data::users)
for (data::user &u : data::users)
{
u.titleInfo.clear();
if(clearUsers)
}
if (clearUsers)
{
systemUserCount = 4;
for(data::user& u : data::users)
for (data::user &u : data::users)
{
u.delIcon();
}
data::users.clear();
loadUserAccounts();
sysBCATPushed = false;
tempPushed = false;
users.emplace_back(util::u128ToAccountUID(3), "Device");
users.emplace_back(util::u128ToAccountUID(2), "BCAT");
users.emplace_back(util::u128ToAccountUID(5), "Cache");
users.emplace_back(util::u128ToAccountUID(0), "System");
s_systemBCATPushed = false;
s_temporaryPushed = false;
users.emplace_back(util::u128ToAccountUID(3), ui::getUIString("saveTypeMainMenu", 0), "Device");
users.emplace_back(util::u128ToAccountUID(2), ui::getUIString("saveTypeMainMenu", 1), "BCAT");
users.emplace_back(util::u128ToAccountUID(5), ui::getUIString("saveTypeMainMenu", 2), "Cache");
users.emplace_back(util::u128ToAccountUID(0), ui::getUIString("saveTypeMainMenu", 3), "System");
}
for(unsigned i = 0; i < 7; i++)
for (unsigned i = 0; i < 7; i++)
{
if(R_FAILED(fsOpenSaveDataInfoReader(&it, (FsSaveDataSpaceId)saveOrder[i])))
if (R_FAILED(fsOpenSaveDataInfoReader(&it, ORDER_SAVE_DATA_SPACES.at(i))))
continue;
while(R_SUCCEEDED(fsSaveDataInfoReaderRead(&it, &info, 1, &total)) && total != 0)
while (R_SUCCEEDED(fsSaveDataInfoReaderRead(&it, &info, 1, &total)) && total != 0)
{
uint64_t tid = 0;
if(info.save_data_type == FsSaveDataType_System || info.save_data_type == FsSaveDataType_SystemBcat)
if (info.save_data_type == FsSaveDataType_System || info.save_data_type == FsSaveDataType_SystemBcat)
tid = info.system_save_data_id;
else
tid = info.application_id;
if(!titleIsLoaded(tid))
addTitleToList(tid);
if (!titleIsLoaded(tid))
{
add_title_to_list(tid);
}
//Don't bother with this stuff
if(cfg::isBlacklisted(tid) || !accountSystemSaveCheck(info) || !testMount(info))
if (cfg::isBlacklisted(tid) || !accountSystemSaveCheck(info) || !testMount(info))
continue;
switch(info.save_data_type)
switch (info.save_data_type)
{
case FsSaveDataType_Bcat:
info.uid = util::u128ToAccountUID(2);
@ -238,11 +426,13 @@ bool data::loadUsersTitles(bool clearUsers)
case FsSaveDataType_SystemBcat:
info.uid = util::u128ToAccountUID(4);
if(!sysBCATPushed)
if (!s_systemBCATPushed)
{
++systemUserCount;
sysBCATPushed = true;
users.emplace_back(util::u128ToAccountUID(4), "Sys. BCAT");
s_systemBCATPushed = true;
users.emplace_back(util::u128ToAccountUID(4),
ui::getUIString("saveTypeMainMenu", 4),
"System BCAT");
}
break;
@ -252,25 +442,30 @@ bool data::loadUsersTitles(bool clearUsers)
case FsSaveDataType_Temporary:
info.uid = util::u128ToAccountUID(6);
if(!tempPushed)
if (!s_temporaryPushed)
{
++systemUserCount;
tempPushed = true;
users.emplace_back(util::u128ToAccountUID(6), "Temp");
s_temporaryPushed = true;
users.emplace_back(util::u128ToAccountUID(6),
ui::getUIString("saveTypeMainMenu", 5),
"Temporary");
}
break;
}
int u = getUserIndex(info.uid);
if(u == -1)
if (u == -1)
{
users.emplace(data::users.end() - systemUserCount, info.uid, "");
users.emplace(data::users.end() - systemUserCount, info.uid, "", "");
u = getUserIndex(info.uid);
}
PdmPlayStatistics playStats;
if(info.save_data_type == FsSaveDataType_Account || info.save_data_type == FsSaveDataType_Device)
pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(info.application_id, info.uid, false, &playStats);
if (info.save_data_type == FsSaveDataType_Account || info.save_data_type == FsSaveDataType_Device)
pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(info.application_id,
info.uid,
false,
&playStats);
else
memset(&playStats, 0, sizeof(PdmPlayStatistics));
users[u].addUserTitleInfo(tid, &info, &playStats);
@ -278,15 +473,15 @@ bool data::loadUsersTitles(bool clearUsers)
fsSaveDataInfoReaderClose(&it);
}
if(cfg::config["incDev"])
if (cfg::config["incDev"])
{
//Get reference to device save user
unsigned devPos = getUserIndex(util::u128ToAccountUID(3));
data::user& dev = data::users[devPos];
for(unsigned i = 0; i < devPos; i++)
data::user &dev = data::users[devPos];
for (unsigned i = 0; i < devPos; i++)
{
//Not needed but makes this easier to read
data::user& u = data::users[i];
data::user &u = data::users[i];
u.titleInfo.insert(u.titleInfo.end(), dev.titleInfo.begin(), dev.titleInfo.end());
}
}
@ -299,32 +494,18 @@ bool data::loadUsersTitles(bool clearUsers)
void data::sortUserTitles()
{
for(data::user& u : data::users)
for (data::user &u : data::users)
std::sort(u.titleInfo.begin(), u.titleInfo.end(), sortTitles);
}
void data::init()
{
if(cfg::config["ovrClk"])
util::setCPU(util::CPU_SPEED_1224MHz);
uint64_t lang;
setGetSystemLanguage(&lang);
setMakeLanguage(lang, &sysLang);
data::loadUsersTitles(true);
}
void data::exit()
{
for(data::user& u : data::users) u.delIcon();
for(auto& tinfo : titles)
SDL_DestroyTexture(tinfo.second.icon);
SDL_DestroyTexture(iconMask);
if(cfg::config["ovrClk"])
util::setCPU(util::CPU_SPEED_1020MHz);
/*Still needed for planned future revisions*/
}
void data::setUserIndex(unsigned _sUser)
@ -357,39 +538,39 @@ unsigned data::getCurrentUserTitleInfoIndex()
return selData;
}
data::titleInfo *data::getTitleInfoByTID(const uint64_t& tid)
data::titleInfo *data::getTitleInfoByTID(const uint64_t &tid)
{
if(titles.find(tid) != titles.end())
if (titles.find(tid) != titles.end())
return &titles[tid];
return NULL;
}
std::string data::getTitleNameByTID(const uint64_t& tid)
std::string data::getTitleNameByTID(const uint64_t &tid)
{
return titles[tid].title;
}
std::string data::getTitleSafeNameByTID(const uint64_t& tid)
std::string data::getTitleSafeNameByTID(const uint64_t &tid)
{
return titles[tid].safeTitle;
}
SDL_Texture *data::getTitleIconByTID(const uint64_t& tid)
SDL_Texture *data::getTitleIconByTID(const uint64_t &tid)
{
return titles[tid].icon;
}
int data::getTitleIndexInUser(const data::user& u, const uint64_t& tid)
int data::getTitleIndexInUser(const data::user &u, const uint64_t &tid)
{
for(unsigned i = 0; i < u.titleInfo.size(); i++)
for (unsigned i = 0; i < u.titleInfo.size(); i++)
{
if(u.titleInfo[i].tid == tid)
if (u.titleInfo[i].tid == tid)
return i;
}
return -1;
}
data::user::user(const AccountUid& _id, const std::string& _backupName)
data::user::user(AccountUid _id, const std::string &_backupName, const std::string &_safeBackupName)
{
userID = _id;
uID128 = util::accountUIDToU128(_id);
@ -397,11 +578,11 @@ data::user::user(const AccountUid& _id, const std::string& _backupName)
AccountProfile prof;
AccountProfileBase base;
if(R_SUCCEEDED(accountGetProfile(&prof, userID)) && R_SUCCEEDED(accountProfileGet(&prof, NULL, &base)))
if (R_SUCCEEDED(accountGetProfile(&prof, userID)) && R_SUCCEEDED(accountProfileGet(&prof, NULL, &base)))
{
username = base.nickname;
userSafe = util::safeString(username);
if(userSafe.empty())
if (userSafe.empty())
{
char tmp[32];
sprintf(tmp, "Acc%08X", (uint32_t)uID128);
@ -412,7 +593,7 @@ data::user::user(const AccountUid& _id, const std::string& _backupName)
accountProfileGetImageSize(&prof, &jpgSize);
uint8_t *jpegData = new uint8_t[jpgSize];
accountProfileLoadImage(&prof, jpegData, jpgSize, &jpgSize);
userIcon = gfx::loadJPEGMem(jpegData, jpgSize);
userIcon = gfx::texMgr->textureLoadFromMem(IMG_FMT_JPG, jpegData, jpgSize);
delete[] jpegData;
accountProfileClose(&prof);
@ -420,26 +601,27 @@ data::user::user(const AccountUid& _id, const std::string& _backupName)
else
{
username = _backupName.empty() ? util::getIDStr((uint64_t)uID128) : _backupName;
userSafe = _backupName.empty() ? util::getIDStr((uint64_t)uID128) : _backupName;
userSafe = _safeBackupName.empty() ? util::getIDStr((uint64_t)uID128) : _safeBackupName;
userIcon = util::createIconGeneric(_backupName.c_str(), 48, false);
}
titles.reserve(64);
}
data::user::user(const AccountUid& _id, const std::string& _backupName, SDL_Texture *img) : user(_id, _backupName)
data::user::user(AccountUid _id, const std::string &_backupName, const std::string &_safeBackupName, SDL_Texture *img)
: user(_id, _backupName, _safeBackupName)
{
delIcon();
userIcon = img;
titles.reserve(64);
}
void data::user::setUID(const AccountUid& _id)
void data::user::setUID(AccountUid _id)
{
userID = _id;
uID128 = util::accountUIDToU128(_id);
}
void data::user::addUserTitleInfo(const uint64_t& tid, const FsSaveDataInfo *_saveInfo, const PdmPlayStatistics *_stats)
void data::user::addUserTitleInfo(const uint64_t &tid, const FsSaveDataInfo *_saveInfo, const PdmPlayStatistics *_stats)
{
data::userTitleInfo newInfo;
newInfo.tid = tid;
@ -448,7 +630,7 @@ void data::user::addUserTitleInfo(const uint64_t& tid, const FsSaveDataInfo *_sa
titleInfo.push_back(newInfo);
}
static const SDL_Color green = {0x00, 0xDD, 0x00, 0xFF};
static constexpr SDL_Color green = {0x00, 0xDD, 0x00, 0xFF};
void data::dispStats()
{
@ -456,12 +638,12 @@ void data::dispStats()
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
//Easiest/laziest way to do this
std::string stats = "User Count: " + std::to_string(users.size()) + "\n";
for(data::user& u : data::users)
std::string stats = ui::getUICString("debugStatus", 0) + std::to_string(users.size()) + "\n";
for (data::user &u : data::users)
stats += u.getUsername() + ": " + std::to_string(u.titleInfo.size()) + "\n";
stats += "Current User: " + cu->getUsername() + "\n";
stats += "Current Title: " + data::getTitleNameByTID(d->tid) + "\n";
stats += "Safe Title: " + data::getTitleSafeNameByTID(d->tid) + "\n";
stats += "Sort Type: " + std::to_string(cfg::sortType) + "\n";
stats += ui::getUICString("debugStatus", 1) + cu->getUsername() + "\n";
stats += ui::getUICString("debugStatus", 2) + data::getTitleNameByTID(d->tid) + "\n";
stats += ui::getUICString("debugStatus", 3) + data::getTitleSafeNameByTID(d->tid) + "\n";
stats += ui::getUICString("debugStatus", 4) + std::to_string(cfg::sortType) + "\n";
gfx::drawTextf(NULL, 16, 2, 2, &green, stats.c_str());
}

View File

@ -1,743 +0,0 @@
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <switch.h>
#include <unistd.h>
#include <cstdarg>
#include <sys/stat.h>
#include "file.h"
#include "util.h"
#include "ui.h"
#include "gfx.h"
#include "data.h"
#include "cfg.h"
static std::string wd = "sdmc:/JKSV/";
static std::vector<std::string> pathFilter;
static FSFILE *debLog;
static FsFileSystem sv;
fs::copyArgs *fs::copyArgsCreate(const std::string& from, const std::string& to, const std::string& dev, zipFile z, unzFile unz, bool _cleanup, bool _trimZipPath, uint8_t _trimPlaces)
{
copyArgs *ret = new copyArgs;
ret->to = to;
ret->from = from;
ret->dev = dev;
ret->z = z;
ret->unz = unz;
ret->cleanup = _cleanup;
ret->prog = new ui::progBar;
ret->prog->setMax(0);
ret->prog->update(0);
ret->offset = 0;
ret->trimZipPath = _trimZipPath;
ret->trimZipPlaces = _trimPlaces;
return ret;
}
void fs::copyArgsDestroy(copyArgs *c)
{
delete c->prog;
delete c;
c = NULL;
}
static struct
{
bool operator()(const fs::dirItem& a, const fs::dirItem& b)
{
if(a.isDir() != b.isDir())
return a.isDir();
for(unsigned i = 0; i < a.getItm().length(); i++)
{
char charA = tolower(a.getItm()[i]);
char charB = tolower(b.getItm()[i]);
if(charA != charB)
return charA < charB;
}
return false;
}
} sortDirList;
void fs::mkDir(const std::string& _p)
{
if(cfg::config["directFsCmd"])
fsMkDir(_p.c_str());
else
mkdir(_p.c_str(), 777);
}
void fs::mkDirRec(const std::string& _p)
{
//skip first slash
size_t pos = _p.find('/', 0) + 1;
while((pos = _p.find('/', pos)) != _p.npos)
{
fs::mkDir(_p.substr(0, pos).c_str());
++pos;
}
}
bool fs::commitToDevice(const std::string& dev)
{
bool ret = true;
Result res = fsdevCommitDevice(dev.c_str());
if(R_FAILED(res))
{
fs::logWrite("Error committing file to device -> 0x%X\n", res);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popErrorCommittingFile", 0));
ret = false;
}
return ret;
}
void fs::createSaveData(FsSaveDataType _type, uint64_t _tid, AccountUid _userID)
{
std::string indexStr;
uint16_t index = 0;
if(_type == FsSaveDataType_Cache && !(indexStr = util::getStringInput(SwkbdType_NumPad, "0", ui::getUIString("swkbdSaveIndex", 0), 2, 0, NULL)).empty())
index = strtoul(indexStr.c_str(), NULL, 10);
else if(_type == FsSaveDataType_Cache && indexStr.empty())
return;
svCreateArgs *send = new svCreateArgs;
send->type = _type;
send->account = _userID;
send->tid = _tid;
send->index = index;
ui::newThread(fs::createSaveData_t, send, NULL);
}
void fs::init()
{
mkDirRec("sdmc:/config/JKSV/");
mkDirRec(wd);
mkdir(std::string(wd + "_TRASH_").c_str(), 777);
fs::logOpen();
}
void fs::exit()
{
}
bool fs::mountSave(const FsSaveDataInfo& _m)
{
Result svOpen;
FsSaveDataAttribute attr = {0};
switch(_m.save_data_type)
{
case FsSaveDataType_System:
case FsSaveDataType_SystemBcat:
{
attr.uid = _m.uid;
attr.system_save_data_id = _m.system_save_data_id;
attr.save_data_type = _m.save_data_type;
svOpen = fsOpenSaveDataFileSystemBySystemSaveDataId(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Account:
{
attr.uid = _m.uid;
attr.application_id = _m.application_id;
attr.save_data_type = _m.save_data_type;
attr.save_data_rank = _m.save_data_rank;
attr.save_data_index = _m.save_data_index;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Device:
{
attr.application_id = _m.application_id;
attr.save_data_type = FsSaveDataType_Device;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Bcat:
{
attr.application_id = _m.application_id;
attr.save_data_type = FsSaveDataType_Bcat;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Cache:
{
attr.application_id = _m.application_id;
attr.save_data_type = FsSaveDataType_Cache;
attr.save_data_index = _m.save_data_index;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Temporary:
{
attr.application_id = _m.application_id;
attr.save_data_type = _m.save_data_type;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
default:
svOpen = 1;
break;
}
return R_SUCCEEDED(svOpen) && fsdevMountDevice("sv", sv) != -1;
}
fs::dirItem::dirItem(const std::string& pathTo, const std::string& sItem)
{
itm = sItem;
std::string fullPath = pathTo + sItem;
struct stat s;
if(stat(fullPath.c_str(), &s) == 0 && S_ISDIR(s.st_mode))
dir = true;
}
std::string fs::dirItem::getName() const
{
size_t extPos = itm.find_last_of('.'), slPos = itm.find_last_of('/');
if(extPos == itm.npos)
return "";
return itm.substr(slPos + 1, extPos);
}
std::string fs::dirItem::getExt() const
{
return util::getExtensionFromString(itm);
}
fs::dirList::dirList(const std::string& _path)
{
path = _path;
d = opendir(path.c_str());
while((ent = readdir(d)))
item.emplace_back(path, ent->d_name);
closedir(d);
std::sort(item.begin(), item.end(), sortDirList);
}
void fs::dirList::reassign(const std::string& _path)
{
path = _path;
d = opendir(path.c_str());
item.clear();
while((ent = readdir(d)))
item.emplace_back(path, ent->d_name);
closedir(d);
std::sort(item.begin(), item.end(), sortDirList);
}
void fs::dirList::rescan()
{
item.clear();
d = opendir(path.c_str());
while((ent = readdir(d)))
item.emplace_back(path, ent->d_name);
closedir(d);
std::sort(item.begin(), item.end(), sortDirList);
}
fs::dataFile::dataFile(const std::string& _path)
{
f = fopen(_path.c_str(), "r");
if(f != NULL)
opened = true;
}
fs::dataFile::~dataFile()
{
fclose(f);
}
bool fs::dataFile::readNextLine(bool proc)
{
bool ret = false;
char tmp[1024];
while(fgets(tmp, 1024, f))
{
if(tmp[0] != '#' && tmp[0] != '\n' && tmp[0] != '\r')
{
line = tmp;
ret = true;
break;
}
}
util::stripChar('\n', line);
util::stripChar('\r', line);
if(proc)
procLine();
return ret;
}
void fs::dataFile::procLine()
{
size_t pPos = line.find_first_of("(=,");
if(pPos != line.npos)
{
lPos = pPos;
name.assign(line.begin(), line.begin() + lPos);
}
else
name = line;
util::stripChar(' ', name);
++lPos;
}
std::string fs::dataFile::getNextValueStr()
{
std::string ret = "";
//Skip all spaces until we hit actual text
size_t pos1 = line.find_first_not_of(", ", lPos);
//If reading from quotes
if(line[pos1] == '"')
lPos = line.find_first_of('"', ++pos1);
else
lPos = line.find_first_of(",;\n", pos1);//Set lPos to end of string we want. This should just set lPos to the end of the line if it fails, which is ok
ret = line.substr(pos1, lPos++ - pos1);
util::replaceStr(ret, "\\n", "\n");
return ret;
}
int fs::dataFile::getNextValueInt()
{
int ret = 0;
std::string no = getNextValueStr();
if(no[0] == '0' && tolower(no[1]) == 'x')
ret = strtoul(no.c_str(), NULL, 16);
else
ret = strtoul(no.c_str(), NULL, 10);
return ret;
}
//Wrapper functions to use functions from `fsthrd.cpp`
void fs::copyFile(const std::string& from, const std::string& to)
{
copyArgs *send = copyArgsCreate(from, to, "", NULL, NULL, false, false, 0);
ui::newThread(copyFile_t, send, _fileDrawFunc);
}
void fs::copyFileCommit(const std::string& from, const std::string& to, const std::string& dev)
{
copyArgs *send = copyArgsCreate(from, to, dev, NULL, NULL, false, false, 0);
ui::newThread(copyFileCommit_t, send, _fileDrawFunc);
}
void fs::copyDirToDir(const std::string& from, const std::string& to)
{
fs::copyArgs *send = fs::copyArgsCreate(from, to, "", NULL, NULL, true, false, 0);
ui::newThread(fs::copyDirToDir_t, send, _fileDrawFunc);
}
void fs::copyDirToZip(const std::string& from, zipFile to)
{
if(cfg::config["ovrClk"])
{
util::setCPU(util::CPU_SPEED_1785MHz);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popCPUBoostEnabled", 0));
}
copyArgs *send = copyArgsCreate(from, "", "", to, NULL, true, false, 0);
ui::newThread(copyDirToZip_t, send, _fileDrawFunc);
}
void fs::copyZipToDir(unzFile unz, const std::string& to, const std::string& dev)
{
if(cfg::config["ovrClk"])
{
util::setCPU(util::CPU_SPEED_1785MHz);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popCPUBoostEnabled", 0));
}
copyArgs *send = copyArgsCreate("", to, dev, NULL, unz, true, false, 0);
ui::newThread(copyZipToDir_t, send, _fileDrawFunc);
}
bool fs::dirNotEmpty(const std::string& _dir)
{
fs::dirList tmp(_dir);
return tmp.getCount() > 0;
}
bool fs::zipNotEmpty(unzFile unzip)
{
return unzGoToFirstFile(unzip) == UNZ_OK;
}
void fs::copyDirToDirCommit(const std::string& from, const std::string& to, const std::string& dev)
{
fs::copyArgs *send = copyArgsCreate(from, to, dev, NULL, NULL, true, false, 0);
ui::newThread(copyDirToDirCommit_t, send, _fileDrawFunc);
}
void fs::delfile(const std::string& path)
{
if(cfg::config["directFsCmd"])
fsremove(path.c_str());
else
remove(path.c_str());
}
void fs::delDir(const std::string& path)
{
dirList list(path);
for(unsigned i = 0; i < list.getCount(); i++)
{
if(pathIsFiltered(path + list.getItem(i)))
continue;
if(list.isDir(i))
{
std::string newPath = path + list.getItem(i) + "/";
delDir(newPath);
std::string delPath = path + list.getItem(i);
rmdir(delPath.c_str());
}
else
{
std::string delPath = path + list.getItem(i);
std::remove(delPath.c_str());
}
}
rmdir(path.c_str());
}
void fs::loadPathFilters(const uint64_t& tid)
{
char path[256];
sprintf(path, "sdmc:/config/JKSV/0x%016lX_filter.txt", tid);
if(fs::fileExists(path))
{
fs::dataFile filter(path);
while(filter.readNextLine(false))
pathFilter.push_back(filter.getLine());
}
}
bool fs::pathIsFiltered(const std::string& _path)
{
if(pathFilter.empty())
return false;
for(std::string& _p : pathFilter)
{
if(_path == _p)
return true;
}
return false;
}
void fs::freePathFilters()
{
pathFilter.clear();
}
void fs::wipeSave()
{
ui::newThread(fs::wipesave_t, NULL, NULL);
}
void fs::dumpAllUserSaves()
{
//This is only really used for the progress bar
fs::copyArgs *send = fs::copyArgsCreate("", "", "", NULL, NULL, true, false, 0);
ui::newThread(fs::backupUserSaves_t, send, _fileDrawFunc);
}
void fs::getShowFileProps(const std::string& _path)
{
size_t size = fs::fsize(_path);
ui::showMessage(ui::getUICString("fileModeFileProperties", 0), _path.c_str(), util::getSizeString(size).c_str());
}
void fs::getShowDirProps(const std::string& _path)
{
fs::dirCountArgs *send = new fs::dirCountArgs;
send->path = _path;
send->origin = true;
ui::newThread(fs::getShowDirProps_t, send, NULL);
}
bool fs::fileExists(const std::string& path)
{
bool ret = false;
FILE *test = fopen(path.c_str(), "rb");
if(test != NULL)
ret = true;
fclose(test);
return ret;
}
size_t fs::fsize(const std::string& _f)
{
size_t ret = 0;
FILE *get = fopen(_f.c_str(), "rb");
if(get != NULL)
{
fseek(get, 0, SEEK_END);
ret = ftell(get);
}
fclose(get);
return ret;
}
std::string fs::getWorkDir() { return wd; }
void fs::setWorkDir(const std::string& _w){ wd = _w; }
bool fs::isDir(const std::string& _path)
{
struct stat s;
return stat(_path.c_str(), &s) == 0 && S_ISDIR(s.st_mode);
}
void fs::createNewBackup(void *a)
{
if(!fs::dirNotEmpty("sv:/"))
{
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popSaveIsEmpty", 0));
return;
}
uint64_t held = ui::padKeysHeld();
data::user *u = data::getCurrentUser();
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
data::titleInfo *t = data::getTitleInfoByTID(d->tid);
std::string out;
if(held & HidNpadButton_R || cfg::config["autoName"])
out = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD);
else if(held & HidNpadButton_L)
out = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YDM);
else if(held & HidNpadButton_ZL)
out = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_HOYSTE);
else
{
const std::string dict[] =
{
util::getDateTime(util::DATE_FMT_YMD),
util::getDateTime(util::DATE_FMT_YDM),
util::getDateTime(util::DATE_FMT_HOYSTE),
util::getDateTime(util::DATE_FMT_JHK),
util::getDateTime(util::DATE_FMT_ASC),
u->getUsernameSafe(),
t->safeTitle,
util::generateAbbrev(d->tid),
".zip"
};
std::string defaultText = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD);
out = util::getStringInput(SwkbdType_QWERTY, defaultText, ui::getUIString("swkbdEnterName", 0), 64, 9, dict);
}
if(!out.empty())
{
std::string ext = util::getExtensionFromString(out);
std::string path = util::generatePathByTID(d->tid) + out;
if(cfg::config["zip"] || ext == "zip")
{
if(ext != "zip")//data::zip is on but extension is not zip
path += ".zip";
zipFile zip = zipOpen64(path.c_str(), 0);
fs::copyDirToZip("sv:/", zip);
}
else
{
fs::mkDir(path);
path += "/";
fs::copyDirToDir("sv:/", path);
}
ui::populateFldMenu();
}
}
void fs::overwriteBackup(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::backupArgs *in = (fs::backupArgs *)t->argPtr;
ui::menu *m = in->m;
fs::dirList *d = in->d;
data::userTitleInfo *cd = data::getCurrentUserTitleInfo();
unsigned ind = m->getSelected() - 1;;//Skip new
std::string itemName = d->getItem(ind);
bool saveHasFiles = fs::dirNotEmpty("sv:/");
if(d->isDir(ind) && saveHasFiles)
{
std::string toPath = util::generatePathByTID(cd->tid) + itemName + "/";
//Delete and recreate
fs::delDir(toPath);
fs::mkDir(toPath);
fs::copyDirToDir("sv:/", toPath);
}
else if(!d->isDir(ind) && d->getItemExt(ind) == "zip" && saveHasFiles)
{
std::string toPath = util::generatePathByTID(cd->tid) + itemName;
fs::delfile(toPath);
zipFile zip = zipOpen64(toPath.c_str(), 0);
fs::copyDirToZip("sv:/", zip);
}
t->finished = true;
}
void fs::restoreBackup(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::backupArgs *in = (fs::backupArgs *)t->argPtr;
ui::menu *m = in->m;
fs::dirList *d = in->d;
data::user *u = data::getCurrentUser();
data::userTitleInfo *cd = data::getCurrentUserTitleInfo();
unsigned ind = m->getSelected() - 1;
std::string itemName = d->getItem(ind);
if((cd->saveInfo.save_data_type != FsSaveDataType_System || cfg::config["sysSaveWrite"]) && m->getSelected() > 0)
{
bool saveHasFiles = fs::dirNotEmpty("sv:/");
if(cfg::config["autoBack"] && cfg::config["zip"] && saveHasFiles)
{
std::string autoZip = util::generatePathByTID(cd->tid) + "/AUTO " + u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD) + ".zip";
zipFile zip = zipOpen64(autoZip.c_str(), 0);
fs::copyDirToZip("sv:/", zip);
}
else if(cfg::config["autoBack"] && saveHasFiles)
{
std::string autoFolder = util::generatePathByTID(cd->tid) + "/AUTO - " + u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD) + "/";
fs::mkDir(autoFolder.substr(0, autoFolder.length() - 1));
fs::copyDirToDir("sv:/", autoFolder);
}
if(d->isDir(ind))
{
std::string fromPath = util::generatePathByTID(cd->tid) + itemName + "/";
if(fs::dirNotEmpty(fromPath))
{
fs::wipeSave();
fs::copyDirToDirCommit(fromPath, "sv:/", "sv");
}
else
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popFolderIsEmpty", 0));
}
else if(!d->isDir(ind) && d->getItemExt(ind) == "zip")
{
std::string path = util::generatePathByTID(cd->tid) + itemName;
unzFile unz = unzOpen64(path.c_str());
if(unz && fs::zipNotEmpty(unz))
{
fs::wipeSave();
fs::copyZipToDir(unz, "sv:/", "sv");
}
else
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popZipIsEmpty", 0));
}
else
{
//Just copy file over
std::string fromPath = util::generatePathByTID(cd->tid) + itemName;
std::string toPath = "sv:/" + itemName;
fs::copyFileCommit(fromPath, toPath, "sv");
}
}
if(cfg::config["autoBack"])
ui::populateFldMenu();
t->finished = true;
}
void fs::deleteBackup(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::backupArgs *in = (fs::backupArgs *)t->argPtr;
ui::menu *m = in->m;
fs::dirList *d = in->d;
data::userTitleInfo *cd = data::getCurrentUserTitleInfo();
unsigned ind = m->getSelected() - 1;
std::string itemName = d->getItem(ind);
t->status->setStatus("Deleting...");
if(cfg::config["trashBin"])
{
data::userTitleInfo *getTID = data::getCurrentUserTitleInfo();
std::string oldPath = util::generatePathByTID(cd->tid) + itemName;
std::string trashPath = wd + "_TRASH_/" + data::getTitleSafeNameByTID(getTID->tid);
fs::mkDir(trashPath);
trashPath += "/" + itemName;
rename(oldPath.c_str(), trashPath.c_str());
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupMovedToTrash", 0), itemName.c_str());
}
else if(d->isDir(ind))
{
std::string delPath = util::generatePathByTID(cd->tid) + itemName + "/";
fs::delDir(delPath);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), itemName.c_str());
}
else
{
std::string delPath = util::generatePathByTID(cd->tid) + itemName;
fs::delfile(delPath);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), itemName.c_str());
}
ui::populateFldMenu();
t->finished = true;
}
void fs::logOpen()
{
std::string logPath = wd + "log.txt";
debLog = fsfopen(logPath.c_str(), FsOpenMode_Write);
fsfclose(debLog);
}
void fs::logWrite(const char *fmt, ...)
{
std::string logPath = wd + "log.txt";
debLog = fsfopen(logPath.c_str(), FsOpenMode_Append | FsOpenMode_Write);
char tmp[256];
va_list args;
va_start(args, fmt);
vsprintf(tmp, fmt, args);
va_end(args);
fsfwrite(tmp, 1, strlen(tmp), debLog);
fsfclose(debLog);
}

730
src/fs.cpp Normal file
View File

@ -0,0 +1,730 @@
#include <string>
#include <switch.h>
#include "cfg.h"
#include "fs.h"
#include "util.h"
static std::string wd = "sdmc:/JKSV/";
static FSFILE *debLog;
static FsFileSystem sv;
static std::vector<std::string> pathFilter;
void fs::init()
{
mkDirRec("sdmc:/config/JKSV/");
mkDirRec(wd);
mkdir(std::string(wd + "_TRASH_").c_str(), 777);
fs::logOpen();
}
bool fs::mountSave(const FsSaveDataInfo &_m)
{
Result svOpen;
FsSaveDataAttribute attr = {0};
switch (_m.save_data_type)
{
case FsSaveDataType_System:
case FsSaveDataType_SystemBcat:
{
attr.uid = _m.uid;
attr.system_save_data_id = _m.system_save_data_id;
attr.save_data_type = _m.save_data_type;
svOpen = fsOpenSaveDataFileSystemBySystemSaveDataId(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Account:
{
attr.uid = _m.uid;
attr.application_id = _m.application_id;
attr.save_data_type = _m.save_data_type;
attr.save_data_rank = _m.save_data_rank;
attr.save_data_index = _m.save_data_index;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Device:
{
attr.application_id = _m.application_id;
attr.save_data_type = FsSaveDataType_Device;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Bcat:
{
attr.application_id = _m.application_id;
attr.save_data_type = FsSaveDataType_Bcat;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Cache:
{
attr.application_id = _m.application_id;
attr.save_data_type = FsSaveDataType_Cache;
attr.save_data_index = _m.save_data_index;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
case FsSaveDataType_Temporary:
{
attr.application_id = _m.application_id;
attr.save_data_type = _m.save_data_type;
svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId)_m.save_data_space_id, &attr);
}
break;
default:
svOpen = 1;
break;
}
return R_SUCCEEDED(svOpen) && fsdevMountDevice("sv", sv) != -1;
}
bool fs::commitToDevice(const std::string &dev)
{
bool ret = true;
Result res = fsdevCommitDevice(dev.c_str());
if (R_FAILED(res))
{
fs::logWrite("Error committing file to device -> 0x%X\n", res);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popErrorCommittingFile", 0));
ret = false;
}
return ret;
}
std::string fs::getWorkDir()
{
return wd;
}
void fs::setWorkDir(const std::string &_w)
{
wd = _w;
}
void fs::loadPathFilters(uint64_t tid)
{
char path[256];
sprintf(path, "sdmc:/config/JKSV/0x%016lX_filter.txt", tid);
if (fs::fileExists(path))
{
fs::dataFile filter(path);
while (filter.readNextLine(false))
pathFilter.push_back(filter.getLine());
}
}
bool fs::pathIsFiltered(const std::string &_path)
{
if (pathFilter.empty())
return false;
for (std::string &_p : pathFilter)
{
if (_path == _p)
return true;
}
return false;
}
void fs::freePathFilters()
{
pathFilter.clear();
}
void fs::createSaveData(FsSaveDataType _type, uint64_t _tid, AccountUid _uid, threadInfo *t)
{
data::titleInfo *tinfo = data::getTitleInfoByTID(_tid);
if (t)
t->status->setStatus(ui::getUICString("threadStatusCreatingSaveData", 0), tinfo->title.c_str());
uint16_t cacheIndex = 0;
std::string indexStr;
if (_type == FsSaveDataType_Cache &&
!(indexStr = util::getStringInput(SwkbdType_NumPad, "0", ui::getUIString("swkbdSaveIndex", 0), 2, 0, NULL))
.empty())
cacheIndex = strtoul(indexStr.c_str(), NULL, 10);
else if (_type == FsSaveDataType_Cache && indexStr.empty())
{
if (t)
t->finished = true;
return;
}
FsSaveDataAttribute attr;
memset(&attr, 0, sizeof(FsSaveDataAttribute));
attr.application_id = _tid;
attr.uid = _uid;
attr.system_save_data_id = 0;
attr.save_data_type = _type;
attr.save_data_rank = 0;
attr.save_data_index = cacheIndex;
FsSaveDataCreationInfo crt;
memset(&crt, 0, sizeof(FsSaveDataCreationInfo));
int64_t saveSize = 0, journalSize = 0;
// Grab a pointer to the nacp of the title data.
NacpStruct *nacp = &tinfo->data.nacp;
switch (_type)
{
case FsSaveDataType_Account:
{
saveSize = nacp->user_account_save_data_size;
journalSize = nacp->user_account_save_data_journal_size;
}
break;
case FsSaveDataType_Device:
{
saveSize = nacp->device_save_data_size;
journalSize = nacp->device_save_data_journal_size;
}
break;
case FsSaveDataType_Bcat:
{
saveSize = nacp->bcat_delivery_cache_storage_size;
journalSize = nacp->bcat_delivery_cache_storage_size;
}
break;
case FsSaveDataType_Cache:
{
saveSize = 32 * 1024 * 1024;
if (nacp->cache_storage_journal_size > nacp->cache_storage_data_and_journal_size_max)
{
journalSize = nacp->cache_storage_journal_size;
}
else
{
journalSize = nacp->cache_storage_data_and_journal_size_max;
}
}
break;
default:
{
if (t)
t->finished = true;
return;
}
break;
}
crt.save_data_size = saveSize;
crt.journal_size = journalSize;
crt.available_size = 0x4000;
crt.owner_id = _type == FsSaveDataType_Bcat ? 0x010000000000000C : nacp->save_data_owner_id;
crt.flags = 0;
crt.save_data_space_id = FsSaveDataSpaceId_User;
FsSaveDataMetaInfo meta;
memset(&meta, 0, sizeof(FsSaveDataMetaInfo));
if (_type != FsSaveDataType_Bcat)
{
meta.size = 0x40060;
meta.type = FsSaveDataMetaType_Thumbnail;
}
Result res = 0;
if (R_SUCCEEDED(res = fsCreateSaveDataFileSystem(&attr, &crt, &meta)))
{
util::createTitleDirectoryByTID(_tid);
data::loadUsersTitles(false);
ui::ttlRefresh();
}
else
{
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataCreationFailed", 0));
fs::logWrite("SaveCreate Failed -> %X\n", res);
}
}
static void createSaveData_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::svCreateArgs *crt = (fs::svCreateArgs *)t->argPtr;
fs::createSaveData(crt->type, crt->tid, crt->account, t);
delete crt;
t->finished = true;
}
void fs::createSaveDataThreaded(FsSaveDataType _type, uint64_t _tid, AccountUid _uid)
{
fs::svCreateArgs *send = new fs::svCreateArgs;
send->type = _type;
send->tid = _tid;
send->account = _uid;
ui::newThread(createSaveData_t, send, NULL);
}
bool fs::extendSaveData(const data::userTitleInfo *tinfo, uint64_t extSize, threadInfo *t)
{
if (t)
t->status->setStatus(ui::getUICString("threadStatusExtendingSaveData", 0),
data::getTitleNameByTID(tinfo->tid).c_str());
uint64_t journal = fs::getJournalSizeMax(tinfo);
uint64_t saveID = tinfo->saveInfo.save_data_id;
FsSaveDataSpaceId space = (FsSaveDataSpaceId)tinfo->saveInfo.save_data_space_id;
Result res = 0;
if (R_FAILED((res = fsExtendSaveDataFileSystem(space, saveID, extSize, journal))))
{
int64_t totalSize = 0;
fs::mountSave(tinfo->saveInfo);
fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &totalSize);
fs::unmountSave();
fs::logWrite("Extend Failed: %uMB to %uMB -> %X\n", totalSize / 1024 / 1024, extSize / 1024 / 1024, res);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataExtendFailed", 0));
return false;
}
return true;
}
static void extendSaveData_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::svExtendArgs *e = (fs::svExtendArgs *)t->argPtr;
fs::extendSaveData(e->tinfo, e->extSize, t);
delete e;
t->finished = true;
}
void fs::extendSaveDataThreaded(const data::userTitleInfo *tinfo, uint64_t extSize)
{
fs::svExtendArgs *send = new fs::svExtendArgs;
send->tinfo = tinfo;
send->extSize = extSize;
ui::newThread(extendSaveData_t, send, NULL);
}
uint64_t fs::getJournalSize(const data::userTitleInfo *tinfo)
{
uint64_t ret = 0;
data::titleInfo *t = data::getTitleInfoByTID(tinfo->tid);
// NACP pointer.
NacpStruct *nacp = &t->data.nacp;
switch (tinfo->saveInfo.save_data_type)
{
case FsSaveDataType_Account:
{
ret = nacp->user_account_save_data_journal_size;
}
break;
case FsSaveDataType_Device:
{
ret = nacp->device_save_data_journal_size;
}
break;
case FsSaveDataType_Bcat:
{
ret = nacp->bcat_delivery_cache_storage_size;
}
break;
case FsSaveDataType_Cache:
{
if (nacp->cache_storage_journal_size > 0)
{
ret = nacp->cache_storage_journal_size;
}
else
{
ret = nacp->cache_storage_data_and_journal_size_max;
}
}
break;
default:
{
ret = BUFF_SIZE;
}
break;
}
return ret;
}
uint64_t fs::getJournalSizeMax(const data::userTitleInfo *tinfo)
{
uint64_t ret = 0;
data::titleInfo *extend = data::getTitleInfoByTID(tinfo->tid);
// NACP pointer
NacpStruct *nacp = &extend->data.nacp;
switch (tinfo->saveInfo.save_data_type)
{
case FsSaveDataType_Account:
{
if (nacp->user_account_save_data_journal_size_max > nacp->user_account_save_data_journal_size)
ret = nacp->user_account_save_data_journal_size_max;
else
ret = nacp->user_account_save_data_journal_size;
}
break;
case FsSaveDataType_Bcat:
{
ret = nacp->bcat_delivery_cache_storage_size;
}
break;
case FsSaveDataType_Cache:
{
if (nacp->cache_storage_data_and_journal_size_max > nacp->cache_storage_journal_size)
ret = nacp->cache_storage_data_and_journal_size_max;
else
ret = nacp->cache_storage_journal_size;
}
break;
case FsSaveDataType_Device:
{
if (nacp->device_save_data_journal_size_max > nacp->device_save_data_journal_size)
ret = nacp->device_save_data_journal_size_max;
else
ret = nacp->device_save_data_journal_size;
}
break;
default:
{
//will just fail
ret = 0;
}
break;
}
return ret;
}
static void wipeSave_t(void *a)
{
threadInfo *t = (threadInfo *)a;
t->status->setStatus(ui::getUICString("threadStatusResettingSaveData", 0));
fs::delDir("sv:/");
fs::commitToDevice("sv");
t->finished = true;
}
void fs::wipeSave()
{
ui::newThread(wipeSave_t, NULL, NULL);
}
void fs::createNewBackup(void *a)
{
if (!fs::dirNotEmpty("sv:/"))
{
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popSaveIsEmpty", 0));
return;
}
uint64_t held = ui::padKeysHeld();
data::user *u = data::getCurrentUser();
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
data::titleInfo *t = data::getTitleInfoByTID(d->tid);
std::string out;
if (held & HidNpadButton_R || cfg::config["autoName"])
out = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD);
else if (held & HidNpadButton_L)
out = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YDM);
else if (held & HidNpadButton_ZL)
out = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_HOYSTE);
else
{
const std::string dict[] = {util::getDateTime(util::DATE_FMT_YMD),
util::getDateTime(util::DATE_FMT_YDM),
util::getDateTime(util::DATE_FMT_HOYSTE),
util::getDateTime(util::DATE_FMT_JHK),
util::getDateTime(util::DATE_FMT_ASC),
u->getUsernameSafe(),
t->safeTitle,
util::generateAbbrev(d->tid),
".zip"};
std::string defaultText = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD);
out = util::getStringInput(SwkbdType_QWERTY, defaultText, ui::getUIString("swkbdEnterName", 0), 64, 9, dict);
out = util::safeString(out);
}
if (!out.empty())
{
std::string ext = util::getExtensionFromString(out);
std::string path = util::generatePathByTID(d->tid) + out;
if (cfg::config["zip"] || ext == "zip")
{
if (ext != "zip") //data::zip is on but extension is not zip
path += ".zip";
zipFile zip = zipOpen64(path.c_str(), 0);
fs::copyDirToZipThreaded("sv:/", zip, false, 0);
}
else
{
fs::mkDir(path);
path += "/";
fs::copyDirToDirThreaded("sv:/", path);
}
ui::fldRefreshMenu();
}
}
void fs::overwriteBackup(void *a)
{
threadInfo *t = (threadInfo *)a;
std::string *dst = (std::string *)t->argPtr;
bool saveHasFiles = fs::dirNotEmpty("sv:/");
if (fs::isDir(*dst) && saveHasFiles)
{
fs::delDir(*dst);
fs::mkDir(*dst);
dst->append("/");
fs::copyDirToDirThreaded("sv:/", *dst);
}
else if (!fs::isDir(*dst) && util::getExtensionFromString(*dst) == "zip" && saveHasFiles)
{
fs::delfile(*dst);
zipFile zip = zipOpen64(dst->c_str(), 0);
fs::copyDirToZipThreaded("sv:/", zip, false, 0);
}
delete dst;
t->finished = true;
}
void fs::restoreBackup(void *a)
{
threadInfo *t = (threadInfo *)a;
std::string *restore = (std::string *)t->argPtr;
data::user *u = data::getCurrentUser();
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
if ((utinfo->saveInfo.save_data_type != FsSaveDataType_System || cfg::config["sysSaveWrite"]))
{
bool saveHasFiles = fs::dirNotEmpty("sv:/");
if (cfg::config["autoBack"] && cfg::config["zip"] && saveHasFiles)
{
std::string autoZip = util::generatePathByTID(utinfo->tid) + "/AUTO " + u->getUsernameSafe() + " - " +
util::getDateTime(util::DATE_FMT_YMD) + ".zip";
zipFile zip = zipOpen64(autoZip.c_str(), 0);
fs::copyDirToZipThreaded("sv:/", zip, false, 0);
}
else if (cfg::config["autoBack"] && saveHasFiles)
{
std::string autoFolder = util::generatePathByTID(utinfo->tid) + "/AUTO - " + u->getUsernameSafe() + " - " +
util::getDateTime(util::DATE_FMT_YMD) + "/";
fs::mkDir(autoFolder.substr(0, autoFolder.length() - 1));
fs::copyDirToDirThreaded("sv:/", autoFolder);
}
if (fs::isDir(*restore))
{
restore->append("/");
if (fs::dirNotEmpty(*restore))
{
t->status->setStatus(ui::getUICString("threadStatusCalculatingSaveSize", 0));
unsigned dirCount = 0, fileCount = 0;
uint64_t saveSize = 0;
int64_t availSize = 0;
fs::getDirProps(*restore, dirCount, fileCount, saveSize);
fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &availSize);
if ((int)saveSize > availSize)
{
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
fs::unmountSave();
fs::extendSaveData(utinfo, saveSize + 0x500000, t);
fs::mountSave(utinfo->saveInfo);
}
fs::wipeSave();
fs::copyDirToDirCommitThreaded(*restore, "sv:/", "sv");
}
else
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popFolderIsEmpty", 0));
}
else if (!fs::isDir(*restore) && util::getExtensionFromString(*restore) == "zip")
{
unzFile unz = unzOpen64(restore->c_str());
if (unz && fs::zipNotEmpty(unz))
{
t->status->setStatus(ui::getUICString("threadStatusCalculatingSaveSize", 0));
uint64_t saveSize = fs::getZipTotalSize(unz);
int64_t availSize = 0;
fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &availSize);
if ((int)saveSize > availSize)
{
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
fs::unmountSave();
fs::extendSaveData(utinfo, saveSize + 0x500000, t);
fs::mountSave(utinfo->saveInfo);
}
fs::wipeSave();
fs::copyZipToDirThreaded(unz, "sv:/", "sv");
}
else
{
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popZipIsEmpty", 0));
unzClose(unz);
}
}
else
{
std::string dstPath = "sv:/" + util::getFilenameFromPath(*restore);
fs::copyFileCommitThreaded(*restore, dstPath, "sv");
}
}
if (cfg::config["autoBack"])
ui::fldRefreshMenu();
delete restore;
t->finished = true;
}
void fs::deleteBackup(void *a)
{
threadInfo *t = (threadInfo *)a;
std::string *deletePath = (std::string *)t->argPtr;
std::string backupName = util::getFilenameFromPath(*deletePath);
t->status->setStatus(ui::getUICString("threadStatusDeletingFile", 0));
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
if (cfg::config["trashBin"])
{
std::string oldPath = *deletePath;
std::string trashPath = wd + "_TRASH_/" + data::getTitleSafeNameByTID(utinfo->tid);
fs::mkDir(trashPath);
trashPath += "/" + backupName;
rename(oldPath.c_str(), trashPath.c_str());
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupMovedToTrash", 0), backupName.c_str());
}
else if (fs::isDir(*deletePath))
{
*deletePath += "/";
fs::delDir(*deletePath);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), backupName.c_str());
}
else
{
fs::delfile(*deletePath);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), backupName.c_str());
}
ui::fldRefreshMenu();
delete deletePath;
t->finished = true;
}
void fs::dumpAllUserSaves(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *c = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0);
t->argPtr = c;
data::user *u = data::getCurrentUser();
for (unsigned i = 0; i < u->titleInfo.size(); i++)
{
bool saveMounted = fs::mountSave(u->titleInfo[i].saveInfo);
util::createTitleDirectoryByTID(u->titleInfo[i].tid);
if (saveMounted && fs::dirNotEmpty("sv:/") && cfg::config["zip"])
{
fs::loadPathFilters(u->titleInfo[i].tid);
std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " +
util::getDateTime(util::DATE_FMT_YMD) + ".zip";
zipFile zip = zipOpen64(dst.c_str(), 0);
fs::copyDirToZip("sv:/", zip, false, 0, t);
zipClose(zip, NULL);
fs::freePathFilters();
}
else if (saveMounted && fs::dirNotEmpty("sv:/"))
{
fs::loadPathFilters(u->titleInfo[i].tid);
std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " +
util::getDateTime(util::DATE_FMT_YMD) + "/";
fs::mkDir(dst.substr(0, dst.length() - 1));
fs::copyDirToDir("sv:/", dst, t);
fs::freePathFilters();
}
fs::unmountSave();
}
fs::copyArgsDestroy(c);
t->finished = true;
}
void fs::dumpAllUsersAllSaves(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *c = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0);
t->argPtr = c;
unsigned curUser = 0;
while (data::users[curUser].getUID128() != 2)
{
data::user *u = &data::users[curUser++];
for (unsigned i = 0; i < u->titleInfo.size(); i++)
{
bool saveMounted = fs::mountSave(u->titleInfo[i].saveInfo);
util::createTitleDirectoryByTID(u->titleInfo[i].tid);
if (saveMounted && fs::dirNotEmpty("sv:/") && cfg::config["zip"])
{
fs::loadPathFilters(u->titleInfo[i].tid);
std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " +
util::getDateTime(util::DATE_FMT_YMD) + ".zip";
zipFile zip = zipOpen64(dst.c_str(), 0);
fs::copyDirToZip("sv:/", zip, false, 0, t);
zipClose(zip, NULL);
fs::freePathFilters();
}
else if (saveMounted && fs::dirNotEmpty("sv:/"))
{
fs::loadPathFilters(u->titleInfo[i].tid);
std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " +
util::getDateTime(util::DATE_FMT_YMD) + "/";
fs::mkDir(dst.substr(0, dst.length() - 1));
fs::copyDirToDir("sv:/", dst, t);
fs::freePathFilters();
}
fs::unmountSave();
}
}
fs::copyArgsDestroy(c);
t->finished = true;
}
void fs::logOpen()
{
std::string logPath = wd + "log.txt";
debLog = fsfopen(logPath.c_str(), FsOpenMode_Write);
fsfclose(debLog);
}
void fs::logWrite(const char *fmt, ...)
{
std::string logPath = wd + "log.txt";
debLog = fsfopen(logPath.c_str(), FsOpenMode_Append | FsOpenMode_Write);
char tmp[256];
va_list args;
va_start(args, fmt);
vsprintf(tmp, fmt, args);
va_end(args);
fsfwrite(tmp, 1, strlen(tmp), debLog);
fsfclose(debLog);
}

270
src/fs/dir.cpp Normal file
View File

@ -0,0 +1,270 @@
#include <switch.h>
#include <algorithm>
#include "fs.h"
#include "cfg.h"
#include "util.h"
static struct
{
bool operator()(const fs::dirItem& a, const fs::dirItem& b)
{
if(a.isDir() != b.isDir())
return a.isDir();
for(unsigned i = 0; i < a.getItm().length(); i++)
{
char charA = tolower(a.getItm()[i]);
char charB = tolower(b.getItm()[i]);
if(charA != charB)
return charA < charB;
}
return false;
}
} sortDirList;
void fs::mkDir(const std::string& _p)
{
if(cfg::config["directFsCmd"])
fsMkDir(_p.c_str());
else
mkdir(_p.c_str(), 777);
}
void fs::mkDirRec(const std::string& _p)
{
//skip first slash
size_t pos = _p.find('/', 0) + 1;
while((pos = _p.find('/', pos)) != _p.npos)
{
fs::mkDir(_p.substr(0, pos).c_str());
++pos;
}
}
void fs::delDir(const std::string& path)
{
dirList list(path);
for(unsigned i = 0; i < list.getCount(); i++)
{
if(pathIsFiltered(path + list.getItem(i)))
continue;
if(list.isDir(i))
{
std::string newPath = path + list.getItem(i) + "/";
delDir(newPath);
std::string delPath = path + list.getItem(i);
rmdir(delPath.c_str());
}
else
{
std::string delPath = path + list.getItem(i);
std::remove(delPath.c_str());
}
}
rmdir(path.c_str());
}
bool fs::dirNotEmpty(const std::string& _dir)
{
fs::dirList tmp(_dir);
return tmp.getCount() > 0;
}
bool fs::isDir(const std::string& _path)
{
struct stat s;
return stat(_path.c_str(), &s) == 0 && S_ISDIR(s.st_mode);
}
void fs::copyDirToDir(const std::string& src, const std::string& dst, threadInfo *t)
{
if(t)
t->status->setStatus(ui::getUICString("threadStatusOpeningFolder", 0), src.c_str());
fs::dirList *list = new fs::dirList(src);
for(unsigned i = 0; i < list->getCount(); i++)
{
if(pathIsFiltered(src + list->getItem(i)))
continue;
if(list->isDir(i))
{
std::string newSrc = src + list->getItem(i) + "/";
std::string newDst = dst + list->getItem(i) + "/";
fs::mkDir(newDst.substr(0, newDst.length() - 1));
fs::copyDirToDir(newSrc, newDst, t);
}
else
{
std::string fullSrc = src + list->getItem(i);
std::string fullDst = dst + list->getItem(i);
if(t)
t->status->setStatus(ui::getUICString("threadStatusCopyingFile", 0), fullSrc.c_str());
fs::copyFile(fullSrc, fullDst, t);
}
}
delete list;
}
static void copyDirToDir_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
fs::copyDirToDir(in->src, in->dst, t);
if(in->cleanup)
fs::copyArgsDestroy(in);
t->finished = true;
}
void fs::copyDirToDirThreaded(const std::string& src, const std::string& dst)
{
fs::copyArgs *send = fs::copyArgsCreate(src, dst, "", NULL, NULL, true, false, 0);
ui::newThread(copyDirToDir_t, send, fs::fileDrawFunc);
}
void fs::copyDirToDirCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t)
{
if(t)
t->status->setStatus(ui::getUICString("threadStatusOpeningFolder", 0), src.c_str());
fs::dirList *list = new fs::dirList(src);
for(unsigned i = 0; i < list->getCount(); i++)
{
if(pathIsFiltered(src + list->getItem(i)))
continue;
if(list->isDir(i))
{
std::string newSrc = src + list->getItem(i) + "/";
std::string newDst = dst + list->getItem(i) + "/";
fs::mkDir(newDst.substr(0, newDst.length() - 1));
fs::copyDirToDirCommit(newSrc, newDst, dev, t);
}
else
{
std::string fullSrc = src + list->getItem(i);
std::string fullDst = dst + list->getItem(i);
if(t)
t->status->setStatus(ui::getUICString("threadStatusCopyingFile", 0), fullSrc.c_str());
fs::copyFileCommit(fullSrc, fullDst, dev, t);
}
}
delete list;
}
static void copyDirToDirCommit_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
fs::copyDirToDirCommit(in->src, in->dst, in->dev, t);
if(in->cleanup)
fs::copyArgsDestroy(in);
t->finished = true;
}
void fs::copyDirToDirCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev)
{
fs::copyArgs *send = fs::copyArgsCreate(src, dst, dev, NULL, NULL, true, false, 0);
ui::newThread(copyDirToDirCommit_t, send, fs::fileDrawFunc);
}
void fs::getDirProps(const std::string& path, unsigned& dirCount, unsigned& fileCount, uint64_t& totalSize)
{
fs::dirList *d = new fs::dirList(path);
for(unsigned i = 0; i < d->getCount(); i++)
{
if(d->isDir(i))
{
++dirCount;
std::string newPath = path + d->getItem(i) + "/";
fs::getDirProps(newPath, dirCount, fileCount, totalSize);
}
else
{
++fileCount;
std::string filePath = path + d->getItem(i);
totalSize += fs::fsize(filePath);
}
}
delete d;
}
fs::dirItem::dirItem(const std::string& pathTo, const std::string& sItem)
{
itm = sItem;
std::string fullPath = pathTo + sItem;
struct stat s;
if(stat(fullPath.c_str(), &s) == 0 && S_ISDIR(s.st_mode))
dir = true;
}
std::string fs::dirItem::getName() const
{
size_t extPos = itm.find_last_of('.'), slPos = itm.find_last_of('/');
if(extPos == itm.npos)
return "";
return itm.substr(slPos + 1, extPos);
}
std::string fs::dirItem::getExt() const
{
return util::getExtensionFromString(itm);
}
fs::dirList::dirList(const std::string& _path, bool ignoreDotFiles)
{
DIR *d;
struct dirent *ent;
path = _path;
d = opendir(path.c_str());
while((ent = readdir(d)))
if (!ignoreDotFiles || ent->d_name[0] != '.')
item.emplace_back(path, ent->d_name);
closedir(d);
std::sort(item.begin(), item.end(), sortDirList);
}
void fs::dirList::reassign(const std::string& _path)
{
DIR *d;
struct dirent *ent;
path = _path;
d = opendir(path.c_str());
item.clear();
while((ent = readdir(d)))
item.emplace_back(path, ent->d_name);
closedir(d);
std::sort(item.begin(), item.end(), sortDirList);
}
void fs::dirList::rescan()
{
item.clear();
DIR *d;
struct dirent *ent;
d = opendir(path.c_str());
while((ent = readdir(d)))
item.emplace_back(path, ent->d_name);
closedir(d);
std::sort(item.begin(), item.end(), sortDirList);
}

395
src/fs/file.cpp Normal file
View File

@ -0,0 +1,395 @@
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <switch.h>
#include <unistd.h>
#include <cstdarg>
#include <mutex>
#include <condition_variable>
#include <sys/stat.h>
#include "fs.h"
#include "util.h"
#include "ui.h"
#include "gfx.h"
#include "data.h"
#include "cfg.h"
static std::string wd = "sdmc:/JKSV/";
typedef struct
{
std::mutex bufferLock;
std::condition_variable cond;
std::vector<uint8_t> sharedBuffer;
std::string dst, dev;
bool bufferIsFull = false;
unsigned int filesize = 0, writeLimit = 0;
} fileCpyThreadArgs;
static void writeFile_t(void *a)
{
fileCpyThreadArgs *in = (fileCpyThreadArgs *)a;
size_t written = 0;
std::vector<uint8_t> localBuffer;
FILE *out = fopen(in->dst.c_str(), "wb");
while(written < in->filesize)
{
std::unique_lock<std::mutex> buffLock(in->bufferLock);
in->cond.wait(buffLock, [in]{ return in->bufferIsFull;});
localBuffer.clear();
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
in->sharedBuffer.clear();
in->bufferIsFull = false;
buffLock.unlock();
in->cond.notify_one();
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
}
fclose(out);
}
static void writeFileCommit_t(void *a)
{
fileCpyThreadArgs *in = (fileCpyThreadArgs *)a;
size_t written = 0, journalCount = 0;
std::vector<uint8_t> localBuffer;
FILE *out = fopen(in->dst.c_str(), "wb");
while(written < in->filesize)
{
std::unique_lock<std::mutex> buffLock(in->bufferLock);
in->cond.wait(buffLock, [in]{ return in->bufferIsFull; });
localBuffer.clear();
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
in->sharedBuffer.clear();
in->bufferIsFull = false;
buffLock.unlock();
in->cond.notify_one();
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
journalCount += written;
if(journalCount >= in->writeLimit)
{
journalCount = 0;
fclose(out);
fs::commitToDevice(in->dev.c_str());
out = fopen(in->dst.c_str(), "ab");
}
}
fclose(out);
}
fs::copyArgs *fs::copyArgsCreate(const std::string& src, const std::string& dst, const std::string& dev, zipFile z, unzFile unz, bool _cleanup, bool _trimZipPath, uint8_t _trimPlaces)
{
copyArgs *ret = new copyArgs;
ret->src = src;
ret->dst = dst;
ret->dev = dev;
ret->z = z;
ret->unz = unz;
ret->cleanup = _cleanup;
ret->prog = new ui::progBar;
ret->prog->setMax(0);
ret->prog->update(0);
ret->offset = 0;
ret->trimZipPath = _trimZipPath;
ret->trimZipPlaces = _trimPlaces;
return ret;
}
void fs::copyArgsDestroy(copyArgs *c)
{
delete c->prog;
delete c;
c = NULL;
}
fs::dataFile::dataFile(const std::string& _path)
{
f = fopen(_path.c_str(), "r");
if(f != NULL)
opened = true;
}
fs::dataFile::~dataFile()
{
fclose(f);
}
bool fs::dataFile::readNextLine(bool proc)
{
bool ret = false;
char tmp[1024];
while(fgets(tmp, 1024, f))
{
if(tmp[0] != '#' && tmp[0] != '\n' && tmp[0] != '\r')
{
line = tmp;
ret = true;
break;
}
}
util::stripChar('\n', line);
util::stripChar('\r', line);
if(proc)
procLine();
return ret;
}
void fs::dataFile::procLine()
{
size_t pPos = line.find_first_of("(=,");
if(pPos != line.npos)
{
lPos = pPos;
name.assign(line.begin(), line.begin() + lPos);
}
else
name = line;
util::stripChar(' ', name);
++lPos;
}
std::string fs::dataFile::getNextValueStr()
{
std::string ret = "";
//Skip all spaces until we hit actual text
size_t pos1 = line.find_first_not_of(", ", lPos);
//If reading from quotes
if(line[pos1] == '"')
lPos = line.find_first_of('"', ++pos1);
else
lPos = line.find_first_of(",;\n", pos1);//Set lPos to end of string we want. This should just set lPos to the end of the line if it fails, which is ok
ret = line.substr(pos1, lPos++ - pos1);
util::replaceStr(ret, "\\n", "\n");
return ret;
}
int fs::dataFile::getNextValueInt()
{
int ret = 0;
std::string no = getNextValueStr();
if(no[0] == '0' && tolower(no[1]) == 'x')
ret = strtoul(no.c_str(), NULL, 16);
else
ret = strtoul(no.c_str(), NULL, 10);
return ret;
}
void fs::copyFile(const std::string& src, const std::string& dst, threadInfo *t)
{
fs::copyArgs *c = NULL;
size_t filesize = fs::fsize(src);
if(t)
{
c = (fs::copyArgs *)t->argPtr;
c->offset = 0;
c->prog->setMax(filesize);
c->prog->update(0);
}
FILE *fsrc = fopen(src.c_str(), "rb");
if(!fsrc)
{
fclose(fsrc);
return;
}
fileCpyThreadArgs thrdArgs;
thrdArgs.dst = dst;
thrdArgs.filesize = filesize;
uint8_t *buff = new uint8_t[BUFF_SIZE];
std::vector<uint8_t> transferBuffer;
Thread writeThread;
threadCreate(&writeThread, writeFile_t, &thrdArgs, NULL, 0x40000, 0x2E, 2);
threadStart(&writeThread);
size_t readIn = 0;
uint64_t readCount = 0;
while((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0)
{
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
readCount += readIn;
if(c)
c->offset = readCount;
if(transferBuffer.size() >= TRANSFER_BUFFER_LIMIT || readCount == filesize)
{
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
thrdArgs.cond.wait(buffLock, [&thrdArgs]{ return thrdArgs.bufferIsFull == false; });
thrdArgs.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end());
transferBuffer.clear();
thrdArgs.bufferIsFull = true;
buffLock.unlock();
thrdArgs.cond.notify_one();
}
}
threadWaitForExit(&writeThread);
threadClose(&writeThread);
fclose(fsrc);
delete[] buff;
}
static void copyFileThreaded_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
t->status->setStatus(ui::getUICString("threadStatusCopyingFile", 0), in->src.c_str());
fs::copyFile(in->src, in->dst, t);
if(in->cleanup)
fs::copyArgsDestroy(in);
t->finished = true;
}
void fs::copyFileThreaded(const std::string& src, const std::string& dst)
{
fs::copyArgs *send = fs::copyArgsCreate(src, dst, "", NULL, NULL, true, false, 0);
ui::newThread(copyFileThreaded_t, send, fs::fileDrawFunc);
}
void fs::copyFileCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t)
{
fs::copyArgs *c = NULL;
size_t filesize = fs::fsize(src);
if(t)
{
c = (fs::copyArgs *)t->argPtr;
c->offset = 0;
c->prog->setMax(filesize);
c->prog->update(0);
}
FILE *fsrc = fopen(src.c_str(), "rb");
if(!fsrc)
{
fclose(fsrc);
return;
}
fileCpyThreadArgs thrdArgs;
thrdArgs.dst = dst;
thrdArgs.dev = dev;
thrdArgs.filesize = filesize;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
uint64_t journalSpace = fs::getJournalSize(utinfo);
thrdArgs.writeLimit = (journalSpace - 0x100000) < TRANSFER_BUFFER_LIMIT ? journalSpace - 0x100000 : TRANSFER_BUFFER_LIMIT;
Thread writeThread;
threadCreate(&writeThread, writeFileCommit_t, &thrdArgs, NULL, 0x040000, 0x2E, 2);
uint8_t *buff = new uint8_t[BUFF_SIZE];
size_t readIn = 0;
uint64_t readCount = 0;
std::vector<uint8_t> transferBuffer;
threadStart(&writeThread);
while((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0)
{
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
readCount += readIn;
if(c)
c->offset = readCount;
if(transferBuffer.size() >= thrdArgs.writeLimit || readCount == filesize)
{
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
thrdArgs.cond.wait(buffLock, [&thrdArgs]{ return thrdArgs.bufferIsFull == false; });
thrdArgs.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end());
transferBuffer.clear();
thrdArgs.bufferIsFull = true;
buffLock.unlock();
thrdArgs.cond.notify_one();
}
}
threadWaitForExit(&writeThread);
threadClose(&writeThread);
fclose(fsrc);
fs::commitToDevice(dev);
delete[] buff;
}
static void copyFileCommit_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
t->status->setStatus(ui::getUICString("threadStatusCopyingFile", 0), in->src.c_str());
in->prog->setMax(fs::fsize(in->src));
in->prog->update(0);
fs::copyFileCommit(in->src, in->dst, in->dev, t);
if(in->cleanup)
fs::copyArgsDestroy(in);
t->finished = true;
}
void fs::copyFileCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev)
{
fs::copyArgs *send = fs::copyArgsCreate(src, dst, dev, NULL, NULL, true, false, 0);
ui::newThread(copyFileCommit_t, send, fs::fileDrawFunc);
}
void fs::fileDrawFunc(void *a)
{
threadInfo *t = (threadInfo *)a;
if(!t->finished && t->argPtr)
{
copyArgs *c = (copyArgs *)t->argPtr;
std::string tmp;
t->status->getStatus(tmp);
c->argLock();
c->prog->update(c->offset);
c->prog->draw(tmp);
c->argUnlock();
}
}
void fs::delfile(const std::string& path)
{
if(cfg::config["directFsCmd"])
fsremove(path.c_str());
else
remove(path.c_str());
}
void fs::getShowFileProps(const std::string& _path)
{
size_t size = fs::fsize(_path);
ui::showMessage(ui::getUICString("fileModeFileProperties", 0), _path.c_str(), util::getSizeString(size).c_str());
}
bool fs::fileExists(const std::string& path)
{
bool ret = false;
FILE *test = fopen(path.c_str(), "rb");
if(test != NULL)
ret = true;
fclose(test);
return ret;
}
size_t fs::fsize(const std::string& _f)
{
size_t ret = 0;
FILE *get = fopen(_f.c_str(), "rb");
if(get != NULL)
{
fseek(get, 0, SEEK_END);
ret = ftell(get);
}
fclose(get);
return ret;
}

View File

@ -3,7 +3,7 @@
#include <stdint.h>
#include <malloc.h>
#include "fsfile.h"
#include "fs/fsfile.h"
char *getDeviceFromPath(char *dev, size_t _max, const char *path)
{

134
src/fs/remote.cpp Normal file
View File

@ -0,0 +1,134 @@
#include "fs.h"
#include "rfs.h"
#include "gd.h"
#include "webdav.h"
#include "cfg.h"
#include "ui.h"
rfs::IRemoteFS *fs::rfs = NULL;
std::string fs::rfsRootID;
void fs::remoteInit()
{
// Google Drive has priority
driveInit();
webDavInit();
}
void fs::remoteExit()
{
if(rfs) {
delete rfs;
rfs = NULL;
}
}
void fs::driveInit()
{
// Already initialized?
if (rfs)
return;
if(cfg::driveClientID.empty() || cfg::driveClientSecret.empty())
return;
bool refreshed = false, exchanged = false;
drive::gd *gDrive = new drive::gd;
gDrive->setClientID(cfg::driveClientID);
gDrive->setClientSecret(cfg::driveClientSecret);
if(!cfg::driveRefreshToken.empty())
{
gDrive->setRefreshToken(cfg::driveRefreshToken);
refreshed = gDrive->refreshToken();
}
if(!refreshed)
{
std::string authCode = driveSignInGetAuthCode();
exchanged = gDrive->exhangeAuthCode(authCode);
}
if(gDrive->hasToken())
{
if(exchanged)
{
cfg::driveRefreshToken = gDrive->getRefreshToken();
cfg::saveConfig();
}
gDrive->driveListInit("");
if(!gDrive->dirExists(JKSV_DRIVE_FOLDER))
gDrive->createDir(JKSV_DRIVE_FOLDER, "");
rfsRootID = gDrive->getDirID(JKSV_DRIVE_FOLDER);
rfs = gDrive;
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popDriveStarted", 0));
}
else
{
delete gDrive;
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popDriveFailed", 0));
}
}
std::string fs::driveSignInGetAuthCode()
{
std::string url = "https://accounts.google.com/o/oauth2/v2/auth?client_id=" + cfg::driveClientID + "&redirect_uri=urn:ietf:wg:oauth:2.0:oob:auto&response_type=code&scope=https://www.googleapis.com/auth/drive";
std::string replyURL;
WebCommonConfig webCfg;
WebCommonReply webReply;
webPageCreate(&webCfg, url.c_str());
webConfigSetCallbackUrl(&webCfg, "https://accounts.google.com/o/oauth2/approval/");
webConfigShow(&webCfg, &webReply);
size_t rLength = 0;
char replyURLCstr[0x1000];
webReplyGetLastUrl(&webReply, replyURLCstr, 0x1000, &rLength);
//Prevent crash if empty.
if(strlen(replyURLCstr) == 0)
return "";
replyURL.assign(replyURLCstr);
int unescLength = 0;
size_t codeBegin = replyURL.find("approvalCode") + 13, codeEnd = replyURL.find_last_of('#');
size_t codeLength = codeEnd - codeBegin;
replyURL = replyURL.substr(codeBegin, codeLength);
char *urlUnesc = curl_easy_unescape(NULL, replyURL.c_str(), replyURL.length(), &unescLength);
replyURL = urlUnesc;
curl_free(urlUnesc);
//Finally
return replyURL;
}
void fs::webDavInit() {
// Already initialized?
if (rfs)
return;
if (cfg::webdavOrigin.empty())
return;
rfs::WebDav *webdav = new rfs::WebDav(cfg::webdavOrigin,
cfg::webdavUser,
cfg::webdavPassword);
std::string baseId = "/" + cfg::webdavBasePath + (cfg::webdavBasePath.empty() ? "" : "/");
rfsRootID = webdav->getDirID(JKSV_DRIVE_FOLDER, baseId);
// check access
if (!webdav->dirExists(JKSV_DRIVE_FOLDER, baseId)) // this could return false on auth/config related errors
{
if (!webdav->createDir(JKSV_DRIVE_FOLDER, baseId))
{
delete webdav;
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popWebdavFailed", 0));
return;
}
}
rfs = webdav;
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popWebdavStarted", 0));
}

256
src/fs/zip.cpp Normal file
View File

@ -0,0 +1,256 @@
#include <switch.h>
#include <time.h>
#include <mutex>
#include <vector>
#include <condition_variable>
#include "fs.h"
#include "util.h"
#include "cfg.h"
typedef struct
{
std::mutex buffLock;
std::condition_variable cond;
std::vector<uint8_t> sharedBuffer;
std::string dst, dev;
bool bufferIsFull = false;
unzFile unz;
unsigned int fileSize, writeLimit = 0;
} unzThrdArgs;
static void writeFileFromZip_t(void *a)
{
unzThrdArgs *in = (unzThrdArgs *)a;
std::vector<uint8_t> localBuffer;
unsigned int written = 0, journalCount = 0;
FILE *out = fopen(in->dst.c_str(), "wb");
while(written < in->fileSize)
{
std::unique_lock<std::mutex> buffLock(in->buffLock);
in->cond.wait(buffLock, [in]{ return in->bufferIsFull; });
localBuffer.clear();
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
in->sharedBuffer.clear();
in->bufferIsFull = false;
buffLock.unlock();
in->cond.notify_one();
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
journalCount += written;
if(journalCount >= in->writeLimit)
{
journalCount = 0;
fclose(out);
fs::commitToDevice(in->dev);
out = fopen(in->dst.c_str(), "ab");
}
}
fclose(out);
}
void fs::copyDirToZip(const std::string& src, zipFile dst, bool trimPath, int trimPlaces, threadInfo *t)
{
fs::copyArgs *c = NULL;
if(t)
{
t->status->setStatus(ui::getUICString("threadStatusOpeningFolder", 0), src.c_str());
c = (fs::copyArgs *)t->argPtr;
}
fs::dirList *list = new fs::dirList(src);
for(unsigned i = 0; i < list->getCount(); i++)
{
std::string itm = list->getItem(i);
if(fs::pathIsFiltered(src + itm))
continue;
if(list->isDir(i))
{
std::string newSrc = src + itm + "/";
fs::copyDirToZip(newSrc, dst, trimPath, trimPlaces, t);
}
else
{
time_t raw;
time(&raw);
tm *locTime = localtime(&raw);
zip_fileinfo inf = { locTime->tm_sec, locTime->tm_min, locTime->tm_hour,
locTime->tm_mday, locTime->tm_mon, (1900 + locTime->tm_year), 0, 0, 0 };
std::string filename = src + itm;
size_t zipNameStart = 0;
if(trimPath)
util::trimPath(filename, trimPlaces);
else
zipNameStart = filename.find_first_of('/') + 1;
if(t)
t->status->setStatus(ui::getUICString("threadStatusAddingFileToZip", 0), itm.c_str());
int zipOpenFile = zipOpenNewFileInZip64(dst, filename.substr(zipNameStart, filename.npos).c_str(), &inf, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0);
if(zipOpenFile == ZIP_OK)
{
std::string fullSrc = src + itm;
if(c)
{
c->offset = 0;
c->prog->setMax(fs::fsize(fullSrc));
c->prog->update(0);
}
FILE *fsrc = fopen(fullSrc.c_str(), "rb");
size_t readIn = 0;
uint8_t *buff = new uint8_t[ZIP_BUFF_SIZE];
while((readIn = fread(buff, 1, ZIP_BUFF_SIZE, fsrc)) > 0)
{
zipWriteInFileInZip(dst, buff, readIn);
if(c)
c->offset += readIn;
}
delete[] buff;
fclose(fsrc);
}
}
}
}
void copyDirToZip_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
if(cfg::config["ovrClk"])
{
util::sysBoost();
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popCPUBoostEnabled", 0));
}
fs::copyDirToZip(c->src, c->z, c->trimZipPath, c->trimZipPlaces, t);
if(cfg::config["ovrClk"])
util::sysNormal();
if(c->cleanup)
{
zipClose(c->z, NULL);
delete c;
}
t->finished = true;
}
void fs::copyDirToZipThreaded(const std::string& src, zipFile dst, bool trimPath, int trimPlaces)
{
fs::copyArgs *send = fs::copyArgsCreate(src, "", "", dst, NULL, true, false, 0);
ui::newThread(copyDirToZip_t, send, fs::fileDrawFunc);
}
void fs::copyZipToDir(unzFile src, const std::string& dst, const std::string& dev, threadInfo *t)
{
fs::copyArgs *c = NULL;
if(t)
c = (fs::copyArgs *)t->argPtr;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
uint64_t journalSize = getJournalSize(utinfo);
char filename[FS_MAX_PATH];
uint8_t *buff = new uint8_t[BUFF_SIZE];
int readIn = 0;
unz_file_info64 info;
do
{
unzGetCurrentFileInfo64(src, &info, filename, FS_MAX_PATH, NULL, 0, NULL, 0);
if(unzOpenCurrentFile(src) == UNZ_OK)
{
if(t)
t->status->setStatus(ui::getUICString("threadStatusDecompressingFile", 0), filename);
if(c)
{
c->prog->setMax(info.uncompressed_size);
c->prog->update(0);
c->offset = 0;
}
std::string fullDst = dst + filename;
fs::mkDirRec(fullDst.substr(0, fullDst.find_last_of('/') + 1));
unzThrdArgs unzThrd;
unzThrd.dst = fullDst;
unzThrd.fileSize = info.uncompressed_size;
unzThrd.dev = dev;
unzThrd.writeLimit = (journalSize - 0x100000) < TRANSFER_BUFFER_LIMIT ? (journalSize - 0x100000) : TRANSFER_BUFFER_LIMIT;
Thread writeThread;
threadCreate(&writeThread, writeFileFromZip_t, &unzThrd, NULL, 0x8000, 0x2B, 2);
threadStart(&writeThread);
std::vector<uint8_t> transferBuffer;
uint64_t readCount = 0;
while((readIn = unzReadCurrentFile(src, buff, BUFF_SIZE)) > 0)
{
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
readCount += readIn;
if(c)
c->offset += readIn;
if(transferBuffer.size() >= unzThrd.writeLimit || readCount == info.uncompressed_size)
{
std::unique_lock<std::mutex> buffLock(unzThrd.buffLock);
unzThrd.cond.wait(buffLock, [&unzThrd]{ return unzThrd.bufferIsFull == false; });
unzThrd.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end());
transferBuffer.clear();
unzThrd.bufferIsFull = true;
unzThrd.cond.notify_one();
}
}
threadWaitForExit(&writeThread);
threadClose(&writeThread);
fs::commitToDevice(dev);
}
}
while(unzGoToNextFile(src) != UNZ_END_OF_LIST_OF_FILE);
delete[] buff;
}
static void copyZipToDir_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
fs::copyZipToDir(c->unz, c->dst, c->dev, t);
if(c->cleanup)
{
unzClose(c->unz);
delete c;
}
t->finished = true;
}
void fs::copyZipToDirThreaded(unzFile src, const std::string& dst, const std::string& dev)
{
fs::copyArgs *send = fs::copyArgsCreate("", dst, dev, NULL, src, true, false, 0);
ui::newThread(copyZipToDir_t, send, fs::fileDrawFunc);
}
uint64_t fs::getZipTotalSize(unzFile unz)
{
uint64_t ret = 0;
if(unzGoToFirstFile(unz) == UNZ_OK)
{
unz_file_info64 finfo;
char filename[FS_MAX_PATH];
do
{
unzGetCurrentFileInfo64(unz, &finfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0);
ret += finfo.uncompressed_size;
} while(unzGoToNextFile(unz) != UNZ_END_OF_LIST_OF_FILE);
unzGoToFirstFile(unz);
}
return ret;
}
bool fs::zipNotEmpty(unzFile unz)
{
return unzGoToFirstFile(unz) == UNZ_OK;
}

View File

@ -1,708 +0,0 @@
#include <switch.h>
#include "file.h"
#include "util.h"
#include "cfg.h"
static uint64_t getJournalSize(const data::titleInfo *t)
{
uint64_t journalSize = 0;
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
switch(d->saveInfo.save_data_type)
{
case FsSaveDataType_Account:
journalSize = t->nacp.user_account_save_data_journal_size;
break;
case FsSaveDataType_Device:
journalSize = t->nacp.device_save_data_journal_size;
break;
case FsSaveDataType_Bcat:
journalSize = t->nacp.bcat_delivery_cache_storage_size;
break;
case FsSaveDataType_Cache:
if(t->nacp.cache_storage_journal_size > 0)
journalSize = t->nacp.cache_storage_journal_size;
else
journalSize = t->nacp.cache_storage_data_and_journal_size_max;
break;
default:
journalSize = BUFF_SIZE;
break;
}
return journalSize;
}
void fs::_fileDrawFunc(void *a)
{
threadInfo *t = (threadInfo *)a;
if(!t->finished)
{
copyArgs *c = (copyArgs *)t->argPtr;
std::string tmp;
t->status->getStatus(tmp);
c->argLock();
c->prog->draw(tmp);
c->argUnlock();
}
}
void fs::createSaveData_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::svCreateArgs *s = (fs::svCreateArgs *)t->argPtr;
data::titleInfo *create = data::getTitleInfoByTID(s->tid);
t->status->setStatus(ui::getUICString("threadStatusCreatingSaveData", 0), create->title.c_str());
FsSaveDataAttribute attr;
memset(&attr, 0, sizeof(FsSaveDataAttribute));
attr.application_id = s->tid;
attr.uid = s->account;
attr.system_save_data_id = 0;
attr.save_data_type = s->type;
attr.save_data_rank = 0;
attr.save_data_index = s->index;
FsSaveDataCreationInfo svCreate;
memset(&svCreate, 0, sizeof(FsSaveDataCreationInfo));
int64_t saveSize = 0, journalSize = 0;
switch(s->type)
{
case FsSaveDataType_Account:
saveSize = create->nacp.user_account_save_data_size;
journalSize = create->nacp.user_account_save_data_journal_size;
break;
case FsSaveDataType_Device:
saveSize = create->nacp.device_save_data_size;
journalSize = create->nacp.device_save_data_journal_size;
break;
case FsSaveDataType_Bcat:
saveSize = create->nacp.bcat_delivery_cache_storage_size;
journalSize = create->nacp.bcat_delivery_cache_storage_size;
break;
case FsSaveDataType_Cache:
saveSize = 32 * 1024 * 1024;//Todo: Add target folder/zip selection for size
if(create->nacp.cache_storage_journal_size > create->nacp.cache_storage_data_and_journal_size_max)
journalSize = create->nacp.cache_storage_journal_size;
else
journalSize = create->nacp.cache_storage_data_and_journal_size_max;
break;
default:
delete s;
t->finished = true;
return;
break;
}
svCreate.save_data_size = saveSize;
svCreate.journal_size = journalSize;
svCreate.available_size = 0x4000;
svCreate.owner_id = s->type == FsSaveDataType_Bcat ? 0x010000000000000C : create->nacp.save_data_owner_id;
svCreate.flags = 0;
svCreate.save_data_space_id = FsSaveDataSpaceId_User;
FsSaveDataMetaInfo meta;
memset(&meta, 0, sizeof(FsSaveDataMetaInfo));
if(s->type != FsSaveDataType_Bcat)
{
meta.size = 0x40060;
meta.type = FsSaveDataMetaType_Thumbnail;
}
Result res = 0;
if(R_SUCCEEDED(res = fsCreateSaveDataFileSystem(&attr, &svCreate, &meta)))
{
util::createTitleDirectoryByTID(s->tid);
data::loadUsersTitles(false);
ui::ttlRefresh();
}
else
{
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataCreationFailed", 0));
fs::logWrite("SaveCreate Failed -> %X\n", res);
}
delete s;
t->finished = true;
}
void fs::copyFile_t(void *a)
{
threadInfo *t = (threadInfo *)a;
copyArgs *args = (copyArgs *)t->argPtr;
t->status->setStatus(ui::getUICString("threadStatusCopyingFile", 0), args->from.c_str());
args->prog->setMax(fs::fsize(args->from));
args->prog->update(0);
uint8_t *buff = new uint8_t[BUFF_SIZE];
if(cfg::config["directFsCmd"])
{
FSFILE *in = fsfopen(args->from.c_str(), FsOpenMode_Read);
FSFILE *out = fsfopen(args->to.c_str(), FsOpenMode_Write);
if(!in || !out)
{
fsfclose(in);
fsfclose(out);
t->finished = true;
return;
}
size_t readIn = 0;
while((readIn = fsfread(buff, 1, BUFF_SIZE, in)) > 0)
{
fsfwrite(buff, 1, readIn, out);
args->argLock();
args->offset = in->offset;
args->prog->update(args->offset);
args->argUnlock();
}
fsfclose(in);
fsfclose(out);
}
else
{
FILE *in = fopen(args->from.c_str(), "rb");
FILE *out = fopen(args->to.c_str(), "wb");
if(!in || !out)
{
fclose(in);
fclose(out);
t->finished = true;
return;
}
size_t readIn = 0;
while((readIn = fread(buff, 1, BUFF_SIZE, in)) > 0)
{
fwrite(buff, 1, readIn, out);
args->argLock();
args->offset = ftell(in);
args->prog->update(args->offset);
args->argUnlock();
}
fclose(in);
fclose(out);
}
delete[] buff;
if(args->cleanup)
copyArgsDestroy(args);
t->finished = true;
}
void fs::copyFileCommit_t(void *a)
{
threadInfo *t = (threadInfo *)a;
copyArgs *args = (copyArgs *)t->argPtr;
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
data::titleInfo *info = data::getTitleInfoByTID(d->tid);
t->status->setStatus(ui::getUICString("threadStatusCopyingFile", 0), args->from.c_str());
args->prog->setMax(fs::fsize(args->from));
args->prog->update(0);
uint64_t journalSize = getJournalSize(info), writeCount = 0;
uint8_t *buff = new uint8_t[BUFF_SIZE];
if(cfg::config["directFsCmd"])
{
FSFILE *in = fsfopen(args->from.c_str(), FsOpenMode_Read);
FSFILE *out = fsfopen(args->to.c_str(), FsOpenMode_Write);
if(!in || !out)
{
fsfclose(in);
fsfclose(out);
t->finished = true;
return;
}
size_t readIn = 0;
while((readIn = fsfread(buff, 1, BUFF_SIZE, in)) > 0)
{
fsfwrite(buff, 1, readIn, out);
writeCount += readIn;
if(writeCount >= (journalSize - 0x100000))
{
writeCount = 0;
fsfclose(out);
if(!commitToDevice(args->dev))
break;
out = fsfopen(args->to.c_str(), FsOpenMode_Write | FsOpenMode_Append);
}
args->argLock();
args->offset = out->offset;
args->prog->update(args->offset);
args->argUnlock();
}
fsfclose(in);
fsfclose(out);
}
else
{
FILE *in = fopen(args->from.c_str(), "rb");
FILE *out = fopen(args->to.c_str(), "wb");
if(!in || !out)
{
fclose(in);
fclose(out);
t->finished = true;
return;
}
size_t readIn = 0;
while((readIn = fread(buff, 1, BUFF_SIZE, in)) > 0)
{
fwrite(buff, 1, readIn, out);
writeCount += readIn;
if(writeCount >= (journalSize - 0x100000))
{
writeCount = 0;
fclose(out);
if(!commitToDevice(args->dev))
break;
out = fopen(args->to.c_str(), "ab");
}
args->argLock();
args->offset = ftell(out);
args->prog->update(args->offset);
args->argUnlock();
}
fclose(in);
fclose(out);
}
delete[] buff;
commitToDevice(args->dev.c_str());
if(args->cleanup)
copyArgsDestroy(args);
t->finished = true;
}
void fs::copyDirToDir_t(void *a)
{
threadInfo *t = (threadInfo *)a;
copyArgs *args = (copyArgs *)t->argPtr;
fs::dirList *list = new fs::dirList(args->from);
for(int i = 0; i < (int)list->getCount(); i++)
{
if(pathIsFiltered(args->from + list->getItem(i)))
continue;
if(list->isDir(i))
{
std::string newSrc = args->from + list->getItem(i) + "/";
std::string newDst = args->to + list->getItem(i) + "/";
fs::mkDir(newDst.substr(0, newDst.length() - 1));
threadInfo *fakeThread = new threadInfo;
fs::copyArgs *tmpArgs = new fs::copyArgs;
fakeThread->status = t->status;
fakeThread->argPtr = tmpArgs;
tmpArgs->from = newSrc;
tmpArgs->to = newDst;
tmpArgs->prog = args->prog;
tmpArgs->cleanup = false;
fs::copyDirToDir_t(fakeThread);
delete fakeThread;
delete tmpArgs;
}
else
{
std::string fullSrc = args->from + list->getItem(i);
std::string fullDst = args->to + list->getItem(i);
threadInfo *fakeThread = new threadInfo;
fs::copyArgs *tmpArgs = new fs::copyArgs;
fakeThread->status = t->status;
fakeThread->argPtr = tmpArgs;
tmpArgs->from = fullSrc;
tmpArgs->to = fullDst;
tmpArgs->prog = args->prog;
tmpArgs->cleanup = false;
fs::copyFile_t(fakeThread);
delete fakeThread;
delete tmpArgs;
}
}
delete list;
if(args->cleanup)
copyArgsDestroy(args);
t->finished = true;
}
void fs::copyDirToDirCommit_t(void *a)
{
threadInfo *t = (threadInfo *)a;
copyArgs *args = (copyArgs *)t->argPtr;
fs::dirList *list = new fs::dirList(args->from);
for(int i = 0; i < (int)list->getCount(); i++)
{
if(pathIsFiltered(args->from + list->getItem(i)))
continue;
if(list->isDir(i))
{
std::string newSrc = args->from + list->getItem(i) + "/";
std::string newDst = args->to + list->getItem(i) + "/";
fs::mkDir(newDst.substr(0, newDst.length() - 1));
threadInfo *fakeThread = new threadInfo;
fs::copyArgs *tmpArgs = new fs::copyArgs;
fakeThread->status = t->status;
fakeThread->argPtr = tmpArgs;
tmpArgs->from = newSrc;
tmpArgs->to = newDst;
tmpArgs->dev = args->dev;
tmpArgs->prog = args->prog;
tmpArgs->cleanup = false;
fs::copyDirToDirCommit_t(fakeThread);
delete fakeThread;
delete tmpArgs;
}
else
{
std::string fullSrc = args->from + list->getItem(i);
std::string fullDst = args->to + list->getItem(i);
threadInfo *fakeThread = new threadInfo;
fs::copyArgs *tmpArgs = new fs::copyArgs;
fakeThread->status = t->status;
fakeThread->argPtr = tmpArgs;
tmpArgs->from = fullSrc;
tmpArgs->to = fullDst;
tmpArgs->dev = args->dev;
tmpArgs->prog = args->prog;
tmpArgs->cleanup = false;
fs::copyFileCommit_t(fakeThread);
delete fakeThread;
delete tmpArgs;
}
}
delete list;
if(args->cleanup)
copyArgsDestroy(args);
t->finished = true;
}
void fs::copyDirToZip_t(void *a)
{
threadInfo *t = (threadInfo *)a;
copyArgs *args = (copyArgs *)t->argPtr;
bool trimPath = args->trimZipPath;
uint8_t trimPlaces = args->trimZipPlaces;
t->status->setStatus(ui::getUICString("threadStatusOpeningFolder", 0), args->from.c_str());
fs::dirList *list = new fs::dirList(args->from);
unsigned listTotal = list->getCount();
for(unsigned i = 0; i < listTotal; i++)
{
std::string itm = list->getItem(i);
if(fs::pathIsFiltered(args->from + itm))
continue;
if(list->isDir(i))
{
std::string newFrom = args->from + itm + "/";
//Fake thread and new args to point to src thread stuff
//This wouldn't work spawning a new thread.
threadInfo *tmpThread = new threadInfo;
tmpThread->status = t->status;
fs::copyArgs *tmpArgs = new fs::copyArgs;
tmpArgs->from = newFrom;
tmpArgs->prog = args->prog;
tmpArgs->z = args->z;
tmpArgs->cleanup = false;
tmpArgs->trimZipPath = args->trimZipPath;
tmpArgs->trimZipPlaces = args->trimZipPlaces;
tmpThread->argPtr = tmpArgs;
copyDirToZip_t(tmpThread);
delete tmpThread;
delete tmpArgs;
}
else
{
time_t raw;
time(&raw);
tm *locTime = localtime(&raw);
zip_fileinfo inf = { (unsigned)locTime->tm_sec, (unsigned)locTime->tm_min, (unsigned)locTime->tm_hour,
(unsigned)locTime->tm_mday, (unsigned)locTime->tm_mon, (unsigned)(1900 + locTime->tm_year), 0, 0, 0 };
std::string filename = args->from + itm;
size_t zipFileNameStart = 0;
if(trimPath)
util::trimPath(filename, trimPlaces);
else
zipFileNameStart = filename.find_first_of('/') + 1;
t->status->setStatus(ui::getUICString("threadStatusAddingFileToZip", 0), itm.c_str());
int zOpenFile = zipOpenNewFileInZip64(args->z, filename.substr(zipFileNameStart, filename.length()).c_str(), &inf, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0);
if(zOpenFile == ZIP_OK)
{
std::string fullFrom = args->from + itm;
args->prog->setMax(fs::fsize(fullFrom));
args->prog->update(0);
args->offset = 0;
FILE *cpy = fopen(fullFrom.c_str(), "rb");
size_t readIn = 0;
uint8_t *buff = new uint8_t[BUFF_SIZE];
while((readIn = fread(buff, 1, BUFF_SIZE, cpy)) > 0)
{
zipWriteInFileInZip(args->z, buff, readIn);
args->offset += readIn;
args->prog->update(args->offset);
}
delete[] buff;
fclose(cpy);
zipCloseFileInZip(args->z);
}
}
}
delete list;
if(args->cleanup)
{
if(cfg::config["ovrClk"])
util::setCPU(util::CPU_SPEED_1224MHz);
ui::newThread(closeZip_t, args->z, NULL);
delete args->prog;
delete args;
}
t->finished = true;
}
void fs::copyZipToDir_t(void *a)
{
threadInfo *t = (threadInfo *)a;
copyArgs *args = (copyArgs *)t->argPtr;
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
data::titleInfo *tinfo = data::getTitleInfoByTID(d->tid);
uint64_t journalSize = getJournalSize(tinfo), writeCount = 0;
char filename[FS_MAX_PATH];
uint8_t *buff = new uint8_t[BUFF_SIZE];
int readIn = 0;
unz_file_info64 info;
do
{
unzGetCurrentFileInfo64(args->unz, &info, filename, FS_MAX_PATH, NULL, 0, NULL, 0);
if(unzOpenCurrentFile(args->unz) == UNZ_OK)
{
t->status->setStatus(ui::getUICString("threadStatusDecompressingFile", 0), filename);
std::string path = args->to + filename;
mkDirRec(path.substr(0, path.find_last_of('/') + 1));
args->prog->setMax(info.uncompressed_size);
args->prog->update(0);
args->offset = 0;
size_t done = 0;
if(cfg::config["directFsCmd"])
{
FSFILE *out = fsfopen(path.c_str(), FsOpenMode_Write);
while((readIn = unzReadCurrentFile(args->unz, buff, BUFF_SIZE)) > 0)
{
done += readIn;
writeCount += readIn;
args->offset += readIn;
args->prog->update(args->offset);
fsfwrite(buff, 1, readIn, out);
if(writeCount >= (journalSize - 0x100000))
{
writeCount = 0;
fsfclose(out);
if(!commitToDevice(args->dev.c_str()))
break;
out = fsfopen(path.c_str(), FsOpenMode_Write | FsOpenMode_Append);
}
}
fsfclose(out);
}
else
{
FILE *out = fopen(path.c_str(), "wb");
while((readIn = unzReadCurrentFile(args->unz, buff, BUFF_SIZE)) > 0)
{
done += readIn;
writeCount += readIn;
args->offset += readIn;
args->prog->update(args->offset);
fwrite(buff, 1, readIn, out);
if(writeCount >= (journalSize - 0x100000))
{
writeCount = 0;
fclose(out);
if(!commitToDevice(args->dev.c_str()))
break;
out = fopen(path.c_str(), "ab");
}
}
fclose(out);
}
unzCloseCurrentFile(args->unz);
commitToDevice(args->dev.c_str());
}
}
while(unzGoToNextFile(args->unz) != UNZ_END_OF_LIST_OF_FILE);
if(args->cleanup)
{
unzClose(args->unz);
copyArgsDestroy(args);
if(cfg::config["ovrClk"])
util::setCPU(util::CPU_SPEED_1224MHz);
}
delete[] buff;
t->finished = true;
}
void fs::wipesave_t(void *a)
{
threadInfo *t = (threadInfo *)a;
t->status->setStatus(ui::getUICString("threadStatusResettingSaveData", 0));
fs::delDir("sv:/");
fs::commitToDevice("sv");
t->finished = true;
}
void fs::closeZip_t(void *a)
{
threadInfo *t = (threadInfo *)a;
zipFile z = t->argPtr;
zipClose(z, NULL);
t->finished = true;
}
void fs::backupUserSaves_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
data::user *u = data::getCurrentUser();
if(cfg::config["ovrClk"] && cfg::config["zip"])
{
util::setCPU(util::CPU_SPEED_1785MHz);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popCPUBoostEnabled", 0));
}
for(unsigned i = 0; i < u->titleInfo.size(); i++)
{
std::string title = data::getTitleNameByTID(u->titleInfo[i].tid);
t->status->setStatus(std::string("#" + title + "#").c_str());
if((ui::padKeysDown() & HidNpadButton_B) || (ui::padKeysHeld() & HidNpadButton_B))
{
delete c;
t->finished = true;
return;
}
bool saveMounted = fs::mountSave(u->titleInfo[i].saveInfo);
util::createTitleDirectoryByTID(u->titleInfo[i].tid);
if(saveMounted && cfg::config["zip"] && fs::dirNotEmpty("sv:/"))
{
fs::loadPathFilters(u->titleInfo[i].tid);
std::string outPath = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD) + ".zip";
zipFile zip = zipOpen64(outPath.c_str(), 0);
threadInfo *fakeThread = new threadInfo;
fs::copyArgs *tmpArgs = new fs::copyArgs;
tmpArgs->from = "sv:/";
tmpArgs->z = zip;
tmpArgs->cleanup = false;
tmpArgs->prog = c->prog;
fakeThread->status = t->status;
fakeThread->argPtr = tmpArgs;
copyDirToZip_t(fakeThread);
zipClose(zip, NULL);
fs::freePathFilters();
delete fakeThread;
delete tmpArgs;
}
else if(saveMounted && fs::dirNotEmpty("sv:/"))
{
fs::loadPathFilters(u->titleInfo[i].tid);
std::string outPath = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD) + "/";
fs::mkDir(outPath.substr(0, outPath.length() - 1));
threadInfo *fakeThread = new threadInfo;
fs::copyArgs *tmpArgs = new fs::copyArgs;
tmpArgs->from = "sv:/";
tmpArgs->to = outPath;
tmpArgs->cleanup = false;
tmpArgs->prog = c->prog;
fakeThread->status = t->status;
fakeThread->argPtr = tmpArgs;
copyDirToDir_t(fakeThread);
fs::freePathFilters();
delete fakeThread;
delete tmpArgs;
}
fs::unmountSave();
}
delete c;
if(cfg::config["ovrClk"] && cfg::config["zip"])
util::setCPU(util::CPU_SPEED_1224MHz);
t->finished = true;
}
void fs::getShowDirProps_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::dirCountArgs *d = (fs::dirCountArgs *)t->argPtr;
t->status->setStatus(ui::getUICString("threadStatusGetDirProps", 0));
fs::dirList *dir = new fs::dirList(d->path);
for(unsigned i = 0; i < dir->getCount(); i++)
{
if(dir->isDir(i))
{
d->dirCount++;
threadInfo *fakeThread = new threadInfo;
dirCountArgs *tmpArgs = new dirCountArgs;
tmpArgs->path = d->path + dir->getItem(i) + "/";
tmpArgs->origin = false;
fakeThread->status = t->status;
fakeThread->argPtr = tmpArgs;
getShowDirProps_t(fakeThread);
d->dirCount += tmpArgs->dirCount;
d->fileCount += tmpArgs->fileCount;
d->totalSize += tmpArgs->totalSize;
delete fakeThread;
delete tmpArgs;
}
else
{
std::string filePath = d->path + dir->getItem(i);
d->fileCount++;
d->totalSize += fs::fsize(filePath);
}
}
if(d->origin)
{
ui::showMessage(ui::getUICString("fileModeFolderProperties", 0), d->path.c_str(), d->dirCount, d->fileCount, util::getSizeString(d->totalSize).c_str());
delete d;
}
t->finished = true;
}

646
src/gd.cpp Normal file
View File

@ -0,0 +1,646 @@
#include <stdio.h>
#include <curl/curl.h>
#include <json-c/json.h>
#include <string>
#include <vector>
#include <mutex>
#include <condition_variable>
#include "gd.h"
#include "fs.h"
#include "curlfuncs.h"
#include "util.h"
/*
Google Drive code for JKSV.
Still major WIP
*/
#define DRIVE_DEFAULT_PARAMS_AND_QUERY "?fields=files(name,id,mimeType,size,parents)&pageSize=1000&q=trashed=false\%20and\%20\%27me\%27\%20in\%20owners"
#define tokenURL "https://oauth2.googleapis.com/token"
#define tokenCheckURL "https://oauth2.googleapis.com/tokeninfo"
#define driveURL "https://www.googleapis.com/drive/v3/files"
#define driveUploadURL "https://www.googleapis.com/upload/drive/v3/files"
static inline void writeDriveError(const std::string& _function, const std::string& _message)
{
fs::logWrite("Drive/%s: %s\n", _function.c_str(), _message.c_str());
}
static inline void writeCurlError(const std::string& _function, int _cerror)
{
fs::logWrite("Drive/%s: CURL returned error %i\n", _function.c_str(), _cerror);
}
bool drive::gd::exhangeAuthCode(const std::string& _authCode)
{
// Header
curl_slist *postHeader = NULL;
postHeader = curl_slist_append(postHeader, HEADER_CONTENT_TYPE_APP_JSON);
// Post json
json_object *post = json_object_new_object();
json_object *clientIDString = json_object_new_string(clientID.c_str());
json_object *secretIDString = json_object_new_string(secretID.c_str());
json_object *authCodeString = json_object_new_string(_authCode.c_str());
json_object *redirectUriString = json_object_new_string("urn:ietf:wg:oauth:2.0:oob:auto");
json_object *grantTypeString = json_object_new_string("authorization_code");
json_object_object_add(post, "client_id", clientIDString);
json_object_object_add(post, "client_secret", secretIDString);
json_object_object_add(post, "code", authCodeString);
json_object_object_add(post, "redirect_uri", redirectUriString);
json_object_object_add(post, "grant_type", grantTypeString);
// Curl Request
std::string *jsonResp = new std::string;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, postHeader);
curl_easy_setopt(curl, CURLOPT_URL, tokenURL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlFuncs::writeDataString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, jsonResp);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_object_get_string(post));
int error = curl_easy_perform(curl);
json_object *respParse = json_tokener_parse(jsonResp->c_str());
if (error == CURLE_OK)
{
json_object *accessToken = json_object_object_get(respParse, "access_token");
json_object *refreshToken = json_object_object_get(respParse, "refresh_token");
if(accessToken && refreshToken)
{
token = json_object_get_string(accessToken);
rToken = json_object_get_string(refreshToken);
}
else
writeDriveError("exchangeAuthCode", jsonResp->c_str());
}
else
writeCurlError("exchangeAuthCode", error);
delete jsonResp;
json_object_put(post);
json_object_put(respParse);
curl_slist_free_all(postHeader);
curl_easy_cleanup(curl);
return true;
}
bool drive::gd::refreshToken()
{
bool ret = false;
// Header
curl_slist *header = NULL;
header = curl_slist_append(header, HEADER_CONTENT_TYPE_APP_JSON);
// Post Json
json_object *post = json_object_new_object();
json_object *clientIDString = json_object_new_string(clientID.c_str());
json_object *secretIDString = json_object_new_string(secretID.c_str());
json_object *refreshTokenString = json_object_new_string(rToken.c_str());
json_object *grantTypeString = json_object_new_string("refresh_token");
json_object_object_add(post, "client_id", clientIDString);
json_object_object_add(post, "client_secret", secretIDString);
json_object_object_add(post, "refresh_token", refreshTokenString);
json_object_object_add(post, "grant_type", grantTypeString);
// Curl
std::string *jsonResp = new std::string;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header);
curl_easy_setopt(curl, CURLOPT_URL, tokenURL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlFuncs::writeDataString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, jsonResp);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_object_get_string(post));
int error = curl_easy_perform(curl);
json_object *parse = json_tokener_parse(jsonResp->c_str());
if (error == CURLE_OK)
{
json_object *accessToken, *error;
json_object_object_get_ex(parse, "access_token", &accessToken);
json_object_object_get_ex(parse, "error", &error);
if(accessToken)
{
token = json_object_get_string(accessToken);
ret = true;
}
else if(error)
writeDriveError("refreshToken", jsonResp->c_str());
}
delete jsonResp;
json_object_put(post);
json_object_put(parse);
curl_slist_free_all(header);
curl_easy_cleanup(curl);
return ret;
}
bool drive::gd::tokenIsValid()
{
bool ret = false;
std::string url = tokenCheckURL;
url.append("?access_token=" + token);
CURL *curl = curl_easy_init();
std::string *jsonResp = new std::string;
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlFuncs::writeDataString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, jsonResp);
int error = curl_easy_perform(curl);
json_object *parse = json_tokener_parse(jsonResp->c_str());
if (error == CURLE_OK)
{
json_object *checkError;
json_object_object_get_ex(parse, "error", &checkError);
if(!checkError)
ret = true;
}
delete jsonResp;
json_object_put(parse);
curl_easy_cleanup(curl);
return ret;
}
static int requestList(const std::string& _url, const std::string& _token, std::string *_respOut)
{
int ret = 0;
// Headers needed
curl_slist *postHeaders = NULL;
postHeaders = curl_slist_append(postHeaders, std::string(HEADER_AUTHORIZATION + _token).c_str());
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, postHeaders);
curl_easy_setopt(curl, CURLOPT_URL, _url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlFuncs::writeDataString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, _respOut);
ret = curl_easy_perform(curl);
curl_slist_free_all(postHeaders);
curl_easy_cleanup(curl);
return ret;
}
static void processList(const std::string& _json, std::vector<rfs::RfsItem>& _drvl, bool _clear)
{
if(_clear)
_drvl.clear();
json_object *parse = json_tokener_parse(_json.c_str()), *fileArray;
json_object_object_get_ex(parse, "files", &fileArray);
if(fileArray)
{
size_t arrayLength = json_object_array_length(fileArray);
_drvl.reserve(_drvl.size() + arrayLength);
for(unsigned i = 0; i < arrayLength; i++)
{
json_object *idString, *nameString, *mimeTypeString, *size, *parentArray;
json_object *curFile = json_object_array_get_idx(fileArray, i);
json_object_object_get_ex(curFile, "id", &idString);
json_object_object_get_ex(curFile, "name", &nameString);
json_object_object_get_ex(curFile, "mimeType", &mimeTypeString);
json_object_object_get_ex(curFile, "size", &size);
json_object_object_get_ex(curFile, "parents", &parentArray);
rfs::RfsItem newDirItem;
newDirItem.name = json_object_get_string(nameString);
newDirItem.id = json_object_get_string(idString);
newDirItem.size = json_object_get_int(size);
if(strcmp(json_object_get_string(mimeTypeString), MIMETYPE_FOLDER) == 0)
newDirItem.isDir = true;
if (parentArray)
{
size_t parentCount = json_object_array_length(parentArray);
//There can only be 1 parent, but it's held in an array...
for (unsigned j = 0; j < parentCount; j++)
{
json_object *parent = json_object_array_get_idx(parentArray, j);
newDirItem.parent = json_object_get_string(parent);
}
}
_drvl.push_back(newDirItem);
}
}
json_object_put(parse);
}
void drive::gd::driveListInit(const std::string& _q)
{
if(!tokenIsValid())
refreshToken();
// Request url with specific fields needed.
std::string url = std::string(driveURL) + std::string(DRIVE_DEFAULT_PARAMS_AND_QUERY);
if(!_q.empty())
{
char *qEsc = curl_easy_escape(NULL, _q.c_str(), _q.length());
url.append(std::string("\%20and\%20") + std::string(qEsc));
curl_free(qEsc);
}
std::string jsonResp;
int error = requestList(url, token, &jsonResp);
if(error == CURLE_OK)
processList(jsonResp, driveList, true);
else
writeCurlError("driveListInit", error);
}
void drive::gd::driveListAppend(const std::string& _q)
{
if(!tokenIsValid())
refreshToken();
std::string url = std::string(driveURL) + std::string(DRIVE_DEFAULT_PARAMS_AND_QUERY);
if(!_q.empty())
{
char *qEsc = curl_easy_escape(NULL, _q.c_str(), _q.length());
url.append(std::string("\%20and\%20") + std::string(qEsc));
curl_free(qEsc);
}
std::string jsonResp;
int error = requestList(url, token, &jsonResp);
if(error == CURLE_OK)
processList(jsonResp, driveList, false);
else
writeCurlError("driveListAppend", error);
}
std::vector<rfs::RfsItem> drive::gd::getListWithParent(const std::string& _parent) {
std::vector<rfs::RfsItem> filtered;
for(unsigned i = 0; i < driveList.size(); i++)
{
if(driveList[i].parent == _parent)
filtered.push_back(driveList[i]);
}
return filtered;
}
void drive::gd::debugWriteList()
{
for(auto& di : driveList)
{
fs::logWrite("%s\n\t%s\n", di.name.c_str(), di.id.c_str());
if(!di.parent.empty())
fs::logWrite("\t%s\n", di.parent.c_str());
}
}
bool drive::gd::createDir(const std::string& _dirName, const std::string& _parent)
{
if(!tokenIsValid())
refreshToken();
bool ret = true;
// Headers to use
curl_slist *postHeaders = NULL;
postHeaders = curl_slist_append(postHeaders, std::string(HEADER_AUTHORIZATION + token).c_str());
postHeaders = curl_slist_append(postHeaders, HEADER_CONTENT_TYPE_APP_JSON);
// JSON To Post
json_object *post = json_object_new_object();
json_object *nameString = json_object_new_string(_dirName.c_str());
json_object *mimeTypeString = json_object_new_string(MIMETYPE_FOLDER);
json_object_object_add(post, "name", nameString);
json_object_object_add(post, "mimeType", mimeTypeString);
if (!_parent.empty())
{
json_object *parentsArray = json_object_new_array();
json_object *parentString = json_object_new_string(_parent.c_str());
json_object_array_add(parentsArray, parentString);
json_object_object_add(post, "parents", parentsArray);
}
// Curl Request
std::string *jsonResp = new std::string;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, postHeaders);
curl_easy_setopt(curl, CURLOPT_URL, driveURL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlFuncs::writeDataString);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, jsonResp);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_object_get_string(post));
int error = curl_easy_perform(curl);
json_object *respParse = json_tokener_parse(jsonResp->c_str()), *checkError;
json_object_object_get_ex(respParse, "error", &checkError);
if (error == CURLE_OK && !checkError)
{
//Append it to list
json_object *id;
json_object_object_get_ex(respParse, "id", &id);
rfs::RfsItem newDir;
newDir.name = _dirName;
newDir.id = json_object_get_string(id);
newDir.isDir = true;
newDir.size = 0;
newDir.parent = _parent;
driveList.push_back(newDir);
}
else
ret = false;
delete jsonResp;
json_object_put(post);
json_object_put(respParse);
curl_slist_free_all(postHeaders);
curl_easy_cleanup(curl);
return ret;
}
bool drive::gd::dirExists(const std::string& _dirName)
{
for(unsigned i = 0; i < driveList.size(); i++)
{
if(driveList[i].isDir && driveList[i].name == _dirName)
return true;
}
return false;
}
bool drive::gd::dirExists(const std::string& _dirName, const std::string& _parent)
{
for(unsigned i = 0; i < driveList.size(); i++)
{
if(driveList[i].isDir && driveList[i].name == _dirName && driveList[i].parent == _parent)
return true;
}
return false;
}
bool drive::gd::fileExists(const std::string& _filename, const std::string& _parent)
{
for(unsigned i = 0; i < driveList.size(); i++)
{
if(!driveList[i].isDir && driveList[i].name == _filename && driveList[i].parent == _parent)
return true;
}
return false;
}
void drive::gd::uploadFile(const std::string& _filename, const std::string& _parent, curlFuncs::curlUpArgs *_upload)
{
if(!tokenIsValid())
refreshToken();
std::string url = driveUploadURL;
url.append("?uploadType=resumable");
// Headers
curl_slist *postHeaders = NULL;
postHeaders = curl_slist_append(postHeaders, std::string(HEADER_AUTHORIZATION + token).c_str());
postHeaders = curl_slist_append(postHeaders, HEADER_CONTENT_TYPE_APP_JSON);
// Post JSON
json_object *post = json_object_new_object();
json_object *nameString = json_object_new_string(_filename.c_str());
json_object_object_add(post, "name", nameString);
if (!_parent.empty())
{
json_object *parentArray = json_object_new_array();
json_object *parentString = json_object_new_string(_parent.c_str());
json_object_array_add(parentArray, parentString);
json_object_object_add(post, "parents", parentArray);
}
// Curl upload request
std::string *jsonResp = new std::string;
std::vector<std::string> *headers = new std::vector<std::string>;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_HTTPPOST, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, postHeaders);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlFuncs::writeHeaders);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_object_get_string(post));
int error = curl_easy_perform(curl);
std::string location = curlFuncs::getHeader("Location", headers);
if (error == CURLE_OK && location != HEADER_ERROR)
{
CURL *curlUp = curl_easy_init();
curl_easy_setopt(curlUp, CURLOPT_PUT, 1);
curl_easy_setopt(curlUp, CURLOPT_URL, location.c_str());
curl_easy_setopt(curlUp, CURLOPT_WRITEFUNCTION, curlFuncs::writeDataString);
curl_easy_setopt(curlUp, CURLOPT_WRITEDATA, jsonResp);
curl_easy_setopt(curlUp, CURLOPT_READFUNCTION, curlFuncs::readDataFile);
curl_easy_setopt(curlUp, CURLOPT_READDATA, _upload);
curl_easy_setopt(curlUp, CURLOPT_UPLOAD_BUFFERSIZE, UPLOAD_BUFFER_SIZE);
curl_easy_setopt(curlUp, CURLOPT_UPLOAD, 1);
curl_easy_perform(curlUp);
curl_easy_cleanup(curlUp);
json_object *parse = json_tokener_parse(jsonResp->c_str()), *id, *name, *mimeType;
json_object_object_get_ex(parse, "id", &id);
json_object_object_get_ex(parse, "name", &name);
json_object_object_get_ex(parse, "mimeType", &mimeType);
if(name && id && mimeType)
{
rfs::RfsItem uploadData;
uploadData.id = json_object_get_string(id);
uploadData.name = json_object_get_string(name);
uploadData.isDir = false;
uploadData.size = *_upload->o;//should be safe to use
uploadData.parent = _parent;
driveList.push_back(uploadData);
}
json_object_put(parse);
}
else
writeCurlError("uploadFile", error);
delete jsonResp;
delete headers;
json_object_put(post);
curl_slist_free_all(postHeaders);
}
void drive::gd::updateFile(const std::string& _fileID, curlFuncs::curlUpArgs *_upload)
{
if(!tokenIsValid())
refreshToken();
//URL
std::string url = driveUploadURL;
url.append("/" + _fileID);
url.append("?uploadType=resumable");
//Header
curl_slist *patchHeader = NULL;
patchHeader = curl_slist_append(patchHeader, std::string(HEADER_AUTHORIZATION + token).c_str());
//Curl
std::string *jsonResp = new std::string;
std::vector<std::string> *headers = new std::vector<std::string>;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, patchHeader);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlFuncs::writeDataString);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curlFuncs::writeHeaders);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, jsonResp);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, headers);
int error = curl_easy_perform(curl);
std::string location = curlFuncs::getHeader("Location", headers);
if(error == CURLE_OK && location != HEADER_ERROR)
{
CURL *curlPatch = curl_easy_init();
curl_easy_setopt(curlPatch, CURLOPT_PUT, 1);
curl_easy_setopt(curlPatch, CURLOPT_URL, location.c_str());
curl_easy_setopt(curlPatch, CURLOPT_READFUNCTION, curlFuncs::readDataFile);
curl_easy_setopt(curlPatch, CURLOPT_READDATA, _upload);
curl_easy_setopt(curlPatch, CURLOPT_UPLOAD_BUFFERSIZE, UPLOAD_BUFFER_SIZE);
curl_easy_setopt(curlPatch, CURLOPT_UPLOAD, 1);
curl_easy_perform(curlPatch);
curl_easy_cleanup(curlPatch);
for(unsigned i = 0; i < driveList.size(); i++)
{
if(driveList[i].id == _fileID)
{
driveList[i].size = *_upload->o;
break;
}
}
}
delete jsonResp;
delete headers;
curl_slist_free_all(patchHeader);
curl_easy_cleanup(curl);
}
void drive::gd::downloadFile(const std::string& _fileID, curlFuncs::curlDlArgs *_download)
{
if(!tokenIsValid())
refreshToken();
//URL
std::string url = driveURL;
url.append("/" + _fileID);
url.append("?alt=media");
//Headers
curl_slist *getHeaders = NULL;
getHeaders = curl_slist_append(getHeaders, std::string(HEADER_AUTHORIZATION + token).c_str());
//Downloading is threaded because it's too slow otherwise
rfs::dlWriteThreadStruct dlWrite;
dlWrite.cfa = _download;
Thread writeThread;
threadCreate(&writeThread, rfs::writeThread_t, &dlWrite, NULL, 0x8000, 0x2B, 2);
//Curl
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, getHeaders);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, rfs::writeDataBufferThreaded);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &dlWrite);
threadStart(&writeThread);
curl_easy_perform(curl);
threadWaitForExit(&writeThread);
threadClose(&writeThread);
curl_slist_free_all(getHeaders);
curl_easy_cleanup(curl);
}
void drive::gd::deleteFile(const std::string& _fileID)
{
if(!tokenIsValid())
refreshToken();
//URL
std::string url = driveURL;
url.append("/" + _fileID);
//Header
curl_slist *delHeaders = NULL;
delHeaders = curl_slist_append(delHeaders, std::string(HEADER_AUTHORIZATION + token).c_str());
//Curl
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, delHeaders);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_perform(curl);
for(unsigned i = 0; i < driveList.size(); i++)
{
if(driveList[i].id == _fileID)
{
driveList.erase(driveList.begin() + i);
break;
}
}
curl_slist_free_all(delHeaders);
curl_easy_cleanup(curl);
}
std::string drive::gd::getFileID(const std::string& _name, const std::string& _parent)
{
for(unsigned i = 0; i < driveList.size(); i++)
{
if(!driveList[i].isDir && driveList[i].name == _name && driveList[i].parent == _parent)
return driveList[i].id;
}
return "";
}
std::string drive::gd::getDirID(const std::string& _name)
{
for(unsigned i = 0; i < driveList.size(); i++)
{
if(driveList[i].isDir && driveList[i].name == _name)
return driveList[i].id;
}
return "";
}
std::string drive::gd::getDirID(const std::string& _name, const std::string& _parent)
{
for(unsigned i = 0; i < driveList.size(); i++)
{
if(driveList[i].isDir && driveList[i].name == _name && driveList[i].parent == _parent)
return driveList[i].id;
}
return "";
}

View File

@ -1,8 +1,8 @@
#include <stdio.h>
#include <map>
#include <switch.h>
#include <SDL.h>
#include <SDL_image.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <ft2build.h>
#include FT_FREETYPE_H
@ -13,6 +13,7 @@
static SDL_Window *wind;
SDL_Renderer *gfx::render;
gfx::textureMgr *gfx::texMgr;
static FT_Library lib;
static FT_Face face[6];
@ -31,8 +32,6 @@ static const uint32_t blueMask = 0x0000FF00;
static const uint32_t alphaMask = 0x000000FF;
static const uint32_t breakPoints[7] = {' ', L' ', '/', '_', '-', L'', L''};
static uint8_t *alphaMod;
static inline bool compClr(const SDL_Color *c1, const SDL_Color *c2)
{
return (c1->r == c2->r) && (c1->b == c2->b) && (c1->g == c2->g) && (c1->a == c2->a);
@ -54,7 +53,7 @@ static bool loadSystemFont()
PlFontData shared[6];
uint64_t langCode = 0;
if(R_FAILED(plInitialize(PlServiceType_System)))
if(R_FAILED(plInitialize(PlServiceType_User)))
return false;
if(FT_Init_FreeType(&lib))
@ -104,25 +103,23 @@ void gfx::init()
SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_BLEND);
//Load the alpha mod to round icon corners
alphaMod = (uint8_t *)malloc(256 * 256);
FILE *modLoad = fopen("romfs:/img/icn/icon.msk", "rb");
fread(alphaMod, 1, 256 * 256, modLoad);
fclose(modLoad);
gfx::texMgr = new gfx::textureMgr;
loadSystemFont();
//This is to avoid blank, black glyphs
for(unsigned i = 0x20; i < 0x7E; i++)
gfx::drawTextf(NULL, 18, 32, 32, &ui::txtCont, "%c", i);
}
void gfx::exit()
{
delete gfx::texMgr;
SDL_DestroyRenderer(gfx::render);
SDL_DestroyWindow(wind);
IMG_Quit();
SDL_Quit();
freeSystemFont();
free(alphaMod);
for(auto c : glyphCache)
SDL_DestroyTexture(c.second.tex);
}
void gfx::present()
@ -130,37 +127,6 @@ void gfx::present()
SDL_RenderPresent(render);
}
SDL_Texture *gfx::loadJPEGMem(const void *jpegData, size_t jpegsize)
{
SDL_Texture *ret = NULL;
SDL_RWops *jpeg = SDL_RWFromConstMem(jpegData, jpegsize);
SDL_Surface *tmpSurf = IMG_LoadJPG_RW(jpeg);
if(tmpSurf)
{
ret = SDL_CreateTextureFromSurface(render, tmpSurf);
SDL_FreeSurface(tmpSurf);
}
SDL_RWclose(jpeg);
SDL_SetTextureBlendMode(ret, SDL_BLENDMODE_BLEND);
return ret;
}
SDL_Texture *gfx::loadImageFile(const char *file)
{
SDL_Texture *ret = NULL;
SDL_Surface *tmpSurf = IMG_Load(file);
if(tmpSurf)
{
ret = SDL_CreateTextureFromSurface(render, tmpSurf);
SDL_FreeSurface(tmpSurf);
}
SDL_SetTextureBlendMode(ret, SDL_BLENDMODE_BLEND);
return ret;
}
static inline void resizeFont(int sz)
{
for(int i = 0; i < totalFonts; i++)
@ -207,6 +173,9 @@ static glyphData *getGlyph(uint32_t chr, int size)
SDL_FreeSurface(tmpSurf);
free(tmpBuff);
//Add it to texture manager so textures are freed on exit
gfx::texMgr->textureAdd(tex);
//Add it to cache map
glyphCache[std::make_pair(chr, size)] = {(uint16_t)bmp.width, (uint16_t)bmp.rows, (int)glyph->advance.x >> 6, glyph->bitmap_top, glyph->bitmap_left, tex};

124
src/gfx/textureMgr.cpp Normal file
View File

@ -0,0 +1,124 @@
#include <vector>
#include <algorithm>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include "gfx.h"
gfx::textureMgr::~textureMgr()
{
for(auto tex : textures)
SDL_DestroyTexture(tex);
}
void gfx::textureMgr::textureAdd(SDL_Texture *_tex)
{
textures.push_back(_tex);
}
SDL_Texture *gfx::textureMgr::textureCreate(int _w, int _h)
{
SDL_Texture *ret = NULL;
ret = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, _w, _h);
if(ret)
{
SDL_SetTextureBlendMode(ret, SDL_BLENDMODE_BLEND);
textures.push_back(ret);
}
return ret;
}
SDL_Texture *gfx::textureMgr::textureLoadFromFile(const char *_path)
{
SDL_Texture *ret = NULL;
SDL_Surface *tmp = IMG_Load(_path);
if(tmp)
{
ret = SDL_CreateTextureFromSurface(gfx::render, tmp);
textures.push_back(ret);
SDL_FreeSurface(tmp);
SDL_SetTextureBlendMode(ret, SDL_BLENDMODE_BLEND);
}
return ret;
}
static SDL_Texture *loadPNGMem(const void *_dat, size_t _datSize)
{
SDL_Texture *ret = NULL;
SDL_RWops *pngData = SDL_RWFromConstMem(_dat, _datSize);
SDL_Surface *tmp = IMG_LoadPNG_RW(pngData);
if(tmp)
{
ret = SDL_CreateTextureFromSurface(gfx::render, tmp);
SDL_FreeSurface(tmp);
SDL_SetTextureBlendMode(ret, SDL_BLENDMODE_BLEND);
}
SDL_RWclose(pngData);
return ret;
}
static SDL_Texture *loadJPEGMem(const void *_dat, size_t _datSize)
{
SDL_Texture *ret = NULL;
SDL_RWops *jpegData = SDL_RWFromConstMem(_dat, _datSize);
SDL_Surface *tmp = IMG_LoadJPG_RW(jpegData);
if(tmp)
{
ret = SDL_CreateTextureFromSurface(gfx::render, tmp);
SDL_FreeSurface(tmp);
SDL_SetTextureBlendMode(ret, SDL_BLENDMODE_BLEND);
}
SDL_RWclose(jpegData);
return ret;
}
static SDL_Texture *loadBMPMem(const void *_dat, size_t _datSize)
{
SDL_Texture *ret = NULL;
SDL_RWops *bmpData = SDL_RWFromConstMem(_dat, _datSize);
SDL_Surface *tmp = IMG_LoadBMP_RW(bmpData);
if(tmp)
{
ret = SDL_CreateTextureFromSurface(gfx::render, tmp);
SDL_FreeSurface(tmp);
SDL_SetTextureBlendMode(ret, SDL_BLENDMODE_BLEND);
}
SDL_RWclose(bmpData);
return ret;
}
SDL_Texture *gfx::textureMgr::textureLoadFromMem(imgTypes _type, const void *_dat, size_t _datSize)
{
SDL_Texture *ret = NULL;
switch (_type)
{
case IMG_FMT_PNG:
ret = loadPNGMem(_dat, _datSize);
break;
case IMG_FMT_JPG:
ret = loadJPEGMem(_dat, _datSize);
break;
case IMG_FMT_BMP:
ret = loadBMPMem(_dat, _datSize);
break;
}
if(ret)
textures.push_back(ret);
return ret;
}
void gfx::textureMgr::textureResize(SDL_Texture **_tex, int _w, int _h)
{
auto texIt = std::find(textures.begin(), textures.end(), *_tex);
if(texIt != textures.end())
{
SDL_DestroyTexture(*texIt);
*_tex = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, _w, _h);
SDL_SetTextureBlendMode(*_tex, SDL_BLENDMODE_BLEND);
*texIt = *_tex;
}
}

View File

@ -1,4 +1,5 @@
#include <switch.h>
#include <curl/curl.h>
#include "gfx.h"
#include "file.h"
@ -49,11 +50,18 @@ int main(int argc, const char *argv[])
ui::init();
romfsExit();
curl_global_init(CURL_GLOBAL_ALL);
//Drive needs config read
if(!util::isApplet())
fs::remoteInit();
else
ui::showMessage(ui::getUICString("appletModeWarning", 0));
while(ui::runApp()){ }
cfg::saveConfig();
fs::remoteExit();
curl_global_cleanup();
ui::exit();
data::exit();
gfx::exit();
fs::exit();
}

51
src/rfs.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "rfs.h"
std::vector<uint8_t> rfs::downloadBuffer;
void rfs::writeThread_t(void *a)
{
rfs::dlWriteThreadStruct *in = (rfs::dlWriteThreadStruct *)a;
std::vector<uint8_t> localBuff;
unsigned written = 0;
FILE *out = fopen(in->cfa->path.c_str(), "wb");
while(written < in->cfa->size)
{
std::unique_lock<std::mutex> dataLock(in->dataLock);
in->cond.wait(dataLock, [in]{ return in->bufferFull; });
localBuff.clear();
localBuff.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
in->sharedBuffer.clear();
in->bufferFull = false;
dataLock.unlock();
in->cond.notify_one();
written += fwrite(localBuff.data(), 1, localBuff.size(), out);
}
fclose(out);
rfs::downloadBuffer.clear();
}
size_t rfs::writeDataBufferThreaded(uint8_t *buff, size_t sz, size_t cnt, void *u)
{
rfs::dlWriteThreadStruct *in = (rfs::dlWriteThreadStruct *)u;
rfs::downloadBuffer.insert(rfs::downloadBuffer.end(), buff, buff + (sz * cnt));
in->downloaded += sz * cnt;
if(in->downloaded == in->cfa->size || rfs::downloadBuffer.size() == DOWNLOAD_BUFFER_SIZE)
{
std::unique_lock<std::mutex> dataLock(in->dataLock);
in->cond.wait(dataLock, [in]{ return in->bufferFull == false; });
in->sharedBuffer.assign(rfs::downloadBuffer.begin(), rfs::downloadBuffer.end());
rfs::downloadBuffer.clear();
in->bufferFull = true;
dataLock.unlock();
in->cond.notify_one();
}
if(in->cfa->o)
*in->cfa->o = in->downloaded;
return sz * cnt;
}

View File

@ -63,6 +63,12 @@ const std::string ui::loadGlyphArray[] =
void ui::initTheme()
{
uint64_t lang;
setGetSystemLanguage(&lang);
setMakeLanguage(lang, &data::sysLang);
loadTrans();
setsysGetColorSetId(&thmID);
switch(thmID)
@ -96,10 +102,10 @@ void ui::initTheme()
void ui::init()
{
mnuTopLeft = gfx::loadImageFile("romfs:/img/fb/menuTopLeft.png");
mnuTopRight = gfx::loadImageFile("romfs:/img/fb/menuTopRight.png");
mnuBotLeft = gfx::loadImageFile("romfs:/img/fb/menuBotLeft.png");
mnuBotRight = gfx::loadImageFile("romfs:/img/fb/menuBotRight.png");
mnuTopLeft = gfx::texMgr->textureLoadFromFile("romfs:/img/fb/menuTopLeft.png");
mnuTopRight = gfx::texMgr->textureLoadFromFile("romfs:/img/fb/menuTopRight.png");
mnuBotLeft = gfx::texMgr->textureLoadFromFile("romfs:/img/fb/menuBotLeft.png");
mnuBotRight = gfx::texMgr->textureLoadFromFile("romfs:/img/fb/menuBotRight.png");
corePanel = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET | SDL_TEXTUREACCESS_STATIC, 1220, 559);
SDL_SetTextureBlendMode(corePanel, SDL_BLENDMODE_BLEND);
@ -108,59 +114,45 @@ void ui::init()
{
case ColorSetId_Light:
//Dark corners
cornerTopLeft = gfx::loadImageFile("romfs:/img/tboxLght/tboxCornerTopLeft.png");
cornerTopRight = gfx::loadImageFile("romfs:/img/tboxLght/tboxCornerTopRight.png");
cornerBottomLeft = gfx::loadImageFile("romfs:/img/tboxLght/tboxCornerBotLeft.png");
cornerBottomRight = gfx::loadImageFile("romfs:/img/tboxLght/tboxCornerBotRight.png");
progCovLeft = gfx::loadImageFile("romfs:/img/tboxDrk/progBarCoverLeftDrk.png");
progCovRight = gfx::loadImageFile("romfs:/img/tboxDrk/progBarCoverRightDrk.png");
icn = gfx::loadImageFile("romfs:/img/icn/icnDrk.png");
sideBar = gfx::loadImageFile("romfs:/img/fb/lLight.png");
cornerTopLeft = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxLght/tboxCornerTopLeft.png");
cornerTopRight = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxLght/tboxCornerTopRight.png");
cornerBottomLeft = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxLght/tboxCornerBotLeft.png");
cornerBottomRight = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxLght/tboxCornerBotRight.png");
progCovLeft = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxDrk/progBarCoverLeftDrk.png");
progCovRight = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxDrk/progBarCoverRightDrk.png");
icn = gfx::texMgr->textureLoadFromFile("romfs:/img/icn/icnDrk.png");
sideBar = gfx::texMgr->textureLoadFromFile("romfs:/img/fb/lLight.png");
break;
default:
//Light corners
cornerTopLeft = gfx::loadImageFile("romfs:/img/tboxDrk/tboxCornerTopLeft.png");
cornerTopRight = gfx::loadImageFile("romfs:/img/tboxDrk/tboxCornerTopRight.png");
cornerBottomLeft = gfx::loadImageFile("romfs:/img/tboxDrk/tboxCornerBotLeft.png");
cornerBottomRight = gfx::loadImageFile("romfs:/img/tboxDrk/tboxCornerBotRight.png");
progCovLeft = gfx::loadImageFile("romfs:/img/tboxLght/progBarCoverLeftLight.png");
progCovRight = gfx::loadImageFile("romfs:/img/tboxLght/progBarCoverRightLight.png");
icn = gfx::loadImageFile("romfs:/img/icn/icnLght.png");
sideBar = gfx::loadImageFile("romfs:/img/fb/lDark.png");
cornerTopLeft = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxDrk/tboxCornerTopLeft.png");
cornerTopRight = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxDrk/tboxCornerTopRight.png");
cornerBottomLeft = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxDrk/tboxCornerBotLeft.png");
cornerBottomRight = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxDrk/tboxCornerBotRight.png");
progCovLeft = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxLght/progBarCoverLeftLight.png");
progCovRight = gfx::texMgr->textureLoadFromFile("romfs:/img/tboxLght/progBarCoverRightLight.png");
icn = gfx::texMgr->textureLoadFromFile("romfs:/img/icn/icnLght.png");
sideBar = gfx::texMgr->textureLoadFromFile("romfs:/img/fb/lDark.png");
break;
}
loadTrans();
//Replace the button [x] in strings that need it. Needs to be outside loadTrans so even defaults will get replaced
util::replaceButtonsInString(ui::strings[std::make_pair("helpUser", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("helpTitle", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("helpFolder", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("helpSettings", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("dialogYes", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("dialogNo", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("dialogOK", 0)]);
//setup pad
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
padInitializeDefault(&ui::pad);
//Setup touch
hidInitializeTouchScreen();
ui::usrInit();
ui::ttlInit();
ui::settInit();
ui::extInit();
ui::fmInit();
ui::fldInit();
popMessages = new ui::popMessageMngr;
threadMngr = new ui::threadProcMngr;
//Need these from user/main menu
settPos = ui::usrMenu->getOptPos("Settings");
extPos = ui::usrMenu->getOptPos("Extras");
settPos = ui::usrMenu->getOptPos(ui::getUICString("mainMenuSettings", 0));
extPos = ui::usrMenu->getOptPos(ui::getUICString("mainMenuExtras", 0));
}
void ui::exit()
@ -170,25 +162,10 @@ void ui::exit()
ui::settExit();
ui::extExit();
ui::fmExit();
ui::fldExit();
delete popMessages;
delete threadMngr;
SDL_DestroyTexture(cornerTopLeft);
SDL_DestroyTexture(cornerTopRight);
SDL_DestroyTexture(cornerBottomLeft);
SDL_DestroyTexture(cornerBottomRight);
SDL_DestroyTexture(progCovLeft);
SDL_DestroyTexture(progCovRight);
SDL_DestroyTexture(mnuTopLeft);
SDL_DestroyTexture(mnuTopRight);
SDL_DestroyTexture(mnuBotLeft);
SDL_DestroyTexture(mnuBotRight);
SDL_DestroyTexture(corePanel);
SDL_DestroyTexture(icn);
}
int ui::registerPanel(slideOutPanel *sop)
@ -204,12 +181,11 @@ threadInfo *ui::newThread(ThreadFunc func, void *args, funcPtr _drawFunc)
void ui::showLoadScreen()
{
SDL_Texture *icon = gfx::loadImageFile("romfs:/icon.png");
SDL_Texture *icon = gfx::texMgr->textureLoadFromFile("romfs:/icon.png");
gfx::clearTarget(NULL, &ui::clearClr);
gfx::texDraw(NULL, icon, 512, 232);
gfx::drawTextf(NULL, 16, 1100, 673, &ui::txtCont, "Loading...");
gfx::drawTextf(NULL, 16, 1100, 673, &ui::txtCont, ui::getUICString("loadingStartPage", 0));
gfx::present();
SDL_DestroyTexture(icon);
}
void ui::drawUI()
@ -220,12 +196,16 @@ void ui::drawUI()
gfx::drawLine(NULL, &divClr, 30, 88, 1250, 88);
gfx::drawLine(NULL, &divClr, 30, 648, 1250, 648);
gfx::texDraw(NULL, icn, 66, 27);
gfx::drawTextf(NULL, 24, 130, 38, &ui::txtCont, "JKSV");
if(util::isApplet())
gfx::drawTextf(NULL, 24, 130, 38, &ui::txtCont, "JKSV *APPLET MODE*");
else
gfx::drawTextf(NULL, 24, 130, 38, &ui::txtCont, "JKSV");
//Version / translation author
gfx::drawTextf(NULL, 12, 8, 700, &ui::txtCont, "v. %02d.%02d.%04d", BLD_MON, BLD_DAY, BLD_YEAR);
if(ui::getUIString("author", 0) != "NULL")
gfx::drawTextf(NULL, 12, 8, 682, &ui::txtCont, "Translation: %s", ui::getUICString("author", 0));
gfx::drawTextf(NULL, 12, 8, 682, &ui::txtCont, "%s%s", ui::getUICString("translationMainPage", 0), ui::getUICString("author", 0));
//This only draws the help text now and only does when user select is open
ui::usrDraw(NULL);
@ -317,7 +297,7 @@ void ui::toTTL(void *a)
if(u->titleInfo.size() > 0)
{
ui::changeState(TTL_SEL);
ui::ttlSetActive(curUserIndex);
ui::ttlSetActive(curUserIndex, true, true);
ui::usrMenu->setActive(false);
}
else

View File

@ -1,5 +1,5 @@
#include <switch.h>
#include <SDL.h>
#include <SDL2/SDL.h>
#include "ui.h"
#include "file.h"
@ -28,7 +28,7 @@ static inline void closeUserPanel()
static void toFMSDtoSD(void *a)
{
ui::fmPrep(FsSaveDataType_Account, "sdmc:/", false);
ui::fmPrep(FsSaveDataType_Account, "sdmc:/", "sdmc:/", false);
closeUserPanel();
ui::changeState(FIL_MDE);
}
@ -39,7 +39,7 @@ static void toFMProdInfoF(void *a)
fsOpenBisFileSystem(&prodf, FsBisPartitionId_CalibrationFile, "");
fsdevMountDevice("prod-f", prodf);
closeUserPanel();
ui::fmPrep(FsSaveDataType_System, "prod-f:/", false);
ui::fmPrep(FsSaveDataType_System, "prod-f:/", "sdmc:/", false);
ui::changeState(FIL_MDE);
}
@ -49,7 +49,7 @@ static void toFMSafe(void *a)
fsOpenBisFileSystem(&safe, FsBisPartitionId_SafeMode, "");
fsdevMountDevice("safe", safe);
closeUserPanel();
ui::fmPrep(FsSaveDataType_System, "safe:/", false);
ui::fmPrep(FsSaveDataType_System, "safe:/", "sdmc:/", false);
ui::changeState(FIL_MDE);
}
@ -59,7 +59,7 @@ static void toFMSystem(void *a)
fsOpenBisFileSystem(&sys, FsBisPartitionId_System, "");
fsdevMountDevice("sys", sys);
closeUserPanel();
ui::fmPrep(FsSaveDataType_System, "sys:/", false);
ui::fmPrep(FsSaveDataType_System, "sys:/", "sdmc:/", false);
ui::changeState(FIL_MDE);
}
@ -69,7 +69,7 @@ static void toFMUser(void *a)
fsOpenBisFileSystem(&user, FsBisPartitionId_User, "");
fsdevMountDevice("user", user);
closeUserPanel();
ui::fmPrep(FsSaveDataType_System, "user:/", false);
ui::fmPrep(FsSaveDataType_System, "user:/", "sdmc:/", false);
ui::changeState(FIL_MDE);
}
@ -109,7 +109,7 @@ static void extMenuMountSysSave(void *a)
if(R_SUCCEEDED(fsOpen_SystemSaveData(&sys, FsSaveDataSpaceId_System, mountID, (AccountUid) {0})))
{
fsdevMountDevice("sv", sys);
ui::fmPrep(FsSaveDataType_System, "sv:/", true);
ui::fmPrep(FsSaveDataType_System, "sv:/", "sdmc:/", true);
ui::usrSelPanel->closePanel();
ui::changeState(FIL_MDE);
}
@ -129,8 +129,8 @@ static void extMenuMountRomFS(void *a)
if(R_SUCCEEDED(fsOpenDataFileSystemByCurrentProcess(&tromfs)))
{
fsdevMountDevice("tromfs", tromfs);
ui::fmPrep(FsSaveDataType_System, "tromfs:/", false);
ui::usrSelPanel->closePanel();
ui::fmPrep(FsSaveDataType_System, "tromfs:/", "sdmc:/", false);
ui::usrSelPanel->closePanel();
ui::changeState(FIL_MDE);
}
}
@ -142,13 +142,12 @@ static void extMenuPackJKSVZip_t(void *a)
t->status->setStatus(ui::getUICString("threadStatusPackingJKSV", 0));
if(cfg::config["ovrClk"])
{
util::setCPU(util::CPU_SPEED_1785MHz);
util::sysBoost();
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popCPUBoostEnabled", 0));
}
fs::dirList *jksv = new fs::dirList(fs::getWorkDir());
uint8_t pathTrimPlaces = util::getTotalPlacesInPath(fs::getWorkDir());
fs::logWrite("pathTrimPlaces = %u\n", pathTrimPlaces);
size_t dirCount = jksv->getCount();
if(dirCount > 0)
{
@ -159,24 +158,13 @@ static void extMenuPackJKSVZip_t(void *a)
if(jksv->isDir(i) && jksv->getItem(i) != "_TRASH_")
{
std::string srcPath = fs::getWorkDir() + jksv->getItem(i) + "/";
threadInfo *fakeThread = new threadInfo;
fs::copyArgs *tmpArgs = new fs::copyArgs;
tmpArgs->from = srcPath;
tmpArgs->z = zip;
tmpArgs->prog = c->prog;
tmpArgs->trimZipPath = true;
tmpArgs->trimZipPlaces = pathTrimPlaces;
fakeThread->argPtr = tmpArgs;
fakeThread->status = t->status;
fs::copyDirToZip_t(fakeThread);
delete tmpArgs;
delete fakeThread;
fs::copyDirToZip(srcPath, zip, true, pathTrimPlaces, t);
}
}
zipClose(zip, NULL);
}
if(cfg::config["ovrClk"])
util::setCPU(util::CPU_SPEED_1224MHz);
util::sysNormal();
delete jksv;
delete c;
@ -187,12 +175,12 @@ static void extMenuPackJKSV(void *a)
{
//Only for progress bar
fs::copyArgs *send = fs::copyArgsCreate("", "", "", NULL, NULL, true, false, 0);
ui::newThread(extMenuPackJKSVZip_t, send, fs::_fileDrawFunc);
ui::newThread(extMenuPackJKSVZip_t, send, fs::fileDrawFunc);
}
static void extMenuOutputEnUs(void *a)
{
ui::newThread(ui::saveTranslationFile, NULL, NULL);
ui::newThread(ui::saveTranslationFiles, NULL, NULL);
}
void ui::extInit()

391
src/ui/fld.cpp Normal file
View File

@ -0,0 +1,391 @@
#include <switch.h>
#include "ui.h"
#include "fs.h"
#include "util.h"
#include "cfg.h"
static ui::menu *fldMenu = NULL;
ui::slideOutPanel *ui::fldPanel = NULL;
static fs::dirList *fldList = NULL;
static SDL_Texture *fldBuffer;
static unsigned int fldGuideWidth = 0;
static Mutex fldLock = 0;
static std::string driveParent;
static std::vector<rfs::RfsItem> driveFldList;
static void fldMenuCallback(void *a)
{
switch(ui::padKeysDown())
{
case HidNpadButton_B:
fs::unmountSave();
fs::freePathFilters();
fldMenu->setActive(false);
ui::fldPanel->closePanel();
unsigned cusr = data::getCurrentUserIndex();
ui::ttlSetActive(cusr, true, true);
ui::updateInput();
break;
}
}
static void fldPanelDraw(void *a)
{
mutexLock(&fldLock);
SDL_Texture *target = (SDL_Texture *)a;
gfx::clearTarget(fldBuffer, &ui::slidePanelColor);
fldMenu->draw(fldBuffer, &ui::txtCont, true);
gfx::texDraw(target, fldBuffer, 0, 0);
gfx::drawLine(target, &ui::divClr, 10, 648, fldGuideWidth + 54, 648);
gfx::drawTextf(target, 18, 32, 673, &ui::txtCont, ui::getUICString("helpFolder", 0));
mutexUnlock(&fldLock);
}
static void fldFuncCancel(void *a)
{
std::string *del = (std::string *)a;
delete del;
}
static void fldFuncOverwrite(void *a)
{
fs::dirItem *in = (fs::dirItem *)a;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
std::string *send = new std::string;
send->assign(util::generatePathByTID(utinfo->tid) + in->getItm());
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdOver"], fs::overwriteBackup, fldFuncCancel, send, ui::getUICString("confirmOverwrite", 0), in->getItm().c_str());
ui::confirm(conf);
}
static void fldFuncDelete(void *a)
{
fs::dirItem *in = (fs::dirItem *)a;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
std::string *send = new std::string;
send->assign(util::generatePathByTID(utinfo->tid) + in->getItm());
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdDel"], fs::deleteBackup, fldFuncCancel, send, ui::getUICString("confirmDelete", 0), in->getItm().c_str());
ui::confirm(conf);
}
static void fldFuncRestore(void *a)
{
fs::dirItem *in = (fs::dirItem *)a;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
std::string *send = new std::string;
send->assign(util::generatePathByTID(utinfo->tid) + in->getItm());
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdRest"], fs::restoreBackup, fldFuncCancel, send, ui::getUICString("confirmRestore", 0), in->getItm().c_str());
ui::confirm(conf);
}
static void fldFuncUpload_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::dirItem *di = (fs::dirItem *)t->argPtr;
fsSetPriority(FsPriority_Realtime);
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
std::string path, tmpZip, filename;//Final path to upload from
if(cfg::config["ovrClk"])
util::sysBoost();
//Zip first then upload if folder based backup
if(di->isDir())
{
t->status->setStatus(ui::getUICString("threadStatusCompressingSaveForUpload", 0), di->getItm().c_str());
filename = di->getItm() + ".zip";
tmpZip = util::generatePathByTID(utinfo->tid) + di->getItm() + ".zip";
std::string fldPath = util::generatePathByTID(utinfo->tid) + di->getItm() + "/";
int zipTrim = util::getTotalPlacesInPath(fs::getWorkDir()) + 2;//Trim path down to save root
zipFile tmp = zipOpen64(tmpZip.c_str(), 0);
fs::copyDirToZip(fldPath, tmp, true, zipTrim, NULL);
zipClose(tmp, NULL);
path = tmpZip;
}
else
{
filename = di->getItm();
path = util::generatePathByTID(utinfo->tid) + di->getItm();
}
//Change thread stuff so upload status can be shown
t->status->setStatus(ui::getUICString("threadStatusUploadingFile", 0), di->getItm().c_str());
fs::copyArgs *cpyArgs = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0);
cpyArgs->prog->setMax(fs::fsize(path));
cpyArgs->prog->update(0);
t->argPtr = cpyArgs;
t->drawFunc = fs::fileDrawFunc;
//curlDlArgs
curlFuncs::curlUpArgs upload;
upload.f = fopen(path.c_str(), "rb");
upload.o = &cpyArgs->offset;
if(fs::rfs->fileExists(filename, driveParent))
{
std::string id = fs::rfs->getFileID(filename, driveParent);
fs::rfs->updateFile(id, &upload);
}
else
fs::rfs->uploadFile(filename, driveParent, &upload);
fclose(upload.f);
if(!tmpZip.empty())
fs::delfile(tmpZip);
fs::copyArgsDestroy(cpyArgs);
t->drawFunc = NULL;
if(cfg::config["ovrClk"])
util::sysNormal();
ui::fldRefreshMenu();
t->finished = true;
}
static void fldFuncUpload(void *a)
{
if(fs::rfs)
ui::newThread(fldFuncUpload_t, a, NULL);
else
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popRemoteNotActive", 0));
}
static void fldFuncDownload_t(void *a)
{
threadInfo *t = (threadInfo *)a;
rfs::RfsItem *in = (rfs::RfsItem *)t->argPtr;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
std::string targetPath = util::generatePathByTID(utinfo->tid) + in->name;
t->status->setStatus(ui::getUICString("threadStatusDownloadingFile", 0), in->name.c_str());
if(cfg::config["ovrClk"])
util::sysBoost();
if(fs::fileExists(targetPath))
fs::delfile(targetPath);
//Use this for progress bar
fs::copyArgs *cpy = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0);
cpy->prog->setMax(in->size);
cpy->prog->update(0);
t->argPtr = cpy;
t->drawFunc = fs::fileDrawFunc;
//DL struct
curlFuncs::curlDlArgs dlFile;
dlFile.path = targetPath;
dlFile.size = in->size;
dlFile.o = &cpy->offset;
fs::rfs->downloadFile(in->id, &dlFile);
//fclose(dlFile.f);
fs::copyArgsDestroy(cpy);
t->drawFunc = NULL;
if(cfg::config["ovrClk"])
util::sysNormal();
ui::fldRefreshMenu();
t->finished = true;
}
static void fldFuncDownload(void *a)
{
rfs::RfsItem *in = (rfs::RfsItem *)a;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
std::string testPath = util::generatePathByTID(utinfo->tid) + in->name;
if(fs::fileExists(testPath))
{
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdOver"], fldFuncDownload_t, NULL, a, ui::getUICString("confirmDriveOverwrite", 0));
ui::confirm(conf);
}
else
ui::newThread(fldFuncDownload_t, a, NULL);
}
static void fldFuncDriveDelete_t(void *a)
{
threadInfo *t = (threadInfo *)a;
rfs::RfsItem *gdi = (rfs::RfsItem *)t->argPtr;
t->status->setStatus(ui::getUICString("threadStatusDeletingFile", 0));
fs::rfs->deleteFile(gdi->id);
ui::fldRefreshMenu();
t->finished = true;
}
static void fldFuncDriveDelete(void *a)
{
rfs::RfsItem *in = (rfs::RfsItem *)a;
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdDel"], fldFuncDriveDelete_t, NULL, a, ui::getUICString("confirmDelete", 0), in->name.c_str());
ui::confirm(conf);
}
static void fldFuncDriveRestore_t(void *a)
{
threadInfo *t = (threadInfo *)a;
rfs::RfsItem *gdi = (rfs::RfsItem *)t->argPtr;
t->status->setStatus(ui::getUICString("threadStatusDownloadingFile", 0), gdi->name.c_str());
fs::copyArgs *cpy = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0);
cpy->prog->setMax(gdi->size);
cpy->prog->update(0);
t->argPtr = cpy;
t->drawFunc = fs::fileDrawFunc;
curlFuncs::curlDlArgs dlFile;
dlFile.path = "sdmc:/tmp.zip";
dlFile.size = gdi->size;
dlFile.o = &cpy->offset;
fs::rfs->downloadFile(gdi->id, &dlFile);
unzFile tmp = unzOpen64("sdmc:/tmp.zip");
fs::copyZipToDir(tmp, "sv:/", "sv", t);
unzClose(tmp);
fs::delfile("sdmc:/tmp.zip");
fs::copyArgsDestroy(cpy);
t->argPtr = NULL;
t->drawFunc = NULL;
t->finished = true;
}
static void fldFuncDriveRestore(void *a)
{
rfs::RfsItem *in = (rfs::RfsItem *)a;
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdOver"], fldFuncDriveRestore_t, NULL, a, ui::getUICString("confirmRestore", 0), in->name.c_str());
ui::confirm(conf);
}
void ui::fldInit()
{
fldGuideWidth = gfx::getTextWidth(ui::getUICString("helpFolder", 0), 18);
fldMenu = new ui::menu(10, 4, fldGuideWidth + 44, 18, 6);
fldMenu->setCallback(fldMenuCallback, NULL);
fldMenu->setActive(false);
ui::fldPanel = new ui::slideOutPanel(fldGuideWidth + 64, 720, 0, ui::SLD_RIGHT, fldPanelDraw);
fldBuffer = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, fldGuideWidth + 64, 647);
ui::registerPanel(fldPanel);
fldList = new fs::dirList;
}
void ui::fldExit()
{
delete ui::fldPanel;
delete fldMenu;
delete fldList;
}
void ui::fldUpdate()
{
fldMenu->update();
}
void ui::fldPopulateMenu()
{
mutexLock(&fldLock);
fldMenu->reset();
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
data::titleInfo *t = data::getTitleInfoByTID(d->tid);
util::createTitleDirectoryByTID(d->tid);
std::string targetDir = util::generatePathByTID(d->tid);
fldList->reassign(targetDir);
fs::loadPathFilters(d->tid);
fldMenu->addOpt(NULL, ui::getUICString("folderMenuNew", 0));
fldMenu->optAddButtonEvent(0, HidNpadButton_A, fs::createNewBackup, NULL);
unsigned fldInd = 1;
if(fs::rfs)
{
if(!fs::rfs->dirExists(t->safeTitle, fs::rfsRootID))
fs::rfs->createDir(t->safeTitle, fs::rfsRootID);
driveParent = fs::rfs->getDirID(t->safeTitle, fs::rfsRootID);
driveFldList = fs::rfs->getListWithParent(driveParent);
for(unsigned i = 0; i < driveFldList.size(); i++, fldInd++)
{
fldMenu->addOpt(NULL, "[R] " + driveFldList[i].name);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_A, fldFuncDownload, &driveFldList[i]);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_X, fldFuncDriveDelete, &driveFldList[i]);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_Y, fldFuncDriveRestore, &driveFldList[i]);
}
}
for(unsigned i = 0; i < fldList->getCount(); i++, fldInd++)
{
fs::dirItem *di = fldList->getDirItemAt(i);
fldMenu->addOpt(NULL, di->getItm());
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_A, fldFuncOverwrite, di);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_X, fldFuncDelete, di);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_Y, fldFuncRestore, di);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_ZR, fldFuncUpload, di);
}
fldMenu->setActive(true);
ui::fldPanel->openPanel();
mutexUnlock(&fldLock);
}
void ui::fldRefreshMenu()
{
mutexLock(&fldLock);
fldMenu->reset();
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
std::string targetDir = util::generatePathByTID(utinfo->tid);
fldList->reassign(targetDir);
fldMenu->addOpt(NULL, ui::getUIString("folderMenuNew", 0));
fldMenu->optAddButtonEvent(0, HidNpadButton_A, fs::createNewBackup, NULL);
unsigned fldInd = 1;
if(fs::rfs)
{
driveFldList = fs::rfs->getListWithParent(driveParent);
for(unsigned i = 0; i < driveFldList.size(); i++, fldInd++)
{
fldMenu->addOpt(NULL, "[R] " + driveFldList[i].name);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_A, fldFuncDownload, &driveFldList[i]);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_X, fldFuncDriveDelete, &driveFldList[i]);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_Y, fldFuncDriveRestore, &driveFldList[i]);
}
}
for(unsigned i = 0; i < fldList->getCount(); i++, fldInd++)
{
fs::dirItem *di = fldList->getDirItemAt(i);
fldMenu->addOpt(NULL, di->getItm());
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_A, fldFuncOverwrite, di);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_X, fldFuncDelete, di);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_Y, fldFuncRestore, di);
fldMenu->optAddButtonEvent(fldInd, HidNpadButton_ZR, fldFuncUpload, di);
}
mutexUnlock(&fldLock);
}

View File

@ -1,11 +1,11 @@
#include <string>
#include "ui.h"
#include "file.h"
#include "util.h"
#include "fm.h"
#include "cfg.h"
#include "file.h"
#include "fm.h"
#include "type.h"
#include "ui.h"
#include "util.h"
//This is going to be a mess but the old one was too.
//It gets more complicated because of system saves and committing.
@ -16,9 +16,9 @@
//Struct to hold info so i don't have to if else if if if
typedef struct
{
std::string *path;
//These can hold the same info needed for the listing menus so might as well use em
fs::backupArgs *b;
std::string *path;
ui::menu *m;
fs::dirList *d;
} menuFuncArgs;
static ui::slideOutPanel *devPanel, *sdPanel;
@ -37,13 +37,14 @@ static void refreshMenu(void *a)
{
threadInfo *t = (threadInfo *)a;
menuFuncArgs *ma = (menuFuncArgs *)t->argPtr;
fs::backupArgs *b = ma->b;
ui::menu *m = ma->m;
fs::dirList *d = ma->d;
b->d->reassign(*ma->path);
util::copyDirListToMenu(*b->d, *b->m);
for(int i = 1; i < b->m->getCount(); i++)
d->reassign(*ma->path);
util::copyDirListToMenu(*d, *m);
for (int i = 1; i < m->getCount(); i++)
{
b->m->optAddButtonEvent(i, HidNpadButton_A, _listFunctionA, ma);
m->optAddButtonEvent(i, HidNpadButton_A, _listFunctionA, ma);
}
t->finished = true;
}
@ -52,10 +53,10 @@ static void refreshMenu(void *a)
static void _devMenuCallback(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_B:
if(*ma->path != dev && *ma->path != "sdmc:/")
if (*ma->path != dev && *ma->path != "sdmc:/")
{
util::removeLastFolderFromString(*ma->path);
threadInfo fake;
@ -74,7 +75,7 @@ static void _devMenuCallback(void *a)
static void _devCopyMenuCallback(void *a)
{
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_B:
devCopyMenu->setActive(false);
@ -87,10 +88,10 @@ static void _devCopyMenuCallback(void *a)
static void _sdMenuCallback(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_B:
if(*ma->path != dev && *ma->path != "sdmc:/")
if (*ma->path != dev && *ma->path != "sdmc:/")
{
util::removeLastFolderFromString(*ma->path);
threadInfo fake;
@ -109,7 +110,7 @@ static void _sdMenuCallback(void *a)
static void _sdCopyMenuCallback(void *a)
{
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_B:
sdCopyMenu->setActive(false);
@ -135,83 +136,83 @@ static void _sdCopyPanelDraw(void *a)
static void _listFunctionA(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
fs::backupArgs *b = ma->b;
ui::menu *m = ma->m;
fs::dirList *d = ma->d;
int sel = b->m->getSelected();
bool isDir = b->d->isDir(sel - 2);
if(sel == 1 && (*ma->path != dev && *ma->path != "sdmc:/"))
int sel = m->getSelected();
bool isDir = d->isDir(sel - 2);
if (sel == 1 && (*ma->path != dev && *ma->path != "sdmc:/"))
{
util::removeLastFolderFromString(*ma->path);
b->d->reassign(*ma->path);
util::copyDirListToMenu(*b->d, *b->m);
d->reassign(*ma->path);
util::copyDirListToMenu(*d, *m);
}
else if(sel > 1 && isDir)
else if (sel > 1 && isDir)
{
std::string addToPath = b->d->getItem(sel - 2);
std::string addToPath = d->getItem(sel - 2);
*ma->path += addToPath + "/";
b->d->reassign(*ma->path);
util::copyDirListToMenu(*b->d, *b->m);
d->reassign(*ma->path);
util::copyDirListToMenu(*d, *m);
}
for(int i = 1; i < b->m->getCount(); i++)
{
b->m->optAddButtonEvent(i, HidNpadButton_A, _listFunctionA, ma);
}
for (int i = 1; i < m->getCount(); i++)
m->optAddButtonEvent(i, HidNpadButton_A, _listFunctionA, ma);
}
static void _copyMenuCopy_t(void *a)
{
threadInfo *t = (threadInfo *)a;
menuFuncArgs *ma = (menuFuncArgs *)t->argPtr;
fs::backupArgs *b = ma->b;
ui::menu *m = ma->m;
fs::dirList *d = ma->d;
int sel = b->m->getSelected();
if(ma == devArgs)
int sel = m->getSelected();
if (ma == devArgs)
{
if(sel == 0)
fs::copyDirToDir(*ma->path, *sdmcArgs->path);
else if(sel > 1 && b->d->isDir(sel - 2))
if (sel == 0)
fs::copyDirToDirThreaded(*ma->path, *sdmcArgs->path);
else if (sel > 1 && d->isDir(sel - 2))
{
std::string srcPath = *ma->path + b->d->getItem(sel - 2) + "/";
std::string dstPath = *sdmcArgs->path + b->d->getItem(sel - 2) + "/";
std::string srcPath = *ma->path + d->getItem(sel - 2) + "/";
std::string dstPath = *sdmcArgs->path + d->getItem(sel - 2) + "/";
mkdir(dstPath.substr(0, dstPath.length() - 1).c_str(), 777);
fs::copyDirToDir(srcPath, dstPath);
fs::copyDirToDirThreaded(srcPath, dstPath);
}
else if(sel > 1)
else if (sel > 1)
{
std::string srcPath = *ma->path + b->d->getItem(sel - 2);
std::string dstPath = *sdmcArgs->path + b->d->getItem(sel - 2);
fs::copyFile(srcPath, dstPath);
std::string srcPath = *ma->path + d->getItem(sel - 2);
std::string dstPath = *sdmcArgs->path + d->getItem(sel - 2);
fs::copyFileThreaded(srcPath, dstPath);
}
}
else if(ma == sdmcArgs)
else if (ma == sdmcArgs)
{
//I know
if(sel == 0)
if (sel == 0)
{
if(commit)
fs::copyDirToDirCommit(*ma->path, *devArgs->path, dev);
if (commit)
fs::copyDirToDirCommitThreaded(*ma->path, *devArgs->path, dev);
else
fs::copyDirToDir(*ma->path, *devArgs->path);
fs::copyDirToDirThreaded(*ma->path, *devArgs->path);
}
else if(sel > 1 && b->d->isDir(sel - 2))
else if (sel > 1 && d->isDir(sel - 2))
{
std::string srcPath = *ma->path + b->d->getItem(sel - 2) + "/";
std::string dstPath = *devArgs->path + b->d->getItem(sel - 2) + "/";
std::string srcPath = *ma->path + d->getItem(sel - 2) + "/";
std::string dstPath = *devArgs->path + d->getItem(sel - 2) + "/";
mkdir(dstPath.substr(0, dstPath.length() - 1).c_str(), 777);
if(commit)
fs::copyDirToDirCommit(srcPath, dstPath, dev);
if (commit)
fs::copyDirToDirCommitThreaded(srcPath, dstPath, dev);
else
fs::copyDirToDir(srcPath, dstPath);
fs::copyDirToDirThreaded(srcPath, dstPath);
}
else if(sel > 1)
else if (sel > 1)
{
std::string srcPath = *ma->path + b->d->getItem(sel - 2);
std::string dstPath = *devArgs->path + b->d->getItem(sel - 2);
if(commit)
fs::copyFileCommit(srcPath, dstPath, dev);
std::string srcPath = *ma->path + d->getItem(sel - 2);
std::string dstPath = *devArgs->path + d->getItem(sel - 2);
if (commit)
fs::copyFileCommitThreaded(srcPath, dstPath, dev);
else
fs::copyFile(srcPath, dstPath);
fs::copyFileThreaded(srcPath, dstPath);
}
}
ui::newThread(refreshMenu, devArgs, NULL);
@ -223,33 +224,41 @@ static void _copyMenuCopy_t(void *a)
static void _copyMenuCopy(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
fs::backupArgs *b = ma->b;
ui::menu *m = ma->m;
fs::dirList *d = ma->d;
std::string srcPath, dstPath;
int sel = b->m->getSelected();
if(sel == 0 && ma == devArgs)
int sel = m->getSelected();
if (sel == 0 && ma == devArgs)
{
srcPath = *ma->path;
dstPath = *sdmcArgs->path;
}
else if(sel > 1 && ma == devArgs)
else if (sel > 1 && ma == devArgs)
{
srcPath = *ma->path + b->d->getItem(sel - 2);
dstPath = *sdmcArgs->path + b->d->getItem(sel - 2);
srcPath = *ma->path + d->getItem(sel - 2);
dstPath = *sdmcArgs->path + d->getItem(sel - 2);
}
else if(sel == 0 && ma == sdmcArgs)
else if (sel == 0 && ma == sdmcArgs)
{
srcPath = *ma->path;
dstPath = *devArgs->path;
}
else if(sel > 1 && ma == sdmcArgs)
else if (sel > 1 && ma == sdmcArgs)
{
srcPath = *ma->path + b->d->getItem(b->m->getSelected() - 2);
dstPath = *devArgs->path + b->d->getItem(b->m->getSelected() - 2);
srcPath = *ma->path + d->getItem(m->getSelected() - 2);
dstPath = *devArgs->path + d->getItem(m->getSelected() - 2);
}
if(ma == devArgs || (ma == sdmcArgs && (type != FsSaveDataType_System || cfg::config["sysSaveWrite"])))
if (ma == devArgs || (ma == sdmcArgs && (type != FsSaveDataType_System || cfg::config["sysSaveWrite"])))
{
ui::confirmArgs *send = ui::confirmArgsCreate(false, _copyMenuCopy_t, ma, true, ui::getUICString("confirmCopy", 0), srcPath.c_str(), dstPath.c_str());
ui::confirmArgs *send = ui::confirmArgsCreate(false,
_copyMenuCopy_t,
NULL,
ma,
ui::getUICString("confirmCopy", 0),
srcPath.c_str(),
dstPath.c_str());
ui::confirm(send);
}
}
@ -258,46 +267,47 @@ static void _copyMenuDelete_t(void *a)
{
threadInfo *t = (threadInfo *)a;
menuFuncArgs *ma = (menuFuncArgs *)t->argPtr;
fs::backupArgs *b = ma->b;
ui::menu *m = ma->m;
fs::dirList *d = ma->d;
t->status->setStatus(ui::getUICString("threadStatusDeletingFile", 0));
int sel = b->m->getSelected();
if(ma == devArgs)
int sel = m->getSelected();
if (ma == devArgs)
{
if(sel == 0 && *ma->path != "sdmc:/")
if (sel == 0 && *ma->path != "sdmc:/")
{
fs::delDir(*ma->path);
if(commit)
if (commit)
fs::commitToDevice(dev);
}
else if(sel > 1 && b->d->isDir(sel - 2))
else if (sel > 1 && d->isDir(sel - 2))
{
std::string delPath = *ma->path + b->d->getItem(sel - 2) + "/";
std::string delPath = *ma->path + d->getItem(sel - 2) + "/";
fs::delDir(delPath);
if(commit)
if (commit)
fs::commitToDevice(dev);
}
else if(sel > 1)
else if (sel > 1)
{
std::string delPath = *ma->path + b->d->getItem(sel - 2);
std::string delPath = *ma->path + d->getItem(sel - 2);
fs::delfile(delPath);
if(commit)
if (commit)
fs::commitToDevice(dev);
}
}
else
{
if(sel == 0 && *ma->path != "sdmc:/")
if (sel == 0 && *ma->path != "sdmc:/")
fs::delDir(*ma->path);
else if(sel > 1 && b->d->isDir(sel - 2))
else if (sel > 1 && d->isDir(sel - 2))
{
std::string delPath = *ma->path + b->d->getItem(sel - 2) + "/";
std::string delPath = *ma->path + d->getItem(sel - 2) + "/";
fs::delDir(delPath);
}
else if(sel > 1)
else if (sel > 1)
{
std::string delPath = *ma->path + b->d->getItem(sel - 2);
std::string delPath = *ma->path + d->getItem(sel - 2);
fs::delfile(delPath);
}
}
@ -310,18 +320,25 @@ static void _copyMenuDelete_t(void *a)
static void _copyMenuDelete(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
fs::backupArgs *b = ma->b;
ui::menu *m = ma->m;
fs::dirList *d = ma->d;
int sel = b->m->getSelected();
int sel = m->getSelected();
std::string itmPath;
if(sel == 0)
if (sel == 0)
itmPath = *ma->path;
else if(sel > 1)
itmPath = *ma->path + b->d->getItem(sel - 2);
else if (sel > 1)
itmPath = *ma->path + d->getItem(sel - 2);
if(ma == sdmcArgs || (ma == devArgs && (sel == 0 || sel > 1) && (type != FsSaveDataType_System || cfg::config["sysSaveWrite"])))
if (ma == sdmcArgs ||
(ma == devArgs && (sel == 0 || sel > 1) && (type != FsSaveDataType_System || cfg::config["sysSaveWrite"])))
{
ui::confirmArgs *send = ui::confirmArgsCreate(cfg::config["holdDel"], _copyMenuDelete_t, a, true, ui::getUICString("confirmDelete", 0), itmPath.c_str());
ui::confirmArgs *send = ui::confirmArgsCreate(cfg::config["holdDel"],
_copyMenuDelete_t,
NULL,
a,
ui::getUICString("confirmDelete", 0),
itmPath.c_str());
ui::confirm(send);
}
}
@ -329,16 +346,18 @@ static void _copyMenuDelete(void *a)
static void _copyMenuRename(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
fs::backupArgs *b = ma->b;
ui::menu *m = ma->m;
fs::dirList *d = ma->d;
int sel = b->m->getSelected();
if(sel > 1)
int sel = m->getSelected();
if (sel > 1)
{
std::string getNewName = util::getStringInput(SwkbdType_QWERTY, b->d->getItem(sel - 2), ui::getUIString("swkbdRename", 0), 64, 0, NULL);
if(!getNewName.empty())
std::string getNewName =
util::getStringInput(SwkbdType_QWERTY, d->getItem(sel - 2), ui::getUIString("swkbdRename", 0), 64, 0, NULL);
if (!getNewName.empty())
{
std::string prevPath = *ma->path + b->d->getItem(sel - 2);
std::string newPath = *ma->path + getNewName;
std::string prevPath = *ma->path + d->getItem(sel - 2);
std::string newPath = *ma->path + getNewName;
rename(prevPath.c_str(), newPath.c_str());
}
threadInfo fake;
@ -352,8 +371,13 @@ static void _copyMenuRename(void *a)
static void _copyMenuMkDir(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
std::string getNewFolder = util::getStringInput(SwkbdType_QWERTY, "New Folder", ui::getUIString("swkbdMkDir", 0), 64, 0, NULL);
if(!getNewFolder.empty())
std::string getNewFolder = util::getStringInput(SwkbdType_QWERTY,
ui::getUIString("fileModeMenuMkDir", 0),
ui::getUIString("swkbdMkDir", 0),
64,
0,
NULL);
if (!getNewFolder.empty())
{
std::string createPath = *ma->path + getNewFolder;
mkdir(createPath.c_str(), 777);
@ -365,22 +389,44 @@ static void _copyMenuMkDir(void *a)
refreshMenu(&fake);
}
static void _copyMenuGetShowDirProps_t(void *a)
{
threadInfo *t = (threadInfo *)a;
std::string *p = (std::string *)t->argPtr;
unsigned dirCount = 0, fileCount = 0;
uint64_t totalSize = 0;
t->status->setStatus(ui::getUICString("threadStatusGetDirProps", 0));
fs::getDirProps(*p, dirCount, fileCount, totalSize);
ui::showMessage(ui::getUICString("fileModeFolderProperties", 0),
p->c_str(),
dirCount,
fileCount,
util::getSizeString(totalSize).c_str());
delete p;
t->finished = true;
}
static void _copyMenuGetProps(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
fs::backupArgs *b = (fs::backupArgs *)ma->b;
ui::menu *m = ma->m;
fs::dirList *d = ma->d;
int sel = b->m->getSelected();
if(sel == 0)
fs::getShowDirProps(*ma->path);
else if(sel > 1 && b->d->isDir(sel - 2))
int sel = m->getSelected();
if (sel == 0)
{
std::string folderPath = *ma->path + b->d->getItem(sel - 2) + "/";
fs::getShowDirProps(folderPath);
std::string *folderPath = new std::string(*ma->path);
ui::newThread(_copyMenuGetShowDirProps_t, folderPath, NULL);
}
else if(sel > 1)
else if (sel > 1 && d->isDir(sel - 2))
{
std::string filePath = *ma->path + b->d->getItem(sel - 2);
std::string *folderPath = new std::string;
folderPath->assign(*ma->path + d->getItem(sel - 2) + "/");
ui::newThread(_copyMenuGetShowDirProps_t, folderPath, NULL);
}
else if (sel > 1)
{
std::string filePath = *ma->path + d->getItem(sel - 2);
fs::getShowFileProps(filePath);
}
}
@ -388,7 +434,7 @@ static void _copyMenuGetProps(void *a)
static void _copyMenuClose(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
if(ma == devArgs)
if (ma == devArgs)
{
devCopyMenu->setActive(false);
devMenu->setActive(true);
@ -405,14 +451,15 @@ static void _copyMenuClose(void *a)
static void _devMenuAddToPathFilter(void *a)
{
menuFuncArgs *ma = (menuFuncArgs *)a;
fs::backupArgs *b = ma->b;
ui::menu *m = ma->m;
fs::dirList *d = ma->d;
int sel = b->m->getSelected();
if(sel > 1)
int sel = m->getSelected();
if (sel > 1)
{
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
std::string filterPath = *ma->path + b->d->getItem(sel - 2);
cfg::addPathToFilter(d->tid, filterPath);
data::userTitleInfo *tinfo = data::getCurrentUserTitleInfo();
std::string filterPath = *ma->path + d->getItem(sel - 2);
cfg::addPathToFilter(tinfo->tid, filterPath);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popAddedToPathFilter", 0), filterPath.c_str());
}
}
@ -421,24 +468,22 @@ void ui::fmInit()
{
//This needs to be done in a strange order so everything works
devArgs = new menuFuncArgs;
devArgs->b = new fs::backupArgs;
devArgs->path = &devPath;
sdmcArgs = new menuFuncArgs;
sdmcArgs->b = new fs::backupArgs;
sdmcArgs->path = &sdPath;
devMenu = new ui::menu(10, 8, 590, 18, 5);
devMenu->setCallback(_devMenuCallback, devArgs);
devMenu->setActive(true);
devArgs->b->m = devMenu;
devArgs->m = devMenu;
devPanel = new ui::slideOutPanel(288, 720, 0, ui::SLD_LEFT, _devCopyPanelDraw);
devCopyMenu = new ui::menu(10, 185, 268, 20, 5);
devCopyMenu->setActive(false);
devCopyMenu->setCallback(_devCopyMenuCallback, NULL);
devCopyMenu->addOpt(NULL, ui::getUIString("fileModeMenu", 0) + "SDMC");
for(int i = 1; i < 4; i++)
for (int i = 1; i < 4; i++)
devCopyMenu->addOpt(NULL, ui::getUIString("fileModeMenu", i));
//Manually do this so I can place the last option higher up
devCopyMenu->addOpt(NULL, ui::getUIString("fileModeMenu", 6));
@ -457,13 +502,13 @@ void ui::fmInit()
sdMenu = new ui::menu(620, 8, 590, 18, 5);
sdMenu->setCallback(_sdMenuCallback, sdmcArgs);
sdMenu->setActive(false);
sdmcArgs->b->m = sdMenu;
sdmcArgs->m = sdMenu;
sdPanel = new ui::slideOutPanel(288, 720, 0, ui::SLD_RIGHT, _sdCopyPanelDraw);
sdCopyMenu = new ui::menu(10, 210, 268, 20, 5);
sdCopyMenu->setActive(false);
sdCopyMenu->setCallback(_sdCopyMenuCallback, NULL);
for(int i = 0; i < 6; i++)
for (int i = 0; i < 6; i++)
sdCopyMenu->addOpt(NULL, ui::getUIString("fileModeMenu", i));
sdCopyMenu->optAddButtonEvent(0, HidNpadButton_A, _copyMenuCopy, sdmcArgs);
sdCopyMenu->optAddButtonEvent(1, HidNpadButton_A, _copyMenuDelete, sdmcArgs);
@ -474,9 +519,9 @@ void ui::fmInit()
ui::registerPanel(sdPanel);
devList = new fs::dirList;
sdList = new fs::dirList;
devArgs->b->d = devList;
sdmcArgs->b->d = sdList;
sdList = new fs::dirList;
devArgs->d = devList;
sdmcArgs->d = sdList;
}
void ui::fmExit()
@ -487,43 +532,41 @@ void ui::fmExit()
delete sdCopyMenu;
delete devList;
delete sdList;
delete devArgs->b;
delete devArgs;
delete sdmcArgs->b;
delete sdmcArgs;
}
void ui::fmPrep(const FsSaveDataType& _type, const std::string& _dev, bool _commit)
void ui::fmPrep(FsSaveDataType _type, const std::string &_dev, const std::string &_baseSDMC, bool _commit)
{
type = _type;
dev = _dev;
dev = _dev;
commit = _commit;
devPath = _dev;
sdPath = "sdmc:/";
sdPath = _baseSDMC;
sdCopyMenu->editOpt(0, NULL, ui::getUIString("fileModeMenu", 0) + _dev);
devList->reassign(dev);
sdList->reassign(sdPath);
util::copyDirListToMenu(*devList, *devMenu);
for(int i = 1; i < devMenu->getCount(); i++)
for (int i = 1; i < devMenu->getCount(); i++)
devMenu->optAddButtonEvent(i, HidNpadButton_A, _listFunctionA, devArgs);
util::copyDirListToMenu(*sdList, *sdMenu);
for(int i = 1; i < sdMenu->getCount(); i++)
for (int i = 1; i < sdMenu->getCount(); i++)
sdMenu->optAddButtonEvent(i, HidNpadButton_A, _listFunctionA, sdmcArgs);
}
void ui::fmUpdate()
{
//For now? Maybe forever?
if(devMenu->getActive() || sdMenu->getActive())
if (devMenu->getActive() || sdMenu->getActive())
{
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_ZL:
case HidNpadButton_ZR:
if(devMenu->getActive())
if (devMenu->getActive())
{
devMenu->setActive(false);
sdMenu->setActive(true);
@ -537,15 +580,15 @@ void ui::fmUpdate()
case HidNpadButton_Minus:
//Can't be 100% sure it's fs's sv
if(dev != "sdmc:/")
if (dev != "sdmc:/")
fsdevUnmountDevice(dev.c_str());
if(ui::prevState == EX_MNU)
if (ui::prevState == EX_MNU)
{
ui::usrSelPanel->openPanel();
ui::changeState(EX_MNU);
}
else if(ui::prevState == TTL_SEL)
else if (ui::prevState == TTL_SEL)
{
ui::usrSelPanel->openPanel();
ui::ttlOptsPanel->openPanel();

View File

@ -1,25 +1,25 @@
#include <cstring>
#include <cstdarg>
#include <cmath>
#include <cstdarg>
#include <cstring>
#include <switch.h>
#include "gfx.h"
#include "ui.h"
#include "miscui.h"
#include "util.h"
#include "type.h"
#include "ui.h"
#include "util.h"
static const SDL_Color divLight = {0x6D, 0x6D, 0x6D, 0xFF};
static const SDL_Color divDark = {0xCC, 0xCC, 0xCC, 0xFF};
static const SDL_Color shadow = {0x66, 0x66, 0x66, 0xFF};
static const SDL_Color divLight = {0x6D, 0x6D, 0x6D, 0xFF};
static const SDL_Color divDark = {0xCC, 0xCC, 0xCC, 0xFF};
static const SDL_Color shadow = {0x66, 0x66, 0x66, 0xFF};
static const SDL_Color fillBack = {0x66, 0x66, 0x66, 0xFF};
static const SDL_Color fillBar = {0x00, 0xDD, 0x00, 0xFF};
static const SDL_Color fillBack = {0x66, 0x66, 0x66, 0xFF};
static const SDL_Color fillBar = {0x00, 0xDD, 0x00, 0xFF};
static const SDL_Color menuColorLight = {0x32, 0x50, 0xF0, 0xFF};
static const SDL_Color menuColorDark = {0x00, 0xFF, 0xC5, 0xFF};
static const SDL_Color menuColorDark = {0x00, 0xFF, 0xC5, 0xFF};
ui::menu::menu(const int& _x, const int& _y, const int& _rW, const int& _fS, const int& _mL)
ui::menu::menu(int _x, int _y, int _rW, int _fS, int _mL)
{
x = _x;
mY = _y;
@ -32,13 +32,12 @@ ui::menu::menu(const int& _x, const int& _y, const int& _rW, const int& _fS, con
rH = _fS + 30;
spcWidth = gfx::getTextWidth(" ", _fS) * 3;
optTex = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, rW - 24, rH - 8);
SDL_SetTextureBlendMode(optTex, SDL_BLENDMODE_BLEND);
optTex = gfx::texMgr->textureCreate(rW - 24, rH - 8);
}
void ui::menu::editParam(int _param, int newVal)
{
switch(_param)
switch (_param)
{
case MENU_X:
x = newVal;
@ -51,14 +50,22 @@ void ui::menu::editParam(int _param, int newVal)
case MENU_RECT_WIDTH:
rW = newVal;
SDL_DestroyTexture(optTex);
optTex = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, rW, rH);
optTex = SDL_CreateTexture(gfx::render,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
rW,
rH);
SDL_SetTextureBlendMode(optTex, SDL_BLENDMODE_BLEND);
break;
case MENU_RECT_HEIGHT:
rH = newVal;
SDL_DestroyTexture(optTex);
optTex = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, rW, rH);
optTex = SDL_CreateTexture(gfx::render,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
rW,
rH);
SDL_SetTextureBlendMode(optTex, SDL_BLENDMODE_BLEND);
break;
@ -72,7 +79,7 @@ void ui::menu::editParam(int _param, int newVal)
}
}
int ui::menu::addOpt(SDL_Texture *_icn, const std::string& add)
int ui::menu::addOpt(SDL_Texture *_icn, const std::string &add)
{
ui::menuOpt newOpt;
newOpt.txt = add;
@ -82,12 +89,13 @@ int ui::menu::addOpt(SDL_Texture *_icn, const std::string& add)
return opt.size() - 1;
}
void ui::menu::editOpt(int ind, SDL_Texture *_icn, const std::string& ch)
void ui::menu::editOpt(int ind, SDL_Texture *_icn, const std::string &ch)
{
if(!opt[ind].icn && _icn)
if (!opt[ind].icn && _icn)
opt[ind].icn = _icn;
opt[ind].txt = ch;
opt[ind].txtWidth = gfx::getTextWidth(ch.c_str(), fSize);
}
void ui::menu::optAddButtonEvent(unsigned _ind, HidNpadButton _button, funcPtr _func, void *args)
@ -96,11 +104,11 @@ void ui::menu::optAddButtonEvent(unsigned _ind, HidNpadButton _button, funcPtr _
opt[_ind].events.push_back(newEvent);
}
int ui::menu::getOptPos(const std::string& txt)
int ui::menu::getOptPos(const std::string &txt)
{
for(unsigned i = 0; i < opt.size(); i++)
for (unsigned i = 0; i < opt.size(); i++)
{
if(opt[i].txt == txt)
if (opt[i].txt == txt)
return i;
}
return -1;
@ -109,55 +117,54 @@ int ui::menu::getOptPos(const std::string& txt)
ui::menu::~menu()
{
SDL_DestroyTexture(optTex);
opt.clear();
}
void ui::menu::update()
{
if(!isActive)
if (!isActive)
return;
uint64_t down = ui::padKeysDown();
uint64_t held = ui::padKeysHeld();
if(held & HidNpadButton_AnyUp || held & HidNpadButton_AnyDown)
if (held & HidNpadButton_AnyUp || held & HidNpadButton_AnyDown)
++fc;
else
fc = 0;
if(fc > 10)
if (fc > 10)
fc = 0;
int oldSel = selected;
int mSize = opt.size() - 1;
bool change = false;
if( (down & HidNpadButton_AnyUp) || ((held & HidNpadButton_AnyUp) && fc == 10) )
if ((down & HidNpadButton_AnyUp) || ((held & HidNpadButton_AnyUp) && fc == 10))
{
if(selected > 0)
--selected;
if (--selected < 0)
selected = mSize;
change = true;
}
else if( (down & HidNpadButton_AnyDown) || ((held & HidNpadButton_AnyDown) && fc == 10))
else if ((down & HidNpadButton_AnyDown) || ((held & HidNpadButton_AnyDown) && fc == 10))
{
if(selected < mSize)
++selected;
change = true;
}
else if(down & HidNpadButton_AnyLeft)
{
selected -= mL;
if(selected < 0)
if (++selected > mSize)
selected = 0;
change = true;
}
else if(down & HidNpadButton_AnyRight)
else if (down & HidNpadButton_AnyLeft)
{
selected -= mL;
if (selected < 0)
selected = 0;
change = true;
}
else if (down & HidNpadButton_AnyRight)
{
selected += mL;
if(selected > mSize)
if (selected > mSize)
selected = mSize;
change = true;
@ -165,120 +172,145 @@ void ui::menu::update()
++hoverCount;
if(!change && hoverCount >= 90)
if (!change && hoverCount >= 90)
hover = true;
else if(change)
else if (change)
{
hoverCount = 0;
hover = false;
}
if(down && !opt[selected].events.empty())
if (down && !opt[selected].events.empty())
{
for(ui::menuOptEvent& e : opt[selected].events)
for (ui::menuOptEvent &e : opt[selected].events)
{
if((down & e.button) && e.func)
if ((down & e.button) && e.func)
(*e.func)(e.args);
}
}
if(selected <= mL)
if (selected <= mL)
tY = mY;
else if(selected >= (mSize - mL) && mSize > mL * 2)
else if (selected >= (mSize - mL) && mSize > mL * 2)
tY = mY + -(rH * (mSize - (mL * 2)));
else if(selected > mL && selected < (mSize - mL))
else if (selected > mL && selected < (mSize - mL))
tY = -(rH * (selected - mL));
if(selected != oldSel && onChange)
if (selected != oldSel && onChange)
(*onChange)(NULL);
if(callback)
if (callback)
(*callback)(callbackArgs);
}
void ui::menu::draw(SDL_Texture *target, const SDL_Color *textClr, bool drawText)
{
if(opt.size() < 1)
if (opt.size() < 1)
return;
int tH = 0;
SDL_QueryTexture(target, NULL, NULL, NULL, &tH);
if(y != tY)
if (y != tY)
{
float add = (float)((float)tY - (float)y) / ui::animScale;
y += ceil(add);
}
if(clrAdd)
if (clrAdd)
{
clrSh += 6;
if(clrSh >= 0x72)
if (clrSh >= 0x72)
clrAdd = false;
}
else
{
clrSh -= 3;
if(clrSh <= 0x00)
if (clrSh <= 0x00)
clrAdd = true;
}
for(int i = 0, tY = y; i < (int)opt.size(); i++, tY += rH)
for (int i = 0, tY = y; i < (int)opt.size(); i++, tY += rH)
{
if(tY < -rH || tY > tH)
if (tY < -rH || tY > tH)
continue;
gfx::clearTarget(optTex, &ui::transparent);
//Todo: Clean this up maybe
if(i == selected && drawText)
if (i == selected && drawText)
{
if(isActive)
if (isActive)
ui::drawBoundBox(target, x, y + (i * rH), rW, rH, clrSh);
gfx::drawRect(target, ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark, x + 10, ((y + (rH / 2 - fSize / 2)) + (i * rH)) - 2, 4, fSize + 4);
gfx::drawRect(target,
ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark,
x + 10,
((y + (rH / 2 - fSize / 2)) + (i * rH)) - 2,
4,
fSize + 4);
static int dX = 0;
if(hover && opt[i].txtWidth > rW - 24)
if (hover && opt[i].txtWidth > rW - 24)
{
if((dX -= 2) <= -(opt[i].txtWidth + spcWidth))
if ((dX -= 2) <= -(opt[i].txtWidth + spcWidth))
{
dX = 0;
hoverCount = 0;
hover = false;
}
gfx::drawTextf(optTex, fSize, dX, ((rH - 8) / 2) - fSize / 2, ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark, opt[i].txt.c_str());
gfx::drawTextf(optTex, fSize, dX + opt[i].txtWidth + spcWidth, ((rH - 8) / 2) - fSize / 2, ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark, opt[i].txt.c_str());
gfx::drawTextf(optTex,
fSize,
dX,
((rH - 8) / 2) - fSize / 2,
ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark,
opt[i].txt.c_str());
gfx::drawTextf(optTex,
fSize,
dX + opt[i].txtWidth + spcWidth,
((rH - 8) / 2) - fSize / 2,
ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark,
opt[i].txt.c_str());
}
else
{
dX = 0;
gfx::drawTextf(optTex, fSize, 0, ((rH - 8) / 2) - fSize / 2, ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark, opt[i].txt.c_str());
gfx::drawTextf(optTex,
fSize,
0,
((rH - 8) / 2) - fSize / 2,
ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark,
opt[i].txt.c_str());
}
gfx::texDraw(target, optTex, x + 20, (y + 4 + (i * rH)));
}
else if(i == selected && !drawText && opt[i].icn)
else if (i == selected && !drawText && opt[i].icn)
{
int w, h;
SDL_QueryTexture(opt[i].icn, NULL, NULL, &w, &h);
float scale = (float)fSize / (float)h;
int dW = scale * w;
int dH = scale * h;
if(isActive)
if (isActive)
ui::drawBoundBox(target, x, y + (i * rH), rW, rH, clrSh);
gfx::drawRect(target, ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark, x + 10, ((y + (rH / 2 - fSize / 2)) + (i * rH)) - 2, 4, fSize + 4);
gfx::drawRect(target,
ui::thmID == ColorSetId_Light ? &menuColorLight : &menuColorDark,
x + 10,
((y + (rH / 2 - fSize / 2)) + (i * rH)) - 2,
4,
fSize + 4);
gfx::texDrawStretch(optTex, opt[i].icn, 0, ((rH - 8) / 2) - fSize / 2, dW, dH);
gfx::texDraw(target, optTex, x + 20, (y + 4 + (i * rH)));
}
else if(drawText)
else if (drawText)
{
gfx::clearTarget(optTex, &ui::transparent);
gfx::drawTextf(optTex, fSize, 0, ((rH - 8) / 2) - fSize / 2, textClr, opt[i].txt.c_str());
gfx::texDraw(target, optTex, x + 20, (y + 4 + (i * rH)));
}
else if(!drawText && opt[i].icn)
else if (!drawText && opt[i].icn)
{
int w, h;
SDL_QueryTexture(opt[i].icn, NULL, NULL, &w, &h);
@ -305,15 +337,15 @@ void ui::menu::setActive(bool _set)
isActive = _set;
}
void ui::progBar::update(const uint64_t& _prog)
void ui::progBar::update(uint64_t _prog)
{
prog = _prog;
if(max > 0)
if (max > 0)
width = (float)((float)prog / (float)max) * 648.0f;
}
void ui::progBar::draw(const std::string& text)
void ui::progBar::draw(const std::string &text)
{
char progStr[64];
sprintf(progStr, "%.2fMB / %.2fMB", (float)prog / 1024.0f / 1024.0f, (float)max / 1024.0f / 1024.0f);
@ -335,29 +367,29 @@ ui::popMessageMngr::~popMessageMngr()
void ui::popMessageMngr::update()
{
//Anything that needs graphics/text must be processed on main thread
for(ui::popMessage& p : popQueue)
for (ui::popMessage &p : popQueue)
{
p.rectWidth = gfx::getTextWidth(p.message.c_str(), 24) + 32;
message.push_back(p);
}
popQueue.clear();
for(unsigned i = 0; i < message.size(); i++)
for (unsigned i = 0; i < message.size(); i++)
{
message[i].frames--;
if(message[i].frames <= 0)
if (message[i].frames <= 0)
message.erase(message.begin() + i);
}
}
void ui::popMessageMngr::popMessageAdd(const std::string& mess, int frameTime)
void ui::popMessageMngr::popMessageAdd(const std::string &mess, int frameTime)
{
//Suppress multiple of the same
int lastMessageIndex = message.size() - 1;
std::string lastMessage;
if(lastMessageIndex >= 0)
if (lastMessageIndex >= 0)
lastMessage = message[lastMessageIndex].message;
if(lastMessage != mess)
if (lastMessage != mess)
{
ui::popMessage newPop = {mess, 0, frameTime};
popQueue.push_back(newPop);
@ -367,10 +399,10 @@ void ui::popMessageMngr::popMessageAdd(const std::string& mess, int frameTime)
void ui::popMessageMngr::draw()
{
int y = 640;
for(auto& p : message)
for (auto &p : message)
{
y -= 48;
if(p.y != y)
if (p.y != y)
p.y += (y - p.y) / ui::animScale;
gfx::drawRect(NULL, &ui::tboxClr, 64, p.y, p.rectWidth, 40);
@ -378,7 +410,12 @@ void ui::popMessageMngr::draw()
}
}
ui::confirmArgs *ui::confirmArgsCreate(bool _hold, funcPtr _func, void *_funcArgs, bool _cleanup, const char *fmt, ...)
ui::confirmArgs *ui::confirmArgsCreate(bool _hold,
funcPtr _confFunc,
funcPtr _cancelFunc,
void *_funcArgs,
const char *fmt,
...)
{
char tmp[1024];
va_list args;
@ -388,9 +425,9 @@ ui::confirmArgs *ui::confirmArgsCreate(bool _hold, funcPtr _func, void *_funcArg
ui::confirmArgs *ret = new confirmArgs;
ret->hold = _hold;
ret->func = _func;
ret->confFunc = _confFunc;
ret->cancelFunc = _cancelFunc;
ret->args = _funcArgs;
ret->cleanup = _cleanup;
ret->text = tmp;
return ret;
@ -402,35 +439,37 @@ void confirm_t(void *a)
threadInfo *t = (threadInfo *)a;
ui::confirmArgs *c = (ui::confirmArgs *)t->argPtr;
while(true)
while (true)
{
uint64_t down = ui::padKeysDown();
uint64_t held = ui::padKeysHeld();
if((down & HidNpadButton_A) && !c->hold)
if ((down & HidNpadButton_A) && !c->hold)
{
ui::newThread(c->func, c->args, NULL);
ui::newThread(c->confFunc, c->args, NULL);
break;
}
else if((held & HidNpadButton_A) && c->hold)
else if ((held & HidNpadButton_A) && c->hold)
{
if(c->frameCount % 4 == 0 && ++c->lgFrame > 7)
if (c->frameCount % 4 == 0 && ++c->lgFrame > 7)
c->lgFrame = 0;
if(c->frameCount >= 120)
if (c->frameCount >= 120)
{
ui::newThread(c->func, c->args, NULL);
ui::newThread(c->confFunc, c->args, NULL);
break;
}
}
else if(down & HidNpadButton_B)
else if (down & HidNpadButton_B)
{
if (c->cancelFunc)
(*c->cancelFunc)(c->args);
break;
}
svcSleepThread(10000000);//Close enough
svcSleepThread(1e+9 / 60); //Close enough
}
if(c->cleanup)
delete c;
delete c;
t->finished = true;
}
@ -439,19 +478,19 @@ void confirmDrawFunc(void *a)
{
threadInfo *t = (threadInfo *)a;
ui::confirmArgs *c = (ui::confirmArgs *)t->argPtr;
if(!t->finished)
if (!t->finished)
{
std::string yesTxt = ui::getUIString("dialogYes", 0);
unsigned yesX = 0;
if((ui::padKeysHeld() & HidNpadButton_A) && c->hold)
if ((ui::padKeysHeld() & HidNpadButton_A) && c->hold)
{
c->frameCount++;
if(c->frameCount <= 40)
if (c->frameCount <= 40)
yesTxt = ui::getUIString("holdingText", 0);
else if(c->frameCount <= 80)
else if (c->frameCount <= 80)
yesTxt = ui::getUIString("holdingText", 1);
else if(c->frameCount <= 120)
else if (c->frameCount <= 120)
yesTxt = ui::getUICString("holdingText", 2);
yesTxt += ui::loadGlyphArray[c->lgFrame];
@ -480,12 +519,12 @@ static void showMessage_t(void *a)
{
threadInfo *t = (threadInfo *)a;
std::string *text = (std::string *)t->argPtr;
while(true)
while (true)
{
if(ui::padKeysDown() & HidNpadButton_A)
if (ui::padKeysDown() & HidNpadButton_A)
break;
svcSleepThread(10000000);
svcSleepThread(1e+9 / 60);
}
delete text;
t->finished = true;
@ -495,7 +534,7 @@ static void showMessageDrawFunc(void *a)
{
threadInfo *t = (threadInfo *)a;
std::string *text = (std::string *)t->argPtr;
if(!t->finished)
if (!t->finished)
{
unsigned okX = 640 - (gfx::getTextWidth(ui::getUICString("dialogOK", 0), 18) / 2);
ui::drawTextbox(NULL, 280, 262, 720, 256);
@ -525,7 +564,7 @@ void ui::drawTextbox(SDL_Texture *target, int x, int y, int w, int h)
gfx::texDraw(target, ui::cornerTopRight, (x + w) - 32, y);
//middle
gfx::drawRect(target, &ui::tboxClr, x, y + 32, w, h - 64);
gfx::drawRect(target, &ui::tboxClr, x, y + 32, w, h - 64);
//bottom
gfx::texDraw(target, ui::cornerBottomLeft, x, (y + h) - 32);
@ -538,7 +577,7 @@ void ui::drawBoundBox(SDL_Texture *target, int x, int y, int w, int h, uint8_t c
SDL_SetRenderTarget(gfx::render, target);
SDL_Color rectClr;
if(ui::thmID == ColorSetId_Light)
if (ui::thmID == ColorSetId_Light)
rectClr = {0xFD, 0xFD, 0xFD, 0xFF};
else
rectClr = {0x21, 0x22, 0x21, 0xFF};

View File

@ -1,5 +1,5 @@
#include <switch.h>
#include <SDL.h>
#include <SDL2/SDL.h>
#include "ui.h"
#include "file.h"
@ -56,7 +56,7 @@ static void settMenuDeleteAllBackups_t(void *a)
fs::dirList *jksvDir = new fs::dirList(fs::getWorkDir());
for(unsigned i = 0; i < jksvDir->getCount(); i++)
{
if(jksvDir->isDir(i))
if(jksvDir->isDir(i) && jksvDir->getItem(i) != "svi")
{
std::string delTarget = fs::getWorkDir() + jksvDir->getItem(i) + "/";
fs::delDir(delTarget);
@ -68,7 +68,7 @@ static void settMenuDeleteAllBackups_t(void *a)
static void settMenuDeleteAllBackups(void *a)
{
ui::confirmArgs *send = ui::confirmArgsCreate(true, settMenuDeleteAllBackups_t, NULL, true, ui::getUICString("confirmDeleteBackupsAll", 0));
ui::confirmArgs *send = ui::confirmArgsCreate(true, settMenuDeleteAllBackups_t, NULL, NULL, ui::getUICString("confirmDeleteBackupsAll", 0));
ui::confirm(send);
}
@ -123,7 +123,7 @@ static void toggleOpt(void *a)
case 0:
fs::delDir(fs::getWorkDir() + "_TRASH_/");
mkdir(std::string(fs::getWorkDir() + "_TRASH_").c_str(), 777);
ui::showPopMessage(POP_FRAME_DEFAULT, "Trash emptied.");
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popTrashEmptied", 0));
break;
case 1:
@ -228,6 +228,7 @@ static void toggleOpt(void *a)
ui::animScale = 1;
break;
}
cfg::saveConfig();
}
static void updateMenuText()

View File

@ -1,4 +1,4 @@
#include <SDL.h>
#include <SDL2/SDL.h>
#include "ui.h"
#include "gfx.h"
@ -15,13 +15,7 @@ ui::slideOutPanel::slideOutPanel(int _w, int _h, int _y, ui::slidePanelOrientati
x = 1280;
drawFunc = _draw;
panel = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, w, h);
SDL_SetTextureBlendMode(panel, SDL_BLENDMODE_BLEND);
}
ui::slideOutPanel::~slideOutPanel()
{
SDL_DestroyTexture(panel);
panel = gfx::texMgr->textureCreate(w, h);
}
void ui::slideOutPanel::resizePanel(int _w, int _h, int _y)
@ -32,9 +26,7 @@ void ui::slideOutPanel::resizePanel(int _w, int _h, int _y)
if(sldSide == ui::SLD_LEFT)
x = -w;
SDL_DestroyTexture(panel);
panel = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, w, h);
SDL_SetTextureBlendMode(panel, SDL_BLENDMODE_BLEND);
gfx::texMgr->textureResize(&panel, w, h);
}
void ui::slideOutPanel::update()

View File

@ -43,7 +43,7 @@ void ui::threadProcMngr::update()
{
Result res = 0;
threadInfo *t = threads[0];
if(!t->running && R_SUCCEEDED((res = threadCreate(&t->thrd, t->thrdFunc, t, NULL, 0x20000, 0x2B, 1))))
if(!t->running && R_SUCCEEDED((res = threadCreate(&t->thrd, t->thrdFunc, t, NULL, 0x80000, 0x2B, 1))))
{
threadStart(&t->thrd);
t->running = true;

View File

@ -1,95 +1,24 @@
#include <vector>
#include "ui.h"
#include "ttl.h"
#include "file.h"
#include "util.h"
#include "cfg.h"
#include "file.h"
#include "ttl.h"
#include "ui.h"
#include "util.h"
static int ttlHelpX = 0, fldHelpWidth = 0;
static int ttlHelpX = 0;
static std::vector<ui::titleview *> ttlViews;
static ui::menu *ttlOpts, *fldMenu;
static ui::menu *ttlOpts;
ui::slideOutPanel *ui::ttlOptsPanel;
static ui::slideOutPanel *infoPanel, *fldPanel;//There's no reason to have a separate folder section
static fs::dirList *fldList;
static fs::backupArgs *backargs;
static std::string infoPanelString;
static SDL_Texture *fldBuffer;//This is so folder menu doesn't draw over guide
static ui::slideOutPanel *infoPanel;
static Mutex ttlViewLock = 0;
void ui::ttlRefresh()
{
for(int i = 0; i < (int)data::users.size(); i++)
mutexLock(&ttlViewLock);
for (int i = 0; i < (int)data::users.size(); i++)
ttlViews[i]->refresh();
}
static void fldFuncOverwrite(void *a)
{
fs::backupArgs *b = (fs::backupArgs *)a;
fs::dirList *d = (fs::dirList *)b->d;
ui::menu *m = (ui::menu *)b->m;
int sel = m->getSelected() - 1;//Skip 'New'
std::string itm = d->getItem(sel);
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdOver"], fs::overwriteBackup, a, true, ui::getUICString("confirmOverwrite", 0), itm.c_str());
ui::confirm(conf);
}
static void fldFuncDelete(void *a)
{
fs::backupArgs *b = (fs::backupArgs *)a;
fs::dirList *d = (fs::dirList *)b->d;
ui::menu *m = (ui::menu *)b->m;
int sel = m->getSelected() - 1;//Skip 'New'
std::string itm = d->getItem(sel);
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdDel"], fs::deleteBackup, a, true, ui::getUICString("confirmDelete", 0), itm.c_str());
ui::confirm(conf);
}
static void fldFuncRestore(void *a)
{
fs::backupArgs *b = (fs::backupArgs *)a;
fs::dirList *d = (fs::dirList *)b->d;
ui::menu *m = (ui::menu *)b->m;
int sel = m->getSelected() - 1;//Skip 'New'
std::string itm = d->getItem(sel);
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdRest"], fs::restoreBackup, a, true, ui::getUICString("confirmRestore", 0), itm.c_str());
ui::confirm(conf);
}
void ui::populateFldMenu()
{
fldMenu->reset();
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
util::createTitleDirectoryByTID(d->tid);
std::string targetDir = util::generatePathByTID(d->tid);
fldList->reassign(targetDir);
char filterPath[128];
sprintf(filterPath, "sdmc:/config/JKSV/0x%016lX_filter.txt", d->tid);
fs::loadPathFilters(d->tid);
*backargs = {fldMenu, fldList};
fldMenu->addOpt(NULL, "New");
fldMenu->optAddButtonEvent(0, HidNpadButton_A, fs::createNewBackup, backargs);
for(unsigned i = 0; i < fldList->getCount(); i++)
{
fldMenu->addOpt(NULL, fldList->getItem(i));
fldMenu->optAddButtonEvent(i + 1, HidNpadButton_A, fldFuncOverwrite, backargs);
fldMenu->optAddButtonEvent(i + 1, HidNpadButton_X, fldFuncDelete, backargs);
fldMenu->optAddButtonEvent(i + 1, HidNpadButton_Y, fldFuncRestore, backargs);
}
fldMenu->setActive(true);
fldPanel->openPanel();
mutexUnlock(&ttlViewLock);
}
static void ttlViewCallback(void *a)
@ -100,11 +29,14 @@ static void ttlViewCallback(void *a)
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
uint64_t tid = d->tid;
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_A:
if(fs::mountSave(d->saveInfo))
ui::populateFldMenu();
if (fs::mountSave(d->saveInfo))
{
ttlViews[curUserIndex]->setActive(false, true);
ui::fldPopulateMenu();
}
break;
case HidNpadButton_B:
@ -120,28 +52,28 @@ static void ttlViewCallback(void *a)
break;
case HidNpadButton_Y:
{
cfg::addTitleToFavorites(tid);
int newSel = data::getTitleIndexInUser(data::users[curUserIndex], tid);
ttlViews[curUserIndex]->setSelected(newSel);
}
break;
{
cfg::addTitleToFavorites(tid);
int newSel = data::getTitleIndexInUser(data::users[curUserIndex], tid);
ttlViews[curUserIndex]->setSelected(newSel);
}
break;
}
}
static void ttlOptsCallback(void *a)
{
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_B:
{
int curUserIndex = data::getCurrentUserIndex();
ttlOpts->setActive(false);
ui::ttlOptsPanel->closePanel();
ttlViews[curUserIndex]->setActive(true, true);
ui::updateInput();
}
break;
{
int curUserIndex = data::getCurrentUserIndex();
ttlOpts->setActive(false);
ui::ttlOptsPanel->closePanel();
ttlViews[curUserIndex]->setActive(true, true);
ui::updateInput();
}
break;
}
}
@ -151,18 +83,38 @@ static void ttlOptsPanelDraw(void *a)
ttlOpts->draw(panel, &ui::txtCont, true);
}
static void ttlOptsShowInfoPanel(void *a)
static void ttlOptsShowInfoPanel(void *)
{
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
data::titleInfo *tinfo = data::getTitleInfoByTID(utinfo->tid);
char tmp[256];
sprintf(tmp, ui::getUICString("infoStatus", 4), tinfo->author.c_str());
size_t titleWidth = gfx::getTextWidth(tinfo->title.c_str(), 18);
size_t pubWidth = gfx::getTextWidth(tmp, 18);
if (titleWidth > 410 || pubWidth > 410)
{
size_t newWidth = titleWidth > pubWidth ? titleWidth : pubWidth;
infoPanel->resizePanel(newWidth + 40, 720, 0);
}
else
infoPanel->resizePanel(410, 720, 0);
ttlOpts->setActive(false);
ui::ttlOptsPanel->closePanel();
infoPanelString = util::getInfoString(*data::getCurrentUser(), data::getCurrentUserTitleInfo()->tid);
infoPanel->openPanel();
}
static void ttlOptsBlacklistTitle(void *a)
{
std::string title = data::getTitleNameByTID(data::getCurrentUserTitleInfo()->tid);
ui::confirmArgs *conf = ui::confirmArgsCreate(false, cfg::addTitleToBlacklist, NULL, true, ui::getUICString("confirmBlacklist", 0), title.c_str());
ui::confirmArgs *conf = ui::confirmArgsCreate(false,
cfg::addTitleToBlacklist,
NULL,
NULL,
ui::getUICString("confirmBlacklist", 0),
title.c_str());
ui::confirm(conf);
}
@ -170,17 +122,20 @@ static void ttlOptsDefinePath(void *a)
{
uint64_t tid = data::getCurrentUserTitleInfo()->tid;
std::string safeTitle = data::getTitleInfoByTID(tid)->safeTitle;
std::string newSafeTitle = util::getStringInput(SwkbdType_QWERTY, safeTitle, "Input New Output Folder", 0x200, 0, NULL);
if(!newSafeTitle.empty())
std::string newSafeTitle =
util::getStringInput(SwkbdType_QWERTY, safeTitle, ui::getUICString("swkbdNewSafeTitle", 0), 0x200, 0, NULL);
if (!newSafeTitle.empty())
cfg::pathDefAdd(tid, newSafeTitle);
}
static void ttlOptsToFileMode(void *a)
{
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
if(fs::mountSave(d->saveInfo))
if (fs::mountSave(d->saveInfo))
{
ui::fmPrep((FsSaveDataType)d->saveInfo.save_data_type, "sv:/", true);
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
std::string sdmcPath = util::generatePathByTID(utinfo->tid);
ui::fmPrep((FsSaveDataType)d->saveInfo.save_data_type, "sv:/", sdmcPath, true);
ui::usrSelPanel->closePanel();
ui::ttlOptsPanel->closePanel();
ui::changeState(FIL_MDE);
@ -194,10 +149,10 @@ static void ttlOptsDeleteAllBackups_t(void *a)
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
std::string targetPath = util::generatePathByTID(d->tid);
fs::dirList *backupList = new fs::dirList(targetPath);
for(unsigned i = 0; i < backupList->getCount(); i++)
for (unsigned i = 0; i < backupList->getCount(); i++)
{
std::string delPath = targetPath + backupList->getItem(i);
if(backupList->isDir(i))
if (backupList->isDir(i))
{
delPath += "/";
fs::delDir(delPath);
@ -213,7 +168,13 @@ static void ttlOptsDeleteAllBackups(void *a)
{
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
std::string currentTitle = data::getTitleNameByTID(d->tid);
ui::confirmArgs *send = ui::confirmArgsCreate(cfg::config["holdDel"], ttlOptsDeleteAllBackups_t, NULL, true, ui::getUICString("confirmDeleteBackupsTitle", 0), currentTitle.c_str());
ui::confirmArgs *send = ui::confirmArgsCreate(cfg::config["holdDel"],
ttlOptsDeleteAllBackups_t,
NULL,
NULL,
ui::getUICString("confirmDeleteBackupsTitle", 0),
currentTitle.c_str());
ui::confirm(send);
}
@ -222,7 +183,7 @@ static void ttlOptsResetSaveData_t(void *a)
threadInfo *t = (threadInfo *)a;
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
std::string title = data::getTitleNameByTID(d->tid);
t->status->setStatus(ui::getUICString("threadStatusResettingSaveData", 0), title.c_str());
t->status->setStatus(ui::getUICString("threadStatusResettingSaveData", 0));
fs::mountSave(d->saveInfo);
fs::delDir("sv:/");
@ -235,10 +196,15 @@ static void ttlOptsResetSaveData_t(void *a)
static void ttlOptsResetSaveData(void *a)
{
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
if(d->saveInfo.save_data_type != FsSaveDataType_System)
if (d->saveInfo.save_data_type != FsSaveDataType_System)
{
std::string title = data::getTitleNameByTID(d->tid);
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdDel"], ttlOptsResetSaveData_t, NULL, true, ui::getUICString("confirmResetSaveData", 0), title.c_str());
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdDel"],
ttlOptsResetSaveData_t,
NULL,
NULL,
ui::getUICString("confirmResetSaveData", 0),
title.c_str());
ui::confirm(conf);
}
}
@ -252,13 +218,14 @@ static void ttlOptsDeleteSaveData_t(void *a)
std::string title = data::getTitleNameByTID(d->tid);
t->status->setStatus(ui::getUICString("threadStatusDeletingSaveData", 0), title.c_str());
if(R_SUCCEEDED(fsDeleteSaveDataFileSystemBySaveDataSpaceId((FsSaveDataSpaceId)d->saveInfo.save_data_space_id, d->saveInfo.save_data_id)))
if (R_SUCCEEDED(fsDeleteSaveDataFileSystemBySaveDataSpaceId((FsSaveDataSpaceId)d->saveInfo.save_data_space_id,
d->saveInfo.save_data_id)))
{
data::loadUsersTitles(false);
if(u->titleInfo.size() == 0)
if (u->titleInfo.size() == 0)
{
//Kick back to user
ui::ttlOptsPanel->closePanel();//JIC
ui::ttlOptsPanel->closePanel(); //JIC
ttlOpts->setActive(false);
ttlViews[userIndex]->setActive(false, false);
ui::usrMenu->setActive(true);
@ -273,97 +240,153 @@ static void ttlOptsDeleteSaveData_t(void *a)
static void ttlOptsDeleteSaveData(void *a)
{
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
if(d->saveInfo.save_data_type != FsSaveDataType_System)
if (d->saveInfo.save_data_type != FsSaveDataType_System)
{
std::string title = data::getTitleNameByTID(d->tid);
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdDel"], ttlOptsDeleteSaveData_t, NULL, true, ui::getUICString("confirmDeleteSaveData", 0), title.c_str());
ui::confirmArgs *conf = ui::confirmArgsCreate(cfg::config["holdDel"],
ttlOptsDeleteSaveData_t,
NULL,
NULL,
ui::getUICString("confirmDeleteSaveData", 0),
title.c_str());
ui::confirm(conf);
}
}
static void ttlOptsExtendSaveData_t(void *a)
{
threadInfo *t = (threadInfo *)a;
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
std::string expSizeStr = util::getStringInput(SwkbdType_NumPad, "", "Enter New Size in MB", 4, 0, NULL);
if(!expSizeStr.empty())
{
int64_t journ = 0, expSize;
data::titleInfo *extend = data::getTitleInfoByTID(d->tid);
t->status->setStatus(ui::getUICString("threadStatusExtendingSaveData", 0), extend->title.c_str());
//Get journal size
switch(d->saveInfo.save_data_type)
{
case FsSaveDataType_Account:
if(extend->nacp.user_account_save_data_journal_size_max > extend->nacp.user_account_save_data_journal_size)
journ = extend->nacp.user_account_save_data_journal_size_max;
else
journ = extend->nacp.user_account_save_data_journal_size;
break;
case FsSaveDataType_Bcat:
journ = extend->nacp.bcat_delivery_cache_storage_size;
break;
case FsSaveDataType_Cache:
if(extend->nacp.cache_storage_data_and_journal_size_max > extend->nacp.cache_storage_journal_size)
journ = extend->nacp.cache_storage_data_and_journal_size_max;
else
journ = extend->nacp.cache_storage_journal_size;
break;
case FsSaveDataType_Device:
if(extend->nacp.device_save_data_journal_size_max > extend->nacp.device_save_data_journal_size)
journ = extend->nacp.device_save_data_journal_size_max;
else
journ = extend->nacp.device_save_data_journal_size;
break;
default:
//will just fail
journ = 0;
break;
}
uint64_t expMB = strtoul(expSizeStr.c_str(), NULL, 10);
expSize = expMB * 0x100000;
FsSaveDataSpaceId space = (FsSaveDataSpaceId)d->saveInfo.save_data_space_id;
uint64_t sid = d->saveInfo.save_data_id;
Result res = 0;
if(R_FAILED(res = fsExtendSaveDataFileSystem(space, sid, expSize, journ)))
{
int64_t totalSize = 0;
fs::mountSave(d->saveInfo);
fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &totalSize);
fs::unmountSave();
fs::logWrite("Extend Failed: %uMB to %uMB -> %X\n", totalSize / 1024 / 1024, expSize / 1024 / 1024, res);
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataExtendFailed", 0));
}
else
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataExtendSuccess", 0), extend->title.c_str());
}
t->finished = true;
}
static void ttlOptsExtendSaveData(void *a)
{
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
if(d->saveInfo.save_data_type != FsSaveDataType_System)
ui::newThread(ttlOptsExtendSaveData_t, NULL, NULL);
if (d->saveInfo.save_data_type != FsSaveDataType_System)
{
std::string expSizeStr =
util::getStringInput(SwkbdType_NumPad, "", ui::getUICString("swkbdExpandSize", 0), 4, 0, NULL);
uint64_t extMB = strtoul(expSizeStr.c_str(), NULL, 10) * 0x100000;
fs::extendSaveDataThreaded(d, extMB);
}
}
static void ttlOptsExportSVI(void *a)
{
// Grab the current user title info.
data::userTitleInfo *userInfo = data::getCurrentUserTitleInfo();
// Use that to grab the corresponding title info.
data::titleInfo *info = data::getTitleInfoByTID(userInfo->tid);
// Ensure the svi directory exists.
std::string sviPath = fs::getWorkDir() + "svi/";
fs::mkDir(sviPath.substr(0, sviPath.length() - 1));
// Append the application ID and extension.
sviPath += util::getIDStr(userInfo->tid) + ".svi";
// Try to open the file for writing.
std::FILE *sviFile = std::fopen(sviPath.data(), "wb");
if (!sviFile)
{
return;
}
// Write the application ID
std::fwrite(&userInfo->tid, 1, sizeof(uint64_t), sviFile);
// Control data.
std::fwrite(&info->data, 1, sizeof(NacpStruct), sviFile);
// Close the file.
std::fclose(sviFile);
// Toast.
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popSVIExported", 0));
}
static void infoPanelDraw(void *a)
{
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
data::titleInfo *t = data::getTitleInfoByTID(d->tid);
SDL_Texture *panel = (SDL_Texture *)a;
gfx::texDraw(panel, data::getTitleIconByTID(d->tid), 77, 32);
gfx::drawTextfWrap(panel, 18, 32, 320, 362, &ui::txtCont, infoPanelString.c_str());
int width = 0, rectWidth = 0, iconX = 0, drawY = 310;
SDL_QueryTexture(panel, NULL, NULL, &width, 0);
rectWidth = width - 20;
iconX = (width / 2) - 128;
gfx::texDraw(panel, data::getTitleIconByTID(d->tid), iconX, 24);
gfx::drawRect(panel, &ui::rectLt, 10, drawY, rectWidth, 38);
gfx::drawTextf(panel, 18, 20, drawY + 10, &ui::txtCont, data::getTitleNameByTID(d->tid).c_str());
drawY += 40;
gfx::drawRect(panel, &ui::rectSh, 10, drawY, rectWidth, 38);
gfx::drawTextf(panel, 18, 20, drawY + 10, &ui::txtCont, ui::getUICString("infoStatus", 4), t->author.c_str());
drawY += 40;
gfx::drawRect(panel, &ui::rectLt, 10, drawY, rectWidth, 38);
gfx::drawTextf(panel, 18, 20, drawY + 10, &ui::txtCont, ui::getUICString("infoStatus", 0), d->tid);
drawY += 40;
gfx::drawRect(panel, &ui::rectSh, 10, drawY, rectWidth, 38);
gfx::drawTextf(panel,
18,
20,
drawY + 10,
&ui::txtCont,
ui::getUICString("infoStatus", 1),
d->saveInfo.save_data_id);
drawY += 40;
uint32_t hours, mins;
hours = ((d->playStats.playtime / 1e+9) / 60) / 60;
mins = ((d->playStats.playtime / 1e+9) / 60) - (hours * 60);
gfx::drawRect(panel, &ui::rectLt, 10, drawY, rectWidth, 38);
gfx::drawTextf(panel, 18, 20, drawY + 10, &ui::txtCont, ui::getUICString("infoStatus", 2), hours, mins);
drawY += 40;
gfx::drawRect(panel, &ui::rectSh, 10, drawY, rectWidth, 38);
gfx::drawTextf(panel,
18,
20,
drawY + 10,
&ui::txtCont,
ui::getUICString("infoStatus", 3),
d->playStats.total_launches);
drawY += 40;
gfx::drawRect(panel, &ui::rectLt, 10, drawY, rectWidth, 38);
gfx::drawTextf(panel,
18,
20,
drawY + 10,
&ui::txtCont,
ui::getUICString("infoStatus", 5),
ui::getUICString("saveDataTypeText", d->saveInfo.save_data_type));
drawY += 40;
uint8_t saveType = d->saveInfo.save_data_type;
if (saveType == FsSaveDataType_Cache)
{
gfx::drawRect(panel, &ui::rectSh, 10, drawY, rectWidth, 38);
gfx::drawTextf(panel,
18,
20,
drawY + 10,
&ui::txtCont,
ui::getUICString("infoStatus", 6),
d->saveInfo.save_data_index);
drawY += 40;
}
gfx::drawRect(panel, saveType == FsSaveDataType_Cache ? &ui::rectLt : &ui::rectSh, 10, drawY, rectWidth, 38);
gfx::drawTextf(panel,
18,
20,
drawY + 10,
&ui::txtCont,
ui::getUICString("infoStatus", 7),
data::getCurrentUser()->getUsername().c_str());
}
static void infoPanelCallback(void *a)
{
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_B:
infoPanel->closePanel();
@ -374,46 +397,17 @@ static void infoPanelCallback(void *a)
}
}
static void fldMenuCallback(void *a)
{
switch(ui::padKeysDown())
{
case HidNpadButton_B:
fs::unmountSave();
fs::freePathFilters();
fldMenu->setActive(false);
fldPanel->closePanel();
break;
}
ui::updateInput();
}
static void fldPanelDraw(void *a)
{
SDL_Texture *target = (SDL_Texture *)a;
gfx::clearTarget(fldBuffer, &ui::slidePanelColor);
fldMenu->draw(fldBuffer, &ui::txtCont, true);
gfx::texDraw(target, fldBuffer, 0, 0);
gfx::drawLine(target, &ui::divClr, 10, 648, fldHelpWidth + 54, 648);
gfx::drawTextf(target, 18, 32, 673, &ui::txtCont, ui::getUICString("helpFolder", 0));
}
void ui::ttlInit()
{
ttlHelpX = 1220 - gfx::getTextWidth(ui::getUICString("helpTitle", 0), 18);
fldHelpWidth = gfx::getTextWidth(ui::getUICString("helpFolder", 0), 18);
for(data::user& u : data::users)
for (data::user &u : data::users)
ttlViews.emplace_back(new ui::titleview(u, 128, 128, 16, 16, 7, ttlViewCallback));
ttlOpts = new ui::menu(10, 32, 390, 20, 7);
ttlOpts->setCallback(ttlOptsCallback, NULL);
ttlOpts->setActive(false);
fldMenu = new ui::menu(10, 8, fldHelpWidth + 44, 20, 6);
fldMenu->setCallback(fldMenuCallback, NULL);
fldMenu->setActive(false);
ttlOptsPanel = new ui::slideOutPanel(410, 720, 0, ui::SLD_RIGHT, ttlOptsPanelDraw);
ui::registerPanel(ttlOptsPanel);
@ -421,15 +415,8 @@ void ui::ttlInit()
ui::registerPanel(infoPanel);
infoPanel->setCallback(infoPanelCallback, NULL);
fldPanel = new ui::slideOutPanel(fldHelpWidth + 64, 720, 0, ui::SLD_RIGHT, fldPanelDraw);
fldBuffer = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, fldHelpWidth + 64, 647);
ui::registerPanel(fldPanel);
fldList = new fs::dirList;
backargs = new fs::backupArgs;
ttlOpts->setActive(false);
for(int i = 0; i < 8; i++)
for (int i = 0; i < 9; i++)
ttlOpts->addOpt(NULL, ui::getUIString("titleOptions", i));
//Information
@ -448,26 +435,23 @@ void ui::ttlInit()
ttlOpts->optAddButtonEvent(6, HidNpadButton_A, ttlOptsDeleteSaveData, NULL);
//Extend
ttlOpts->optAddButtonEvent(7, HidNpadButton_A, ttlOptsExtendSaveData, NULL);
//Export NACP
ttlOpts->optAddButtonEvent(8, HidNpadButton_A, ttlOptsExportSVI, NULL);
}
void ui::ttlExit()
{
for(ui::titleview *t : ttlViews)
for (ui::titleview *t : ttlViews)
delete t;
SDL_DestroyTexture(fldBuffer);
delete ttlOptsPanel;
delete ttlOpts;
delete infoPanel;
delete fldPanel;
delete fldMenu;
delete fldList;
delete backargs;
}
void ui::ttlSetActive(int usr)
void ui::ttlSetActive(int usr, bool _set, bool _showSel)
{
ttlViews[usr]->setActive(true, true);
ttlViews[usr]->setActive(_set, _showSel);
}
void ui::ttlUpdate()
@ -476,7 +460,7 @@ void ui::ttlUpdate()
ttlOpts->update();
infoPanel->update();
fldMenu->update();
ui::fldUpdate();
ttlViews[curUserIndex]->update();
}
@ -484,7 +468,9 @@ void ui::ttlDraw(SDL_Texture *target)
{
unsigned curUserIndex = data::getCurrentUserIndex();
mutexLock(&ttlViewLock);
ttlViews[curUserIndex]->draw(target);
if(ui::mstate == TTL_SEL && !fldPanel->isOpen())
mutexUnlock(&ttlViewLock);
if (ui::mstate == TTL_SEL && !fldPanel->isOpen())
gfx::drawTextf(NULL, 18, ttlHelpX, 673, &ui::txtCont, ui::getUICString("helpTitle", 0));
}

View File

@ -4,6 +4,7 @@
#include "file.h"
#include "cfg.h"
#include "type.h"
#include "util.h"
#include "uistr.h"
std::map<std::pair<std::string, int>, std::string> ui::strings;
@ -13,12 +14,101 @@ static void addUIString(const std::string& _name, int ind, const std::string& _s
ui::strings[std::make_pair(_name, ind)] = _str;
}
static void loadTranslationFile(const std::string& path)
{
if(fs::fileExists(path))
{
fs::dataFile lang(path);
while(lang.readNextLine(true))
{
std::string name = lang.getName();
int ind = lang.getNextValueInt();
std::string str = lang.getNextValueStr();
addUIString(name, ind, str);
}
}
}
static std::string getFilename(int lang)
{
std::string filename;
switch(lang)
{
case SetLanguage_JA:
filename = "ja.txt";
break;
case SetLanguage_ENUS:
filename = "en-US.txt";
break;
case SetLanguage_FR:
filename = "fr.txt";
break;
case SetLanguage_DE:
filename = "de.txt";
break;
case SetLanguage_IT:
filename = "it.txt";
break;
case SetLanguage_ES:
filename = "es.txt";
break;
case SetLanguage_ZHCN:
case SetLanguage_ZHHANS:
filename = "zh-CN.txt";
break;
case SetLanguage_KO:
filename = "ko.txt";
break;
case SetLanguage_NL:
filename = "nl.txt";
break;
case SetLanguage_PT:
filename = "pt.txt";
break;
case SetLanguage_RU:
filename = "ru.txt";
break;
case SetLanguage_ZHTW:
case SetLanguage_ZHHANT:
filename += "zh-TW.txt";
break;
case SetLanguage_ENGB:
filename = "en-GB.txt";
break;
case SetLanguage_FRCA:
filename = "fr-CA.txt";
break;
case SetLanguage_ES419:
filename = "es-419.txt";
break;
case SetLanguage_PTBR:
filename = "pt-BR.txt";
break;
}
return filename;
}
void ui::initStrings()
{
addUIString("author", 0, "NULL");
addUIString("helpUser", 0, "[A] Select [X] User Options");
addUIString("helpUser", 0, "[A] Select [Y] Dump All Saves [X] User Options");
addUIString("helpTitle", 0, "[A] Select [L][R] Jump [Y] Favorite [X] Title Options [B] Back");
addUIString("helpFolder", 0, "[A] Select [Y] Restore [X] Delete [B] Close");
addUIString("helpFolder", 0, "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close");
addUIString("helpSettings", 0, "[A] Toggle [X] Defaults [B] Back");
//Y/N On/Off
@ -42,6 +132,7 @@ void ui::initStrings()
addUIString("confirmCreateAllSaveData", 0, "Are you sure you would like to create all save data on this system for #%s#? This can take a while depending on how many titles are found.");
addUIString("confirmDeleteBackupsTitle", 0, "Are you sure you would like to delete all save backups for #%s#?");
addUIString("confirmDeleteBackupsAll", 0, "Are you sure you would like to delete *all* of your save backups for all of your games?");
addUIString("confirmDriveOverwrite", 0, "Downloading this backup from drive will overwrite the one on your SD card. Continue?");
//Save Data related strings
addUIString("saveDataNoneFound", 0, "No saves found for #%s#!");
@ -54,6 +145,20 @@ void ui::initStrings()
addUIString("saveDataDeleteAllUser", 0, "*ARE YOU SURE YOU WANT TO DELETE ALL SAVE DATA FOR %s?*");
addUIString("saveDataBackupDeleted", 0, "#%s# has been deleted.");
addUIString("saveDataBackupMovedToTrash", 0, "#%s# has been moved to trash.");
addUIString("saveTypeMainMenu", 0, "Device");
addUIString("saveTypeMainMenu", 1, "BCAT");
addUIString("saveTypeMainMenu", 2, "Cache");
addUIString("saveTypeMainMenu", 3, "System");
addUIString("saveTypeMainMenu", 4, "System BCAT");
addUIString("saveTypeMainMenu", 5, "Temporary");
//This is redundant. Need to merge and use one or the other...
addUIString("saveDataTypeText", 0, "System");
addUIString("saveDataTypeText", 1, "Account");
addUIString("saveDataTypeText", 2, "BCAT");
addUIString("saveDataTypeText", 3, "Device");
addUIString("saveDataTypeText", 4, "Temporary");
addUIString("saveDataTypeText", 5, "Cache");
addUIString("saveDataTypeText", 6, "System BCAT");
//Internet Related
addUIString("onlineErrorConnecting", 0, "Error Connecting!");
@ -67,6 +172,10 @@ void ui::initStrings()
addUIString("fileModeMenu", 4, "Properties");
addUIString("fileModeMenu", 5, "Close");
addUIString("fileModeMenu", 6, "Add to Path Filters");
addUIString("fileModeMenuMkDir", 0, "New");
//New folder pop menu strings
addUIString("folderMenuNew", 0, "New Backup");
//File mode properties string
addUIString("fileModeFileProperties", 0, "Path: %s\nSize: %s");
@ -94,6 +203,17 @@ void ui::initStrings()
addUIString("settingsMenu", 18, "Enable Trash Bin: ");
addUIString("settingsMenu", 19, "Title Sorting Type: ");
addUIString("settingsMenu", 20, "Animation Scale: ");
addUIString("settingsMenu", 21, "Auto-upload to Drive/Webdav: ");
//Main menu
addUIString("mainMenuSettings", 0, "Settings");
addUIString("mainMenuExtras", 0, "Extras");
// Translator in main page
addUIString("translationMainPage", 0, "Translation: ");
//Loading page
addUIString("loadingStartPage", 0, "Loading...");
//Sort Strings for ^
addUIString("sortType", 0, "Alphabetical");
@ -129,6 +249,7 @@ void ui::initStrings()
addUIString("titleOptions", 5, "Reset Save Data");
addUIString("titleOptions", 6, "Delete Save Data");
addUIString("titleOptions", 7, "Extend Save Data");
addUIString("titleOptions", 8, "Export SVI");
//Thread Status Strings
addUIString("threadStatusCreatingSaveData", 0, "Creating save data for #%s#...");
@ -137,7 +258,6 @@ void ui::initStrings()
addUIString("threadStatusOpeningFolder", 0, "Opening '#%s#'...");
addUIString("threadStatusAddingFileToZip", 0, "Adding '#%s#' to ZIP...");
addUIString("threadStatusDecompressingFile", 0, "Decompressing '#%s#'...");
addUIString("threadStatusResettingSaveData", 0, "Resetting Save Data for #%s#...");
addUIString("threadStatusDeletingSaveData", 0, "Deleting Save Data for #%s#...");
addUIString("threadStatusExtendingSaveData", 0, "Extending Save Data for #%s#...");
addUIString("threadStatusCreatingSaveData", 0, "Creating Save Data for #%s#...");
@ -147,6 +267,11 @@ void ui::initStrings()
addUIString("threadStatusDownloadingUpdate", 0, "Downloading update...");
addUIString("threadStatusGetDirProps", 0, "Getting Folder Properties...");
addUIString("threadStatusPackingJKSV", 0, "Writing JKSV folder contents to ZIP...");
addUIString("threadStatusSavingTranslations", 0, "Saving the file master...");
addUIString("threadStatusCalculatingSaveSize", 0, "Calculating save data size...");
addUIString("threadStatusUploadingFile", 0, "Uploading #%s#...");
addUIString("threadStatusDownloadingFile", 0, "Downloading #%s#...");
addUIString("threadStatusCompressingSaveForUpload", 0, "Compressing #%s# for upload...");
//Random leftover pop-ups
addUIString("popCPUBoostEnabled", 0, "CPU Boost Enabled for ZIP.");
@ -156,6 +281,15 @@ void ui::initStrings()
addUIString("popSaveIsEmpty", 0, "Save data is empty!");
addUIString("popProcessShutdown", 0, "#%s# successfully shutdown.");
addUIString("popAddedToPathFilter", 0, "'#%s#' added to path filters.");
addUIString("popChangeOutputFolder", 0, "#%s# changed to #%s#");
addUIString("popChangeOutputError", 0, "#%s# contains illegal or non-ASCII characters.");
addUIString("popTrashEmptied", 0, "Trash emptied");
addUIString("popSVIExported", 0, "SVI Exported.");
addUIString("popDriveStarted", 0, "Google Drive started successfully.");
addUIString("popDriveFailed", 0, "Failed to start Google Drive.");
addUIString("popRemoteNotActive", 0, "Remote is not available");
addUIString("popWebdavStarted", 0, "Webdav started successfully.");
addUIString("popWebdavFailed", 0, "Failed to start Webdav.");
//Keyboard hints
addUIString("swkbdEnterName", 0, "Enter a new name");
@ -165,55 +299,93 @@ void ui::initStrings()
addUIString("swkbdSysSavID", 0, "Enter System Save ID");
addUIString("swkbdRename", 0, "Enter a new name for item");
addUIString("swkbdMkDir", 0, "Enter a folder name");
addUIString("swkbdNewSafeTitle", 0, "Input New Output Folder");
addUIString("swkbdExpandSize", 0, "Enter New Size in MB");
//Status informations
addUIString("infoStatus", 0, "TID: %016lX");
addUIString("infoStatus", 1, "SID: %016lX");
addUIString("infoStatus", 2, "Play Time: %02d:%02d");
addUIString("infoStatus", 3, "Total Launches: %u");
addUIString("infoStatus", 4, "Publisher: %s");
addUIString("infoStatus", 5, "Save Type: %s");
addUIString("infoStatus", 6, "Cache Index: %u");
addUIString("infoStatus", 7, "User: %s");
addUIString("debugStatus", 0, "User Count: ");
addUIString("debugStatus", 1, "Current User: ");
addUIString("debugStatus", 2, "Current Title: ");
addUIString("debugStatus", 3, "Safe Title: ");
addUIString("debugStatus", 4, "Sort Type: ");
addUIString("appletModeWarning", 0, "*WARNING*: You are running JKSV in applet mode. Certain functions may not work.");
}
void ui::loadTrans()
{
bool transFile = fs::fileExists(fs::getWorkDir() + "trans.txt");
if(!transFile && (data::sysLang == SetLanguage_ENUS || cfg::config["langOverride"]))
std::string transTestFile = fs::getWorkDir() + "trans.txt";
std::string translationFile = "romfs:/lang/" + getFilename(data::sysLang);
bool transFile = fs::fileExists(transTestFile);
if(!transFile && (data::sysLang == SetLanguage_ENUS || data::sysLang == SetLanguage_ENGB || cfg::config["langOverride"]))
ui::initStrings();
else if(transFile)
loadTranslationFile(transTestFile);
else
{
std::string file;
if(transFile)
file = fs::getWorkDir() + "trans.txt";
else
{
file = "romfs:/lang/";
switch(data::sysLang)
{
case SetLanguage_ZHTW:
case SetLanguage_ZHHANT:
file += "zh-TW.txt";
break;
loadTranslationFile(translationFile);
default:
ui::initStrings();
return;
break;
}
}
fs::dataFile lang(file);
while(lang.readNextLine(true))
{
std::string name = lang.getName();
int ind = lang.getNextValueInt();
std::string str = lang.getNextValueStr();
addUIString(name, ind, str);
}
}
util::replaceButtonsInString(ui::strings[std::make_pair("helpUser", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("helpTitle", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("helpFolder", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("helpSettings", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("dialogYes", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("dialogNo", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("dialogOK", 0)]);
util::replaceButtonsInString(ui::strings[std::make_pair("appletModeWarning", 0)]);
}
void ui::saveTranslationFile(void *a)
void ui::saveTranslationFiles(void *a)
{
threadInfo *t = (threadInfo *)a;
t->status->setStatus("Saving the file master...");
t->status->setStatus(ui::getUICString("infoStatus", 9));
std::string out = fs::getWorkDir() + "en-US.txt";
FILE *enUS = fopen(out.c_str(), "w");
std::string outputFolder = fs::getWorkDir() + "lang", outputPath, romfsPath;
fs::mkDir(outputFolder);
//Save en-us first
ui::initStrings();
outputPath = fs::getWorkDir() + "lang/" + getFilename(SetLanguage_ENUS);
FILE *out = fopen(outputPath.c_str(), "w");
for(auto& s : ui::strings)
fprintf(enUS, "%s = %i, \"%s\"\n", s.first.first.c_str(), s.first.second, s.second.c_str());
fclose(enUS);
{
std::string stringOut = s.second;
util::replaceStr(stringOut, "\n", "\\n");
fprintf(out, "%s = %i, \"%s\"\n", s.first.first.c_str(), s.first.second, stringOut.c_str());
}
fclose(out);
romfsInit();
for(int i = 0; i < SetLanguage_Total; i++)
{
if(i == SetLanguage_ENUS)
continue;
outputPath = fs::getWorkDir() + "lang/" + getFilename(i);
romfsPath = "romfs:/lang/" + getFilename(i);
//Init original US English strings, then load translation over it. Result will be mixed file.
ui::initStrings();
loadTranslationFile(romfsPath);
out = fopen(outputPath.c_str(), "w");
for(auto& s : ui::strings)
{
std::string stringOut = s.second;
util::replaceStr(stringOut, "\n", "\\n");
fprintf(out, "%s = %i, \"%s\"\n", s.first.first.c_str(), s.first.second, stringOut.c_str());
}
fclose(out);
}
loadTrans();
romfsExit();
t->finished = true;
}

View File

@ -1,18 +1,18 @@
#include <string>
#include <vector>
#include <algorithm>
#include <string>
#include <switch.h>
#include <vector>
#include "file.h"
#include "data.h"
#include "file.h"
#include "type.h"
#include "ui.h"
#include "util.h"
#include "type.h"
#include "usr.h"
#include "ttl.h"
#include "usr.h"
static const char *settText = "Settings", *extText = "Extras";
static const char *settText = ui::getUICString("mainMenuSettings", 0), *extText = ui::getUICString("mainMenuExtras", 0);
//Main menu/Users + options, folder
ui::menu *ui::usrMenu;
@ -34,30 +34,30 @@ static unsigned usrHelpX = 0;
//Sort save create tids alphabetically by title from data
static struct
{
bool operator()(const uint64_t& tid1, const uint64_t& tid2)
{
std::string tid1Title = data::getTitleNameByTID(tid1);
std::string tid2Title = data::getTitleNameByTID(tid2);
uint32_t pointA = 0, pointB = 0;
for(unsigned i = 0, j = 0; i < tid1Title.length(); )
bool operator()(const uint64_t &tid1, const uint64_t &tid2)
{
ssize_t tid1Cnt = decode_utf8(&pointA, (const uint8_t *)&tid1Title.c_str()[i]);
ssize_t tid2Cnt = decode_utf8(&pointB, (const uint8_t *)&tid2Title.c_str()[j]);
std::string tid1Title = data::getTitleNameByTID(tid1);
std::string tid2Title = data::getTitleNameByTID(tid2);
pointA = tolower(pointA), pointB = tolower(pointB);
if(pointA != pointB)
return pointA < pointB;
uint32_t pointA = 0, pointB = 0;
for (unsigned i = 0, j = 0; i < tid1Title.length();)
{
ssize_t tid1Cnt = decode_utf8(&pointA, (const uint8_t *)&tid1Title.c_str()[i]);
ssize_t tid2Cnt = decode_utf8(&pointB, (const uint8_t *)&tid2Title.c_str()[j]);
i += tid1Cnt, j += tid2Cnt;
pointA = tolower(pointA), pointB = tolower(pointB);
if (pointA != pointB)
return pointA < pointB;
i += tid1Cnt, j += tid2Cnt;
}
return false;
}
return false;
}
} sortCreateTIDs;
static void onMainChange(void *a)
{
if(ui::usrMenu->getSelected() < (int)data::users.size())
if (ui::usrMenu->getSelected() < (int)data::users.size())
{
unsigned setUser = ui::usrMenu->getSelected();
data::setUserIndex(setUser);
@ -87,7 +87,7 @@ static void toEXT(void *a)
static void usrOptCallback(void *a)
{
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_B:
usrOptPanel->closePanel();
@ -99,7 +99,7 @@ static void usrOptCallback(void *a)
static void saveCreateCallback(void *a)
{
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_B:
usrOptMenu->setActive(true);
@ -119,39 +119,39 @@ static void saveCreateCallback(void *a)
static void usrOptSaveCreate(void *a)
{
ui::menu *m = (ui::menu *)a;
int devPos = m->getOptPos("Device");
int bcatPos = m->getOptPos("BCAT");
int cachePos = m->getOptPos("Cache");
int devPos = m->getOptPos(ui::getUICString("saveTypeMainMenu", 0));
int bcatPos = m->getOptPos(ui::getUICString("saveTypeMainMenu", 1));
int cachePos = m->getOptPos(ui::getUICString("saveTypeMainMenu", 2));
ui::updateInput();
int sel = m->getSelected();
bool closeUsrOpt = false;
if(sel == devPos && deviceSaveMenu->getOptCount() > 0)
if (sel == devPos && deviceSaveMenu->getOptCount() > 0)
{
deviceSaveMenu->setActive(true);
deviceSavePanel->openPanel();
closeUsrOpt = true;
}
else if(sel == bcatPos && bcatSaveMenu->getOptCount() > 0)
else if (sel == bcatPos && bcatSaveMenu->getOptCount() > 0)
{
bcatSaveMenu->setActive(true);
bcatSavePanel->openPanel();
closeUsrOpt = true;
}
else if(sel == cachePos && cacheSaveMenu->getOptCount() > 0)
else if (sel == cachePos && cacheSaveMenu->getOptCount() > 0)
{
cacheSaveMenu->setActive(true);
cacheSavePanel->openPanel();
closeUsrOpt = true;
}
else if(sel < devPos)
else if (sel < devPos)
{
saveCreateMenu->setActive(true);
saveCreatePanel->openPanel();
closeUsrOpt = true;
}
if(closeUsrOpt)
if (closeUsrOpt)
{
usrOptMenu->setActive(false);
usrOptPanel->closePanel();
@ -164,13 +164,15 @@ static void usrOptDeleteAllUserSaves_t(void *a)
threadInfo *t = (threadInfo *)a;
data::user *u = data::getCurrentUser();
int curUserIndex = data::getCurrentUserIndex();
int devUser = ui::usrMenu->getOptPos("Device");
int devUser = ui::usrMenu->getOptPos(ui::getUICString("saveTypeMainMenu", 0));
for(data::userTitleInfo& tinf : u->titleInfo)
for (data::userTitleInfo &tinf : u->titleInfo)
{
if(tinf.saveInfo.save_data_type != FsSaveDataType_System && (tinf.saveInfo.save_data_type != FsSaveDataType_Device || curUserIndex == devUser))
if (tinf.saveInfo.save_data_type != FsSaveDataType_System &&
(tinf.saveInfo.save_data_type != FsSaveDataType_Device || curUserIndex == devUser))
{
t->status->setStatus(ui::getUICString("threadStatusDeletingSaveData", 0), data::getTitleNameByTID(tinf.tid).c_str());
t->status->setStatus(ui::getUICString("threadStatusDeletingSaveData", 0),
data::getTitleNameByTID(tinf.tid).c_str());
fsDeleteSaveDataFileSystemBySaveDataSpaceId(FsSaveDataSpaceId_User, tinf.saveInfo.save_data_id);
}
}
@ -182,7 +184,12 @@ static void usrOptDeleteAllUserSaves_t(void *a)
static void usrOptDeleteAllUserSaves(void *a)
{
data::user *u = data::getCurrentUser();
ui::confirmArgs *conf = ui::confirmArgsCreate(true, usrOptDeleteAllUserSaves_t, NULL, true, ui::getUICString("saveDataDeleteAllUser", 0), u->getUsername().c_str());
ui::confirmArgs *conf = ui::confirmArgsCreate(true,
usrOptDeleteAllUserSaves_t,
NULL,
NULL,
ui::getUICString("saveDataDeleteAllUser", 0),
u->getUsername().c_str());
ui::confirm(conf);
}
@ -218,9 +225,7 @@ static void cacheSavePanelDraw(void *a)
static void usrOptDumpAllUserSaves(void *a)
{
data::user *u = data::getCurrentUser();
if(u->titleInfo.size() > 0)
fs::dumpAllUserSaves();
ui::newThread(fs::dumpAllUserSaves, NULL, fs::fileDrawFunc);
}
static void createSaveData(void *a)
@ -230,29 +235,29 @@ static void createSaveData(void *a)
//Device, BCAT, and Cache are hardcoded user IDs in JKSV's data
uint64_t tid = 0;
switch(uid)
switch (uid)
{
case 0://This is system
case 0: //This is system
break;
case 2:
tid = bcatSids[bcatSaveMenu->getSelected()];
fs::createSaveData(FsSaveDataType_Bcat, tid, util::u128ToAccountUID(0));
fs::createSaveDataThreaded(FsSaveDataType_Bcat, tid, util::u128ToAccountUID(0));
break;
case 3:
tid = devSids[deviceSaveMenu->getSelected()];
fs::createSaveData(FsSaveDataType_Device, tid, util::u128ToAccountUID(0));
fs::createSaveDataThreaded(FsSaveDataType_Device, tid, util::u128ToAccountUID(0));
break;
case 5:
tid = cacheSids[cacheSaveMenu->getSelected()];
fs::createSaveData(FsSaveDataType_Cache, tid, util::u128ToAccountUID(0));
fs::createSaveDataThreaded(FsSaveDataType_Cache, tid, util::u128ToAccountUID(0));
break;
default:
tid = accSids[saveCreateMenu->getSelected()];
fs::createSaveData(FsSaveDataType_Account, tid, u->getUID());
fs::createSaveDataThreaded(FsSaveDataType_Account, tid, u->getUID());
break;
}
}
@ -261,24 +266,24 @@ static void usrOptCreateAllSaves_t(void *a)
{
threadInfo *t = (threadInfo *)a;
data::user *u = data::getCurrentUser();
int devPos = ui::usrMenu->getOptPos("Device");
int bcatPos = ui::usrMenu->getOptPos("BCAT");
int devPos = ui::usrMenu->getOptPos(ui::getUICString("saveTypeMainMenu", 0));
int bcatPos = ui::usrMenu->getOptPos(ui::getUICString("saveTypeMainMenu", 1));
int sel = ui::usrMenu->getSelected();
if(sel < devPos)
if (sel < devPos)
{
AccountUid uid = u->getUID();
for(unsigned i = 0; i < accSids.size(); i++)
fs::createSaveData(FsSaveDataType_Account, accSids[i], uid);
for (unsigned i = 0; i < accSids.size(); i++)
fs::createSaveData(FsSaveDataType_Account, accSids[i], uid, t);
}
else if(sel == devPos)
else if (sel == devPos)
{
for(unsigned i = 0; i < devSids.size(); i++)
fs::createSaveData(FsSaveDataType_Device, devSids[i], util::u128ToAccountUID(0));
for (unsigned i = 0; i < devSids.size(); i++)
fs::createSaveData(FsSaveDataType_Device, devSids[i], util::u128ToAccountUID(0), t);
}
else if(sel == bcatPos)
else if (sel == bcatPos)
{
for(unsigned i = 0; i < bcatSids.size(); i++)
fs::createSaveData(FsSaveDataType_Bcat, bcatSids[i], util::u128ToAccountUID(0));
for (unsigned i = 0; i < bcatSids.size(); i++)
fs::createSaveData(FsSaveDataType_Bcat, bcatSids[i], util::u128ToAccountUID(0), t);
}
t->finished = true;
}
@ -286,8 +291,13 @@ static void usrOptCreateAllSaves_t(void *a)
static void usrOptCreateAllSaves(void *a)
{
data::user *u = data::getCurrentUser();
ui::confirmArgs *send = ui::confirmArgsCreate(true, usrOptCreateAllSaves_t, NULL, true, ui::getUICString("confirmCreateAllSaveData", 0), u->getUsername().c_str());
ui::confirm(send);
ui::confirmArgs *conf = ui::confirmArgsCreate(true,
usrOptCreateAllSaves_t,
NULL,
NULL,
ui::getUICString("confirmCreateAllSaveData", 0),
u->getUsername().c_str());
ui::confirm(conf);
}
//Sets up save create menus
@ -304,20 +314,21 @@ static void initSaveCreateMenus()
cacheSids.clear();
//Group into vectors to match
for(auto& t : data::titles)
for (auto &t : data::titles)
{
NacpStruct *nacp = &t.second.nacp;
NacpStruct *nacp = &t.second.data.nacp;
if(nacp->user_account_save_data_size > 0)
if (nacp->user_account_save_data_size > 0)
accSids.push_back(t.first);
if(nacp->device_save_data_size > 0)
if (nacp->device_save_data_size > 0)
devSids.push_back(t.first);
if(nacp->bcat_delivery_cache_storage_size > 0)
if (nacp->bcat_delivery_cache_storage_size > 0)
bcatSids.push_back(t.first);
if(nacp->cache_storage_size > 0 || nacp->cache_storage_journal_size > 0 || nacp->cache_storage_data_and_journal_size_max > 0)
if (nacp->cache_storage_size > 0 || nacp->cache_storage_journal_size > 0 ||
nacp->cache_storage_data_and_journal_size_max > 0)
cacheSids.push_back(t.first);
}
@ -327,25 +338,25 @@ static void initSaveCreateMenus()
std::sort(bcatSids.begin(), bcatSids.end(), sortCreateTIDs);
std::sort(cacheSids.begin(), cacheSids.end(), sortCreateTIDs);
for(unsigned i = 0; i < accSids.size(); i++)
for (unsigned i = 0; i < accSids.size(); i++)
{
saveCreateMenu->addOpt(NULL, data::getTitleNameByTID(accSids[i]));
saveCreateMenu->optAddButtonEvent(i, HidNpadButton_A, createSaveData, NULL);
}
for(unsigned i = 0; i < devSids.size(); i++)
for (unsigned i = 0; i < devSids.size(); i++)
{
deviceSaveMenu->addOpt(NULL, data::getTitleNameByTID(devSids[i]));
deviceSaveMenu->optAddButtonEvent(i, HidNpadButton_A, createSaveData, NULL);
}
for(unsigned i = 0; i < bcatSids.size(); i++)
for (unsigned i = 0; i < bcatSids.size(); i++)
{
bcatSaveMenu->addOpt(NULL, data::getTitleNameByTID(bcatSids[i]));
bcatSaveMenu->optAddButtonEvent(i, HidNpadButton_A, createSaveData, NULL);
}
for(unsigned i = 0; i < cacheSids.size(); i++)
for (unsigned i = 0; i < cacheSids.size(); i++)
{
cacheSaveMenu->addOpt(NULL, data::getTitleNameByTID(cacheSids[i]));
cacheSaveMenu->optAddButtonEvent(i, HidNpadButton_A, createSaveData, NULL);
@ -375,7 +386,7 @@ void ui::usrInit()
cacheSaveMenu->setActive(false);
cacheSaveMenu->setCallback(saveCreateCallback, NULL);
for(data::user u : data::users)
for (data::user u : data::users)
{
int usrPos = usrMenu->addOpt(u.getUserIcon(), u.getUsername());
usrMenu->optAddButtonEvent(usrPos, HidNpadButton_A, toTTL, NULL);
@ -401,7 +412,7 @@ void ui::usrInit()
usrOptPanel = new ui::slideOutPanel(410, 720, 0, ui::SLD_RIGHT, usrOptPanelDraw);
ui::registerPanel(usrOptPanel);
for(int i = 0; i < 4; i++)
for (int i = 0; i < 4; i++)
usrOptMenu->addOpt(NULL, ui::getUIString("userOptions", i));
//Dump All User Saves
@ -446,9 +457,6 @@ void ui::usrExit()
delete deviceSaveMenu;
delete bcatSaveMenu;
delete cacheSaveMenu;
SDL_DestroyTexture(sett);
SDL_DestroyTexture(ext);
}
void ui::usrRefresh()
@ -458,23 +466,27 @@ void ui::usrRefresh()
void ui::usrUpdate()
{
if(usrMenu->getActive())
if (usrMenu->getActive())
{
switch(ui::padKeysDown())
switch (ui::padKeysDown())
{
case HidNpadButton_X:
{
int cachePos = usrMenu->getOptPos("Cache");
if(usrMenu->getSelected() <= cachePos)
{
data::user *u = data::getCurrentUser();
usrOptMenu->editOpt(0, NULL, ui::getUIString("userOptions", 0) + u->getUsername());
usrOptMenu->setActive(true);
usrMenu->setActive(false);
usrOptPanel->openPanel();
}
}
case HidNpadButton_Y:
ui::newThread(fs::dumpAllUsersAllSaves, NULL, fs::fileDrawFunc);
break;
case HidNpadButton_X:
{
int cachePos = usrMenu->getOptPos(ui::getUIString("saveTypeMainMenu", 2));
if (usrMenu->getSelected() <= cachePos)
{
data::user *u = data::getCurrentUser();
usrOptMenu->editOpt(0, NULL, ui::getUIString("userOptions", 0) + u->getUsername());
usrOptMenu->setActive(true);
usrMenu->setActive(false);
usrOptPanel->openPanel();
}
}
break;
}
}
usrMenu->update();
@ -487,6 +499,6 @@ void ui::usrUpdate()
void ui::usrDraw(SDL_Texture *target)
{
if(ui::mstate == USR_SEL)
if (ui::mstate == USR_SEL)
gfx::drawTextf(NULL, 18, usrHelpX, 673, &ui::txtCont, ui::getUICString("helpUser", 0));
}

View File

@ -1,44 +1,40 @@
#include <string>
#include <cstdio>
#include <ctime>
#include <sys/stat.h>
#include <json-c/json.h>
#include <string>
#include <sys/stat.h>
#include "file.h"
#include "data.h"
#include "gfx.h"
#include "util.h"
#include "ui.h"
#include "cfg.h"
#include "curlfuncs.h"
#include "data.h"
#include "file.h"
#include "gfx.h"
#include "type.h"
#include "ui.h"
#include "util.h"
static const char verboten[] = { ',', '/', '\\', '<', '>', ':', '"', '|', '?', '*', '', '©', '®'};
static const uint32_t verboten[] = {L',', L'/', L'\\', L'<', L'>', L':', L'"', L'|', L'?', L'*', L'', L'©', L'®'};
static bool isVerboten(const uint32_t& t)
static bool isVerboten(const uint32_t &t)
{
for(unsigned i = 0; i < 13; i++)
for (unsigned i = 0; i < 13; i++)
{
if(t == verboten[i])
if (t == verboten[i])
return true;
}
return false;
}
static inline bool isASCII(const uint32_t& t)
{
return t > 30 && t < 127;
}
void util::replaceStr(std::string& _str, const std::string& _find, const std::string& _rep)
void util::replaceStr(std::string &_str, const std::string &_find, const std::string &_rep)
{
size_t pos = 0;
while((pos = _str.find(_find)) != _str.npos)
while ((pos = _str.find(_find)) != _str.npos)
_str.replace(pos, _find.length(), _rep);
}
//Used to split version tag git
static void getVersionFromTag(const std::string& tag, unsigned& _year, unsigned& _month, unsigned& _day)
static void getVersionFromTag(const std::string &tag, unsigned &_year, unsigned &_month, unsigned &_day)
{
_month = strtoul(tag.substr(0, 2).c_str(), NULL, 10);
_day = strtoul(tag.substr(3, 5).c_str(), NULL, 10);
@ -49,13 +45,13 @@ static void getVersionFromTag(const std::string& tag, unsigned& _year, unsigned&
typedef enum
{
SwkbdPosStart = 0,
SwkbdPosEnd = 1
SwkbdPosEnd = 1
} SwkbdInitPos;
typedef struct
{
uint16_t read[0x32 / sizeof(uint16_t)];
uint16_t word[0x32 / sizeof(uint16_t)];
uint16_t read[0x32 / sizeof(uint16_t)];
uint16_t word[0x32 / sizeof(uint16_t)];
} dictWord;
void swkbdDictWordCreate(dictWord *w, const char *read, const char *word)
@ -68,9 +64,9 @@ void swkbdDictWordCreate(dictWord *w, const char *read, const char *word)
uint32_t replaceChar(uint32_t c)
{
switch(c)
switch (c)
{
case 'é':
case L'é':
return 'e';
break;
}
@ -81,9 +77,9 @@ uint32_t replaceChar(uint32_t c)
static inline void replaceCharCStr(char *_s, char _find, char _rep)
{
size_t strLength = strlen(_s);
for(unsigned i = 0; i < strLength; i++)
for (unsigned i = 0; i < strLength; i++)
{
if(_s[i] == _find)
if (_s[i] == _find)
_s[i] = _rep;
}
}
@ -96,14 +92,28 @@ std::string util::getDateTime(int fmt)
time(&raw);
tm *Time = localtime(&raw);
switch(fmt)
switch (fmt)
{
case DATE_FMT_YMD:
sprintf(ret, "%04d.%02d.%02d @ %02d.%02d.%02d", Time->tm_year + 1900, Time->tm_mon + 1, Time->tm_mday, Time->tm_hour, Time->tm_min, Time->tm_sec);
sprintf(ret,
"%04d.%02d.%02d @ %02d.%02d.%02d",
Time->tm_year + 1900,
Time->tm_mon + 1,
Time->tm_mday,
Time->tm_hour,
Time->tm_min,
Time->tm_sec);
break;
case DATE_FMT_YDM:
sprintf(ret, "%04d.%02d.%02d @ %02d.%02d.%02d", Time->tm_year + 1900, Time->tm_mday, Time->tm_mon + 1, Time->tm_hour, Time->tm_min, Time->tm_sec);
sprintf(ret,
"%04d.%02d.%02d @ %02d.%02d.%02d",
Time->tm_year + 1900,
Time->tm_mday,
Time->tm_mon + 1,
Time->tm_hour,
Time->tm_min,
Time->tm_sec);
break;
case DATE_FMT_HOYSTE:
@ -111,7 +121,13 @@ std::string util::getDateTime(int fmt)
break;
case DATE_FMT_JHK:
sprintf(ret, "%04d%02d%02d_%02d%02d", Time->tm_year + 1900, Time->tm_mon + 1, Time->tm_mday, Time->tm_hour, Time->tm_min);
sprintf(ret,
"%04d%02d%02d_%02d%02d",
Time->tm_year + 1900,
Time->tm_mon + 1,
Time->tm_mday,
Time->tm_hour,
Time->tm_min);
break;
case DATE_FMT_ASC:
@ -124,47 +140,47 @@ std::string util::getDateTime(int fmt)
return std::string(ret);
}
void util::copyDirListToMenu(const fs::dirList& d, ui::menu& m)
void util::copyDirListToMenu(const fs::dirList &d, ui::menu &m)
{
m.reset();
m.addOpt(NULL, ".");
m.addOpt(NULL, "..");
for(unsigned i = 0; i < d.getCount(); i++)
for (unsigned i = 0; i < d.getCount(); i++)
{
if(d.isDir(i))
if (d.isDir(i))
m.addOpt(NULL, "D " + d.getItem(i));
else
m.addOpt(NULL, "F " + d.getItem(i));
}
}
void util::removeLastFolderFromString(std::string& _path)
void util::removeLastFolderFromString(std::string &_path)
{
unsigned last = _path.find_last_of('/', _path.length() - 2);
_path.erase(last + 1, _path.length());
}
size_t util::getTotalPlacesInPath(const std::string& _path)
size_t util::getTotalPlacesInPath(const std::string &_path)
{
//Skip device
size_t pos = _path.find_first_of('/'), ret = 0;
while((pos = _path.find_first_of('/', ++pos)) != _path.npos)
while ((pos = _path.find_first_of('/', ++pos)) != _path.npos)
++ret;
return ret;
}
void util::trimPath(std::string& _path, uint8_t _places)
void util::trimPath(std::string &_path, uint8_t _places)
{
size_t pos = _path.find_first_of('/');
for(int i = 0; i < _places; i++)
for (int i = 0; i < _places; i++)
pos = _path.find_first_of('/', ++pos);
_path = _path.substr(++pos, _path.npos);
}
std::string util::safeString(const std::string& s)
std::string util::safeString(const std::string &s)
{
std::string ret = "";
for(unsigned i = 0; i < s.length(); )
for (unsigned i = 0; i < s.length();)
{
uint32_t tmpChr = 0;
ssize_t untCnt = decode_utf8(&tmpChr, (uint8_t *)&s.data()[i]);
@ -173,87 +189,34 @@ std::string util::safeString(const std::string& s)
tmpChr = replaceChar(tmpChr);
if(isVerboten(tmpChr))
if (isVerboten(tmpChr))
ret += ' ';
else if(!isASCII(tmpChr))
else if (!isASCII(tmpChr))
return ""; //return empty string so titledata::init defaults to titleID
else
ret += (char)tmpChr;
}
//Check for spaces at end
while(ret[ret.length() - 1] == ' ' || ret[ret.length() - 1] == '.')
while (ret[ret.length() - 1] == ' ' || ret[ret.length() - 1] == '.')
ret.erase(ret.length() - 1, 1);
return ret;
}
static inline std::string getTimeString(const uint32_t& _h, const uint32_t& _m)
static inline std::string getTimeString(const uint32_t &_h, const uint32_t &_m)
{
char tmp[32];
sprintf(tmp, "%02d:%02d", _h, _m);
return std::string(tmp);
}
std::string util::getInfoString(data::user& u, const uint64_t& tid)
{
data::titleInfo *tinfo = data::getTitleInfoByTID(tid);
data::userTitleInfo *userTinfo = data::getCurrentUserTitleInfo();
std::string ret = tinfo->title + "\n";
ret += "TID: " + util::getIDStr(tid) + "\n";
ret += "SID: " + util::getIDStr(userTinfo->saveInfo.save_data_id) + "\n";
uint32_t hours, mins;
hours = userTinfo->playStats.playtimeMinutes / 60;
mins = userTinfo->playStats.playtimeMinutes - (hours * 60);
ret += "Play Time: " + getTimeString(hours, mins) + "\n";
ret += "Total Launches: " + std::to_string(userTinfo->playStats.totalLaunches) + "\n";
switch(userTinfo->saveInfo.save_data_type)
{
case FsSaveDataType_System:
ret += "System Save\n";
break;
case FsSaveDataType_Account:
ret += "Save Data\n";
break;
case FsSaveDataType_Bcat:
ret += "BCAT\n";
break;
case FsSaveDataType_Device:
ret += "Device Save\n";
break;
case FsSaveDataType_Temporary:
ret += "Temp Storage\n";
break;
case FsSaveDataType_Cache:
{
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
ret += "Cache Storage\n";
ret += "Save Index: " + std::to_string(d->saveInfo.save_data_index) + "\n";
}
break;
case FsSaveDataType_SystemBcat:
ret += "System BCAT\n";
break;
}
ret += u.getUsername();
return ret;
}
std::string util::getStringInput(SwkbdType _type, const std::string& def, const std::string& head, size_t maxLength, unsigned dictCnt, const std::string dictWords[])
std::string util::getStringInput(SwkbdType _type,
const std::string &def,
const std::string &head,
size_t maxLength,
unsigned dictCnt,
const std::string dictWords[])
{
SwkbdConfig swkbd;
swkbdCreate(&swkbd, dictCnt);
@ -267,10 +230,10 @@ std::string util::getStringInput(SwkbdType _type, const std::string& def, const
swkbdConfigSetKeySetDisableBitmask(&swkbd, SwkbdKeyDisableBitmask_Backslash | SwkbdKeyDisableBitmask_Percent);
swkbdConfigSetDicFlag(&swkbd, 1);
if(dictCnt > 0)
if (dictCnt > 0)
{
dictWord words[dictCnt];
for(unsigned i = 0; i < dictCnt; i++)
for (unsigned i = 0; i < dictCnt; i++)
swkbdDictWordCreate(&words[i], dictWords[i].c_str(), dictWords[i].c_str());
swkbdConfigSetDictionary(&swkbd, (SwkbdDictWord *)words, dictCnt);
@ -284,7 +247,25 @@ std::string util::getStringInput(SwkbdType _type, const std::string& def, const
return std::string(out);
}
std::string util::generateAbbrev(const uint64_t& tid)
std::string util::getExtensionFromString(const std::string &get)
{
size_t ext = get.find_last_of('.');
if (ext != get.npos)
return get.substr(ext + 1, get.npos);
else
return "";
}
std::string util::getFilenameFromPath(const std::string &get)
{
size_t nameStart = get.find_last_of('/');
if (nameStart != get.npos)
return get.substr(nameStart + 1, get.npos);
else
return "";
}
std::string util::generateAbbrev(uint64_t tid)
{
data::titleInfo *tmp = data::getTitleInfoByTID(tid);
size_t titleLength = tmp->safeTitle.length();
@ -295,23 +276,23 @@ std::string util::generateAbbrev(const uint64_t& tid)
std::string ret;
char *tok = strtok(temp, " ");
while(tok)
while (tok)
{
if(isASCII(tok[0]))
if (isASCII(tok[0]))
ret += tok[0];
tok = strtok(NULL, " ");
}
return ret;
}
void util::stripChar(char _c, std::string& _s)
void util::stripChar(char _c, std::string &_s)
{
size_t pos = 0;
while((pos = _s.find(_c)) != _s.npos)
while ((pos = _s.find(_c)) != _s.npos)
_s.erase(pos, 1);
}
void util::replaceButtonsInString(std::string& rep)
void util::replaceButtonsInString(std::string &rep)
{
replaceStr(rep, "[A]", "\ue0e0");
replaceStr(rep, "[B]", "\ue0e1");
@ -334,9 +315,9 @@ void util::replaceButtonsInString(std::string& rep)
SDL_Texture *util::createIconGeneric(const char *txt, int fontSize, bool clearBack)
{
SDL_Texture *ret = SDL_CreateTexture(gfx::render, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, 256, 256);
SDL_Texture *ret = gfx::texMgr->textureCreate(256, 256);
SDL_SetRenderTarget(gfx::render, ret);
if(clearBack)
if (clearBack)
{
SDL_SetRenderDrawColor(gfx::render, ui::rectLt.r, ui::rectLt.g, ui::rectLt.b, ui::rectLt.a);
SDL_RenderClear(gfx::render);
@ -352,15 +333,46 @@ SDL_Texture *util::createIconGeneric(const char *txt, int fontSize, bool clearBa
return ret;
}
void util::setCPU(uint32_t hz)
void util::sysBoost()
{
if(R_FAILED(clkrstInitialize()))
if (R_FAILED(clkrstInitialize()))
return;
ClkrstSession cpu;
ClkrstSession cpu, gpu, ram;
clkrstOpenSession(&cpu, PcvModuleId_CpuBus, 3);
clkrstSetClockRate(&cpu, hz);
clkrstOpenSession(&gpu, PcvModuleId_GPU, 3);
clkrstOpenSession(&ram, PcvModuleId_EMC, 3);
clkrstSetClockRate(&cpu, util::CPU_SPEED_1785MHz);
clkrstSetClockRate(&gpu, util::GPU_SPEED_76MHz);
clkrstSetClockRate(&ram, util::RAM_SPEED_1600MHz);
clkrstCloseSession(&cpu);
clkrstCloseSession(&gpu);
clkrstCloseSession(&ram);
clkrstExit();
}
void util::sysNormal()
{
if (R_FAILED(clkrstInitialize()))
return;
ClkrstSession cpu, gpu, ram;
clkrstOpenSession(&cpu, PcvModuleId_CpuBus, 3);
clkrstOpenSession(&gpu, PcvModuleId_GPU, 3);
clkrstOpenSession(&ram, PcvModuleId_EMC, 3);
if (cfg::config["ovrClk"])
clkrstSetClockRate(&cpu, util::CPU_SPEED_1224MHz);
else
clkrstSetClockRate(&cpu, util::CPU_SPEED_1020MHz);
clkrstSetClockRate(&gpu, util::GPU_SPEED_76MHz);
clkrstSetClockRate(&ram, util::RAM_SPEED_1331MHz);
clkrstCloseSession(&cpu);
clkrstCloseSession(&gpu);
clkrstCloseSession(&ram);
clkrstExit();
}
@ -368,8 +380,8 @@ void util::checkForUpdate(void *a)
{
threadInfo *t = (threadInfo *)a;
t->status->setStatus(ui::getUICString("threadStatusCheckingForUpdate", 0));
std::string gitJson = getJSONURL(NULL, "https://api.github.com/repos/J-D-K/JKSV/releases/latest");
if(gitJson.empty())
std::string gitJson = curlFuncs::getJSONURL(NULL, "https://api.github.com/repos/J-D-K/JKSV/releases/latest");
if (gitJson.empty())
{
ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("onlineErrorConnecting", 0));
t->finished = true;
@ -383,7 +395,7 @@ void util::checkForUpdate(void *a)
tagStr = json_object_get_string(tag);
getVersionFromTag(tagStr, year, month, day);
//This can throw false positives as is. need to fix sometime
if(year > BLD_YEAR || month > BLD_MON || month > BLD_DAY)
if (year > BLD_YEAR || month > BLD_MON || month > BLD_DAY)
{
t->status->setStatus(ui::getUICString("threadStatusDownloadingUpdate", 0));
//dunno about NSP yet...
@ -394,7 +406,7 @@ void util::checkForUpdate(void *a)
std::vector<uint8_t> jksvBuff;
std::string url = json_object_get_string(dlUrl);
getBinURL(&jksvBuff, url);
curlFuncs::getBinURL(&jksvBuff, url);
FILE *jksvOut = fopen("sdmc:/switch/JKSV.nro", "wb");
fwrite(jksvBuff.data(), 1, jksvBuff.size(), jksvOut);
fclose(jksvOut);
@ -406,27 +418,16 @@ void util::checkForUpdate(void *a)
t->finished = true;
}
std::string util::getSizeString(const uint64_t& _size)
std::string util::getSizeString(uint64_t _size)
{
char sizeStr[32];
if(_size >= 0x40000000)
if (_size >= 0x40000000)
sprintf(sizeStr, "%.2fGB", (float)_size / 1024.0f / 1024.0f / 1024.0f);
else if(_size >= 0x100000)
else if (_size >= 0x100000)
sprintf(sizeStr, "%.2fMB", (float)_size / 1024.0f / 1024.0f);
else if(_size >= 0x400)
else if (_size >= 0x400)
sprintf(sizeStr, "%.2fKB", (float)_size / 1024.0f);
else
sprintf(sizeStr, "%lu Bytes", _size);
return std::string(sizeStr);
}
Result util::accountDeleteUser(AccountUid *uid)
{
Service *account = accountGetServiceSession();
struct
{
AccountUid uid;
} in = {*uid};
return serviceDispatchIn(account, 203, in);
}

339
src/webdav.cpp Normal file
View File

@ -0,0 +1,339 @@
#include "webdav.h"
#include "fs.h"
#include "tinyxml2.h"
rfs::WebDav::WebDav(const std::string& origin, const std::string& username, const std::string& password)
: origin(origin), username(username), password(password)
{
curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
if (!username.empty())
curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
if (!password.empty())
curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
}
}
rfs::WebDav::~WebDav() {
if (curl) {
curl_easy_cleanup(curl);
}
}
bool rfs::WebDav::resourceExists(const std::string& id) {
CURL* local_curl = curl_easy_duphandle(curl);
// we expect id to be properly escaped and starting with a /
std::string fullUrl = origin + id;
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Depth: 0");
curl_easy_setopt(local_curl, CURLOPT_URL, fullUrl.c_str());
curl_easy_setopt(local_curl, CURLOPT_CUSTOMREQUEST, "PROPFIND");
curl_easy_setopt(local_curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(local_curl, CURLOPT_NOBODY, 1L); // do not include the response body
CURLcode res = curl_easy_perform(local_curl);
curl_slist_free_all(headers); // free the custom headers
bool ret = false;
if(res == CURLE_OK) {
long response_code;
curl_easy_getinfo(local_curl, CURLINFO_RESPONSE_CODE, &response_code);
if(response_code == 207) { // 207 Multi-Status is a successful response for PROPFIND
ret = true;
}
} else {
fs::logWrite("WebDav: directory exists failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(local_curl);
return ret;
}
// parent ID can never be empty
std::string rfs::WebDav::appendResourceToParentId(const std::string& resourceName, const std::string& parentId, bool isDir) {
char *escaped = curl_easy_escape(curl, resourceName.c_str(), 0);
// we always expect parent to be properly URL encoded.
std::string ret = parentId + std::string(escaped) + (isDir ? "/" : "");
curl_free(escaped);
return ret;
}
bool rfs::WebDav::createDir(const std::string& dirName, const std::string& parentId) {
CURL* local_curl = curl_easy_duphandle(curl);
std::string urlPath = appendResourceToParentId(dirName, parentId, true);
std::string fullUrl = origin + urlPath;
fs::logWrite("WebDav: Create directory at %s\n", fullUrl.c_str());
curl_easy_setopt(local_curl, CURLOPT_URL, fullUrl.c_str());
curl_easy_setopt(local_curl, CURLOPT_CUSTOMREQUEST, "MKCOL");
CURLcode res = curl_easy_perform(local_curl);
if(res != CURLE_OK) {
fs::logWrite("WebDav: directory creation failed: %s\n", curl_easy_strerror(res));
}
bool ret = res == CURLE_OK;
curl_easy_cleanup(local_curl);
return ret;
}
// we always expect parent to be properly URL encoded.
void rfs::WebDav::uploadFile(const std::string& filename, const std::string& parentId, curlFuncs::curlUpArgs *_upload) {
std::string fileId = appendResourceToParentId(filename, parentId, false);
updateFile(fileId, _upload);
}
void rfs::WebDav::updateFile(const std::string& _fileID, curlFuncs::curlUpArgs *_upload) {
// for webdav, same as upload
CURL* local_curl = curl_easy_duphandle(curl);
std::string fullUrl = origin + _fileID;
curl_easy_setopt(local_curl, CURLOPT_URL, fullUrl.c_str());
curl_easy_setopt(local_curl, CURLOPT_UPLOAD, 1L); // implicit PUT
curl_easy_setopt(local_curl, CURLOPT_READFUNCTION, curlFuncs::readDataFile);
curl_easy_setopt(local_curl, CURLOPT_READDATA, _upload);
curl_easy_setopt(local_curl, CURLOPT_UPLOAD_BUFFERSIZE, UPLOAD_BUFFER_SIZE);
curl_easy_setopt(local_curl, CURLOPT_UPLOAD, 1);
CURLcode res = curl_easy_perform(local_curl);
if(res != CURLE_OK) {
fs::logWrite("WebDav: file upload failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(local_curl); // Clean up the CURL handle
}
void rfs::WebDav::downloadFile(const std::string& _fileID, curlFuncs::curlDlArgs *_download) {
//Downloading is threaded because it's too slow otherwise
dlWriteThreadStruct dlWrite;
dlWrite.cfa = _download;
Thread writeThread;
threadCreate(&writeThread, writeThread_t, &dlWrite, NULL, 0x8000, 0x2B, 2);
CURL* local_curl = curl_easy_duphandle(curl);
std::string fullUrl = origin + _fileID;
curl_easy_setopt(local_curl, CURLOPT_URL, fullUrl.c_str());
curl_easy_setopt(local_curl, CURLOPT_WRITEFUNCTION, writeDataBufferThreaded);
curl_easy_setopt(local_curl, CURLOPT_WRITEDATA, &dlWrite);
threadStart(&writeThread);
CURLcode res = curl_easy_perform(local_curl);
// Copied from gd.cpp implementation.
// TODO: Not sure how a thread helps if this parent waits here.
threadWaitForExit(&writeThread);
threadClose(&writeThread);
if(res != CURLE_OK) {
fs::logWrite("WebDav: file download failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(local_curl);
}
void rfs::WebDav::deleteFile(const std::string& _fileID) {
CURL* local_curl = curl_easy_duphandle(curl);
std::string fullUrl = origin + _fileID;
curl_easy_setopt(local_curl, CURLOPT_URL, fullUrl.c_str());
curl_easy_setopt(local_curl, CURLOPT_CUSTOMREQUEST, "DELETE");
CURLcode res = curl_easy_perform(local_curl);
if(res != CURLE_OK) {
fs::logWrite("WebDav: file deletion failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(local_curl);
}
bool rfs::WebDav::dirExists(const std::string& dirName, const std::string& parentId) {
std::string urlPath = getDirID(dirName, parentId);
return resourceExists(urlPath);
}
bool rfs::WebDav::fileExists(const std::string& filename, const std::string& parentId) {
std::string urlPath = appendResourceToParentId(filename, parentId, false);
return resourceExists(urlPath);
}
std::string rfs::WebDav::getFileID(const std::string& filename, const std::string& parentId) {
return appendResourceToParentId(filename, parentId, false);
}
std::string rfs::WebDav::getDirID(const std::string& dirName, const std::string& parentId) {
return appendResourceToParentId(dirName, parentId, true);
}
std::vector<rfs::RfsItem> rfs::WebDav::getListWithParent(const std::string& _parentId) {
std::vector<rfs::RfsItem> list;
CURL* local_curl = curl_easy_duphandle(curl);
// we expect _resource to be properly escaped
std::string fullUrl = origin + _parentId;
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Depth: 1");
std::string responseString;
curl_easy_setopt(local_curl, CURLOPT_URL, fullUrl.c_str());
curl_easy_setopt(local_curl, CURLOPT_CUSTOMREQUEST, "PROPFIND");
curl_easy_setopt(local_curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(local_curl, CURLOPT_WRITEFUNCTION, curlFuncs::writeDataString);
curl_easy_setopt(local_curl, CURLOPT_WRITEDATA, &responseString);
CURLcode res = curl_easy_perform(local_curl);
if(res == CURLE_OK) {
long response_code;
curl_easy_getinfo(local_curl, CURLINFO_RESPONSE_CODE, &response_code);
if(response_code == 207) { // 207 Multi-Status is a successful response for PROPFIND
fs::logWrite("WebDav: Response from WebDav. Parsing.\n");
std::vector<rfs::RfsItem> items = parseXMLResponse(responseString);
// insert into array
// TODO: Filter for zip?
list.insert(list.end(), items.begin(), items.end());
}
} else {
fs::logWrite("WebDav: directory listing failed: %s\n", curl_easy_strerror(res));
}
curl_slist_free_all(headers); // free the custom headers
curl_easy_cleanup(local_curl);
return list;
}
// Helper
std::string rfs::WebDav::getNamespacePrefix(tinyxml2::XMLElement* root, const std::string& nsURI) {
for(const tinyxml2::XMLAttribute* attr = root->FirstAttribute(); attr; attr = attr->Next()) {
std::string name = attr->Name();
std::string value = attr->Value();
if(value == nsURI) {
auto pos = name.find(':');
if(pos != std::string::npos) {
return name.substr(pos + 1);
} else {
return ""; // Default namespace (no prefix)
}
}
}
return ""; // No namespace found
}
std::vector<rfs::RfsItem> rfs::WebDav::parseXMLResponse(const std::string& xml) {
std::vector<RfsItem> items;
tinyxml2::XMLDocument doc;
if(doc.Parse(xml.c_str()) != tinyxml2::XML_SUCCESS) {
fs::logWrite("WebDav: Failed to parse XML from Server\n");
return items;
}
// Get the root element
tinyxml2::XMLElement *root = doc.RootElement();
std::string nsPrefix = getNamespacePrefix(root, "DAV:");
nsPrefix = !nsPrefix.empty() ? nsPrefix + ":" : nsPrefix; // Append colon if non-empty
fs::logWrite("WebDav: Parsing response, using prefix: %s\n", nsPrefix.c_str());
// Loop through the responses
tinyxml2::XMLElement* responseElem = root->FirstChildElement((nsPrefix + "response").c_str());
std::string parentId;
while (responseElem) {
RfsItem item;
item.size = 0;
tinyxml2::XMLElement* hrefElem = responseElem->FirstChildElement((nsPrefix + "href").c_str());
if (hrefElem) {
std::string hrefText = hrefElem->GetText();
// href can be absolute URI or relative reference. ALWAYS convert to relative reference
if(hrefText.find(origin) == 0) {
hrefText = hrefText.substr(origin.length());
}
item.id = hrefText;
item.parent = parentId;
}
tinyxml2::XMLElement* propstatElem = responseElem->FirstChildElement((nsPrefix + "propstat").c_str());
if (propstatElem) {
tinyxml2::XMLElement* propElem = propstatElem->FirstChildElement((nsPrefix + "prop").c_str());
if (propElem) {
tinyxml2::XMLElement* displaynameElem = propElem->FirstChildElement((nsPrefix + "displayname").c_str());
if (displaynameElem) {
item.name = displaynameElem->GetText();
} else {
// Fallback to name from href
item.name = getDisplayNameFromURL(item.id);
}
tinyxml2::XMLElement* resourcetypeElem = propElem->FirstChildElement((nsPrefix + "resourcetype").c_str());
if (resourcetypeElem) {
item.isDir = resourcetypeElem->FirstChildElement((nsPrefix + "collection").c_str()) != nullptr;
}
tinyxml2::XMLElement* contentLengthElem = propElem->FirstChildElement((nsPrefix + "getcontentlength").c_str());
if (contentLengthElem) {
const char* sizeStr = contentLengthElem->GetText();
if (sizeStr) {
item.size = std::stoi(sizeStr);
}
}
}
}
responseElem = responseElem->NextSiblingElement((nsPrefix + "response").c_str());
// first Item is always the parent.
if (parentId.empty()) {
parentId = item.id;
continue; // do not push parent to list (we are only interested in the children)
}
items.push_back(item);
}
return items;
}
// Function to extract and URL decode the filename from the URL
std::string rfs::WebDav::getDisplayNameFromURL(const std::string &url) {
// Find the position of the last '/'
size_t pos = url.find_last_of('/');
if (pos == std::string::npos) {
// If '/' is not found, return the whole URL as it is
return url;
}
// Extract the filename from the URL
std::string encodedFilename = url.substr(pos + 1);
// URL decode the filename
int outlength;
char *decodedFilename = curl_easy_unescape(curl, encodedFilename.c_str(), encodedFilename.length(), &outlength);
std::string result(decodedFilename, outlength);
// Free the memory allocated by curl_easy_unescape
curl_free(decodedFilename);
return result;
}