From 7816dab291f3f035fcc992e1cfbebed198ec1e2e Mon Sep 17 00:00:00 2001 From: J-D-K Date: Thu, 5 Dec 2024 20:04:47 -0500 Subject: [PATCH] It's a start. --- .clang-format | 136 ++++ .clang-tidy | 9 + .editorconfig | 17 + .gitattributes | 11 + .gitmodules | 6 + Include/AppStates/AppState.hpp | 46 ++ Include/AppStates/ProgressState.hpp | 27 + Include/AppStates/TaskState.hpp | 18 + Include/Colors.hpp | 16 + Include/Data/Data.hpp | 6 + Include/Input.hpp | 11 + Include/JKSV.hpp | 32 + Include/JSON.hpp | 16 + Include/Logger.hpp | 7 + Include/StringUtil.hpp | 7 + Include/Strings.hpp | 15 + Include/System/ProgressTask.hpp | 25 + Include/System/Task.hpp | 46 ++ Include/UI/RenderFunctions.hpp | 9 + Libraries/FsLib | 1 + Libraries/SDLLib | 1 + Makefile | 32 +- RomFS/Text/ENUS.json | 6 + RomFS/Textures/DialogCorners.png | Bin 0 -> 507 bytes RomFS/Textures/HeaderIcon.png | Bin 0 -> 1075 bytes RomFS/Textures/MenuBounding.png | Bin 0 -> 682 bytes Source/AppStates/ProgressState.cpp | 18 + Source/AppStates/TaskState.cpp | 23 + Source/Input.cpp | 32 + Source/JKSV.cpp | 141 ++++ Source/Logger.cpp | 32 + Source/Main.cpp | 12 + Source/StringUtil.cpp | 19 + Source/Strings.cpp | 90 +++ Source/System/ProgressTask.cpp | 16 + Source/System/Task.cpp | 41 ++ Source/UI/RenderFunctions.cpp | 27 + inc/cfg.h | 39 - inc/curlfuncs.h | 35 - inc/data.h | 103 --- inc/fs.h | 55 -- inc/fs/dir.h | 54 -- inc/fs/file.h | 65 -- inc/fs/fsfile.h | 100 --- inc/fs/fstype.h | 46 -- inc/fs/remote.h | 21 - inc/fs/zip.h | 18 - inc/gd.h | 66 -- inc/gfx.h | 26 - inc/gfx/textureMgr.h | 33 - inc/rfs.h | 53 -- inc/type.h | 28 - inc/ui.h | 110 --- inc/ui/ext.h | 10 - inc/ui/fld.h | 19 - inc/ui/fm.h | 10 - inc/ui/miscui.h | 165 ----- inc/ui/sett.h | 13 - inc/ui/sldpanel.h | 35 - inc/ui/thrdProc.h | 24 - inc/ui/ttl.h | 17 - inc/ui/ttlview.h | 56 -- inc/ui/uistr.h | 15 - inc/ui/usr.h | 14 - inc/util.h | 158 ----- inc/webdav.h | 53 -- romfs/icon.png | Bin 13966 -> 0 bytes romfs/img/fb/lDark.png | Bin 161655 -> 0 bytes romfs/img/fb/lLight.png | Bin 1817 -> 0 bytes romfs/img/fb/menuBotLeft.png | Bin 4854 -> 0 bytes romfs/img/fb/menuBotRight.png | Bin 4843 -> 0 bytes romfs/img/fb/menuTopLeft.png | Bin 4842 -> 0 bytes romfs/img/fb/menuTopRight.png | Bin 4843 -> 0 bytes romfs/img/icn/icnDefault.png | Bin 2896 -> 0 bytes romfs/img/icn/icnDrk.png | Bin 1526 -> 0 bytes romfs/img/icn/icnLght.png | Bin 1103 -> 0 bytes romfs/img/icn/icon.msk | Bin 65536 -> 0 bytes romfs/img/tboxDrk/progBarCoverLeftDrk.png | Bin 256 -> 0 bytes romfs/img/tboxDrk/progBarCoverRightDrk.png | Bin 249 -> 0 bytes romfs/img/tboxDrk/tboxCornerBotLeft.png | Bin 287 -> 0 bytes romfs/img/tboxDrk/tboxCornerBotRight.png | Bin 267 -> 0 bytes romfs/img/tboxDrk/tboxCornerTopLeft.png | Bin 290 -> 0 bytes romfs/img/tboxDrk/tboxCornerTopRight.png | Bin 287 -> 0 bytes romfs/img/tboxLght/progBarCoverLeftLight.png | Bin 257 -> 0 bytes romfs/img/tboxLght/progBarCoverRightLight.png | Bin 253 -> 0 bytes romfs/img/tboxLght/tboxCornerBotLeft.png | Bin 273 -> 0 bytes romfs/img/tboxLght/tboxCornerBotRight.png | Bin 266 -> 0 bytes romfs/img/tboxLght/tboxCornerTopLeft.png | Bin 279 -> 0 bytes romfs/img/tboxLght/tboxCornerTopRight.png | Bin 285 -> 0 bytes romfs/lang/de.txt | 171 ----- romfs/lang/en-GB.txt | 171 ----- romfs/lang/en-US.txt | 171 ----- romfs/lang/es-419.txt | 218 ------ romfs/lang/es.txt | 171 ----- romfs/lang/fr-CA.txt | 171 ----- romfs/lang/fr.txt | 171 ----- romfs/lang/it.txt | 171 ----- romfs/lang/ja.txt | 215 ------ romfs/lang/ko.txt | 171 ----- romfs/lang/nl.txt | 171 ----- romfs/lang/pt-BR.txt | 156 ---- romfs/lang/pt.txt | 171 ----- romfs/lang/ru.txt | 171 ----- romfs/lang/zh-CN.txt | 170 ----- romfs/lang/zh-TW.txt | 171 ----- src/cfg.cpp | 534 -------------- src/curlfuncs.cpp | 101 --- src/data.cpp | 503 ------------- src/fs.cpp | 670 ------------------ src/fs/dir.cpp | 270 ------- src/fs/file.cpp | 395 ----------- src/fs/fsfile.c | 150 ---- src/fs/remote.cpp | 134 ---- src/fs/zip.cpp | 256 ------- src/gd.cpp | 646 ----------------- src/gfx.cpp | 437 ------------ src/gfx/textureMgr.cpp | 124 ---- src/main.cpp | 67 -- src/rfs.cpp | 51 -- src/type.cpp | 25 - src/ui.cpp | 308 -------- src/ui/ext.cpp | 233 ------ src/ui/fld.cpp | 391 ---------- src/ui/fm.cpp | 591 --------------- src/ui/miscui.cpp | 569 --------------- src/ui/sett.cpp | 297 -------- src/ui/sldpanel.cpp | 68 -- src/ui/thrdProc.cpp | 96 --- src/ui/ttl.cpp | 414 ----------- src/ui/ttlview.cpp | 178 ----- src/ui/uistr.cpp | 391 ---------- src/ui/usr.cpp | 491 ------------- src/util.cpp | 420 ----------- src/webdav.cpp | 339 --------- 134 files changed, 942 insertions(+), 13410 deletions(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitmodules create mode 100644 Include/AppStates/AppState.hpp create mode 100644 Include/AppStates/ProgressState.hpp create mode 100644 Include/AppStates/TaskState.hpp create mode 100644 Include/Colors.hpp create mode 100644 Include/Data/Data.hpp create mode 100644 Include/Input.hpp create mode 100644 Include/JKSV.hpp create mode 100644 Include/JSON.hpp create mode 100644 Include/Logger.hpp create mode 100644 Include/StringUtil.hpp create mode 100644 Include/Strings.hpp create mode 100644 Include/System/ProgressTask.hpp create mode 100644 Include/System/Task.hpp create mode 100644 Include/UI/RenderFunctions.hpp create mode 160000 Libraries/FsLib create mode 160000 Libraries/SDLLib create mode 100644 RomFS/Text/ENUS.json create mode 100644 RomFS/Textures/DialogCorners.png create mode 100644 RomFS/Textures/HeaderIcon.png create mode 100644 RomFS/Textures/MenuBounding.png create mode 100644 Source/AppStates/ProgressState.cpp create mode 100644 Source/AppStates/TaskState.cpp create mode 100644 Source/Input.cpp create mode 100644 Source/JKSV.cpp create mode 100644 Source/Logger.cpp create mode 100644 Source/Main.cpp create mode 100644 Source/StringUtil.cpp create mode 100644 Source/Strings.cpp create mode 100644 Source/System/ProgressTask.cpp create mode 100644 Source/System/Task.cpp create mode 100644 Source/UI/RenderFunctions.cpp delete mode 100644 inc/cfg.h delete mode 100644 inc/curlfuncs.h delete mode 100644 inc/data.h delete mode 100644 inc/fs.h delete mode 100644 inc/fs/dir.h delete mode 100644 inc/fs/file.h delete mode 100644 inc/fs/fsfile.h delete mode 100644 inc/fs/fstype.h delete mode 100644 inc/fs/remote.h delete mode 100644 inc/fs/zip.h delete mode 100644 inc/gd.h delete mode 100644 inc/gfx.h delete mode 100644 inc/gfx/textureMgr.h delete mode 100644 inc/rfs.h delete mode 100644 inc/type.h delete mode 100644 inc/ui.h delete mode 100644 inc/ui/ext.h delete mode 100644 inc/ui/fld.h delete mode 100644 inc/ui/fm.h delete mode 100644 inc/ui/miscui.h delete mode 100644 inc/ui/sett.h delete mode 100644 inc/ui/sldpanel.h delete mode 100644 inc/ui/thrdProc.h delete mode 100644 inc/ui/ttl.h delete mode 100644 inc/ui/ttlview.h delete mode 100644 inc/ui/uistr.h delete mode 100644 inc/ui/usr.h delete mode 100644 inc/util.h delete mode 100644 inc/webdav.h delete mode 100644 romfs/icon.png delete mode 100644 romfs/img/fb/lDark.png delete mode 100644 romfs/img/fb/lLight.png delete mode 100644 romfs/img/fb/menuBotLeft.png delete mode 100644 romfs/img/fb/menuBotRight.png delete mode 100644 romfs/img/fb/menuTopLeft.png delete mode 100644 romfs/img/fb/menuTopRight.png delete mode 100644 romfs/img/icn/icnDefault.png delete mode 100644 romfs/img/icn/icnDrk.png delete mode 100644 romfs/img/icn/icnLght.png delete mode 100644 romfs/img/icn/icon.msk delete mode 100644 romfs/img/tboxDrk/progBarCoverLeftDrk.png delete mode 100644 romfs/img/tboxDrk/progBarCoverRightDrk.png delete mode 100644 romfs/img/tboxDrk/tboxCornerBotLeft.png delete mode 100644 romfs/img/tboxDrk/tboxCornerBotRight.png delete mode 100644 romfs/img/tboxDrk/tboxCornerTopLeft.png delete mode 100644 romfs/img/tboxDrk/tboxCornerTopRight.png delete mode 100644 romfs/img/tboxLght/progBarCoverLeftLight.png delete mode 100644 romfs/img/tboxLght/progBarCoverRightLight.png delete mode 100644 romfs/img/tboxLght/tboxCornerBotLeft.png delete mode 100644 romfs/img/tboxLght/tboxCornerBotRight.png delete mode 100644 romfs/img/tboxLght/tboxCornerTopLeft.png delete mode 100644 romfs/img/tboxLght/tboxCornerTopRight.png delete mode 100644 romfs/lang/de.txt delete mode 100644 romfs/lang/en-GB.txt delete mode 100644 romfs/lang/en-US.txt delete mode 100644 romfs/lang/es-419.txt delete mode 100644 romfs/lang/es.txt delete mode 100644 romfs/lang/fr-CA.txt delete mode 100644 romfs/lang/fr.txt delete mode 100644 romfs/lang/it.txt delete mode 100644 romfs/lang/ja.txt delete mode 100644 romfs/lang/ko.txt delete mode 100644 romfs/lang/nl.txt delete mode 100644 romfs/lang/pt-BR.txt delete mode 100644 romfs/lang/pt.txt delete mode 100644 romfs/lang/ru.txt delete mode 100644 romfs/lang/zh-CN.txt delete mode 100644 romfs/lang/zh-TW.txt delete mode 100644 src/cfg.cpp delete mode 100644 src/curlfuncs.cpp delete mode 100644 src/data.cpp delete mode 100644 src/fs.cpp delete mode 100644 src/fs/dir.cpp delete mode 100644 src/fs/file.cpp delete mode 100644 src/fs/fsfile.c delete mode 100644 src/fs/remote.cpp delete mode 100644 src/fs/zip.cpp delete mode 100644 src/gd.cpp delete mode 100644 src/gfx.cpp delete mode 100644 src/gfx/textureMgr.cpp delete mode 100644 src/main.cpp delete mode 100644 src/rfs.cpp delete mode 100644 src/type.cpp delete mode 100644 src/ui.cpp delete mode 100644 src/ui/ext.cpp delete mode 100644 src/ui/fld.cpp delete mode 100644 src/ui/fm.cpp delete mode 100644 src/ui/miscui.cpp delete mode 100644 src/ui/sett.cpp delete mode 100644 src/ui/sldpanel.cpp delete mode 100644 src/ui/thrdProc.cpp delete mode 100644 src/ui/ttl.cpp delete mode 100644 src/ui/ttlview.cpp delete mode 100644 src/ui/uistr.cpp delete mode 100644 src/ui/usr.cpp delete mode 100644 src/util.cpp delete mode 100644 src/webdav.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..cf7abd2 --- /dev/null +++ b/.clang-format @@ -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: 144 +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: false +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 +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..b17808b --- /dev/null +++ b/.clang-tidy @@ -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;' diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7fc84ba --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: http://EditorConfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true +end_of_line = lf + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c9a5f2b --- /dev/null +++ b/.gitattributes @@ -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 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..865dc49 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "Libraries/FsLib"] + path = Libraries/FsLib + url = https://github.com/J-D-K/FsLib.git +[submodule "Libraries/SDLLib"] + path = Libraries/SDLLib + url = https://github.com/J-D-K/SDLLib.git diff --git a/Include/AppStates/AppState.hpp b/Include/AppStates/AppState.hpp new file mode 100644 index 0000000..93c7fc3 --- /dev/null +++ b/Include/AppStates/AppState.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "SDL.hpp" +#include + +class AppState +{ + public: + AppState(void) = default; + virtual ~AppState() = 0; + + virtual void Update(void) = 0; + virtual void Render(void) = 0; + + // Returns whether or not state is still active or can be purged. + bool IsActive(void) const + { + return m_IsActive; + } + + void Deactivate(void) + { + m_IsActive = false; + } + + void GiveFocus(void) + { + m_HasFocus = true; + } + + void TakeFocus(void) + { + m_HasFocus = false; + } + + // Returns whether state is at back of vector and has focus. + bool HasFocus(void) const + { + return m_HasFocus; + } + + private: + // Whether state is still active or can be purged. + bool m_IsActive = true; + // Whether or not state has focus + bool m_HasFocus = false; +}; diff --git a/Include/AppStates/ProgressState.hpp b/Include/AppStates/ProgressState.hpp new file mode 100644 index 0000000..24512de --- /dev/null +++ b/Include/AppStates/ProgressState.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "AppStates/AppState.hpp" +#include "System/ProgressTask.hpp" +#include + +class ProgressState : public AppState +{ + public: + template + ProgressState(void (*Function)(Args...), Args... Arguments) : m_Task(Function, std::forward(Arguments)...){}; + ~ProgressState() {}; + + void Update(void); + void Render(void); + + private: + // Underlying progress tracking task. + System::ProgressTask m_Task; + // Progress as whole number instead of decimal. + size_t m_Progress = 0; + // Width of the green bar. The other is hard coded for 720px. + size_t m_ProgressBarWidth = 0; + // X coordinate of the percentage. + int m_PerentageX = 0; + // String for percentage. + std::string m_PercentageString; +}; diff --git a/Include/AppStates/TaskState.hpp b/Include/AppStates/TaskState.hpp new file mode 100644 index 0000000..f43c4a1 --- /dev/null +++ b/Include/AppStates/TaskState.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "AppStates/AppState.hpp" +#include "System/Task.hpp" + +class TaskState : public AppState +{ + public: + template + TaskState(void (*Function)(Args...), Args... Arguments) : m_Task(Function, std::forward(Arguments)...){}; + ~TaskState() {}; + + void Update(void); + void Render(void); + + private: + // Underlying task. + System::Task m_Task; +}; diff --git a/Include/Colors.hpp b/Include/Colors.hpp new file mode 100644 index 0000000..f36b510 --- /dev/null +++ b/Include/Colors.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "SDL.hpp" + +namespace Colors +{ + static constexpr SDL::Color White = {0xFFFFFFFF}; + static constexpr SDL::Color Black = {0x000000FF}; + static constexpr SDL::Color Red = {0xFF0000FF}; + static constexpr SDL::Color Green = {0x00FF00FF}; + static constexpr SDL::Color Blue = {0x0099EEFF}; + static constexpr SDL::Color Yellow = {0xF8FC00FF}; + static constexpr SDL::Color Pink = {0xFF4444FF}; + static constexpr SDL::Color ClearColor = {0x2D2D2DFF}; + static constexpr SDL::Color DialogBox = {0x505050FF}; + static constexpr SDL::Color BackgroundDim = {0x00000088}; +} // namespace Colors diff --git a/Include/Data/Data.hpp b/Include/Data/Data.hpp new file mode 100644 index 0000000..a241425 --- /dev/null +++ b/Include/Data/Data.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace Data +{ + bool Initialize(void); +} diff --git a/Include/Input.hpp b/Include/Input.hpp new file mode 100644 index 0000000..ea33776 --- /dev/null +++ b/Include/Input.hpp @@ -0,0 +1,11 @@ +#pragma once +#include + +namespace Input +{ + void Initialize(void); + void Update(void); + bool ButtonPressed(HidNpadButton Button); + bool ButtonHeld(HidNpadButton Button); + bool ButtonReleased(HidNpadButton Button); +} // namespace Input diff --git a/Include/JKSV.hpp b/Include/JKSV.hpp new file mode 100644 index 0000000..d1e8465 --- /dev/null +++ b/Include/JKSV.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "AppStates/AppState.hpp" +#include "SDL.hpp" +#include +#include + +class JKSV +{ + public: + // Initializes JKSV. + JKSV(void); + // Exits JKSV + ~JKSV(); + // Returns whether or not JKSV is actually running. + bool IsRunning(void) const; + // Updates input and back of state vector. + void Update(void); + // Renders base of app and states of vector. + void Render(void); + // Pushes a new state to the back of state vector. + static void PushState(std::shared_ptr NewState); + + private: + // Whether or not initialization was successful. + bool m_IsRunning = false; + // Whether or not translation author wants to be acknowledged. + bool m_ShowTranslationInfo = false; + // Header icon + SDL::SharedTexture m_HeaderIcon = nullptr; + // Vector of states. + static inline std::vector> m_StateVector; +}; diff --git a/Include/JSON.hpp b/Include/JSON.hpp new file mode 100644 index 0000000..6080677 --- /dev/null +++ b/Include/JSON.hpp @@ -0,0 +1,16 @@ +#pragma once +#include +#include + +namespace JSON +{ + // Use this instead of default json_object + using Object = std::unique_ptr; + + // Use this instead of json_object_from_x. Pass the function and its arguments instead. + template + static inline JSON::Object NewObject(json_object *(*Function)(Args...), Args... Arguments) + { + return JSON::Object((*Function)(Arguments...), json_object_put); + } +} // namespace JSON diff --git a/Include/Logger.hpp b/Include/Logger.hpp new file mode 100644 index 0000000..dc8bf30 --- /dev/null +++ b/Include/Logger.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace Logger +{ + void Initialize(void); + void Log(const char *Format, ...); +} // namespace Logger diff --git a/Include/StringUtil.hpp b/Include/StringUtil.hpp new file mode 100644 index 0000000..8b85e01 --- /dev/null +++ b/Include/StringUtil.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace StringUtil +{ + std::string GetFormattedString(const char *Format, ...); +} diff --git a/Include/Strings.hpp b/Include/Strings.hpp new file mode 100644 index 0000000..d723a81 --- /dev/null +++ b/Include/Strings.hpp @@ -0,0 +1,15 @@ +#pragma once +#include + +namespace Strings +{ + // Attempts to load strings from file in RomFS. + bool Initialize(void); + // Returns string with name and index. Returns nullptr if string doesn't exist. + const char *GetByName(std::string_view Name, int Index); + // Names of strings to prevent typos. + namespace Names + { + static constexpr std::string_view TranslationInfo = "TranslationInfo"; + } // namespace Names +} // namespace Strings diff --git a/Include/System/ProgressTask.hpp b/Include/System/ProgressTask.hpp new file mode 100644 index 0000000..01cc578 --- /dev/null +++ b/Include/System/ProgressTask.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "System/Task.hpp" + +namespace System +{ + class ProgressTask : public System::Task + { + public: + template + ProgressTask(void (*Function)(System::ProgressTask *, Args...), Args... Arguments) + : System::Task(Function, this, std::forward(Arguments)...){}; + + // Resets current down to 0 and sets goal + void Reset(double Goal); + // Sets current value + void UpdateCurrent(double Current); + + // Returns progress + double GetCurrentProgress(void) const; + + private: + // Current value and goal + double m_Current, m_Goal; + }; +} // namespace System diff --git a/Include/System/Task.hpp b/Include/System/Task.hpp new file mode 100644 index 0000000..d44d34b --- /dev/null +++ b/Include/System/Task.hpp @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include + +namespace System +{ + class Task + { + public: + template + Task(void (*Function)(System::Task *, Args...), Args... Arguments) + { + m_Thread = std::thread(Function, this, std::forward(Arguments)...); + } + + // This is an alternate constructor that passes through a pointer to a derived class. + template + Task(void (*Function)(System::Task *, Args...), System::Task *Task, Args... Arguments) + { + m_Thread = std::thread(Function, Task, std::forward(Arguments)...); + } + + virtual ~Task(); + + // Returns if thread is still running. + bool IsRunning(void) const; + // Signals thread is finished running. + void Finished(void); + + // Sets status to display. + void SetStatus(const char *Format, ...); + // Returns status string. + std::string GetStatus(void); + + private: + // Whether task is still running. + bool m_IsRunning = true; + // Status string the thread can set that the main thread can display. + std::string m_Status; + // Mutex so that string doesn't get messed up. + std::mutex m_StatusLock; + // Thread + std::thread m_Thread; + }; +} // namespace System diff --git a/Include/UI/RenderFunctions.hpp b/Include/UI/RenderFunctions.hpp new file mode 100644 index 0000000..8a5cb3b --- /dev/null +++ b/Include/UI/RenderFunctions.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "SDL.hpp" + +// These are just functions to render generic parts of the UI. +namespace UI +{ + void RenderDialogBox(SDL_Texture *Target, int X, int Y, int Width, int Height); + void RenderBoundingBox(SDL_Texture *Target, int X, int Y, int Width, int Height); +} // namespace UI diff --git a/Libraries/FsLib b/Libraries/FsLib new file mode 160000 index 0000000..960698b --- /dev/null +++ b/Libraries/FsLib @@ -0,0 +1 @@ +Subproject commit 960698b56f3088c6f5f549aceb94d4b66fb67d88 diff --git a/Libraries/SDLLib b/Libraries/SDLLib new file mode 160000 index 0000000..d25a582 --- /dev/null +++ b/Libraries/SDLLib @@ -0,0 +1 @@ +Subproject commit d25a582249299fee8bfbe59487f057b4a2e0d990 diff --git a/Makefile b/Makefile index 0a568f8..598383b 100644 --- a/Makefile +++ b/Makefile @@ -32,14 +32,14 @@ include $(DEVKITPRO)/libnx/switch_rules #--------------------------------------------------------------------------------- TARGET := JKSV BUILD := build -SOURCES := src src/ui src/fs src/gfx +SOURCES := Source Source/AppStates Source/UI DATA := data -INCLUDES := inc inc/ui inc/fs inc/gfx +INCLUDES := Include ./Libraries/FsLib/Switch/FsLib/include ./Libraries/SDLLib/SDL/include EXEFS_SRC := exefs_src APP_TITLE := JKSV APP_AUTHOR := JK -APP_VERSION := 11.5.2024 -ROMFS := romfs +APP_VERSION := 12.05.2024 +ROMFS := RomFS ICON := icon.jpg #--------------------------------------------------------------------------------- @@ -51,12 +51,14 @@ 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 +CXXFLAGS:= $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++23 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 -ltinyxml2 -lnx +LIBS := ../Libraries/FsLib/Switch/FsLib/lib/libFsLib.a ../Libraries/SDLLib/SDL/lib/libSDL.a \ + `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 @@ -141,18 +143,30 @@ ifneq ($(ROMFS),) export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) endif -.PHONY: $(BUILD) clean all +.PHONY: $(BUILD) FsLib SDLLib clean all #--------------------------------------------------------------------------------- -all: $(BUILD) +all: FsLib SDLLib $(BUILD) -$(BUILD): +$(BUILD): FsLib SDLLib @[ -d $@ ] || mkdir -p $@ @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile +#--------------------------------------------------------------------------------- + +FsLib: + @$(MAKE) -C ./Libraries/FsLib/Switch/FsLib/ + +#--------------------------------------------------------------------------------- + +SDLLib: + @$(MAKE) -C ./Libraries/SDLLib/SDL/ + #--------------------------------------------------------------------------------- clean: @echo clean ... + @$(MAKE) -C ./Libraries/FsLib clean + @$(MAKE) -C ./Libraries/SDLLib clean @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf diff --git a/RomFS/Text/ENUS.json b/RomFS/Text/ENUS.json new file mode 100644 index 0000000..81f108a --- /dev/null +++ b/RomFS/Text/ENUS.json @@ -0,0 +1,6 @@ +{ + "TranslationInfo" : [ + "Translated By: %s", + "NULL" + ] +} diff --git a/RomFS/Textures/DialogCorners.png b/RomFS/Textures/DialogCorners.png new file mode 100644 index 0000000000000000000000000000000000000000..01fc15e9308f28033d4e0bcdcbf5c281b2309523 GIT binary patch literal 507 zcmVXS}cOebVa6l7(@`U z9F{8vM?8-^eV*^$;@2K;dB53tJF~wjk&zhV7_b4X0&~DLFzT;gfIFZLTmdyzea`aT zQU>8gpaN_H!x`gk0#)EtRd4zVAjUWj>;l`s5dF~tj)6l}ed@NL+(Uc~I03eLt;t9k zSOFG9o9W<|u>Gg0pN=O;Puyju{H zB64nKuK=O# z_FA+!uDKDsx2Iw?45;R_G8g<-OT~0(yHZmy8c0lVGm(my;7%qLcfoB`D(b;KTq>>t zQ1iA}G)Iu#4RYdNAdG~GuDLY_KRd2hxVl!)q$RjX2fFSC?zN%h)I~SOliHHG{?&tGL`(CGb x&&kL$aqf>xf0&M1z!9+1p7#GSoE9~nzX8|Plm)~PT^RrX002ovPDHLkV1iwh<0Ak7 literal 0 HcmV?d00001 diff --git a/RomFS/Textures/HeaderIcon.png b/RomFS/Textures/HeaderIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..f502f3689c935a73569796ffc54395b2b462e3ff GIT binary patch literal 1075 zcmV-31kC%1P)FV000B}Nkl zxF=DDD5wQrRM=!6{VA5O>13}S?3bh+IDG^dp{ps!MdbPZr3#z`aNCF`OZ1t z@1FlT=R5ZbK$Yg6`poDVL8AAK^ zn)OnL0kkUedNLS2n`mt5$mNuKdOSjjX%vCd<#ud6l?;Y@3j!!I^C7+zcR~Z%hSu_}vNsP@QJsSvLb9a8N@bpKAet&B>IuxfuXdI@}E)G`brAZgD$6 zx!VEQ0N4Q70C2gP%huW%x*yu(sAFGgnXEs6+MFF3anx}|MWxf262L6574WV~Li6gu z9|+u_v_MbWhTa{BMQQ@ENRyP9M=#_T*sop%mY^N$Ic<)`j^T>lcE^`wB6tuhdgoJrQV98=L3 z9034iFo>rs3VpAcyp$t+>>nSWTF%E(;~hgRX8<>qnKTB<3Ri{ViRI@vv64?swfSpu z$|_t!BJj!KR)W=~1p~~aGUb(r^8%<{$tQVvehcDY4fsrBN$`o;1@;{OF01cTZB6WL ztuK7@5xR8uZm$dvr*p4y?Uoz_W+8*wHk?mh$yzs^$~fJFnvxf%p95aWR(UD7tzOhV zl4}NvBb88n`BE^{H(7UQ`4Z_gHs34Qg9vqwx6vDQyA|m5dgl%%f}tUV8I&DrvvYN+ z#5#N`0XoV5xYk+!(Qx~h4Lzs#DZKaZ+PIp~TgrUK#&`8kWz{B=cb~W`Yk#*D_#=F^ zW@8^+auP?w?fvyVpZNrNbMZ2rv6cOA{m4Bb|B4-s#Q$D;o3ZSLH57k;ZG3Ftnp7el z?s&VQ=krNL`++Ke!O+3=cO|Gk1{ zP39)TZIhr6273lN6)ksk#EYh{bM&VS)7o1-F?Ko6Y*aKBrWj^n6y=CN9{E7HHcu_K tbUF?E0Yp*0HVpZwD*DS%u1_ui{{VXBOdZ0y-~9jp002ovPDHLkV1npe3eErk literal 0 HcmV?d00001 diff --git a/RomFS/Textures/MenuBounding.png b/RomFS/Textures/MenuBounding.png new file mode 100644 index 0000000000000000000000000000000000000000..25f79f8b85d08d1eed6e6f6f3f170cc9771f1d7b GIT binary patch literal 682 zcmV;b0#*HqP)EX>4Tx04R}tkv&MmKpe$iQ^hJ3hZYoZ$WWcEgNiuTDionYsTEpvFuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?JWDYS_3;J6>}?mh0_0Yam~RI@7vsG4P@ z6LB${TNQg=5kM~nF@cc8OnokuO2Tt|-NVP%yC~1{KKJM7SMnwUd?Im->4rtTK|Hf* z>74h8!>lAJ#OK8023?T&k?XR{Z=8z`3p_JyWYY7*VPdh^!Ey()lA#h$6Gs$PqkJLj zvch?bvs$UK);;+PgL!Rbnd>x1kia6Akb(#qHIz|-g($5WDJIgiANTMNJN^{8WO9|k z$gzMbR7j2={11M2YZj&^-K1a~=zg*7k5Qm!7iiXP`}^3oneSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{004qXL_t(I%gvL)5rZ%c1lRoO0XlJ0w2~^QfHvsFDPZ&Q z!|^yka30yGbUJA?5&&vcS2cM~%GyX#y!$=78p&>VS%aE@k$XIcM)3ofLj^GBT**jd ztm#ubb*jZdwG`!U9GJZ62RivfgR>=GnP6!Nb8M;OKRKkWV+C9PGxfgn3+lCQf$Rk^ Qr~m)}07*qoM6N<$g4S&%od5s; literal 0 HcmV?d00001 diff --git a/Source/AppStates/ProgressState.cpp b/Source/AppStates/ProgressState.cpp new file mode 100644 index 0000000..cf021a3 --- /dev/null +++ b/Source/AppStates/ProgressState.cpp @@ -0,0 +1,18 @@ +#include "AppStates/ProgressState.hpp" +#include "Colors.hpp" +#include "SDL.hpp" +#include "StringUtil.hpp" +#include + +void ProgressState::Update(void) +{ + m_ProgressBarWidth = std::ceil(720.0f * m_Task.GetCurrentProgress()); + m_Progress = std::ceil(m_Task.GetCurrentProgress() * 100); + m_PercentageString = StringUtil::GetFormattedString("%u", m_Progress); + m_PerentageX = 640 - (SDL::Text::GetWidth(18, m_PercentageString.c_str())); +} + +void ProgressState::Render(void) +{ + SDL::RenderRectFill(NULL, 0, 0, 1280, 720, Colors::BackgroundDim); +} diff --git a/Source/AppStates/TaskState.cpp b/Source/AppStates/TaskState.cpp new file mode 100644 index 0000000..abc2cea --- /dev/null +++ b/Source/AppStates/TaskState.cpp @@ -0,0 +1,23 @@ +#include "AppStates/TaskState.hpp" +#include "Colors.hpp" +#include "SDL.hpp" + +void TaskState::Update(void) +{ + if (!m_Task.IsRunning()) + { + AppState::Deactivate(); + } +} + +void TaskState::Render(void) +{ + // Grab task string. + std::string Status = m_Task.GetStatus(); + // Center so it looks perty + int StatusX = 640 - (SDL::Text::GetWidth(18, Status.c_str()) / 2); + // Dim the background states. + SDL::RenderRectFill(NULL, 0, 0, 1280, 720, Colors::BackgroundDim); + // Render the status. + SDL::Text::Render(NULL, StatusX, 351, 18, SDL::Text::NO_TEXT_WRAP, Colors::White, Status.c_str()); +} diff --git a/Source/Input.cpp b/Source/Input.cpp new file mode 100644 index 0000000..425e417 --- /dev/null +++ b/Source/Input.cpp @@ -0,0 +1,32 @@ +#include "Input.hpp" + +namespace +{ + PadState s_Gamepad; +} + +void Input::Initialize(void) +{ + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + padInitializeDefault(&s_Gamepad); +} + +void Input::Update(void) +{ + padUpdate(&s_Gamepad); +} + +bool Input::ButtonPressed(HidNpadButton Button) +{ + return (s_Gamepad.buttons_cur & Button) && !(s_Gamepad.buttons_old & Button); +} + +bool Input::ButtonHeld(HidNpadButton Button) +{ + return (s_Gamepad.buttons_cur & Button) && (s_Gamepad.buttons_old & Button); +} + +bool Input::ButtonReleased(HidNpadButton Button) +{ + return (s_Gamepad.buttons_old & Button) && !(s_Gamepad.buttons_cur & Button); +} diff --git a/Source/JKSV.cpp b/Source/JKSV.cpp new file mode 100644 index 0000000..c3e03bd --- /dev/null +++ b/Source/JKSV.cpp @@ -0,0 +1,141 @@ +#include "JKSV.hpp" +#include "Colors.hpp" +#include "FsLib.hpp" +#include "Input.hpp" +#include "Logger.hpp" +#include "SDL.hpp" +#include "Strings.hpp" +#include + +#include "UI/RenderFunctions.hpp" + +#define ABORT_ON_FAILURE(x) \ + if (!x) \ + { \ + return; \ + } + +namespace +{ + constexpr uint8_t BUILD_MON = 12; + constexpr uint8_t BUILD_DAY = 5; + constexpr uint16_t BUILD_YEAR = 2024; +} // namespace + +template +static bool InitializeService(Result (*Function)(Args...), const char *ServiceName, Args... Arguments) +{ + Result Error = (*Function)(Arguments...); + if (R_FAILED(Error)) + { + Logger::Log("Error initializing %s: 0x%X.", Error); + return false; + } + return true; +} + +JKSV::JKSV(void) +{ + ABORT_ON_FAILURE(FsLib::Initialize()); + // Need to init RomFS here for now until I update FsLib to take care of this. + ABORT_ON_FAILURE(InitializeService(romfsInit, "RomFS")); + // Let FsLib take care of calls to SDMC instead of fs_dev + ABORT_ON_FAILURE(FsLib::Dev::InitializeSDMC()); + // SDL + ABORT_ON_FAILURE(SDL::Initialize("JKSV", 1280, 720)); + ABORT_ON_FAILURE(SDL::Text::Initialize()); + + // Services. + // Using administrator so JKSV can still run in Applet mode. + ABORT_ON_FAILURE(InitializeService(accountInitialize, "Account", AccountServiceType_Administrator)); + ABORT_ON_FAILURE(InitializeService(nsInitialize, "NS")); + ABORT_ON_FAILURE(InitializeService(pdmqryInitialize, "PDMQry")); + ABORT_ON_FAILURE(InitializeService(plInitialize, "PL", PlServiceType_User)); + ABORT_ON_FAILURE(InitializeService(pmshellInitialize, "PMShell")); + ABORT_ON_FAILURE(InitializeService(setInitialize, "Set")); + ABORT_ON_FAILURE(InitializeService(setsysInitialize, "SetSys")); + ABORT_ON_FAILURE(InitializeService(socketInitializeDefault, "Socket")); + + // This needs Set. JKSV also has no internal strings anymore. This is FATAL now. + ABORT_ON_FAILURE(Strings::Initialize()); + + // Install/setup our color changing characters. + SDL::Text::AddColorCharacter(L'#', Colors::Blue); + SDL::Text::AddColorCharacter(L'*', Colors::Red); + SDL::Text::AddColorCharacter(L'<', Colors::Yellow); + SDL::Text::AddColorCharacter(L'>', Colors::Green); + + // This is to check whether the author wanted credit for their work. + m_ShowTranslationInfo = std::char_traits::compare(Strings::GetByName(Strings::Names::TranslationInfo, 1), "NULL", 4) != 0; + + // This can't be in an initializer list because it needs SDL initialized. + m_HeaderIcon = SDL::TextureManager::CreateLoadTexture("HeaderIcon", "romfs:/Textures/HeaderIcon.png"); + + Input::Initialize(); + + m_IsRunning = true; +} + +JKSV::~JKSV() +{ + socketExit(); + setsysExit(); + setExit(); + pmshellExit(); + plExit(); + pdmqryExit(); + nsExit(); + accountExit(); + SDL::Text::Exit(); + SDL::Exit(); + FsLib::Exit(); +} + +bool JKSV::IsRunning(void) const +{ + return m_IsRunning; +} + +void JKSV::Update(void) +{ + Input::Update(); + + if (Input::ButtonPressed(HidNpadButton_Plus)) + { + m_IsRunning = false; + } +} + +void JKSV::Render(void) +{ + SDL::FrameBegin(Colors::ClearColor); + // Top and bottom divider lines. + SDL::RenderLine(NULL, 30, 88, 1250, 88, Colors::White); + SDL::RenderLine(NULL, 30, 648, 1250, 648, Colors::White); + // Icon + m_HeaderIcon->Render(NULL, 66, 27); + // "JKSV" + SDL::Text::Render(NULL, 130, 32, 34, SDL::Text::NO_TEXT_WRAP, Colors::White, "JKSV"); + // Translation info in bottom left. + if (m_ShowTranslationInfo) + { + SDL::Text::Render(NULL, + 8, + 682, + 12, + SDL::Text::NO_TEXT_WRAP, + Colors::White, + Strings::GetByName(Strings::Names::TranslationInfo, 0), + Strings::GetByName(Strings::Names::TranslationInfo, 1)); + } + SDL::Text::Render(NULL, 8, 700, 12, SDL::Text::NO_TEXT_WRAP, Colors::White, "v %02d.%02d.%04d", BUILD_MON, BUILD_DAY, BUILD_YEAR); + + UI::RenderDialogBox(NULL, 320, 240, 320, 240); + + SDL::FrameEnd(); +} + +void JKSV::PushState(std::shared_ptr NewState) +{ + m_StateVector.push_back(NewState); +} diff --git a/Source/Logger.cpp b/Source/Logger.cpp new file mode 100644 index 0000000..60c7208 --- /dev/null +++ b/Source/Logger.cpp @@ -0,0 +1,32 @@ +#include "Logger.hpp" +#include "FsLib.hpp" +#include + +namespace +{ + // Path to log file. + FsLib::Path s_LogFilePath; + // Size of va buffer for log. + constexpr size_t VA_BUFFER_SIZE = 0x1000; +} // namespace + +void Logger::Initialize(void) +{ + // To do: Update this once config is implemented. + s_LogFilePath = "sdmc:/JKSV/JKSV.log"; + FsLib::File LogFile(s_LogFilePath, FsOpenMode_Create | FsOpenMode_Write); +} + +void Logger::Log(const char *Format, ...) +{ + char VaBuffer[VA_BUFFER_SIZE] = {0}; + + std::va_list VaList; + va_start(VaList, Format); + vsnprintf(VaBuffer, VA_BUFFER_SIZE, Format, VaList); + va_end(VaList); + + FsLib::File LogFile(s_LogFilePath, FsOpenMode_Append); + LogFile << VaBuffer << "\n"; + LogFile.Flush(); +} diff --git a/Source/Main.cpp b/Source/Main.cpp new file mode 100644 index 0000000..895a046 --- /dev/null +++ b/Source/Main.cpp @@ -0,0 +1,12 @@ +#include "JKSV.hpp" + +int main(void) +{ + JKSV Jksv; + while (Jksv.IsRunning()) + { + Jksv.Update(); + Jksv.Render(); + } + return 0; +} diff --git a/Source/StringUtil.cpp b/Source/StringUtil.cpp new file mode 100644 index 0000000..81935be --- /dev/null +++ b/Source/StringUtil.cpp @@ -0,0 +1,19 @@ +#include "StringUtil.hpp" +#include + +namespace +{ + constexpr size_t VA_BUFFER_SIZE = 0x1000; +} + +std::string StringUtil::GetFormattedString(const char *Format, ...) +{ + char VaBuffer[VA_BUFFER_SIZE]; + + std::va_list VaList; + va_start(VaList, Format); + vsnprintf(VaBuffer, VA_BUFFER_SIZE, Format, VaList); + va_end(VaList); + + return std::string(VaBuffer); +} diff --git a/Source/Strings.cpp b/Source/Strings.cpp new file mode 100644 index 0000000..6dac94b --- /dev/null +++ b/Source/Strings.cpp @@ -0,0 +1,90 @@ +#include "Strings.hpp" +#include "FsLib.hpp" +#include "JSON.hpp" +#include +#include +#include + +namespace +{ + // This is the actual map where the strings are. + std::map, std::string> s_StringMap; + // This map is for matching files to the language value + std::unordered_map s_FileMap = {{SetLanguage_JA, "JA.json"}, + {SetLanguage_ENUS, "ENUS.json"}, + {SetLanguage_FR, "FR.json"}, + {SetLanguage_DE, "DE.json"}, + {SetLanguage_IT, "IT.json"}, + {SetLanguage_ES, "ES.json"}, + {SetLanguage_ZHCN, "ZHCN.json"}, + {SetLanguage_KO, "KO.json"}, + {SetLanguage_NL, "NL.json"}, + {SetLanguage_PT, "PT.json"}, + {SetLanguage_RU, "RU.json"}, + {SetLanguage_ZHTW, "ZHTW.json"}, + {SetLanguage_ENGB, "ENGB.json"}, + {SetLanguage_FRCA, "FRCA.json"}, + {SetLanguage_ES419, "ES419.json"}, + {SetLanguage_ZHHANS, "ZHCN.json"}, + {SetLanguage_ZHHANT, "ZHTW.json"}, + {SetLanguage_PTBR, "PTBR.json"}}; +} // namespace + +static FsLib::Path GetStringFilePath(void) +{ + FsLib::Path ReturnPath = "romfs:/Text"; + + uint64_t LanguageCode = 0; + Result SetError = setGetLanguageCode(&LanguageCode); + if (R_FAILED(SetError)) + { + return ReturnPath / s_FileMap.at(SetLanguage_ENUS); + } + + SetLanguage Language; + SetError = setMakeLanguage(LanguageCode, &Language); + if (R_FAILED(SetError)) + { + return ReturnPath / s_FileMap.at(SetLanguage_ENUS); + } + return ReturnPath / s_FileMap.at(Language); +} + +bool Strings::Initialize() +{ + FsLib::Path StringsPath = GetStringFilePath(); + + JSON::Object TextJSON = JSON::NewObject(json_object_from_file, StringsPath.CString()); + if (!TextJSON) + { + return false; + } + + json_object_iterator StringIterator = json_object_iter_begin(TextJSON.get()); + json_object_iterator StringEnd = json_object_iter_end(TextJSON.get()); + while (!json_object_iter_equal(&StringIterator, &StringEnd)) + { + // Get name of string(s) and pointer to array + const char *StringName = json_object_iter_peek_name(&StringIterator); + json_object *StringArray = json_object_iter_peek_value(&StringIterator); + + // Loop through array and add them to map so I can be lazier and not have to edit code or do shit to add more strings. + size_t ArrayLength = json_object_array_length(StringArray); + for (size_t i = 0; i < ArrayLength; i++) + { + json_object *String = json_object_array_get_idx(StringArray, i); + s_StringMap[std::make_pair(StringName, static_cast(i))] = json_object_get_string(String); + } + json_object_iter_next(&StringIterator); + } + return true; +} + +const char *Strings::GetByName(std::string_view Name, int Index) +{ + if (s_StringMap.find(std::make_pair(Name.data(), Index)) == s_StringMap.end()) + { + return nullptr; + } + return s_StringMap.at(std::make_pair(Name.data(), Index)).c_str(); +} diff --git a/Source/System/ProgressTask.cpp b/Source/System/ProgressTask.cpp new file mode 100644 index 0000000..db1f974 --- /dev/null +++ b/Source/System/ProgressTask.cpp @@ -0,0 +1,16 @@ +#include "ProgressTask.hpp" + +void System::ProgressTask::Reset(double Goal) +{ + m_Goal = Goal; +} + +void System::ProgressTask::UpdateCurrent(double Current) +{ + m_Current = Current; +} + +double System::ProgressTask::GetCurrentProgress(void) const +{ + return m_Current / m_Goal; +} diff --git a/Source/System/Task.cpp b/Source/System/Task.cpp new file mode 100644 index 0000000..d820f7c --- /dev/null +++ b/Source/System/Task.cpp @@ -0,0 +1,41 @@ +#include "Task.hpp" +#include + +namespace +{ + constexpr size_t VA_BUFFER_SIZE = 0x1000; +} + +System::Task::~Task() +{ + m_Thread.join(); +} + +bool System::Task::IsRunning(void) const +{ + return m_IsRunning; +} + +void System::Task::Finished(void) +{ + m_IsRunning = false; +} + +void System::Task::SetStatus(const char *Format, ...) +{ + char VaBuffer[VA_BUFFER_SIZE]; + + std::va_list VaList; + va_start(VaList, Format); + vsnprintf(VaBuffer, VA_BUFFER_SIZE, Format, VaList); + va_end(VaList); + + std::scoped_lock StatusLock(m_StatusLock); + m_Status = VaBuffer; +} + +std::string System::Task::GetStatus(void) +{ + std::scoped_lock StatusLock(m_StatusLock); + return m_Status; +} diff --git a/Source/UI/RenderFunctions.cpp b/Source/UI/RenderFunctions.cpp new file mode 100644 index 0000000..21ab292 --- /dev/null +++ b/Source/UI/RenderFunctions.cpp @@ -0,0 +1,27 @@ +#include "UI/RenderFunctions.hpp" +#include "Colors.hpp" + +namespace +{ + SDL::SharedTexture s_DialogCorners = nullptr; + SDL::SharedTexture s_MenuBoundingCorners = nullptr; +} // namespace + +void UI::RenderDialogBox(SDL_Texture *Target, int X, int Y, int Width, int Height) +{ + if (!s_DialogCorners) + { + s_DialogCorners = SDL::TextureManager::CreateLoadTexture("DialogCorners", "romfs:/Textures/DialogCorners.png"); + } + + // Top + s_DialogCorners->RenderPart(Target, X, Y, 0, 0, 16, 16); + SDL::RenderRectFill(Target, X + 16, Y, Width - 32, 16, Colors::DialogBox); + s_DialogCorners->RenderPart(Target, (X + Width) - 16, Y, 16, 0, 16, 16); + // Middle + SDL::RenderRectFill(NULL, X, Y + 16, Width, Height - 32, Colors::DialogBox); + // Bottom + s_DialogCorners->RenderPart(Target, X, (Y + Height) - 16, 0, 16, 16, 16); + SDL::RenderRectFill(NULL, X + 16, (Y + Height) - 16, Width - 32, 16, Colors::DialogBox); + s_DialogCorners->RenderPart(NULL, (X + Width) - 16, (Y + Height) - 16, 16, 16, 16, 16); +} diff --git a/inc/cfg.h b/inc/cfg.h deleted file mode 100644 index 87c937f..0000000 --- a/inc/cfg.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace cfg -{ - typedef enum - { - ALPHA, - MOST_PLAYED, - LAST_PLAYED - } sortTypes; - - void resetConfig(); - void loadConfig(); - void saveConfig(); - - bool isBlacklisted(const uint64_t& tid); - void addTitleToBlacklist(void *a); - void removeTitleFromBlacklist(const uint64_t& tid); - - bool isFavorite(const uint64_t& tid); - void addTitleToFavorites(const 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); - - void addPathToFilter(const uint64_t& tid, const std::string& _p); - - extern std::unordered_map config; - extern std::vector blacklist; - extern std::vector favorites; - extern uint8_t sortType; - extern std::string driveClientID, driveClientSecret, driveRefreshToken; - extern std::string webdavOrigin, webdavBasePath, webdavUser, webdavPassword; -} diff --git a/inc/curlfuncs.h b/inc/curlfuncs.h deleted file mode 100644 index 3a5a7dc..0000000 --- a/inc/curlfuncs.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include - -#define HEADER_ERROR "ERROR" - -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 *h); - - //Shortcuts/legacy - std::string getJSONURL(std::vector *headers, const std::string& url); - bool getBinURL(std::vector *out, const std::string& url); -} diff --git a/inc/data.h b/inc/data.h deleted file mode 100644 index 4974270..0000000 --- a/inc/data.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once -#include - -#include -#include -#include - -#include "gfx.h" - -#define BLD_MON 11 -#define BLD_DAY 5 -#define BLD_YEAR 2024 - -namespace data -{ - //Loads user + title info - void init(); - void exit(); - bool loadUsersTitles(bool clearUsers); - void sortUserTitles(); - - //Draws some stats to the upper left corner - void dispStats(); - - //Global stuff for all titles/saves - typedef struct - { - NacpStruct nacp; - std::string title, safeTitle, author;//Shortcuts sorta. - SDL_Texture *icon = NULL; - bool fav; - } titleInfo; - - //Holds stuff specific to user's titles/saves - typedef struct - { - //Makes it easier to grab id - uint64_t tid; - FsSaveDataInfo saveInfo; - PdmPlayStatistics playStats; - } userTitleInfo; - - //Class to store user info + titles - class user - { - public: - user() = default; - user(const AccountUid& _id, const std::string& _backupName, const std::string& _safeBackupName); - user(const AccountUid& _id, const std::string& _backupName, const std::string& _safeBackupName, SDL_Texture *img); - - //Sets ID - void setUID(const AccountUid& _id); - - //Assigns icon - void assignIcon(SDL_Texture *_icn) { userIcon = _icn; } - - //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; } - - SDL_Texture *getUserIcon(){ return userIcon; } - void delIcon(){ SDL_DestroyTexture(userIcon); } - - std::vector titleInfo; - void addUserTitleInfo(const uint64_t& _tid, const FsSaveDataInfo *_saveInfo, const PdmPlayStatistics *_stats); - - private: - AccountUid userID; - u128 uID128; - std::string username, userSafe; - //User icon - SDL_Texture *userIcon; - }; - - //User vector - extern std::vector users; - //Title data/info map - extern std::unordered_map titles; - - //Sets/Retrieves current user/title - void setUserIndex(unsigned _sUser); - data::user *getCurrentUser(); - unsigned getCurrentUserIndex(); - - void setTitleIndex(unsigned _sTitle); - data::userTitleInfo *getCurrentUserTitleInfo(); - unsigned getCurrentUserTitleInfoIndex(); - - //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); - extern SetLanguage sysLang; - -} diff --git a/inc/fs.h b/inc/fs.h deleted file mode 100644 index 99549c8..0000000 --- a/inc/fs.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#include -#include - -#include "fs/fstype.h" -#include "fs/file.h" -#include "fs/dir.h" -#include "fs/zip.h" -#include "fs/fsfile.h" -#include "fs/remote.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(const 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, ...); -} diff --git a/inc/fs/dir.h b/inc/fs/dir.h deleted file mode 100644 index 7870cbe..0000000 --- a/inc/fs/dir.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include "type.h" - -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 item; - }; -} diff --git a/inc/fs/file.h b/inc/fs/file.h deleted file mode 100644 index 49b064e..0000000 --- a/inc/fs/file.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#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(); -} diff --git a/inc/fs/fsfile.h b/inc/fs/fsfile.h deleted file mode 100644 index c84be82..0000000 --- a/inc/fs/fsfile.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -#include -#include - -//Bare minimum wrapper around switch fs for JKSV -#define FS_SEEK_SET 0 -#define FS_SEEK_CUR 1 -#define FS_SEEK_END 2 - -#ifdef __cplusplus -extern "C" -{ -#endif -typedef struct -{ - FsFile _f; - Result error; - s64 offset, fsize; -} FSFILE; - -int fsremove(const char *_p); -Result fsDelDirRec(const char *_p); - -char *getDeviceFromPath(char *dev, size_t _max, const char *path); -char *getFilePath(char *pathOut, size_t _max, const char *path); - -bool fsMkDir(const char *_p); - -/*Opens file. Device is fetched from path. Libnx romfs doesn't work with this. -Mode needs to be: - FsOpenMode_Read - FsOpenMode_Write - FsOpenMode_Append -*/ -bool fsfcreate(const char *_p, int64_t crSize); - -FSFILE *fsfopen(const char *_p, uint32_t mode); - -/*Same as above, but FsFileSystem _s is used. Path cannot have device in it*/ -FSFILE *fsfopenWithSystem(FsFileSystem *_s, const char *_p, uint32_t mode); - -//Closes _f -inline void fsfclose(FSFILE *_f) -{ - if(_f != NULL) - { - fsFileClose(&_f->_f); - free(_f); - } -} - -//Seeks like stdio -inline void fsfseek(FSFILE *_f, int offset, int origin) -{ - switch(origin) - { - case FS_SEEK_SET: - _f->offset = offset; - break; - - case FS_SEEK_CUR: - _f->offset += offset; - break; - - case FS_SEEK_END: - _f->offset = offset + _f->fsize; - break; - } -} - -//Returns offset -inline size_t fsftell(FSFILE *_f) { return _f->offset; } - -//Writes buf to file. Automatically resizes _f to fit buf -size_t fsfwrite(const void *buf, size_t sz, size_t count, FSFILE *_f); - -//Reads to buff -inline size_t fsfread(void *buf, size_t sz, size_t count, FSFILE *_f) -{ - uint64_t read = 0; - _f->error = fsFileRead(&_f->_f, _f->offset, buf, sz * count, 0, &read); - _f->offset += read; - return read; -} - -//Gets byte from file -inline char fsfgetc(FSFILE *_f) -{ - char ret = 0; - uint64_t read = 0; - _f->error = fsFileRead(&_f->_f, _f->offset++, &ret, 1, 0, &read); - return ret; -} - -//Writes byte to file -inline void fsfputc(int ch, FSFILE *_f) { fsfwrite(&ch, 1, 1, _f); } -#ifdef __cplusplus -} -#endif diff --git a/inc/fs/fstype.h b/inc/fs/fstype.h deleted file mode 100644 index 4f9e2d5..0000000 --- a/inc/fs/fstype.h +++ /dev/null @@ -1,46 +0,0 @@ -#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; -} diff --git a/inc/fs/remote.h b/inc/fs/remote.h deleted file mode 100644 index 1b05b35..0000000 --- a/inc/fs/remote.h +++ /dev/null @@ -1,21 +0,0 @@ -#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(); -} \ No newline at end of file diff --git a/inc/fs/zip.h b/inc/fs/zip.h deleted file mode 100644 index 4f8afb5..0000000 --- a/inc/fs/zip.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include -#include - -#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); -} diff --git a/inc/gd.h b/inc/gd.h deleted file mode 100644 index 3e735cc..0000000 --- a/inc/gd.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -#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 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 driveList; - std::string clientID, secretID, token, rToken; - }; -} \ No newline at end of file diff --git a/inc/gfx.h b/inc/gfx.h deleted file mode 100644 index 19317ed..0000000 --- a/inc/gfx.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include "textureMgr.h" - -namespace gfx -{ - extern SDL_Renderer *render; - extern gfx::textureMgr *texMgr; - - void init(); - void exit(); - void present(); - - 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); - - //Shortcuts for drawing - void texDraw(SDL_Texture *target, SDL_Texture *tex, int x, int y); - void texDrawStretch(SDL_Texture *target, SDL_Texture *tex, int x, int y, int w, int h); - void texDrawPart(SDL_Texture *target, SDL_Texture *tex, int srcX, int srcY, int srcW, int srcH, int dstX, int dstY); - void drawLine(SDL_Texture *target, const SDL_Color *c, int x1, int y1, int x2, int y2); - void drawRect(SDL_Texture *target, const SDL_Color *c, int x, int y, int w, int h); - void clearTarget(SDL_Texture *target, const SDL_Color *clear); -} diff --git a/inc/gfx/textureMgr.h b/inc/gfx/textureMgr.h deleted file mode 100644 index 62002b7..0000000 --- a/inc/gfx/textureMgr.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include - -/*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 textures; - }; -} diff --git a/inc/rfs.h b/inc/rfs.h deleted file mode 100644 index f098f89..0000000 --- a/inc/rfs.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include "curlfuncs.h" -#include - -#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 getListWithParent(const std::string& _parent) = 0; - }; - - // Shared multi-threading definitions - typedef struct - { - curlFuncs::curlDlArgs *cfa; - std::mutex dataLock; - std::condition_variable cond; - std::vector sharedBuffer; - bool bufferFull = false; - unsigned int downloaded = 0; - } dlWriteThreadStruct; - - extern std::vector downloadBuffer; - void writeThread_t(void *a); - size_t writeDataBufferThreaded(uint8_t *buff, size_t sz, size_t cnt, void *u); -} diff --git a/inc/type.h b/inc/type.h deleted file mode 100644 index d1dcc6f..0000000 --- a/inc/type.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include - -//Misc stuff for new menu code -typedef void (*funcPtr)(void *); - -class threadStatus -{ - public: - void setStatus(const char *fmt, ...); - void getStatus(std::string& statusOut); - - private: - Mutex statusLock = 0; - std::string status; -}; - -typedef struct -{ - bool running = false, finished = false; - Thread thrd; - ThreadFunc thrdFunc; - void *argPtr = NULL; - funcPtr drawFunc = NULL;//Draw func is passed threadInfo pointer too - threadStatus *status; -} threadInfo; diff --git a/inc/ui.h b/inc/ui.h deleted file mode 100644 index f76427a..0000000 --- a/inc/ui.h +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "data.h" -#include "gfx.h" - -//ui headers - split up to keep a bit more organized -#include "ui/miscui.h" -#include "ui/uistr.h" -#include "ui/ttlview.h" -#include "ui/thrdProc.h" -#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" - -enum menuState -{ - USR_SEL, - TTL_SEL, - ADV_MDE, - EX_MNU, - OPT_MNU, - FIL_MDE -}; - -namespace ui -{ - //Current menu/ui state - extern int mstate, prevState; - - //Slide/animation scaling - extern float animScale; - - //Loading glyph - extern const std::string loadGlyphArray[]; - - //pad data cause i don't know where else to put it - extern PadState pad; - extern HidTouchScreenState touchState; - static inline void updateInput() { touchState = {0}; padUpdate(&pad); hidGetTouchScreenStates(&touchState, 1); } - inline uint64_t padKeysDown() { return padGetButtonsDown(&pad); } - inline uint64_t padKeysHeld() { return padGetButtons(&pad); } - inline uint64_t padKeysUp() { return padGetButtonsUp(&pad); } - - inline void changeState(int newState) - { - prevState = mstate; - mstate = newState; - } - - //Holds theme set id - extern ColorSetId thmID; - - //Both UI modes need access to this - extern std::string folderMenuInfo; - - /*Colors - clearClr = color to clear buffer - txtCont = text that contrasts clearClr - txtDiag = text color for dialogs - */ - extern SDL_Color clearClr, transparent, txtCont, txtDiag, rectLt, rectSh, tboxClr, sideRect, divClr, heartColor, slidePanelColor; - - //Textbox graphics - extern SDL_Texture *cornerTopLeft, *cornerTopRight, *cornerBottomLeft, *cornerBottomRight; - //Menu bounding - extern SDL_Texture *mnuTopLeft, *mnuTopRight, *mnuBotLeft, *mnuBotRight; - - //Covers left and right of progress bar to fake being not a rectangle. - extern SDL_Texture *progCovLeft, *progCovRight, *diaBox; - - //Side bar from Freebird. RIP. - extern SDL_Texture *sideBar; - - //Sets colors and loads font for icon creation - void initTheme(); - - //Loads graphics and stuff - 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); - threadInfo *newThread(ThreadFunc func, void *args, funcPtr _drawFunc); - - //Just draws a screen and flips JIC boot takes long. - void showLoadScreen(); - - //Clears and draws general stuff used by multiple screens - void drawUI(); - - //switch case so we don't have problems with multiple main loops like 3DS - bool runApp(); - - void showPopMessage(int frameCount, const char *fmt, ...); - - //Used for multiple menu functions/callback - void toTTL(void *); -} diff --git a/inc/ui/ext.h b/inc/ui/ext.h deleted file mode 100644 index 5785572..0000000 --- a/inc/ui/ext.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -namespace ui -{ - extern ui::menu *extMenu; - void extInit(); - void extExit(); - void extUpdate(); - void extDraw(SDL_Texture *target); -} diff --git a/inc/ui/fld.h b/inc/ui/fld.h deleted file mode 100644 index aed957f..0000000 --- a/inc/ui/fld.h +++ /dev/null @@ -1,19 +0,0 @@ -#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(); -} \ No newline at end of file diff --git a/inc/ui/fm.h b/inc/ui/fm.h deleted file mode 100644 index e9dcfac..0000000 --- a/inc/ui/fm.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -namespace ui -{ - void fmInit(); - void fmExit(); - void fmPrep(const FsSaveDataType& _type, const std::string& _dev, const std::string& _baseSDMC, bool _commit); - void fmUpdate(); - void fmDraw(SDL_Texture *target); -} diff --git a/inc/ui/miscui.h b/inc/ui/miscui.h deleted file mode 100644 index e482669..0000000 --- a/inc/ui/miscui.h +++ /dev/null @@ -1,165 +0,0 @@ -#pragma once - -#include -#include - -#include "type.h" -#include "gfx.h" - -#define POP_FRAME_DEFAULT 130 - -#define MENU_FONT_SIZE_DEFAULT 18 -#define MENU_MAX_SCROLL_DEFAULT 15 - -typedef enum -{ - MENU_X, - MENU_Y, - MENU_RECT_WIDTH, - MENU_RECT_HEIGHT, - MENU_FONT_SIZE, - MENU_MAX_SCROLL -} menuParams; - -//For smaller classes that aren't easy to get lost in and general functions -namespace ui -{ - typedef struct - { - 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; - } menuOptEvent; - - typedef struct - { - SDL_Texture *icn = NULL; - std::string txt; - int txtWidth; - std::vector events; - } menuOpt; - - class menu - { - public: - menu() = default; - menu(const int& _x, const int& _y, const int& _rW, const int& _fS, const int& _mL); - void editParam(int _param, int newVal); - - //Gets executed when menu changes at all - void setOnChangeFunc(funcPtr func) { onChange = func; } - - //executed when .update() is called. - void setCallback(funcPtr _callback, void *args) { callback = _callback; callbackArgs = args; } - - //Adds option. - 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); - - //Clears menu stuff - ~menu(); - - //Handles controller input and executes functions for buttons if they're set - void update(); - - //Returns selected option - int getSelected() { return selected; } - - //Returns menu option count - 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); - - //Clears and resets menu - void reset(); - - //Resets selected + start - void resetSel() { selected = 0; } - - //Enables control/disables drawing select box - void setActive(bool _set); - bool getActive() { return isActive; } - - private: - //drawing x and y + rectangle width/height. Height is calc'd with font size - int x = 0, mY = 0, tY = 0, y = 0, rW = 0, rY = 0, fSize = 0, rH = 0, mL = 0; - //Options vector - std::vector opt; - //Selected + frame counting for auto-scroll. Hover count is to not break autoscroll - int selected = 0, fc = 0, hoverCount = 0, spcWidth = 0; - //How much we shift the color of the rectangle - uint8_t clrSh = 0; - bool clrAdd = true, isActive = true, hover = false; - //Option buffer. Basically, text is draw to this so it can't overlap. Also allows scrolling - SDL_Texture *optTex; - funcPtr onChange = NULL, callback = NULL; - void *callbackArgs, *funcArgs; - }; - - //Progress bar for showing loading. Mostly so people know it didn't freeze - class progBar - { - public: - //Constructor. _max is the maximum value - progBar() = default; - progBar(const uint64_t& _max) { max = _max; } - - void setMax(const uint64_t& _max) { max = _max; }; - - //Updates progress - void update(const uint64_t& _prog); - - //Draws with text at top - void draw(const std::string& text); - - private: - uint64_t max = 0, prog = 0; - float width = 0; - }; - - typedef struct - { - std::string message; - int rectWidth = 0, frames = 0, y = 720; - } popMessage; - - class popMessageMngr - { - public: - ~popMessageMngr(); - - void update(); - - void popMessageAdd(const std::string& mess, int frameTime); - void draw(); - - private: - std::vector popQueue;//All graphics need to be on main thread. Directly adding will cause text issues - std::vector message; - }; - - //General use - 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); - 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); -} diff --git a/inc/ui/sett.h b/inc/ui/sett.h deleted file mode 100644 index 38b1d7b..0000000 --- a/inc/ui/sett.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -namespace ui -{ - void settInit(); - void settExit(); - void settUpdate(); - void settDraw(SDL_Texture *target); - - extern ui::menu *settMenu; -} diff --git a/inc/ui/sldpanel.h b/inc/ui/sldpanel.h deleted file mode 100644 index 3476722..0000000 --- a/inc/ui/sldpanel.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -namespace ui -{ - //Maybe more later *if* needed, but not now. - typedef enum - { - SLD_LEFT, - SLD_RIGHT - } slidePanelOrientation; - - //_draw is called and passed the panel texture/target when this.draw() is called. - class slideOutPanel - { - public: - slideOutPanel(int _w, int _h, int _y, slidePanelOrientation _side, funcPtr _draw); - - void resizePanel(int _w, int _h, int _y); - void update(); - void setCallback(funcPtr _cb, void *_args) { callback = _cb; cbArgs = _args; } - void openPanel() { open = true; } - void closePanel() { open = false; } - void setX(int _nX){ x = _nX; }; - bool isOpen() { return open; } - void draw(const SDL_Color *backCol); - - private: - int w, h, x, y; - uint8_t sldSide; - bool open = false; - SDL_Texture *panel; - funcPtr drawFunc, callback = NULL; - void *cbArgs = NULL; - }; -} diff --git a/inc/ui/thrdProc.h b/inc/ui/thrdProc.h deleted file mode 100644 index d434fca..0000000 --- a/inc/ui/thrdProc.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "type.h" - -namespace ui -{ - class threadProcMngr - { - public: - ~threadProcMngr(); - //Draw function is used and called to draw on overlay - threadInfo *newThread(ThreadFunc func, void *args, funcPtr _drawFunc); - void update(); - void draw(); - bool empty() { return threads.empty(); } - - private: - std::vector threads; - uint8_t lgFrame = 0, clrShft = 0; - bool clrAdd = true; - unsigned frameCount = 0; - Mutex threadLock = 0; - }; -} diff --git a/inc/ui/ttl.h b/inc/ui/ttl.h deleted file mode 100644 index 053dfe2..0000000 --- a/inc/ui/ttl.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -namespace ui -{ - void ttlInit(); - void ttlExit(); - void ttlSetActive(int usr, bool _set, bool _showSel); - void ttlRefresh(); - - //JIC for func ptr - void ttlReset(); - void ttlUpdate(); - void ttlDraw(SDL_Texture *target); - - //File mode needs access to this. - extern ui::slideOutPanel *ttlOptsPanel; -} diff --git a/inc/ui/ttlview.h b/inc/ui/ttlview.h deleted file mode 100644 index 2aa9f5a..0000000 --- a/inc/ui/ttlview.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include - -#include "type.h" -#include "data.h" - -namespace ui -{ - class titleTile - { - public: - titleTile(unsigned _w, unsigned _h, bool _fav, SDL_Texture *_icon) - { - w = _w; - h = _h; - wS = _w; - hS = _h; - fav = _fav; - icon = _icon; - } - - void draw(SDL_Texture *target, int x, int y, bool sel); - - private: - unsigned w, h, wS, hS; - bool fav = false; - SDL_Texture *icon; - }; - - //Todo less hardcode etc - class titleview - { - public: - titleview(const data::user& _u, int _iconW, int _iconH, int _horGap, int _vertGap, int _rowCount, funcPtr _callback); - ~titleview(); - - void update(); - void refresh(); - - void setActive(bool _set, bool _showSel) { active = _set; showSel = _showSel; } - bool getActive() { return active; } - void setSelected(int _set) { selected = _set; } - int getSelected() { return selected; } - void draw(SDL_Texture *target); - - private: - const data::user *u;//Might not be safe. Users *shouldn't* be touched after initial load - bool active = false, showSel = false, clrAdd = true; - uint8_t clrShft = 0; - funcPtr callback = NULL; - int x = 200, y = 62, selected = 0, selRectX = 10, selRectY = 45; - int iconW, iconH, horGap, vertGap, rowCount; - std::vector tiles; - }; -} diff --git a/inc/ui/uistr.h b/inc/ui/uistr.h deleted file mode 100644 index b7afb0b..0000000 --- a/inc/ui/uistr.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -//Strings since translation support -namespace ui -{ - void initStrings(); - void loadTrans(); - void saveTranslationFiles(void *a); - extern std::map, std::string> strings; - - inline std::string getUIString(const std::string& _name, int ind){ return strings[std::make_pair(_name, ind)]; } - inline const char *getUICString(const std::string& _name, int ind){ return strings[std::make_pair(_name, ind)].c_str(); } -} diff --git a/inc/ui/usr.h b/inc/ui/usr.h deleted file mode 100644 index 500e517..0000000 --- a/inc/ui/usr.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -namespace ui -{ - void usrInit(); - void usrExit(); - void usrRefresh(); - void usrUpdate(); - void usrDraw(SDL_Texture *target); - - //A lot of stuff needs access to these - extern ui::menu *usrMenu; - extern ui::slideOutPanel *usrSelPanel; -} diff --git a/inc/util.h b/inc/util.h deleted file mode 100644 index 6ae24c9..0000000 --- a/inc/util.h +++ /dev/null @@ -1,158 +0,0 @@ -#pragma once - -#include "data.h" -#include "ui.h" -#include "file.h" -#include "gfx.h" - -namespace util -{ - enum - { - DATE_FMT_YMD, - DATE_FMT_YDM, - DATE_FMT_HOYSTE, - DATE_FMT_JHK, - DATE_FMT_ASC - }; - - typedef enum - { - CPU_SPEED_204MHz = 204000000, - CPU_SPEED_306MHz = 306000000, - CPU_SPEED_408MHz = 408000000, - CPU_SPEED_510MHz = 510000000, - CPU_SPEED_612MHz = 612000000, - CPU_SPEED_714MHz = 714000000, - CPU_SPEED_816MHz = 816000000, - CPU_SPEED_918MHz = 918000000, - CPU_SPEED_1020MHz = 1020000000, //Default - CPU_SPEED_1122MHz = 1122000000, - CPU_SPEED_1224MHz = 1224000000, - CPU_SPEED_1326MHz = 1326000000, - CPU_SPEED_1428MHz = 1428000000, - CPU_SPEED_1581MHz = 1581000000, - CPU_SPEED_1683MHz = 1683000000, - 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); - - //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); - - inline bool isASCII(const uint32_t& t) - { - return t > 30 && t < 127; - } - - 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(const uint64_t& tid); - - //removes char from C++ string - void stripChar(char _c, std::string& _s); - - 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); - - //Creates a basic generic icon for stuff without one - SDL_Texture *createIconGeneric(const char *txt, int fontSize, bool clearBack); - - inline u128 accountUIDToU128(AccountUid uid) - { - return ((u128)uid.uid[0] << 64 | uid.uid[1]); - } - - inline AccountUid u128ToAccountUID(u128 id) - { - AccountUid ret; - ret.uid[0] = id >> 64; - ret.uid[1] = id; - return ret; - } - - inline std::string getIDStr(const uint64_t& _id) - { - char tmp[18]; - sprintf(tmp, "%016lX", _id); - return std::string(tmp); - } - - inline std::string getIDStrLower(const uint64_t& _id) - { - char tmp[18]; - sprintf(tmp, "%08X", (uint32_t)_id); - return std::string(tmp); - } - - inline std::string generatePathByTID(const uint64_t& tid) - { - return fs::getWorkDir() + data::getTitleSafeNameByTID(tid) + "/"; - } - - std::string getSizeString(const uint64_t& _size); - - inline void createTitleDirectoryByTID(const 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 checkForUpdate(void *a); -} diff --git a/inc/webdav.h b/inc/webdav.h deleted file mode 100644 index 3db44b9..0000000 --- a/inc/webdav.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include -#include - -#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. //JKSV/ - // e.g. //JKSV// - // e.g. //JKSV// - // 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 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 getListWithParent(const std::string& _parent); - std::string getDisplayNameFromURL(const std::string &url); - }; -} diff --git a/romfs/icon.png b/romfs/icon.png deleted file mode 100644 index fb07d76456f4dac93386a2c6c846f08f0324b0e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13966 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 diff --git a/romfs/img/fb/lDark.png b/romfs/img/fb/lDark.png deleted file mode 100644 index 7b22141b2122c93ec2c16f4d4c20336873b4b06f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 161655 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 Wnw$iJtpmZ`a`Hd)pjNRjQze@?bUiJ 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�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 L{an^LB{Ts5R2*S; diff --git a/romfs/img/fb/menuBotLeft.png b/romfs/img/fb/menuBotLeft.png deleted file mode 100644 index 5caf7025814c13173cbb826eef673c23b9f40f2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4854 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 diff --git a/romfs/img/fb/menuTopLeft.png b/romfs/img/fb/menuTopLeft.png deleted file mode 100644 index 9088aa29147e13c2a52d31eb8833be46f46b00c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4842 zcmeHKX;c&05)LAP2QtSUbyQ*i2gOc0dpZ(8c5s4GP(eqflXM_vC22@RL2*G4a6=I{ z6k)`11Q&QJ;xdScD2gIaT)<_-T@jwnpyD#O10tR`e>~^SKXOhdw{KN_RrTGvm3vdg zAp!QbBWy_|l6_F1?=ZKI{80eb9k{`cAuat7i6MuQkYv zj^o=et3P1TQ}#seZg&j6@$TK8{kJFuQ9cv<H34oeuJx+STKH0vqgi$9gL<6PSdZmr?w=oc$ra0Swqh13 zb%tCUO`z(RXqVfTF5XM-EgCVpa$fcO?5zt|dmtY#9FE((k9l;@oWnbw-rpkJxN>9R zrqIQ_!SuIF{@DFPLfwT2$&S=uW9@qeRk_LT(~q&~IcNFTBo4tT*A0zZkEyYX2M%0P z6;@G`?}gQBYO~KTdu2ardQ4_&xkA-NACiBp8 zWTZW}y3QrH--!8sJ0H3Yot5}X-{6|tG7)tdls+KmXmW??)%LcuT!y^1|6BZlZA{y~ z_~nf1i*7S4Y%;co_K$U|#9iLqXO!F8JPkISCa3K$NZpI?hc<7EYIpO${?g^pNqdT4 zYoSeY-@c2Sr><(wxRJ89s;`IDx;%^0^-ABpjRRb)*V*tN_jz-c<*$U?cFaF6wD7*N zo6L@|ja;$Q)zPjxH+j`6^7M@PTiD9ffl(_fqhn4cr=?njrpdD=%nn-bT{k%M=MDPt zn~V9!cXRR_4d;~d(~Q-dFj=Y$m=oF(Ek)QLSHw)$YiWavm;Y1*^s=Fw$; z)Ens5qg;P>8X144Vm02J*KfnkwF8n@^Hurl0!sI&_pYhWsZJ1Oa5-~#$`bva?;`*E z#>KuD8&X5$l(R`*jbkkav!}>CrrNkyPscMVjMj5<(_LdvKVhdPMea*t zDNq}8KT5CJ)iJ2K(YM{VWv1p-?zKl%J0Z!hc1^*19ZK%N^Z55_PdPPhYkfQzPrUM4 z7?jk+j(Aw`UKmkTlK*V-!jQQe-<5c_>%Bdymn|GOVBYhqCpM(e|9mz#_>4mb{!ZAs z38%aKo|bT|q5Nu8@@vuEHFXKatfGXn_9V~X_JDz}rH`vFugU$iV@$=VK{r0wdD+Jz zmIWgkNFZQu^@->xvm!%aKeOI)U^2Xx#hY#}~Q(ZpN$CaeC-975~vPIZ( z+rm5VbXc*nkC<~Iuu>}F9vM%bcO`@Lei+GF{xtQqgq(B#*=UcGxBV7mwU>GZ>{!nm zes@snqijw1neWfAwq{*gu!CO{yxx0Jh`Z*pRjOktt?d2T(#HpWV^#Q%hv;kSj*jbx zxBjr?)9|BpH$Tp|%>Lkf;`TLafY&FkE~e>Pv+%CodRTm&i;v#fQg~W5He}Ey)IU-GN&Vx z(lHgL2C4?&mGPC!fFO~$+k&7VN}<-7tpM3yY2pg`7qY&JjgXkr=@|$x@5cQ~`*ZGQ zWuPSz34FEESR%Y2Um=z7FOX@a3Yow>NMpa3{c0dP=MhVUgk1VSVnDa4j>7?6a`2d^j_!C*Ou4P&ex5L5ID zP?e~vXH*1~3_!6E7MF=|IgpgY;z4YLC4~?fi-6)uVFoI}U_NGsl1T;rTD=+t%c)SK zQ5apPi82ca!UdDWK|(5%27j3mt594H48R^xXk=QW;fp9tp~gaSl;D%WWit6(7R+S8 zd=8I`d{LT#=?$P32~-A5W3tT>VqpXz9Y8HgR4M>4&x33PK6(trwfZovRwbkoK~V@x zbAeObJC`L;VE`Hnh??)J`cN#U^R06es1#-qg<>vT0V?fuVnE|Cnb{Gr>l8_&QB4#E zc6e7weV$kR&2sU$3>cAuMPe}dOo+{8uporT<3VyZ3*#X&Sb|79)9XezXyv#O)nk*Q zK&BuYP(tQxD6U;(j{7CuE1!#m|E(Ei~m@EN9s@J-6S)zwSaH!<){ z%DvU~f1}Ix%XJ6SfD@1rTxO){2le10M3zhr@HJnFNN;`07K72hbb%2D63K25@mP?y zY#Rn9t??j{pY;Q>mE{17!Lw5u!PIcvFC6#Ls)3`;&N?yHt@AvNg?tM4! zWhkbF3~(9cLL!j{$U=i7z^^0m^m7K^Kep$mkVp z|NE`GgXXrZpQi|8o^484<^{XEWe1e#Up}mC$(lGxmSwZ1oKo~J^eW%9Atl`#{jMXK z8dTo7yMVJjZEgL@aaePC$lF71W*fImG*~#}rCz+xtv@|0w=pq!xaB~4@xh}r28OL~ z-3C>MkDVRpb8}18ssxpfTf43@eE*T-*7Eg+m3Q9iCscl2zwFG8nEvS-V1HjH_-Fsa z%y37ZFZZ>`p)GYNt-|E*)^Y!G^oiA%#8bA-U38#$y&r49w7A{RcE90=Om9Z8=0{iV z4XBFrkN&Ayw(W(PRyoLf=j-!tYRASm4t=*Nu8sQJiOc6`IfM5%9_A+AIq!ws=l;O) z$&6i|rP7sRWe+p=LU;4_78MQPZSq+4jZ>Vm=$b#a)5YDPO}FOai0kRJROw@v=e`I# zWUKrDTNb)x<=i1foiCO>KAKFMIetvrXvA;Wsv-IOEejX^%6qo8pg!c%;7+{SC8fQ_ zc9>bS+Pjpr^!JtHR~+?J2_4?P92M>4n7CYkEU7Z4XULoL=fM1|Co{|T)rUFOrIVNT zi=UV`hD`R6$x%MD_1&4t&ZEwj?WkxvaBjL;rQ~*45!FUP41*Qvh1L%!`JQ1F;Cc3 z%0Iq~v%}kRUa%oe>N?3?G>@?(XEpEE>0NW`bYXMqr%Hdnt~gj7$NA6Pum^r61#9N$ zY9lTbx0H=t|8n0`Z|j|F`5jl+#?7-e-A2pvZLNKhpYHBfbGV4XABmpw@1AKp(?qc0kjZs{9U;6_iZ(6U#gM&HahcTlAb0u>Y>j~a-g0f8%Nf^Zn$!Jd`1}ib9jWFG zecFSLKK1-{Ki)G+@T$8P)o+oeXm0VUNa1^WpBMV3J6FmJ-k)8Azu8`~Xj3b1WL4=% zzxm(5BocY4Rw7ZzB$A$82=-pyKi7&wD+9f%=10a$C%7yaoKU0KI%&m9{?rkf(FJ8A zzDr~rdEvU5?K__KeOmmfQ(0AemSmQe3@k~5UXkxVeq7i@`RW~geA$ZjhQmH>t>V^t za?p;`%F_5g3eJVlDpbWSpWr<9wcAQlZ+QC=llmxz5P$rOOtaujs%2k%jS7D z49jTTXqa_o^cmKc+@F)T@h?uz4*JJ5k>QF{hR+e&u{YI6n)X$&DyR5yrhEB~%>3IV zD`TSPgm!j9a5(3@`vKa-ymJ1(){lz6yY3_-=f;eyb9W2U__=EWhf>tn6;qr>{!kWI zxH0sXl#7)CEw9!+%sv<8Joap#J&G}cl;;7j?iSD9xEWs;#Dt!!?)dBoG`Yvt>R-RU zd1%Ymi{E=5zIyvzoWsVqzCYF1QbGdXb4>|%Yafam%zZsnHn%*x$0~l8Lb_37*4j3bpMY-Zp#@!W zmi${GYShyZwNZu9lJq9fHAy5?(M?m@-SQfFwpU z2C->u8iOiL(k_50{w|Qntkwu4f~RytfHyHE7ROCOI^Alu(yT0+(Hui(3IqZ=1E#|; z6=+Z`$p#!rq8cn?35YI?V9bJ=wI*C^G(ZF6i}F162!HmHEk%Au_q*g9SlBj8<>5TLH2^(Zscyk7Rw~8zHfW)7ueX{sH$B?Vi}} z%0Npl7X};AM8Z8;u$V%uFH{>*ty*XwqH2x?VWR>no5@G1Y#xhGMffm>%7?icCX6xI zD5mZOB{NuX#DHQ16ac4b0S*`Az$zAsP+^p*rm|TogsNh*cvOtX;%Edg%xAH=y&$HW zwV)~yU2m@lC^djWczm7)X7H#e!sJldD59YX*nA$9r%_={n8(3*Y}gK^Muk(1W<3JZ zsnsJf7~N!uu?q;og_9LBF$JbEKF%m~2(AGJU=L^wYNOTiQKZ!Bu}B;t_+)Z{w_Fy3 zC1AiX%oKc7nt_=upcV;KCW8jE?Ghp}LJ$t179lDX0NCe2G(w3PLvW*6X*BA@6v8Qp zu(TIAB*d5d_%_SBRipi&&5a-8lhvT_Q9VF~neC zhj*7$&%E|e(#3)~8a|5hscem!OJtk}(gj}Cuv*1pGT983ie(Su1G>ei!L5iHn;Zio z1<`;KvPT1r>n3yjr~1}dj9`iZGpP(NmBCiREFqI81bouFgQXLDx+hu@{eSon*%f*< z0btiP2bv4$RrC+ds+%u@!oTp-ork}01pxhJkM;ou_VtO^SWwqJ=zoswgVz^K0|bhd>=averIWYXq5 z4=~vmm&v7lA2~ZY3>nsVzcU9+dE(MpxWuR@PR%6yQ4Ui>RxKV25l6Y1sRP{!C0X!f rWxpHft3gg~iV8=x!qIar7Ot!SlJ|pNp5m`j0wt16IxV;)C_3#AiXth4 diff --git a/romfs/img/icn/icnDefault.png b/romfs/img/icn/icnDefault.png deleted file mode 100644 index 72f6676a7de2ef43bbf9189c39e2daf7ad241d81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2896 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 XLN9U6fo2DwW_A&2QflPzh@<}p2az45 diff --git a/romfs/img/icn/icnDrk.png b/romfs/img/icn/icnDrk.png deleted file mode 100644 index ce1838570bc7bab5c1f9d54a38d6cd75fd88c4ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1526 zcmV zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=K7lH4c^hTl0wj({YD#Bnf~s=dJ;KR-0~Oiz-@ zq&5%x&`=GwkR_oHxx2&p?>{4aL*qy}BsI?^=ZKa{DqL~l@zLrUDW)~9CVL6@Px5dt z!0gi60?%81Le9|VEpOXSg9LM+jDx{ACGOt zT-@Gop>6%K*H;?w33EtdoHdgrjESRJF1U7%$&j}`0~K5TZ$}^R!==iIiBBPk81sx! z5Wcw7DZ|K;rVj3^=$7wDL5o8)T$d}apSRB<4_D|L`4iInihdz~M>?#8UJLSpP`>A% z%1Phk;pvo3Ztpzje0zS9o7^sLa^b=^M)xt!$L?y%*mB;Ry0)^epm)q{t*0`&;iJ)K zvS%;oj*@7=*>T7Y^hSjk$Oa|qRB6z;=0*)6CO+9PGdG}Y0YV3i!foMBv(sRcA{~%f z8jgcN%6BZZ+?M68k;8E(4>&ypV}wWkQx9KJ8e`kh>PdRMii4AQHTUJ5TUV? zsw=@)|JWv10YP19c5JZ5V_E?IL+R~s$pS4>} zLLwc2Dng$HbpQ*gaz~JyL^9x@3g8>h7%f#+eu(iR1PGNJ#K5LV1+0`5=f{%c3{@3P zs+!f*wP?+fXNBc#dA?Dri6v9ZX69C`1{Y7Rp55HNcr9E6Z(J>?$Ks`wTH&<9a>dgX z3bh`5$dL{`@?nP`<*1EAla`vc+^l&kt#)uPRC!IJ-XFB!Fr=5P5vkn}! zAtMbPdD!q#!l*a3jrE8A2h?a&<3(yJ+D#2ov&#g{*NIMMAjXLR+$I5%z&x2nr<8b- zo6KTiRE08v)Cs54BnE=%B-TMUyEk$gB>x?50U-EqkP8#t7s&m{?Pt__U0-yv2+cB_ zNsWT)8)ckc+3WnK{B#b#Q~2uqGW0U^GW0U^GW0U^GW7p8H1OL2e;mVi-R89$Zosq> z00006VoOIv0RI600RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru;tCl7 zIu0``iPQi902y>eSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00KryL_t(& z-tF1Xi%nq|2k_6#+>w|`vN1yvVMVdB5Ptv*Sy&Q6B+^wVq^XgtWNATJC=2pqwj{E% zFiUmIV#Y*%sGh^XQ-HvnbJ!kH{-E*e%)VsLnyyv;k_kG^yd%n+mXs@kqvwz*@ znJvI!yu(b3Mzt+V`0PXML06pM3wYkbz@q@HouiKttg03CtR2FRIsnh1j9&>dT4>SE z*omh&if6SZjn-L#3z+C&24=@Zm>8;ycvGDfn8ZK|YIFeiu_!4EeM=G1Y{9Xt)94xW zCjsy#WdN3Ach+BI+p`W}&AgBjIe-q@(yRm6oNu8VKn`FY04Ss^o-Zf`a(NvVCV9AC zBXOY3EoB9#s&#%K*luDfrtgjcs9rsQa`Y~S1ciaq2;{<*tOnq;R zIlFKYHzMwJ;R;qXn}Or_UG;kmuOr6j9Nz=e0q3s|K=)!BUM0wGA{s5$Ie2R3WNs>8 zok{?z;r2|Ml;}>X&$2A${$w4%i@YDm0W@coyaqaGleiNG@G(OG?%{nHKrutBe=(|v z@8WU>0POegW8Z)Mzzl{04Kma~Rc^(R27guSDXVa736#xJL07*qoM6N<$f{#DLHvj+t diff --git a/romfs/img/icn/icnLght.png b/romfs/img/icn/icnLght.png deleted file mode 100644 index 71594c2806d1fb74584c2b38a120dc6a8a965f3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1103 zcmV-V1hD&wP)9gsx$xq02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00Xy4L_t(&-tC%8h#XZEhQHHx zbcPwpLLDC@ItUS?QBa7YB1W=NR9v{yg&PsGaiIx3y z3|p*=@&-%*w*X_npy+-yv(v)>P>-%hfk%KXz@5Nl13teYX)CZ>k^vt9YceKe5jY8a z0~`cSo7rL!0Gok#fa|&`1k4v$kyc{tHNXborks232c7~>0iR3S0h~0m&wqaqH}nHwxyap;9+yU@#=!h%-0}#xtQ%hO^_5rU0Gi3l6a{zM=qDae{{f~iT3B}u+eEYY&v~r{j z6RHDm0I&Lk)6&cJz_FC7#QAO&3rWA5St~VCnIY+K;ODelxE~)F2i|h!8$I6B1h!@T ze@RaH(eiXG0$>rC^`+0MU??(^X18~n*+xkpn%P2^F%w=N8W=#sx48HH`YPaDj>C7r zEM&~?Cg5I4Uz?etTGcAxAveu|0n7nAfOq}+4Dbr@N1Sv6Q^3ZA2|b_j+#B62w@W(U zfvhw0kNFNYUIu_x6dmV)1Hf5#wod;j@N9-jrO6#WIo|_keP>+n{bR!nWY%L}FET;9 zD*nGAa~G=&Kpq<^y#n(qWtC;XVk-+^-l=zG0sP_#P%40LD_}ai9YiXCm-6^`9ATp~ zW(^s@abRbX4c8Og8`|fyF9L4^w?)o=d&GG*09QoOcNAD1Mdq^3$^LZSbTmo9>rny` zfc?M=5r{G19`85jMb`ssQ(iRfB=4Y)n=L{4Ip71&;F_7-#aE&RJQ#tP1^y~%CQV7N z0XxlX68M7rgYH<)IgNi0nCa>J3qLciN!l#wkfcVR=evy%!j?YIiO);gCFx;FBi%km zsb}P)IAwkYcmlYxAYbX^#D}BAC{IoSdx2fF&!%S*iGFGHw>Sfn)&jp3=Il8p^B?hp VGN(Uzsxp#drJ=DZNH6eV$jKan4@y zUH%?*1WNy=j{2!T+V@tlNjq+Vu=&^3UimfHL;sgsQx5sxY^{m#+zPe+t^}X!XvszQ~SlzPI`jJ!Jsb{sN prj*+1rhV48SAIIe0<`1qPpO9d2aNmW|BPJv40RAXXD|8P{s+qnxmN%H diff --git a/romfs/img/tboxDrk/progBarCoverLeftDrk.png b/romfs/img/tboxDrk/progBarCoverLeftDrk.png deleted file mode 100644 index 3dab469851c29105f008106a413c94dd5aec851e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^96-#&!3HGb=lz)rq}Yw<%pw+t5DV`iaiF3^PZ!4!iOa1QHu4^F5OBGe?!uebJVDB1jWHuj8T-0+AsJ=I z2UZ!)QXC%s>XqNy7mNFbd%Y|?)-!t<^S+A5ecE-pE2@(^7;-9H7_RZu7%4C>=kXET kk#Lh)IqqwLOT|}tu~;6jd8UmqK+70BUHx3vIVCg!0JFtO7ytkO diff --git a/romfs/img/tboxDrk/tboxCornerBotLeft.png b/romfs/img/tboxDrk/tboxCornerBotLeft.png deleted file mode 100644 index 32baa3aad38c80425f00502a5845fd5664e954af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 287 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI!om))7XnW(s8$co1%#er@=ltB<)VvZPmw~~#C^fMp zHASI3vm`^o-P1Q9MK6^dDBkSp;usQf`0e$DyoVA*SRSm`WwP-MPvGE@{&TnCqQDHl z#+Q7qCW#C(Opdce4)WD@lnd9Ke0n5w=@i$5tb?8oOhOYHSX2}kIX!^P58@4$QXh_} z3l$vJ7AiO>66)H^7;d8^aJEAJ`M(SP70La&W*OHQLb~r;zxUdHSTKFgg>{CP_5bY? UkNM8McR$FTp00i_>zopr0Qz%WxBvhE diff --git a/romfs/img/tboxDrk/tboxCornerBotRight.png b/romfs/img/tboxDrk/tboxCornerBotRight.png deleted file mode 100644 index 29672ff8d51b805ebf759314827dda4a782a5297..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 267 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI!om)&%Ay#?eMWB#uW=KSdbAE1aYF-JD%fR4Vl$uzQ znxasiS(2gP?&%wlqL<1J6wmi`aSVw#{PyBT-sS)S<_qFS8hSh$FV&eXNbK>L+0e$O z>Et=<%mLAj;vYWW&6Q~DKUC5s?7+atA^;=)*gkl}eLDVzmi~t1l}vLg65e#3nm=R1 vH}U^9|8?gV&`_`+r6XYgOS3j3^P6RMTp diff --git a/romfs/img/tboxDrk/tboxCornerTopLeft.png b/romfs/img/tboxDrk/tboxCornerTopLeft.png deleted file mode 100644 index d2fa70fef0ed4b04296d8f4d48fce025980f435f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 290 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI!om)(iB~if@D8e9{84^+AoS&PUnpXniGdLHeCYGe8 zD3oWGWGJ|M`Ua%vrLqIX+dN$yLn02py}Xh4kbwxxh4#s6LQabuu6VqRXjGF{(d`nD zHpo`v>a}GuJG!6s-OqhBH{K@9oX*I<$M9j-ljARoe@gnVktkdh$amK)pE>TwgFa=e zsgpjv(auYqILq_rv*hkiOfx>6oxSAv(yOmeGo3wk^;Z@nhXVtXKm!Ad0t1NoPwE3J Xn-kZS@W#e(Ag6k|`njxgN@xNA23ujI diff --git a/romfs/img/tboxDrk/tboxCornerTopRight.png b/romfs/img/tboxDrk/tboxCornerTopRight.png deleted file mode 100644 index b119f32291db9697c3e6f5d317d040ff5daf60df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 287 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI!om)&%kmv8h*FYiJ%#er@=ltB<)VvZPmw~~#C^fMp zHASI3vm`^o-P1Q9MK6^dDBkSp;usQf`0crkf{czL3>TiKJ$E|1fWcU5Vn=y#V_=t4 z51${qQ0NN>RwKQIEW1zEi|+9}_w(`pdliq`xZfQvt#Dl4$H)Dq;Lye=M(s=H$$6jI zC#gEU`0N4yLsHc?R=FxU1sS3Zn%i`lZNqe#Cq%KkC@=|jIIt)QG;)BL8FCVgi~sO1 Ty4-o~Fvy*ru6{1-oD!M8d`3_gFmruXD@kE?jQ?cmqU*RVU4({RQoBe;%bjuz8Zhm6D u#?f)Wkg1mCoQ12jT*h~YPEPYHEAEBz^Y1c#HO~WRDTAl0pUXO@geCxV>QsIJ diff --git a/romfs/img/tboxLght/progBarCoverRightLight.png b/romfs/img/tboxLght/progBarCoverRightLight.png deleted file mode 100644 index 82424870d397616f66e5393c2de0fff3e6129c63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253 zcmeAS@N?(olHy`uVBq!ia0vp^96-#&!3HGb=lz)rq}YaiF49PZ!4!iOa1QHu5$*@Hkv_FEQTWcIu2nvx~E{*$Ep-MPDfu zsb>j%`i`w7R-M-6|ElutxVrR<-@F?PICYN diff --git a/romfs/img/tboxLght/tboxCornerBotLeft.png b/romfs/img/tboxLght/tboxCornerBotLeft.png deleted file mode 100644 index c0dc01f591be9f3bd6c91c753f90d5c52c9d7735..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI!om)&+cp}423!so}W=KSdbAE1aYF-JD%fR4Vl$uzQ znxasiS(2gP?&%wlqL<1J6fgC3aSVw#{PyxjP9{bHmy6}%9BNGyd1EBiwkfhrOG}Vf zn^u^>I9sx7L8s{*sWa;z95iqatu-;r(raK~;!uDQKe!L<>&pI!om)(f#qwXi4NyonGbEzKIX^cyHLnE7WngeFN=+<>&pI!om)&+G4lG09Y7)3%#er@=ltB<)VvZPmw~~#C^fMp zHASI3vm`^o-P1Q9MK6^dC|>R9;usQf`0eG5yoU@#ST49fX=CeT3RE^^^S#y)l+w)e zPBr6=;0DciVmrQ{|K#eu_G=eE-)g5n>&A;O|#W{0&zQxB^D*_{Lz4_hq ziO=HJo5QEA6LRzIR9X8YuYYGpRI++)jIvj4AoqE?`njxg HN@xNA&O=^H diff --git a/romfs/img/tboxLght/tboxCornerTopRight.png b/romfs/img/tboxLght/tboxCornerTopRight.png deleted file mode 100644 index d379d1ab8d1b9363bb21acb43ced355e3c861a94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI!om))S)S1`jC{Rc?GbEzKIX^cyHLnE7WngeFN=+?sP&k|!M4j3zYlfS51jelUHqkuczA R@O1~d($m$?Wt~$(695fZT2uf4 diff --git a/romfs/lang/de.txt b/romfs/lang/de.txt deleted file mode 100644 index 090e6ee..0000000 --- a/romfs/lang/de.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/en-GB.txt b/romfs/lang/en-GB.txt deleted file mode 100644 index 090e6ee..0000000 --- a/romfs/lang/en-GB.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/en-US.txt b/romfs/lang/en-US.txt deleted file mode 100644 index 090e6ee..0000000 --- a/romfs/lang/en-US.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/es-419.txt b/romfs/lang/es-419.txt deleted file mode 100644 index f8ad784..0000000 --- a/romfs/lang/es-419.txt +++ /dev/null @@ -1,218 +0,0 @@ -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" diff --git a/romfs/lang/es.txt b/romfs/lang/es.txt deleted file mode 100644 index 090e6ee..0000000 --- a/romfs/lang/es.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/fr-CA.txt b/romfs/lang/fr-CA.txt deleted file mode 100644 index 090e6ee..0000000 --- a/romfs/lang/fr-CA.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/fr.txt b/romfs/lang/fr.txt deleted file mode 100644 index 51a2121..0000000 --- a/romfs/lang/fr.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/it.txt b/romfs/lang/it.txt deleted file mode 100644 index 86af9fd..0000000 --- a/romfs/lang/it.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/ja.txt b/romfs/lang/ja.txt deleted file mode 100644 index 31641d3..0000000 --- a/romfs/lang/ja.txt +++ /dev/null @@ -1,215 +0,0 @@ -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, "全てのユーザーセーブデータを削除" diff --git a/romfs/lang/ko.txt b/romfs/lang/ko.txt deleted file mode 100644 index 3ff87d8..0000000 --- a/romfs/lang/ko.txt +++ /dev/null @@ -1,171 +0,0 @@ -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, "모든 사용자 저장 삭제" diff --git a/romfs/lang/nl.txt b/romfs/lang/nl.txt deleted file mode 100644 index 090e6ee..0000000 --- a/romfs/lang/nl.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/pt-BR.txt b/romfs/lang/pt-BR.txt deleted file mode 100644 index cacbdf3..0000000 --- a/romfs/lang/pt-BR.txt +++ /dev/null @@ -1,156 +0,0 @@ -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" diff --git a/romfs/lang/pt.txt b/romfs/lang/pt.txt deleted file mode 100644 index 090e6ee..0000000 --- a/romfs/lang/pt.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/ru.txt b/romfs/lang/ru.txt deleted file mode 100644 index 090e6ee..0000000 --- a/romfs/lang/ru.txt +++ /dev/null @@ -1,171 +0,0 @@ -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" diff --git a/romfs/lang/zh-CN.txt b/romfs/lang/zh-CN.txt deleted file mode 100644 index bcf2353..0000000 --- a/romfs/lang/zh-CN.txt +++ /dev/null @@ -1,170 +0,0 @@ -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, "删除所有用户存档" diff --git a/romfs/lang/zh-TW.txt b/romfs/lang/zh-TW.txt deleted file mode 100644 index 08e8783..0000000 --- a/romfs/lang/zh-TW.txt +++ /dev/null @@ -1,171 +0,0 @@ -author = 0, "Leo" -confirmBlacklist = 0, "是否確定要將#%s#加入黑名單?" -confirmCopy = 0, "是否確定要將#%s#複製到#%s#?" -confirmCreateAllSaveData = 0, "是否確定要複製系統內所有遊戲進度並新增到使用者#%s#帳號內? 完成此操作的等待時間將取決於系統內的遊戲進度檔案數量." -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]" -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, "重新掃瞄Titles" -extrasMenu = 9, "掛載RomFS程序" -extrasMenu = 10, "備份JKSV資料夾" -extrasMenu = 11, "*[DEV]* 使用英文介面" -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] 選定 [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#已被移動至資源回收筒." -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, "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進度存檔匯出的資料夾" -settingsMenu = 3, "編輯Titles黑名單" -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, "請輸入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, "變更匯出檔案資料夾" -titleOptions = 3, "以檔案管理器開啟" -titleOptions = 4, "刪除所有進度存檔" -titleOptions = 5, "重置進度存檔" -titleOptions = 6, "刪除進度存檔" -titleOptions = 7, "擴充存檔空間" -titleOptions = 8, "匯出SVI" -translationMainPage = 0, "翻譯:" -userOptions = 0, "備份使用者全部存檔:" -userOptions = 1, "新增遊戲存檔進度" -userOptions = 2, "新增所有遊戲存檔進度" -userOptions = 3, "刪除使用者全部存檔" diff --git a/src/cfg.cpp b/src/cfg.cpp deleted file mode 100644 index 10d9620..0000000 --- a/src/cfg.cpp +++ /dev/null @@ -1,534 +0,0 @@ -#include -#include -#include -#include - -#include "cfg.h" -#include "data.h" -#include "file.h" -#include "ui.h" -#include "util.h" -#include "type.h" - -std::unordered_map cfg::config; -std::vector cfg::blacklist; -std::vector cfg::favorites; -static std::unordered_map pathDefs; -uint8_t cfg::sortType; -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 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) -{ - for(uint64_t& bid : cfg::blacklist) - if(tid == bid) return true; - - return false; -} - -//Has to be threaded to be compatible with ui::confirm -void cfg::addTitleToBlacklist(void *a) -{ - threadInfo *t = (threadInfo *)a; - data::userTitleInfo *d = data::getCurrentUserTitleInfo(); - uint64_t tid = d->tid; - cfg::blacklist.push_back(tid); - 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); - } - ui::ttlRefresh(); - cfg::saveConfig(); - t->finished = true; -} - -void cfg::removeTitleFromBlacklist(const uint64_t& tid) -{ - for(unsigned i = 0; i < cfg::blacklist.size(); i++) - { - 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) -{ - for(uint64_t& fid : cfg::favorites) - if(tid == fid) return true; - - return false; -} - -static int getFavoriteIndex(const uint64_t& tid) -{ - for(unsigned i = 0; i < cfg::favorites.size(); i++) - { - if(tid == cfg::favorites[i]) - return i; - } - return -1; -} - -void cfg::addTitleToFavorites(const uint64_t& tid) -{ - if(cfg::isFavorite(tid)) - { - int rem = getFavoriteIndex(tid); - cfg::favorites.erase(cfg::favorites.begin() + rem); - } - else - cfg::favorites.push_back(tid); - - data::sortUserTitles(); - ui::ttlRefresh(); - cfg::saveConfig(); -} - -bool cfg::isDefined(const uint64_t& tid) -{ - for(auto& def : pathDefs) - if(def.first == tid) return true; - - return false; -} - -void cfg::pathDefAdd(const uint64_t& tid, const std::string& newPath) -{ - data::titleInfo *t = data::getTitleInfoByTID(tid); - std::string oldSafe = t->safeTitle; - std::string tmp = util::safeString(newPath); - if(!tmp.empty()) - { - pathDefs[tid] = 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, ui::getUICString("popChangeOutputFolder", 0), oldSafe.c_str(), tmp.c_str()); - } - else - ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popChangeOutputError", 0), newPath.c_str()); -} - -std::string cfg::getPathDefinition(const uint64_t& tid) -{ - return pathDefs[tid]; -} - -void cfg::addPathToFilter(const uint64_t& tid, const std::string& _p) -{ - char outpath[128]; - sprintf(outpath, "sdmc:/config/JKSV/0x%016lX_filter.txt", tid); - FILE *filters = fopen(outpath, "a"); - fprintf(filters, "%s\n", _p.c_str()); - fclose(filters); -} - -void cfg::resetConfig() -{ - cfg::config["incDev"] = false; - cfg::config["autoBack"] = true; - cfg::config["ovrClk"] = false; - cfg::config["holdDel"] = true; - cfg::config["holdRest"] = true; - cfg::config["holdOver"] = true; - cfg::config["forceMount"] = true; - cfg::config["accSysSave"] = false; - cfg::config["sysSaveWrite"] = false; - cfg::config["directFsCmd"] = 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) -{ - return _txt == _true_ ? true : false; -} - -static inline std::string boolToText(bool _b) -{ - return _b ? _true_ : _false_; -} - -static inline std::string sortTypeText() -{ - switch(cfg::sortType) - { - case cfg::ALPHA: - return "ALPHA"; - break; - - case cfg::MOST_PLAYED: - return "MOST_PLAYED"; - break; - - case cfg::LAST_PLAYED: - return "LAST_PLAYED"; - break; - } - return ""; -} - -static void loadWorkDirLegacy() -{ - if(fs::fileExists(workDirLegacy)) - { - char tmp[256]; - memset(tmp, 0, 256); - - FILE *getDir = fopen(workDirLegacy, "r"); - fgets(tmp, 256, getDir); - fclose(getDir); - std::string tmpStr = tmp; - util::stripChar('\n', tmpStr); - util::stripChar('\r', tmpStr); - fs::setWorkDir(tmpStr); - fs::delfile(workDirLegacy); - } -} - -static void loadConfigLegacy() -{ - std::string legacyCfgPath = fs::getWorkDir() + "cfg.bin"; - 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) - ui::animScale = 3.0f; - fclose(oldCfg); - - cfg::config["incDev"] = cfgIn >> 63 & 1; - cfg::config["autoBack"] = cfgIn >> 62 & 1; - cfg::config["ovrClk"] = cfgIn >> 61 & 1; - cfg::config["holdDel"] = cfgIn >> 60 & 1; - cfg::config["holdRest"] = cfgIn >> 59 & 1; - cfg::config["holdOver"] = cfgIn >> 58 & 1; - cfg::config["forceMount"] = cfgIn >> 57 & 1; - cfg::config["accSysSave"] = cfgIn >> 56 & 1; - cfg::config["sysSaveWrite"] = cfgIn >> 55 & 1; - cfg::config["directFsCmd"] = cfgIn >> 53 & 1; - cfg::config["zip"] = cfgIn >> 51 & 1; - cfg::config["langOverride"] = cfgIn >> 50 & 1; - cfg::config["trashBin"] = cfgIn >> 49 & 1; - fs::delfile(legacyCfgPath.c_str()); - } -} - -static void loadFavoritesLegacy() -{ - std::string legacyFavPath = fs::getWorkDir() + "favorites.txt"; - if(fs::fileExists(legacyFavPath)) - { - fs::dataFile fav(legacyFavPath); - while(fav.readNextLine(false)) - cfg::favorites.push_back(strtoul(fav.getLine().c_str(), NULL, 16)); - fav.close(); - fs::delfile(legacyFavPath); - } -} - -static void loadBlacklistLegacy() -{ - std::string legacyBlPath = fs::getWorkDir() + "blacklist.txt"; - if(fs::fileExists(legacyBlPath)) - { - fs::dataFile bl(legacyBlPath); - while(bl.readNextLine(false)) - cfg::blacklist.push_back(strtoul(bl.getLine().c_str(), NULL, 16)); - bl.close(); - fs::delfile(legacyBlPath); - } -} - -static void loadTitleDefsLegacy() -{ - std::string titleDefLegacy = fs::getWorkDir() + "titleDefs.txt"; - if(fs::fileExists(titleDefLegacy)) - { - fs::dataFile getPaths(titleDefLegacy); - while(getPaths.readNextLine(true)) - { - uint64_t tid = strtoul(getPaths.getName().c_str(), NULL, 16); - pathDefs[tid] = getPaths.getNextValueStr(); - } - getPaths.close(); - fs::delfile(titleDefLegacy); - } -} - -//Oops -static void loadTitleDefs() -{ - if(fs::fileExists(titleDefPath)) - { - fs::dataFile getPaths(titleDefPath); - while(getPaths.readNextLine(true)) - { - uint64_t tid = strtoul(getPaths.getName().c_str(), NULL, 16); - pathDefs[tid] = getPaths.getNextValueStr(); - } - } -} - -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(); - loadBlacklistLegacy(); - loadTitleDefsLegacy(); - loadTitleDefs(); - - if(fs::fileExists(cfgPath)) - { - fs::dataFile cfgRead(cfgPath); - while(cfgRead.readNextLine(true)) - { - std::string varName = cfgRead.getName(); - if(cfgStrings.find(varName) != cfgStrings.end()) - { - switch(cfgStrings[varName]) - { - case 0: - fs::setWorkDir(cfgRead.getNextValueStr()); - break; - - case 1: - cfg::config["incDev"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 2: - cfg::config["autoBack"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 3: - cfg::config["ovrClk"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 4: - cfg::config["holdDel"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 5: - cfg::config["holdRest"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 6: - cfg::config["holdOver"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 7: - cfg::config["forceMount"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 8: - cfg::config["accSysSave"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 9: - cfg::config["sysSaveWrite"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 10: - cfg::config["directFsCmd"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 11: - cfg::config["zip"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 12: - cfg::config["langOverride"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 13: - cfg::config["trashBin"] = textToBool(cfgRead.getNextValueStr()); - break; - - case 14: - { - std::string getSort = cfgRead.getNextValueStr(); - if(getSort == "ALPHA") - cfg::sortType = cfg::ALPHA; - else if(getSort == "MOST_PLAYED") - cfg::sortType = cfg::MOST_PLAYED; - else - cfg::sortType = cfg::LAST_PLAYED; - } - break; - - case 15: - { - std::string animFloat = cfgRead.getNextValueStr(); - ui::animScale = strtof(animFloat.c_str(), NULL); - } - break; - - case 16: - { - std::string tid = cfgRead.getNextValueStr(); - cfg::favorites.push_back(strtoul(tid.c_str(), NULL, 16)); - } - break; - - 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 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) - fprintf(pathOut, "0x%016lX = \"%s\"\n", p.first, p.second.c_str()); - fclose(pathOut); -} - -void cfg::saveConfig() -{ - FILE *cfgOut = fopen("sdmc:/config/JKSV/JKSV.cfg", "w"); - fprintf(cfgOut, "#JKSV config.\nworkDir = \"%s\"\n\n", fs::getWorkDir().c_str()); - fprintf(cfgOut, "includeDeviceSaves = %s\n", boolToText(cfg::config["incDev"]).c_str()); - fprintf(cfgOut, "autoBackup = %s\n", boolToText(cfg::config["autoBack"]).c_str()); - fprintf(cfgOut, "autoName = %s\n", boolToText(cfg::config["autoName"]).c_str()); - fprintf(cfgOut, "overclock = %s\n", boolToText(cfg::config["ovrClk"]).c_str()); - fprintf(cfgOut, "holdToDelete = %s\n", boolToText(cfg::config["holdDel"]).c_str()); - fprintf(cfgOut, "holdToRestore = %s\n", boolToText(cfg::config["holdRest"]).c_str()); - fprintf(cfgOut, "holdToOverwrite = %s\n", boolToText(cfg::config["holdOver"]).c_str()); - fprintf(cfgOut, "forceMount = %s\n", boolToText(cfg::config["forceMount"]).c_str()); - fprintf(cfgOut, "accountSystemSaves = %s\n", boolToText(cfg::config["accSysSaves"]).c_str()); - fprintf(cfgOut, "allowSystemSaveWrite = %s\n", boolToText(cfg::config["sysSaveWrite"]).c_str()); - fprintf(cfgOut, "directFSCommands = %s\n", boolToText(cfg::config["directFsCmd"]).c_str()); - fprintf(cfgOut, "exportToZIP = %s\n", boolToText(cfg::config["zip"]).c_str()); - fprintf(cfgOut, "languageOverride = %s\n", boolToText(cfg::config["langOverride"]).c_str()); - fprintf(cfgOut, "enableTrashBin = %s\n", boolToText(cfg::config["trashBin"]).c_str()); - fprintf(cfgOut, "titleSortType = %s\n", sortTypeText().c_str()); - fprintf(cfgOut, "animationScale = %f\n", ui::animScale); - - 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) - fprintf(cfgOut, "favorite = 0x%016lX\n", f); - } - - if(!cfg::blacklist.empty()) - { - fprintf(cfgOut, "\n#blacklist\n"); - for(uint64_t& b : cfg::blacklist) - fprintf(cfgOut, "blacklist = 0x%016lX\n", b); - } - fclose(cfgOut); - - if(!pathDefs.empty()) - savePathDefs(); -} diff --git a/src/curlfuncs.cpp b/src/curlfuncs.cpp deleted file mode 100644 index 8ae0389..0000000 --- a/src/curlfuncs.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include -#include -#include - -#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 curlFuncs::writeHeaders(const char *buff, size_t sz, size_t cnt, void *u) -{ - std::vector *headers = (std::vector *)u; - headers->push_back(buff); - return sz * cnt; -} - -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 *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 *headers, const std::string& _url) -{ - std::string ret; - CURL *handle = curl_easy_init(); - curl_easy_setopt(handle, CURLOPT_URL, _url.c_str()); - curl_easy_setopt(handle, CURLOPT_HTTPGET, 1); - curl_easy_setopt(handle, CURLOPT_USERAGENT, "JKSV"); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeDataString); - curl_easy_setopt(handle, CURLOPT_WRITEDATA, &ret); - curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(handle, CURLOPT_TIMEOUT, 15); - if(headers) - { - curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, writeHeaders); - curl_easy_setopt(handle, CURLOPT_HEADERDATA, headers); - } - - if(curl_easy_perform(handle) != CURLE_OK) - ret.clear();//JIC - - curl_easy_cleanup(handle); - return ret; -} - -size_t writeDataBin(uint8_t *buff, size_t sz, size_t cnt, void *u) -{ - size_t sizeIn = sz * cnt; - std::vector *binData = (std::vector *)u; - binData->reserve(sizeIn); - binData->insert(binData->end(), buff, buff + sizeIn); - return sizeIn; -} - -bool curlFuncs::getBinURL(std::vector *out, const std::string& _url) -{ - bool ret = false; - CURL *handle = curl_easy_init(); - curl_easy_setopt(handle, CURLOPT_URL, _url.c_str()); - curl_easy_setopt(handle, CURLOPT_HTTPGET, 1); - curl_easy_setopt(handle, CURLOPT_USERAGENT, "JKSV"); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeDataBin); - curl_easy_setopt(handle, CURLOPT_WRITEDATA, out); - curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(handle, CURLOPT_TIMEOUT, 15); - if(curl_easy_perform(handle) == CURLE_OK) - ret = true; - - curl_easy_cleanup(handle); - return ret; -} diff --git a/src/data.cpp b/src/data.cpp deleted file mode 100644 index 821fbd8..0000000 --- a/src/data.cpp +++ /dev/null @@ -1,503 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "data.h" -#include "file.h" -#include "util.h" -#include "type.h" -#include "cfg.h" - -//FsSaveDataSpaceId_All doesn't work for SD -static const unsigned saveOrder [] = { 0, 1, 2, 3, 4, 100, 101 }; - -int selUser = 0, selData = 0; - -//User vector -std::vector data::users; - -//System language -SetLanguage data::sysLang; - -//For other save types -static bool sysBCATPushed = false, tempPushed = false; -std::unordered_map data::titles; - -//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) - { - 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(); ) - { - 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) - return pointA < pointB; - - i += aCnt; - j += bCnt; - } - } - break; - - case cfg::MOST_PLAYED: - return a.playStats.playtime > b.playStats.playtime; - break; - - case cfg::LAST_PLAYED: - return a.playStats.last_timestamp_user> b.playStats.last_timestamp_user; - break; - } - return false; - } -} sortTitles; - -//Returns -1 for new -static int getUserIndex(const AccountUid& id) -{ - u128 nId = util::accountUIDToU128(id); - 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) -{ - 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) -{ - bool ret = false; - if(!cfg::config["forceMount"]) - return true; - - if((ret = fs::mountSave(_inf))) - fs::unmountSave(); - - return ret; -} - -static inline void addTitleToList(const uint64_t& tid) -{ - 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); - - if(R_SUCCEEDED(ctrlRes) && !(outSize < sizeof(ctrlData->nacp)) && R_SUCCEEDED(nacpRes) && iconSize > 0) - { - //Copy nacp - memcpy(&data::titles[tid].nacp, &ctrlData->nacp, sizeof(NacpStruct)); - - //Setup 'shortcuts' to strings - NacpLanguageEntry *ent; - nacpGetLanguageEntry(&data::titles[tid].nacp, &ent); - if(strlen(ent->name) == 0) - data::titles[tid].title = ctrlData->nacp.lang[SetLanguage_ENUS].name; - else - 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); - - if(cfg::isFavorite(tid)) - data::titles[tid].fav = true; - - data::titles[tid].icon = gfx::texMgr->textureLoadFromMem(IMG_FMT_JPG, ctrlData->icon, iconSize); - if(!data::titles[tid].icon) - data::titles[tid].icon = util::createIconGeneric(util::getIDStrLower(tid).c_str(), 32, true); - } - 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); - else - data::titles[tid].safeTitle = util::getIDStr(tid); - - data::titles[tid].icon = util::createIconGeneric(util::getIDStrLower(tid).c_str(), 32, 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))) - { - for(int i = 0; i < total; i++) - data::users.emplace_back(uids[i], "", ""); - } - delete[] uids; -} - -//This can load titles installed without having save data -static void loadTitlesFromRecords() -{ - NsApplicationRecord nsRecord; - int32_t entryCount = 0, recordOffset = 0; - while(R_SUCCEEDED(nsListApplicationRecord(&nsRecord, 1, recordOffset++, &entryCount)) && entryCount > 0) - { - if(!titleIsLoaded(nsRecord.application_id)) - addTitleToList(nsRecord.application_id); - } -} - -static void importSVIs() -{ - std::string sviDir = fs::getWorkDir() + "svi/"; - fs::dirList sviList(sviDir); - if(sviList.getCount() > 0) - { - for(unsigned i = 0; i < sviList.getCount(); i++) - { - uint64_t tid = 0; - NacpStruct *nacp = new NacpStruct; - NacpLanguageEntry *ent; - std::string sviPath = fs::getWorkDir() + "svi/" + sviList.getItem(i); - - size_t iconSize = fs::fsize(sviPath) - (sizeof(uint64_t) + sizeof(NacpStruct)); - uint8_t *iconBuffer = new uint8_t[iconSize]; - FILE *sviIn = fopen(sviPath.c_str(), "rb"); - fread(&tid, sizeof(uint64_t), 1, sviIn); - fread(nacp, sizeof(NacpStruct), 1, sviIn); - fread(iconBuffer, 1, iconSize, sviIn); - - if(!titleIsLoaded(tid)) - { - nacpGetLanguageEntry(nacp, &ent); - memcpy(&data::titles[tid].nacp, nacp, sizeof(NacpStruct)); - 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); - - if(cfg::isFavorite(tid)) - data::titles[tid].fav = true; - - if(!data::titles[tid].icon && iconSize > 0) - data::titles[tid].icon = gfx::texMgr->textureLoadFromMem(IMG_FMT_JPG, iconBuffer, iconSize); - else if(!data::titles[tid].icon && iconSize == 0) - data::titles[tid].icon = util::createIconGeneric(util::getIDStrLower(tid).c_str(), 32, true); - } - delete nacp; - delete[] iconBuffer; - fclose(sviIn); - } - } -} - -bool data::loadUsersTitles(bool clearUsers) -{ - static unsigned systemUserCount = 4; - FsSaveDataInfoReader it; - FsSaveDataInfo info; - s64 total = 0; - - loadTitlesFromRecords(); - importSVIs(); - - //Clear titles - for(data::user& u : data::users) - u.titleInfo.clear(); - if(clearUsers) - { - systemUserCount = 4; - for(data::user& u : data::users) - u.delIcon(); - data::users.clear(); - - loadUserAccounts(); - sysBCATPushed = false; - tempPushed = 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++) - { - if(R_FAILED(fsOpenSaveDataInfoReader(&it, (FsSaveDataSpaceId)saveOrder[i]))) - continue; - - 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) - tid = info.system_save_data_id; - else - tid = info.application_id; - - if(!titleIsLoaded(tid)) - addTitleToList(tid); - - //Don't bother with this stuff - if(cfg::isBlacklisted(tid) || !accountSystemSaveCheck(info) || !testMount(info)) - continue; - - switch(info.save_data_type) - { - case FsSaveDataType_Bcat: - info.uid = util::u128ToAccountUID(2); - break; - - case FsSaveDataType_Device: - info.uid = util::u128ToAccountUID(3); - break; - - case FsSaveDataType_SystemBcat: - info.uid = util::u128ToAccountUID(4); - if(!sysBCATPushed) - { - ++systemUserCount; - sysBCATPushed = true; - users.emplace_back(util::u128ToAccountUID(4), ui::getUIString("saveTypeMainMenu", 4), "System BCAT"); - } - break; - - case FsSaveDataType_Cache: - info.uid = util::u128ToAccountUID(5); - break; - - case FsSaveDataType_Temporary: - info.uid = util::u128ToAccountUID(6); - if(!tempPushed) - { - ++systemUserCount; - tempPushed = true; - users.emplace_back(util::u128ToAccountUID(6), ui::getUIString("saveTypeMainMenu", 5), "Temporary"); - } - break; - } - - int u = getUserIndex(info.uid); - if(u == -1) - { - 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); - else - memset(&playStats, 0, sizeof(PdmPlayStatistics)); - users[u].addUserTitleInfo(tid, &info, &playStats); - } - fsSaveDataInfoReaderClose(&it); - } - - 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++) - { - //Not needed but makes this easier to read - data::user& u = data::users[i]; - u.titleInfo.insert(u.titleInfo.end(), dev.titleInfo.begin(), dev.titleInfo.end()); - } - } - - data::sortUserTitles(); - - return true; -} - -void data::sortUserTitles() -{ - - for(data::user& u : data::users) - std::sort(u.titleInfo.begin(), u.titleInfo.end(), sortTitles); -} - -void data::init() -{ - data::loadUsersTitles(true); -} - -void data::exit() -{ - /*Still needed for planned future revisions*/ -} - -void data::setUserIndex(unsigned _sUser) -{ - selUser = _sUser; -} - -data::user *data::getCurrentUser() -{ - return &users[selUser]; -} - -unsigned data::getCurrentUserIndex() -{ - return selUser; -} - -void data::setTitleIndex(unsigned _sTitle) -{ - selData = _sTitle; -} - -data::userTitleInfo *data::getCurrentUserTitleInfo() -{ - return &users[selUser].titleInfo[selData]; -} - -unsigned data::getCurrentUserTitleInfoIndex() -{ - return selData; -} - -data::titleInfo *data::getTitleInfoByTID(const uint64_t& tid) -{ - if(titles.find(tid) != titles.end()) - return &titles[tid]; - return NULL; -} - -std::string data::getTitleNameByTID(const uint64_t& tid) -{ - return titles[tid].title; -} - -std::string data::getTitleSafeNameByTID(const uint64_t& tid) -{ - return titles[tid].safeTitle; -} - -SDL_Texture *data::getTitleIconByTID(const uint64_t& tid) -{ - return titles[tid].icon; -} - -int data::getTitleIndexInUser(const data::user& u, const uint64_t& tid) -{ - for(unsigned i = 0; i < u.titleInfo.size(); i++) - { - if(u.titleInfo[i].tid == tid) - return i; - } - return -1; -} - -data::user::user(const AccountUid& _id, const std::string& _backupName, const std::string& _safeBackupName) -{ - userID = _id; - uID128 = util::accountUIDToU128(_id); - - AccountProfile prof; - AccountProfileBase base; - - if(R_SUCCEEDED(accountGetProfile(&prof, userID)) && R_SUCCEEDED(accountProfileGet(&prof, NULL, &base))) - { - username = base.nickname; - userSafe = util::safeString(username); - if(userSafe.empty()) - { - char tmp[32]; - sprintf(tmp, "Acc%08X", (uint32_t)uID128); - userSafe = tmp; - } - - uint32_t jpgSize = 0; - accountProfileGetImageSize(&prof, &jpgSize); - uint8_t *jpegData = new uint8_t[jpgSize]; - accountProfileLoadImage(&prof, jpegData, jpgSize, &jpgSize); - userIcon = gfx::texMgr->textureLoadFromMem(IMG_FMT_JPG, jpegData, jpgSize); - delete[] jpegData; - - accountProfileClose(&prof); - } - else - { - username = _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, const std::string& _safeBackupName, SDL_Texture *img) : user(_id, _backupName, _safeBackupName) -{ - delIcon(); - userIcon = img; - titles.reserve(64); -} - -void data::user::setUID(const AccountUid& _id) -{ - userID = _id; - uID128 = util::accountUIDToU128(_id); -} - -void data::user::addUserTitleInfo(const uint64_t& tid, const FsSaveDataInfo *_saveInfo, const PdmPlayStatistics *_stats) -{ - data::userTitleInfo newInfo; - newInfo.tid = tid; - memcpy(&newInfo.saveInfo, _saveInfo, sizeof(FsSaveDataInfo)); - memcpy(&newInfo.playStats, _stats, sizeof(PdmPlayStatistics)); - titleInfo.push_back(newInfo); -} - -static const SDL_Color green = {0x00, 0xDD, 0x00, 0xFF}; - -void data::dispStats() -{ - data::user *cu = data::getCurrentUser(); - data::userTitleInfo *d = data::getCurrentUserTitleInfo(); - - //Easiest/laziest way to do this - 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 += 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()); -} \ No newline at end of file diff --git a/src/fs.cpp b/src/fs.cpp deleted file mode 100644 index bda4025..0000000 --- a/src/fs.cpp +++ /dev/null @@ -1,670 +0,0 @@ -#include -#include - -#include "fs.h" -#include "cfg.h" -#include "util.h" - -static std::string wd = "sdmc:/JKSV/"; - -static FSFILE *debLog; - -static FsFileSystem sv; - -static std::vector 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(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::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; - switch(_type) - { - case FsSaveDataType_Account: - saveSize = tinfo->nacp.user_account_save_data_size; - journalSize = tinfo->nacp.user_account_save_data_journal_size; - break; - - case FsSaveDataType_Device: - saveSize = tinfo->nacp.device_save_data_size; - journalSize = tinfo->nacp.device_save_data_journal_size; - break; - - case FsSaveDataType_Bcat: - saveSize = tinfo->nacp.bcat_delivery_cache_storage_size; - journalSize = tinfo->nacp.bcat_delivery_cache_storage_size; - break; - - case FsSaveDataType_Cache: - saveSize = 32 * 1024 * 1024; - if(tinfo->nacp.cache_storage_journal_size > tinfo->nacp.cache_storage_data_and_journal_size_max) - journalSize = tinfo->nacp.cache_storage_journal_size; - else - journalSize = tinfo->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 : tinfo->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); - switch(tinfo->saveInfo.save_data_type) - { - case FsSaveDataType_Account: - ret = t->nacp.user_account_save_data_journal_size; - break; - - case FsSaveDataType_Device: - ret = t->nacp.device_save_data_journal_size; - break; - - case FsSaveDataType_Bcat: - ret = t->nacp.bcat_delivery_cache_storage_size; - break; - - case FsSaveDataType_Cache: - if(t->nacp.cache_storage_journal_size > 0) - ret = t->nacp.cache_storage_journal_size; - else - ret = t->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); - switch(tinfo->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) - ret = extend->nacp.user_account_save_data_journal_size_max; - else - ret = extend->nacp.user_account_save_data_journal_size; - break; - - case FsSaveDataType_Bcat: - ret = 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) - ret = extend->nacp.cache_storage_data_and_journal_size_max; - else - ret = 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) - ret = extend->nacp.device_save_data_journal_size_max; - else - ret = extend->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); -} - diff --git a/src/fs/dir.cpp b/src/fs/dir.cpp deleted file mode 100644 index bc4eacd..0000000 --- a/src/fs/dir.cpp +++ /dev/null @@ -1,270 +0,0 @@ -#include -#include - -#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); -} diff --git a/src/fs/file.cpp b/src/fs/file.cpp deleted file mode 100644 index b51da12..0000000 --- a/src/fs/file.cpp +++ /dev/null @@ -1,395 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 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 localBuffer; - FILE *out = fopen(in->dst.c_str(), "wb"); - - while(written < in->filesize) - { - std::unique_lock 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 localBuffer; - FILE *out = fopen(in->dst.c_str(), "wb"); - - while(written < in->filesize) - { - std::unique_lock 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 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 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 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 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; -} diff --git a/src/fs/fsfile.c b/src/fs/fsfile.c deleted file mode 100644 index d0b7d52..0000000 --- a/src/fs/fsfile.c +++ /dev/null @@ -1,150 +0,0 @@ -#include -#include -#include -#include - -#include "fs/fsfile.h" - -char *getDeviceFromPath(char *dev, size_t _max, const char *path) -{ - memset(dev, 0, _max); - char *c = strchr(path, ':'); - if(c - path > _max) - return NULL; - - //probably not good? idk - memcpy(dev, path, c - path); - - return dev; -} - -char *getFilePath(char *pathOut, size_t _max, const char *path) -{ - memset(pathOut, 0, _max); - char *c = strchr(path, '/'); - size_t pLength = strlen(c); - if(pLength > _max) - return NULL; - - memcpy(pathOut, c, pLength); - - return pathOut; -} - -bool fsMkDir(const char *_p) -{ - char devStr[16]; - char path[FS_MAX_PATH]; - if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(path, FS_MAX_PATH, _p)) - return false; - - Result res = fsFsCreateDirectory(fsdevGetDeviceFileSystem(devStr), path); - return res == 0; -} - -int fsremove(const char *_p) -{ - char devStr[16]; - char path[FS_MAX_PATH]; - if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(path, FS_MAX_PATH, _p)) - return -1; - - Result res = fsFsDeleteFile(fsdevGetDeviceFileSystem(devStr), path); - return res; -} - -Result fsDelDirRec(const char *_p) -{ - char devStr[16]; - char path[FS_MAX_PATH]; - if(!getDeviceFromPath(devStr, 16, _p) || ! getFilePath(path, FS_MAX_PATH, _p)) - return 1; - - return fsFsDeleteDirectoryRecursively(fsdevGetDeviceFileSystem(devStr), path); -} - -bool fsfcreate(const char *_p, int64_t crSize) -{ - char devStr[16]; - char filePath[FS_MAX_PATH]; - if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(filePath, FS_MAX_PATH, _p)) - return false; - - FsFileSystem *s = fsdevGetDeviceFileSystem(devStr); - if(s == NULL) - return false; - - Result res = fsFsCreateFile(s, filePath, crSize, 0); - if(R_SUCCEEDED(res)) - res = fsdevCommitDevice(devStr); - - return R_SUCCEEDED(res) ? true : false; -} - -FSFILE *fsfopen(const char *_p, uint32_t mode) -{ - char devStr[16]; - char filePath[FS_MAX_PATH]; - if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(filePath, FS_MAX_PATH, _p)) - return NULL; - - FsFileSystem *s = fsdevGetDeviceFileSystem(devStr); - if(s == NULL) - return NULL; - - if(mode == FsOpenMode_Write) - { - fsFsDeleteFile(s, filePath); - fsFsCreateFile(s, filePath, 0, 0); - } - - FSFILE *ret = malloc(sizeof(FSFILE)); - ret->error = fsFsOpenFile(s, filePath, mode, &ret->_f); - if(R_FAILED(ret->error)) - { - free(ret); - return NULL; - } - fsFileGetSize(&ret->_f, &ret->fsize); - ret->offset = (mode & FsOpenMode_Append) ? ret->fsize : 0; - - return ret; -} - -FSFILE *fsfopenWithSystem(FsFileSystem *_s, const char *_p, uint32_t mode) -{ - if(mode & FsOpenMode_Write) - { - fsFsDeleteFile(_s, _p); - fsFsCreateFile(_s, _p, 0, 0); - } - else if(mode & FsOpenMode_Append) - fsFsCreateFile(_s, _p, 0, 0); - - FSFILE *ret = malloc(sizeof(FSFILE)); - ret->error = fsFsOpenFile(_s, _p, mode, &ret->_f); - if(R_FAILED(ret->error)) - { - free(ret); - return NULL; - } - fsFileGetSize(&ret->_f, &ret->fsize); - ret->offset = (mode & FsOpenMode_Append) ? ret->fsize : 0; - - return ret; -} - -size_t fsfwrite(const void *buf, size_t sz, size_t count, FSFILE *_f) -{ - size_t fullSize = sz * count; - if(_f->offset + fullSize > _f->fsize) - { - s64 newSize = (_f->fsize + fullSize) - (_f->fsize - _f->offset); - fsFileSetSize(&_f->_f, newSize); - _f->fsize = newSize; - } - _f->error = fsFileWrite(&_f->_f, _f->offset, buf, fullSize, FsWriteOption_Flush); - _f->offset += fullSize; - - return fullSize; -} diff --git a/src/fs/remote.cpp b/src/fs/remote.cpp deleted file mode 100644 index 1e0bef2..0000000 --- a/src/fs/remote.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#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)); -} \ No newline at end of file diff --git a/src/fs/zip.cpp b/src/fs/zip.cpp deleted file mode 100644 index 069aa9b..0000000 --- a/src/fs/zip.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include -#include -#include -#include -#include - -#include "fs.h" -#include "util.h" -#include "cfg.h" - -typedef struct -{ - std::mutex buffLock; - std::condition_variable cond; - std::vector 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 localBuffer; - unsigned int written = 0, journalCount = 0; - - FILE *out = fopen(in->dst.c_str(), "wb"); - while(written < in->fileSize) - { - std::unique_lock 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 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 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; -} diff --git a/src/gd.cpp b/src/gd.cpp deleted file mode 100644 index b6f9c86..0000000 --- a/src/gd.cpp +++ /dev/null @@ -1,646 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#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& _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 drive::gd::getListWithParent(const std::string& _parent) { - std::vector 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 *headers = new std::vector; - 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 *headers = new std::vector; - 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 ""; -} \ No newline at end of file diff --git a/src/gfx.cpp b/src/gfx.cpp deleted file mode 100644 index 7b2f6df..0000000 --- a/src/gfx.cpp +++ /dev/null @@ -1,437 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include FT_FREETYPE_H - -#define VA_SIZE 1024 - -#include "gfx.h" -#include "file.h" - -static SDL_Window *wind; -SDL_Renderer *gfx::render; -gfx::textureMgr *gfx::texMgr; - -static FT_Library lib; -static FT_Face face[6]; -static int totalFonts = 0; -static bool loaded = false; - -static const SDL_Color *textcol; -static SDL_Color red = {0xFF, 0x00, 0x00, 0xFF}; -static SDL_Color green = {0x00, 0xFF, 0x00, 0xFF}; -static SDL_Color blue = {0x00, 0x99, 0xEE, 0xFF}; -static SDL_Color yellow = {0xF8, 0xFC, 0x00, 0xFF}; - -static const uint32_t redMask = 0xFF000000; -static const uint32_t greenMask = 0x00FF0000; -static const uint32_t blueMask = 0x0000FF00; -static const uint32_t alphaMask = 0x000000FF; -static const uint32_t breakPoints[7] = {' ', L' ', '/', '_', '-', L'。', L'、'}; - -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); -} - -//Cache glyph textures -typedef struct -{ - uint16_t w, h; - int advX, top, left; - SDL_Texture *tex; -} glyphData; - -//, tex -std::map, glyphData> glyphCache; - -static bool loadSystemFont() -{ - PlFontData shared[6]; - uint64_t langCode = 0; - - if(R_FAILED(plInitialize(PlServiceType_User))) - return false; - - if(FT_Init_FreeType(&lib)) - return false; - - if(R_FAILED(setGetLanguageCode(&langCode))) - return false; - - - if(R_FAILED(plGetSharedFont(langCode, shared, 6, &totalFonts))) - return false; - - for(int i = 0; i < totalFonts; i++) - { - if(FT_New_Memory_Face(lib, (FT_Byte *)shared[i].address, shared[i].size, 0, &face[i])) - return false; - } - - loaded = true; - - return true; -} - -static void freeSystemFont() -{ - if(loaded) - { - for(int i = 0; i < totalFonts; i++) - FT_Done_Face(face[i]); - - FT_Done_FreeType(lib); - } - - plExit(); -} - -void gfx::init() -{ - SDL_Init(SDL_INIT_VIDEO); - IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG); - - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); - SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); - - wind = SDL_CreateWindow("JKSV", 0, 0, 1280, 720, SDL_WINDOW_SHOWN); - render = SDL_CreateRenderer(wind, -1, SDL_RENDERER_ACCELERATED); - - SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_BLEND); - - 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(); -} - -void gfx::present() -{ - SDL_RenderPresent(render); -} - -static inline void resizeFont(int sz) -{ - for(int i = 0; i < totalFonts; i++) - FT_Set_Char_Size(face[i], 0, sz * 64, 90, 90); -} - -static inline FT_GlyphSlot loadGlyph(const uint32_t c, FT_Int32 flags) -{ - for(int i = 0; i < totalFonts; i++) - { - FT_UInt cInd = 0; - if( (cInd = FT_Get_Char_Index(face[i], c)) != 0 && FT_Load_Glyph(face[i], cInd, flags) == 0) - return face[i]->glyph; - } - return NULL; -} - -static glyphData *getGlyph(uint32_t chr, int size) -{ - //If it's already been loaded and rendered, grab the texture - if(glyphCache.find(std::make_pair(chr, size)) != glyphCache.end()) - return &glyphCache[std::make_pair(chr, size)]; - - //Load glyph with Freetype - FT_GlyphSlot glyph = loadGlyph(chr, FT_LOAD_RENDER); - FT_Bitmap bmp = glyph->bitmap; - if(bmp.pixel_mode != FT_PIXEL_MODE_GRAY) - return NULL; - - //Convert to SDL_Surface -> Texture - SDL_Texture *tex; - size_t glyphSize = bmp.rows * bmp.width; - uint8_t *bmpPtr = bmp.buffer; - uint32_t basePixel = 0xFFFFFF00; - uint32_t *tmpBuff = (uint32_t *)malloc(sizeof(uint32_t) * glyphSize); - - //Loop through and fill out buffer - for(size_t i = 0; i < glyphSize; i++) - tmpBuff[i] = basePixel | *bmpPtr++; - - SDL_Surface *tmpSurf = SDL_CreateRGBSurfaceFrom(tmpBuff, bmp.width, bmp.rows, 32, 4 * bmp.width, redMask, greenMask, blueMask, alphaMask); - tex = SDL_CreateTextureFromSurface(gfx::render, tmpSurf); - - 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}; - - return &glyphCache[std::make_pair(chr, size)]; -} - -//Takes care of special characters/color switching -static inline bool specialChar(const uint32_t *p, const int *fontSize, const SDL_Color *c, const int *baseX, int *modX, int *modY) -{ - //set to false on default - bool ret = true; - - switch(*p) - { - case '\n': - *modX = *baseX; - *modY += *fontSize + 8; - break; - - case '#': - if(compClr(textcol, &blue)) - textcol = c; - else - textcol = &blue; - break; - - case '*': - if(compClr(textcol, &red)) - textcol = c; - else - textcol = &red; - break; - - case '<': - if(compClr(textcol, &yellow)) - textcol = c; - else - textcol = &yellow; - break; - - case '>': - if(compClr(textcol, &green)) - textcol = c; - else - textcol = &green; - break; - - default: - ret = false;//no special char - break; - } - return ret; -} - -void gfx::drawTextf(SDL_Texture *target, int fontSize, int x, int y, const SDL_Color *c, const char *fmt, ...) -{ - SDL_SetRenderTarget(gfx::render, target); - char tmp[VA_SIZE]; - va_list args; - va_start(args, fmt); - vsprintf(tmp, fmt, args); - va_end(args); - - int tmpX = x; - uint32_t point = 0; - ssize_t unitCnt = 0; - size_t textLength = strlen(tmp); - - resizeFont(fontSize); - - textcol = c; - - for(unsigned i = 0; i < textLength; ) - { - unitCnt = decode_utf8(&point, (const uint8_t *)&tmp[i]); - if(unitCnt <= 0) - break; - - i += unitCnt; - if(specialChar(&point, &fontSize, c, &x, &tmpX, &y)) - continue; - - glyphData *g = getGlyph(point, fontSize); - if(g != NULL) - { - SDL_Rect src = {0, 0, g->w, g->h}; - SDL_Rect dst = {tmpX + g->left, y + (fontSize - g->top), g->w, g->h}; - SDL_SetTextureColorMod(g->tex, textcol->r, textcol->g, textcol->b); - SDL_RenderCopy(render, g->tex, &src, &dst); - - tmpX += g->advX; - } - } - SDL_SetRenderTarget(gfx::render, NULL); -} - -inline bool isBreakChar(uint32_t point) -{ - for(int i = 0; i < 7; i++) - { - if(breakPoints[i] == point) - return true; - } - return false; -} - -inline size_t findNextBreak(const char *str) -{ - size_t length = strlen(str); - for(size_t i = 0; i < length; ) - { - uint32_t nextPoint = 0; - ssize_t unitCnt = decode_utf8(&nextPoint, (const uint8_t *)&str[i]); - i += unitCnt; - if(unitCnt <= 0) - return length; - - if(isBreakChar(nextPoint)) - return i; - } - return length; -} - -void gfx::drawTextfWrap(SDL_Texture *target, int fontSize, int x, int y, int maxWidth, const SDL_Color *c, const char *fmt, ...) -{ - SDL_SetRenderTarget(gfx::render, target); - char tmp[VA_SIZE], wordBuff[128]; - va_list args; - va_start(args, fmt); - vsprintf(tmp, fmt, args); - va_end(args); - - resizeFont(fontSize); - size_t nextBreak = 0, strlength = strlen(tmp); - int tmpX = x; - - textcol = c; - - for(unsigned i = 0; i < strlength; ) - { - nextBreak = findNextBreak(&tmp[i]); - memset(wordBuff, 0, 128); - memcpy(wordBuff, &tmp[i], nextBreak); - - size_t width = gfx::getTextWidth(wordBuff, fontSize); - - if((int)(tmpX + width) >= (int)(x + maxWidth)) - { - tmpX = x; - y += fontSize + 8; - } - - size_t wordLength = strlen(wordBuff); - uint32_t point = 0; - for(unsigned j = 0; j < wordLength; ) - { - ssize_t unitCnt = decode_utf8(&point, (const uint8_t *)&wordBuff[j]); - if(unitCnt <= 0) - break; - - j += unitCnt; - - if(specialChar(&point, &fontSize, c, &x, &tmpX, &y)) - continue; - - glyphData *g = getGlyph(point, fontSize); - if(g != NULL) - { - SDL_Rect src = {0, 0, g->w, g->h}; - SDL_Rect dst = {tmpX + g->left, y + (fontSize - g->top), g->w, g->h}; - SDL_SetTextureColorMod(g->tex, textcol->r, textcol->g, textcol->b); - SDL_RenderCopy(render, g->tex, &src, &dst); - - tmpX += g->advX; - } - } - i += wordLength; - } - SDL_SetRenderTarget(gfx::render, NULL); -} - -size_t gfx::getTextWidth(const char *str, int fontSize) -{ - resizeFont(fontSize); - size_t width = 0, strlength = strlen(str); - uint32_t unitCnt = 0, point = 0; - - for(unsigned i = 0; i < strlength; ) - { - unitCnt = decode_utf8(&point, (const uint8_t *)&str[i]); - if(unitCnt <= 0) - break; - - i += unitCnt; - - //Ignore these - if(point == '\n' || point == '#' || point == '*' || point == '<' || point == '>') - continue; - - glyphData *g = getGlyph(point, fontSize); - if(g != NULL) - width += g->advX; - } - return width; -} - -void gfx::texDraw(SDL_Texture *target, SDL_Texture *tex, int x, int y) -{ - int tW = 0, tH = 0; - if(SDL_QueryTexture(tex, NULL, NULL, &tW, &tH) == 0) - { - SDL_SetRenderTarget(gfx::render, target); - SDL_Rect src = {0, 0, tW, tH}; - SDL_Rect pos = {x, y, tW, tH}; - SDL_RenderCopy(gfx::render, tex, &src, &pos); - } -} - -void gfx::texDrawStretch(SDL_Texture *target, SDL_Texture *tex, int x, int y, int w, int h) -{ - int tW = 0, tH = 0; - if(SDL_QueryTexture(tex, NULL, NULL, &tW, &tH) == 0) - { - SDL_SetRenderTarget(gfx::render, target); - SDL_Rect src = {0, 0, tW, tH}; - SDL_Rect pos = {x, y, w, h}; - SDL_RenderCopy(gfx::render, tex, &src, &pos); - } -} - -void gfx::texDrawPart(SDL_Texture *target, SDL_Texture *tex, int srcX, int srcY, int srcW, int srcH, int dstX, int dstY) -{ - SDL_Rect src = {srcX, srcY, srcW, srcH}; - SDL_Rect dst = {dstX, dstY, srcW, srcH}; - SDL_SetRenderTarget(gfx::render, target); - SDL_RenderCopy(gfx::render, tex, &src, &dst); -} - -void gfx::drawLine(SDL_Texture *target, const SDL_Color *c, int x1, int y1, int x2, int y2) -{ - SDL_SetRenderTarget(gfx::render, target); - SDL_SetRenderDrawColor(gfx::render, c->r, c->g, c->b, c->a); - SDL_RenderDrawLine(gfx::render, x1, y1, x2, y2); -} - -void gfx::drawRect(SDL_Texture *target, const SDL_Color *c, int x, int y, int w, int h) -{ - SDL_SetRenderTarget(gfx::render, target); - SDL_SetRenderDrawColor(gfx::render, c->r, c->g, c->b, c->a); - SDL_Rect rect = {x, y, w, h}; - SDL_RenderFillRect(gfx::render, &rect); -} - -void gfx::clearTarget(SDL_Texture *target, const SDL_Color *clear) -{ - SDL_SetRenderTarget(gfx::render, target); - SDL_SetRenderDrawColor(gfx::render, clear->r, clear->g, clear->b, clear->a); - SDL_RenderClear(gfx::render); -} diff --git a/src/gfx/textureMgr.cpp b/src/gfx/textureMgr.cpp deleted file mode 100644 index 97e498c..0000000 --- a/src/gfx/textureMgr.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include -#include -#include -#include - -#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; - } -} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index b64f252..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include - -#include "gfx.h" -#include "file.h" -#include "data.h" -#include "ui.h" -#include "util.h" -#include "cfg.h" - -extern "C" -{ - void userAppInit(void) - { - appletInitialize(); - hidInitialize(); - nsInitialize(); - setsysInitialize(); - setInitialize(); - accountInitialize(AccountServiceType_Administrator); - pmshellInitialize(); - socketInitializeDefault(); - pdmqryInitialize(); - } - - void userAppExit(void) - { - appletExit(); - hidExit(); - nsExit(); - setsysExit(); - setExit(); - accountExit(); - pmshellExit(); - socketExit(); - pdmqryExit(); - } -} - -int main(int argc, const char *argv[]) -{ - romfsInit(); - cfg::resetConfig(); - cfg::loadConfig(); - fs::init(); - gfx::init(); - ui::initTheme(); - ui::showLoadScreen(); - data::init(); - 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()){ } - - fs::remoteExit(); - curl_global_cleanup(); - ui::exit(); - data::exit(); - gfx::exit(); -} diff --git a/src/rfs.cpp b/src/rfs.cpp deleted file mode 100644 index 6c6c1e5..0000000 --- a/src/rfs.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "rfs.h" - -std::vector rfs::downloadBuffer; - -void rfs::writeThread_t(void *a) -{ - rfs::dlWriteThreadStruct *in = (rfs::dlWriteThreadStruct *)a; - std::vector localBuff; - unsigned written = 0; - - FILE *out = fopen(in->cfa->path.c_str(), "wb"); - - while(written < in->cfa->size) - { - std::unique_lock 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 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; -} diff --git a/src/type.cpp b/src/type.cpp deleted file mode 100644 index 15325e6..0000000 --- a/src/type.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include - -#include "type.h" - -void threadStatus::setStatus(const char *fmt, ...) -{ - char tmp[1024]; - va_list args; - va_start(args, fmt); - vsprintf(tmp, fmt, args); - va_end(args); - - mutexLock(&statusLock); - status = tmp; - mutexUnlock(&statusLock); -} - -void threadStatus::getStatus(std::string& statusOut) -{ - mutexLock(&statusLock); - statusOut = status; - mutexUnlock(&statusLock); -} diff --git a/src/ui.cpp b/src/ui.cpp deleted file mode 100644 index 4a34d79..0000000 --- a/src/ui.cpp +++ /dev/null @@ -1,308 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "file.h" -#include "ui.h" -#include "gfx.h" -#include "util.h" - -//Current menu state -int ui::mstate = USR_SEL, ui::prevState = USR_SEL; - -float ui::animScale = 3.0f; - -//pad data? -PadState ui::pad; -HidTouchScreenState ui::touchState; - -//Theme id -ColorSetId ui::thmID; - -//Info printed on folder menu -std::string ui::folderMenuInfo; - -//UI colors -SDL_Color ui::clearClr, ui::txtCont, ui::txtDiag, ui::rectLt, ui::rectSh, ui::tboxClr, ui::divClr, ui::slidePanelColor; -SDL_Color ui::transparent = {0x00, 0x00, 0x00, 0x00}; - -//textbox pieces -//I was going to flip them when I draw them, but then laziness kicked in. -SDL_Texture *ui::cornerTopLeft, *ui::cornerTopRight, *ui::cornerBottomLeft, *ui::cornerBottomRight; - -//Progress bar covers + dialog box predrawn -SDL_Texture *ui::progCovLeft, *ui::progCovRight, *ui::diaBox; - -//Menu box pieces -SDL_Texture *ui::mnuTopLeft, *ui::mnuTopRight, *ui::mnuBotLeft, *ui::mnuBotRight; - -//Select box + top left icon -SDL_Texture *ui::sideBar; - -static SDL_Texture *icn, *corePanel; -SDL_Color ui::heartColor = {0xFF, 0x44, 0x44, 0xFF}; - -static int settPos, extPos; - -//Vector of pointers to slideOutPanels. Is looped and drawn last so they are always on top -std::vector panels; - -static ui::popMessageMngr *popMessages; -static ui::threadProcMngr *threadMngr; - -//8 -const std::string ui::loadGlyphArray[] = -{ - "\ue020", "\ue021", "\ue022", "\ue023", - "\ue024", "\ue025", "\ue026", "\ue027" -}; - -void ui::initTheme() -{ - uint64_t lang; - setGetSystemLanguage(&lang); - setMakeLanguage(lang, &data::sysLang); - - loadTrans(); - - setsysGetColorSetId(&thmID); - - switch(thmID) - { - case ColorSetId_Light: - clearClr = {0xEB, 0xEB, 0xEB, 0xFF}; - txtCont = {0x00, 0x00, 0x00, 0xFF}; - txtDiag = {0xFF, 0xFF, 0xFF, 0xFF}; - rectLt = {0xDF, 0xDF, 0xDF, 0xFF}; - rectSh = {0xCA, 0xCA, 0xCA, 0xFF}; - tboxClr = {0xEB, 0xEB, 0xEB, 0xFF}; - divClr = {0x00, 0x00, 0x00, 0xFF}; - slidePanelColor = {0xEE, 0xEE, 0xEE, 0xEE}; - break; - - default: - case ColorSetId_Dark: - //jic - thmID = ColorSetId_Dark; - clearClr = {0x2D, 0x2D, 0x2D, 0xFF}; - txtCont = {0xFF, 0xFF, 0xFF, 0xFF}; - txtDiag = {0x00, 0x00, 0x00, 0xFF}; - rectLt = {0x50, 0x50, 0x50, 0xFF}; - rectSh = {0x20, 0x20, 0x20, 0xFF}; - tboxClr = {0x50, 0x50, 0x50, 0xFF}; - divClr = {0xFF, 0xFF, 0xFF, 0xFF}; - slidePanelColor = {0x00, 0x00, 0x00, 0xEE}; - break; - } -} - -void ui::init() -{ - 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); - - switch(ui::thmID) - { - case ColorSetId_Light: - //Dark corners - 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::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; - } - - padConfigureInput(1, HidNpadStyleSet_NpadStandard); - padInitializeDefault(&ui::pad); - - 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(ui::getUICString("mainMenuSettings", 0)); - extPos = ui::usrMenu->getOptPos(ui::getUICString("mainMenuExtras", 0)); -} - -void ui::exit() -{ - ui::usrExit(); - ui::ttlExit(); - ui::settExit(); - ui::extExit(); - ui::fmExit(); - ui::fldExit(); - - delete popMessages; - delete threadMngr; -} - -int ui::registerPanel(slideOutPanel *sop) -{ - panels.push_back(sop); - return panels.size() - 1; -} - -threadInfo *ui::newThread(ThreadFunc func, void *args, funcPtr _drawFunc) -{ - return threadMngr->newThread(func, args, _drawFunc); -} - -void ui::showLoadScreen() -{ - 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, ui::getUICString("loadingStartPage", 0)); - gfx::present(); -} - -void ui::drawUI() -{ - gfx::clearTarget(NULL, &ui::clearClr); - gfx::clearTarget(corePanel, &transparent); - - gfx::drawLine(NULL, &divClr, 30, 88, 1250, 88); - gfx::drawLine(NULL, &divClr, 30, 648, 1250, 648); - gfx::texDraw(NULL, icn, 66, 27); - - 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, "%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); - - if((ui::usrMenu->getActive() && ui::usrMenu->getSelected() == settPos) || ui::mstate == OPT_MNU) - ui::settDraw(corePanel); - else if((ui::usrMenu->getActive() && ui::usrMenu->getSelected() == extPos) || ui::mstate == EX_MNU) - ui::extDraw(corePanel); - else if(ui::mstate == FIL_MDE) - ui::fmDraw(corePanel); - else - ui::ttlDraw(corePanel); - - gfx::texDraw(NULL, corePanel, 30, 89); - for(slideOutPanel *s : panels) - s->draw(&ui::slidePanelColor); - - threadMngr->draw(); - - popMessages->draw(); -} - -static bool debugDisp = false; - -bool ui::runApp() -{ - ui::updateInput(); - uint64_t down = ui::padKeysDown(); - - if(threadMngr->empty()) - { - if(down & HidNpadButton_StickL && down & HidNpadButton_StickR) - debugDisp = true; - else if(down & HidNpadButton_Plus) - return false; - - switch(ui::mstate) - { - case USR_SEL: - ui::usrUpdate(); - break; - - case TTL_SEL: - ui::ttlUpdate(); - break; - - case OPT_MNU: - ui::settUpdate(); - break; - - case EX_MNU: - ui::extUpdate(); - break; - - case FIL_MDE: - ui::fmUpdate(); - break; - } - } - else - threadMngr->update(); - - popMessages->update(); - - drawUI(); - if(debugDisp) - data::dispStats(); - - gfx::present(); - - return true; -} - -void ui::showPopMessage(int frameCount, const char *fmt, ...) -{ - char tmp[256]; - va_list args; - va_start(args, fmt); - vsprintf(tmp, fmt, args); - va_end(args); - - popMessages->popMessageAdd(tmp, frameCount); -} - -void ui::toTTL(void *a) -{ - data::user *u = data::getCurrentUser(); - unsigned curUserIndex = data::getCurrentUserIndex(); - if(u->titleInfo.size() > 0) - { - ui::changeState(TTL_SEL); - ui::ttlSetActive(curUserIndex, true, true); - ui::usrMenu->setActive(false); - } - else - { - data::user *u = data::getCurrentUser(); - ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataNoneFound", 0), u->getUsername().c_str()); - } -} diff --git a/src/ui/ext.cpp b/src/ui/ext.cpp deleted file mode 100644 index 3e9a28c..0000000 --- a/src/ui/ext.cpp +++ /dev/null @@ -1,233 +0,0 @@ -#include -#include - -#include "ui.h" -#include "file.h" -#include "util.h" -#include "cfg.h" - -ui::menu *ui::extMenu; - -static void extMenuCallback(void *a) -{ - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - ui::extMenu->setActive(false); - ui::usrMenu->setActive(true); - ui::changeState(USR_SEL); - break; - } -} - -static inline void closeUserPanel() -{ - ui::usrMenu->setActive(false); - ui::usrSelPanel->closePanel(); -} - -static void toFMSDtoSD(void *a) -{ - ui::fmPrep(FsSaveDataType_Account, "sdmc:/", "sdmc:/", false); - closeUserPanel(); - ui::changeState(FIL_MDE); -} - -static void toFMProdInfoF(void *a) -{ - FsFileSystem prodf; - fsOpenBisFileSystem(&prodf, FsBisPartitionId_CalibrationFile, ""); - fsdevMountDevice("prod-f", prodf); - closeUserPanel(); - ui::fmPrep(FsSaveDataType_System, "prod-f:/", "sdmc:/", false); - ui::changeState(FIL_MDE); -} - -static void toFMSafe(void *a) -{ - FsFileSystem safe; - fsOpenBisFileSystem(&safe, FsBisPartitionId_SafeMode, ""); - fsdevMountDevice("safe", safe); - closeUserPanel(); - ui::fmPrep(FsSaveDataType_System, "safe:/", "sdmc:/", false); - ui::changeState(FIL_MDE); -} - -static void toFMSystem(void *a) -{ - FsFileSystem sys; - fsOpenBisFileSystem(&sys, FsBisPartitionId_System, ""); - fsdevMountDevice("sys", sys); - closeUserPanel(); - ui::fmPrep(FsSaveDataType_System, "sys:/", "sdmc:/", false); - ui::changeState(FIL_MDE); -} - -static void toFMUser(void *a) -{ - FsFileSystem user; - fsOpenBisFileSystem(&user, FsBisPartitionId_User, ""); - fsdevMountDevice("user", user); - closeUserPanel(); - ui::fmPrep(FsSaveDataType_System, "user:/", "sdmc:/", false); - ui::changeState(FIL_MDE); -} - -static void _delUpdate(void *a) -{ - threadInfo *t = (threadInfo *)a; - t->status->setStatus(ui::getUICString("threadStatusDeletingUpdate", 0)); - FsFileSystem sys; - fsOpenBisFileSystem(&sys, FsBisPartitionId_System, ""); - fsdevMountDevice("sys", sys); - fs::delDir("sys:/Contents/placehld/"); - fsdevUnmountDevice("sys"); - t->finished = true; -} - -static void extMenuOptRemoveUpdate(void *a) -{ - ui::newThread(_delUpdate, NULL, NULL); -} - -static void extMenuTerminateProcess(void *a) -{ - std::string idStr = util::getStringInput(SwkbdType_QWERTY, "0100000000000000", ui::getUIString("swkbdProcessID", 0), 18, 0, NULL); - if(!idStr.empty()) - { - uint64_t termID = std::strtoull(idStr.c_str(), NULL, 16); - if(R_SUCCEEDED(pmshellTerminateProgram(termID))) - ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popProcessShutdown", 0), idStr.c_str()); - } -} - -static void extMenuMountSysSave(void *a) -{ - FsFileSystem sys; - std::string idStr = util::getStringInput(SwkbdType_QWERTY, "8000000000000000", ui::getUIString("swkbdSysSavID", 0), 18, 0, NULL); - uint64_t mountID = std::strtoull(idStr.c_str(), NULL, 16); - if(R_SUCCEEDED(fsOpen_SystemSaveData(&sys, FsSaveDataSpaceId_System, mountID, (AccountUid) {0}))) - { - fsdevMountDevice("sv", sys); - ui::fmPrep(FsSaveDataType_System, "sv:/", "sdmc:/", true); - ui::usrSelPanel->closePanel(); - ui::changeState(FIL_MDE); - } -} - -//Todo: Not so simple now. -static void extMenuReloadTitles(void *a) -{ - data::loadUsersTitles(false); - ui::usrRefresh(); - ui::ttlRefresh(); -} - -static void extMenuMountRomFS(void *a) -{ - FsFileSystem tromfs; - if(R_SUCCEEDED(fsOpenDataFileSystemByCurrentProcess(&tromfs))) - { - fsdevMountDevice("tromfs", tromfs); - ui::fmPrep(FsSaveDataType_System, "tromfs:/", "sdmc:/", false); - ui::usrSelPanel->closePanel(); - ui::changeState(FIL_MDE); - } -} - -static void extMenuPackJKSVZip_t(void *a) -{ - threadInfo *t = (threadInfo *)a; - fs::copyArgs *c = (fs::copyArgs *)t->argPtr; - t->status->setStatus(ui::getUICString("threadStatusPackingJKSV", 0)); - if(cfg::config["ovrClk"]) - { - 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()); - size_t dirCount = jksv->getCount(); - if(dirCount > 0) - { - std::string zipPath = fs::getWorkDir() + "JKSV - " + util::getDateTime(util::DATE_FMT_ASC) + ".zip"; - zipFile zip = zipOpen64(zipPath.c_str(), 0); - for(unsigned i = 0; i < jksv->getCount(); i++) - { - if(jksv->isDir(i) && jksv->getItem(i) != "_TRASH_") - { - std::string srcPath = fs::getWorkDir() + jksv->getItem(i) + "/"; - fs::copyDirToZip(srcPath, zip, true, pathTrimPlaces, t); - } - } - zipClose(zip, NULL); - } - if(cfg::config["ovrClk"]) - util::sysNormal(); - - delete jksv; - delete c; - t->finished = true; -} - -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); -} - -static void extMenuOutputEnUs(void *a) -{ - ui::newThread(ui::saveTranslationFiles, NULL, NULL); -} - -void ui::extInit() -{ - ui::extMenu = new ui::menu(200, 24, 1002, 24, 4); - ui::extMenu->setCallback(extMenuCallback, NULL); - ui::extMenu->setActive(false); - for(unsigned i = 0; i < 12; i++) - ui::extMenu->addOpt(NULL, ui::getUIString("extrasMenu", i)); - - //SD to SD - ui::extMenu->optAddButtonEvent(0, HidNpadButton_A, toFMSDtoSD, NULL); - //Prodinfo-F - ui::extMenu->optAddButtonEvent(1, HidNpadButton_A, toFMProdInfoF, NULL); - //Safe - ui::extMenu->optAddButtonEvent(2, HidNpadButton_A, toFMSafe, NULL); - //System - ui::extMenu->optAddButtonEvent(3, HidNpadButton_A, toFMSystem, NULL); - //User - ui::extMenu->optAddButtonEvent(4, HidNpadButton_A, toFMUser, NULL); - //Del update - ui::extMenu->optAddButtonEvent(5, HidNpadButton_A, extMenuOptRemoveUpdate, NULL); - //Terminate Process - ui::extMenu->optAddButtonEvent(6, HidNpadButton_A, extMenuTerminateProcess, NULL); - //Mount system save - ui::extMenu->optAddButtonEvent(7, HidNpadButton_A, extMenuMountSysSave, NULL); - //Rescan - ui::extMenu->optAddButtonEvent(8, HidNpadButton_A, extMenuReloadTitles, NULL); - //RomFS - ui::extMenu->optAddButtonEvent(9, HidNpadButton_A, extMenuMountRomFS, NULL); - //Backup JKSV - ui::extMenu->optAddButtonEvent(10, HidNpadButton_A, extMenuPackJKSV, NULL); - //Translation so I can be lazy - ui::extMenu->optAddButtonEvent(11, HidNpadButton_A, extMenuOutputEnUs, NULL); -} - -void ui::extExit() -{ - delete ui::extMenu; -} - -void ui::extUpdate() -{ - ui::extMenu->update(); -} - -void ui::extDraw(SDL_Texture *target) -{ - ui::extMenu->draw(target, &ui::txtCont, true); -} diff --git a/src/ui/fld.cpp b/src/ui/fld.cpp deleted file mode 100644 index efc111e..0000000 --- a/src/ui/fld.cpp +++ /dev/null @@ -1,391 +0,0 @@ -#include - -#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 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); -} \ No newline at end of file diff --git a/src/ui/fm.cpp b/src/ui/fm.cpp deleted file mode 100644 index 955ba07..0000000 --- a/src/ui/fm.cpp +++ /dev/null @@ -1,591 +0,0 @@ -#include - -#include "ui.h" -#include "file.h" -#include "util.h" -#include "fm.h" -#include "cfg.h" -#include "type.h" - -//This is going to be a mess but the old one was too. -//It gets more complicated because of system saves and committing. - -//Major Todo: have thread status update -//Todo: Not have menus completely reset. - -//Struct to hold info so i don't have to if else if if if -typedef struct -{ - std::string *path; - ui::menu *m; - fs::dirList *d; -} menuFuncArgs; - -static ui::slideOutPanel *devPanel, *sdPanel; -static ui::menu *devMenu, *sdMenu, *devCopyMenu, *sdCopyMenu; -static fs::dirList *devList, *sdList; -static std::string devPath, sdPath, dev; -static menuFuncArgs *devArgs, *sdmcArgs; -static FsSaveDataType type; -static bool commit = false; - -//Declarations, implementations down further -static void _listFunctionA(void *a); - -/*General stuff*/ -static void refreshMenu(void *a) -{ - threadInfo *t = (threadInfo *)a; - menuFuncArgs *ma = (menuFuncArgs *)t->argPtr; - ui::menu *m = ma->m; - fs::dirList *d = ma->d; - - d->reassign(*ma->path); - util::copyDirListToMenu(*d, *m); - for(int i = 1; i < m->getCount(); i++) - { - m->optAddButtonEvent(i, HidNpadButton_A, _listFunctionA, ma); - } - t->finished = true; -} - -/*Callbacks and menu functions*/ -static void _devMenuCallback(void *a) -{ - menuFuncArgs *ma = (menuFuncArgs *)a; - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - if(*ma->path != dev && *ma->path != "sdmc:/") - { - util::removeLastFolderFromString(*ma->path); - threadInfo fake; - fake.argPtr = ma; - refreshMenu(&fake); - } - break; - - case HidNpadButton_X: - devMenu->setActive(false); - devCopyMenu->setActive(true); - devPanel->openPanel(); - break; - } -} - -static void _devCopyMenuCallback(void *a) -{ - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - devCopyMenu->setActive(false); - devMenu->setActive(true); - devPanel->closePanel(); - break; - } -} - -static void _sdMenuCallback(void *a) -{ - menuFuncArgs *ma = (menuFuncArgs *)a; - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - if(*ma->path != dev && *ma->path != "sdmc:/") - { - util::removeLastFolderFromString(*ma->path); - threadInfo fake; - fake.argPtr = ma; - refreshMenu(&fake); - } - break; - - case HidNpadButton_X: - sdMenu->setActive(false); - sdCopyMenu->setActive(true); - sdPanel->openPanel(); - break; - } -} - -static void _sdCopyMenuCallback(void *a) -{ - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - sdCopyMenu->setActive(false); - sdMenu->setActive(true); - sdPanel->closePanel(); - break; - } -} - -//These draw to the panel texture passed -static void _devCopyPanelDraw(void *a) -{ - SDL_Texture *target = (SDL_Texture *)a; - devCopyMenu->draw(target, &ui::txtCont, true); -} - -static void _sdCopyPanelDraw(void *a) -{ - SDL_Texture *target = (SDL_Texture *)a; - sdCopyMenu->draw(target, &ui::txtCont, true); -} - -static void _listFunctionA(void *a) -{ - menuFuncArgs *ma = (menuFuncArgs *)a; - ui::menu *m = ma->m; - fs::dirList *d = ma->d; - - int sel = m->getSelected(); - bool isDir = d->isDir(sel - 2); - if(sel == 1 && (*ma->path != dev && *ma->path != "sdmc:/")) - { - util::removeLastFolderFromString(*ma->path); - d->reassign(*ma->path); - util::copyDirListToMenu(*d, *m); - } - else if(sel > 1 && isDir) - { - std::string addToPath = d->getItem(sel - 2); - *ma->path += addToPath + "/"; - d->reassign(*ma->path); - util::copyDirListToMenu(*d, *m); - } - - 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; - ui::menu *m = ma->m; - fs::dirList *d = ma->d; - - int sel = m->getSelected(); - if(ma == devArgs) - { - if(sel == 0) - fs::copyDirToDirThreaded(*ma->path, *sdmcArgs->path); - else if(sel > 1 && d->isDir(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::copyDirToDirThreaded(srcPath, dstPath); - } - else if(sel > 1) - { - 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) - { - //I know - if(sel == 0) - { - if(commit) - fs::copyDirToDirCommitThreaded(*ma->path, *devArgs->path, dev); - else - fs::copyDirToDirThreaded(*ma->path, *devArgs->path); - } - else if(sel > 1 && d->isDir(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::copyDirToDirCommitThreaded(srcPath, dstPath, dev); - else - fs::copyDirToDirThreaded(srcPath, dstPath); - } - else if(sel > 1) - { - 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::copyFileThreaded(srcPath, dstPath); - } - } - ui::newThread(refreshMenu, devArgs, NULL); - ui::newThread(refreshMenu, sdmcArgs, NULL); - t->finished = true; -} - -//Sets up a confirm thread that then runs ^ -static void _copyMenuCopy(void *a) -{ - menuFuncArgs *ma = (menuFuncArgs *)a; - ui::menu *m = ma->m; - fs::dirList *d = ma->d; - - std::string srcPath, dstPath; - int sel = m->getSelected(); - if(sel == 0 && ma == devArgs) - { - srcPath = *ma->path; - dstPath = *sdmcArgs->path; - } - else if(sel > 1 && ma == devArgs) - { - srcPath = *ma->path + d->getItem(sel - 2); - dstPath = *sdmcArgs->path + d->getItem(sel - 2); - } - else if(sel == 0 && ma == sdmcArgs) - { - srcPath = *ma->path; - dstPath = *devArgs->path; - } - else if(sel > 1 && ma == sdmcArgs) - { - 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"]))) - { - ui::confirmArgs *send = ui::confirmArgsCreate(false, _copyMenuCopy_t, NULL, ma, ui::getUICString("confirmCopy", 0), srcPath.c_str(), dstPath.c_str()); - ui::confirm(send); - } -} - -static void _copyMenuDelete_t(void *a) -{ - threadInfo *t = (threadInfo *)a; - menuFuncArgs *ma = (menuFuncArgs *)t->argPtr; - ui::menu *m = ma->m; - fs::dirList *d = ma->d; - - t->status->setStatus(ui::getUICString("threadStatusDeletingFile", 0)); - - int sel = m->getSelected(); - if(ma == devArgs) - { - if(sel == 0 && *ma->path != "sdmc:/") - { - fs::delDir(*ma->path); - if(commit) - fs::commitToDevice(dev); - } - else if(sel > 1 && d->isDir(sel - 2)) - { - std::string delPath = *ma->path + d->getItem(sel - 2) + "/"; - fs::delDir(delPath); - if(commit) - fs::commitToDevice(dev); - } - else if(sel > 1) - { - std::string delPath = *ma->path + d->getItem(sel - 2); - fs::delfile(delPath); - if(commit) - fs::commitToDevice(dev); - } - } - else - { - if(sel == 0 && *ma->path != "sdmc:/") - fs::delDir(*ma->path); - else if(sel > 1 && d->isDir(sel - 2)) - { - std::string delPath = *ma->path + d->getItem(sel - 2) + "/"; - fs::delDir(delPath); - } - else if(sel > 1) - { - std::string delPath = *ma->path + d->getItem(sel - 2); - fs::delfile(delPath); - } - } - ui::newThread(refreshMenu, devArgs, NULL); - ui::newThread(refreshMenu, sdmcArgs, NULL); - t->finished = true; -} - -//Prepares confirm then runs ^ -static void _copyMenuDelete(void *a) -{ - menuFuncArgs *ma = (menuFuncArgs *)a; - ui::menu *m = ma->m; - fs::dirList *d = ma->d; - - int sel = m->getSelected(); - std::string itmPath; - if(sel == 0) - itmPath = *ma->path; - 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"]))) - { - ui::confirmArgs *send = ui::confirmArgsCreate(cfg::config["holdDel"], _copyMenuDelete_t, NULL, a, ui::getUICString("confirmDelete", 0), itmPath.c_str()); - ui::confirm(send); - } -} - -static void _copyMenuRename(void *a) -{ - menuFuncArgs *ma = (menuFuncArgs *)a; - ui::menu *m = ma->m; - fs::dirList *d = ma->d; - - int sel = m->getSelected(); - if(sel > 1) - { - 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 + d->getItem(sel - 2); - std::string newPath = *ma->path + getNewName; - rename(prevPath.c_str(), newPath.c_str()); - } - threadInfo fake; - fake.argPtr = devArgs; - refreshMenu(&fake); - fake.argPtr = sdmcArgs; - refreshMenu(&fake); - } -} - -static void _copyMenuMkDir(void *a) -{ - menuFuncArgs *ma = (menuFuncArgs *)a; - 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); - } - threadInfo fake; - fake.argPtr = devArgs; - refreshMenu(&fake); - fake.argPtr = sdmcArgs; - 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; - ui::menu *m = ma->m; - fs::dirList *d = ma->d; - - int sel = m->getSelected(); - if(sel == 0) - { - std::string *folderPath = new std::string(*ma->path); - ui::newThread(_copyMenuGetShowDirProps_t, folderPath, NULL); - } - else if(sel > 1 && d->isDir(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); - } -} - -static void _copyMenuClose(void *a) -{ - menuFuncArgs *ma = (menuFuncArgs *)a; - if(ma == devArgs) - { - devCopyMenu->setActive(false); - devMenu->setActive(true); - devPanel->closePanel(); - } - else - { - sdCopyMenu->setActive(false); - sdMenu->setActive(true); - sdPanel->closePanel(); - } -} - -static void _devMenuAddToPathFilter(void *a) -{ - menuFuncArgs *ma = (menuFuncArgs *)a; - ui::menu *m = ma->m; - fs::dirList *d = ma->d; - - int sel = m->getSelected(); - if(sel > 1) - { - 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()); - } -} - -void ui::fmInit() -{ - //This needs to be done in a strange order so everything works - devArgs = new menuFuncArgs; - devArgs->path = &devPath; - - sdmcArgs = new menuFuncArgs; - sdmcArgs->path = &sdPath; - - devMenu = new ui::menu(10, 8, 590, 18, 5); - devMenu->setCallback(_devMenuCallback, devArgs); - devMenu->setActive(true); - 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++) - 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)); - devCopyMenu->addOpt(NULL, ui::getUIString("fileModeMenu", 4)); - devCopyMenu->addOpt(NULL, ui::getUIString("fileModeMenu", 5)); - - devCopyMenu->optAddButtonEvent(0, HidNpadButton_A, _copyMenuCopy, devArgs); - devCopyMenu->optAddButtonEvent(1, HidNpadButton_A, _copyMenuDelete, devArgs); - devCopyMenu->optAddButtonEvent(2, HidNpadButton_A, _copyMenuRename, devArgs); - devCopyMenu->optAddButtonEvent(3, HidNpadButton_A, _copyMenuMkDir, devArgs); - devCopyMenu->optAddButtonEvent(4, HidNpadButton_A, _devMenuAddToPathFilter, devArgs); - devCopyMenu->optAddButtonEvent(5, HidNpadButton_A, _copyMenuGetProps, devArgs); - devCopyMenu->optAddButtonEvent(6, HidNpadButton_A, _copyMenuClose, devArgs); - ui::registerPanel(devPanel); - - sdMenu = new ui::menu(620, 8, 590, 18, 5); - sdMenu->setCallback(_sdMenuCallback, sdmcArgs); - sdMenu->setActive(false); - 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++) - sdCopyMenu->addOpt(NULL, ui::getUIString("fileModeMenu", i)); - sdCopyMenu->optAddButtonEvent(0, HidNpadButton_A, _copyMenuCopy, sdmcArgs); - sdCopyMenu->optAddButtonEvent(1, HidNpadButton_A, _copyMenuDelete, sdmcArgs); - sdCopyMenu->optAddButtonEvent(2, HidNpadButton_A, _copyMenuRename, sdmcArgs); - sdCopyMenu->optAddButtonEvent(3, HidNpadButton_A, _copyMenuMkDir, sdmcArgs); - sdCopyMenu->optAddButtonEvent(4, HidNpadButton_A, _copyMenuGetProps, sdmcArgs); - sdCopyMenu->optAddButtonEvent(5, HidNpadButton_A, _copyMenuClose, sdmcArgs); - ui::registerPanel(sdPanel); - - devList = new fs::dirList; - sdList = new fs::dirList; - devArgs->d = devList; - sdmcArgs->d = sdList; -} - -void ui::fmExit() -{ - delete devMenu; - delete sdMenu; - delete devCopyMenu; - delete sdCopyMenu; - delete devList; - delete sdList; - delete devArgs; - delete sdmcArgs; -} - -void ui::fmPrep(const FsSaveDataType& _type, const std::string& _dev, const std::string& _baseSDMC, bool _commit) -{ - type = _type; - dev = _dev; - commit = _commit; - devPath = _dev; - 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++) - devMenu->optAddButtonEvent(i, HidNpadButton_A, _listFunctionA, devArgs); - - util::copyDirListToMenu(*sdList, *sdMenu); - 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()) - { - switch(ui::padKeysDown()) - { - case HidNpadButton_ZL: - case HidNpadButton_ZR: - if(devMenu->getActive()) - { - devMenu->setActive(false); - sdMenu->setActive(true); - } - else - { - devMenu->setActive(true); - sdMenu->setActive(false); - } - break; - - case HidNpadButton_Minus: - //Can't be 100% sure it's fs's sv - if(dev != "sdmc:/") - fsdevUnmountDevice(dev.c_str()); - - if(ui::prevState == EX_MNU) - { - ui::usrSelPanel->openPanel(); - ui::changeState(EX_MNU); - } - else if(ui::prevState == TTL_SEL) - { - ui::usrSelPanel->openPanel(); - ui::ttlOptsPanel->openPanel(); - ui::changeState(TTL_SEL); - } - break; - } - } - devMenu->update(); - sdMenu->update(); - devCopyMenu->update(); - sdCopyMenu->update(); -} - -void ui::fmDraw(SDL_Texture *target) -{ - devMenu->draw(target, &ui::txtCont, true); - sdMenu->draw(target, &ui::txtCont, true); - gfx::drawLine(target, &ui::divClr, 610, 0, 610, 559); - gfx::drawTextfWrap(NULL, 14, 30, 654, 600, &ui::txtCont, devPath.c_str()); - gfx::drawTextfWrap(NULL, 14, 640, 654, 600, &ui::txtCont, sdPath.c_str()); -} diff --git a/src/ui/miscui.cpp b/src/ui/miscui.cpp deleted file mode 100644 index 32320e6..0000000 --- a/src/ui/miscui.cpp +++ /dev/null @@ -1,569 +0,0 @@ -#include -#include -#include -#include - -#include "gfx.h" -#include "ui.h" -#include "miscui.h" -#include "util.h" -#include "type.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 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}; - -ui::menu::menu(const int& _x, const int& _y, const int& _rW, const int& _fS, const int& _mL) -{ - x = _x; - mY = _y; - y = _y; - tY = _y; - rW = _rW; - rY = _y; - fSize = _fS; - mL = _mL; - rH = _fS + 30; - spcWidth = gfx::getTextWidth(" ", _fS) * 3; - - optTex = gfx::texMgr->textureCreate(rW - 24, rH - 8); -} - -void ui::menu::editParam(int _param, int newVal) -{ - switch(_param) - { - case MENU_X: - x = newVal; - break; - - case MENU_Y: - y = newVal; - break; - - 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); - 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); - SDL_SetTextureBlendMode(optTex, SDL_BLENDMODE_BLEND); - break; - - case MENU_FONT_SIZE: - fSize = newVal; - break; - - case MENU_MAX_SCROLL: - mL = newVal; - break; - } -} - -int ui::menu::addOpt(SDL_Texture *_icn, const std::string& add) -{ - ui::menuOpt newOpt; - newOpt.txt = add; - newOpt.txtWidth = gfx::getTextWidth(newOpt.txt.c_str(), fSize); - newOpt.icn = _icn; - opt.push_back(newOpt); - return opt.size() - 1; -} - -void ui::menu::editOpt(int ind, SDL_Texture *_icn, const std::string& ch) -{ - 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) -{ - ui::menuOptEvent newEvent = {_func, args, _button}; - opt[_ind].events.push_back(newEvent); -} - -int ui::menu::getOptPos(const std::string& txt) -{ - for(unsigned i = 0; i < opt.size(); i++) - { - if(opt[i].txt == txt) - return i; - } - return -1; -} - - -ui::menu::~menu() -{ - opt.clear(); -} - -void ui::menu::update() -{ - if(!isActive) - return; - - uint64_t down = ui::padKeysDown(); - uint64_t held = ui::padKeysHeld(); - - if(held & HidNpadButton_AnyUp || held & HidNpadButton_AnyDown) - ++fc; - else - fc = 0; - - 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(--selected < 0) - selected = mSize; - - change = true; - } - else if( (down & HidNpadButton_AnyDown) || ((held & HidNpadButton_AnyDown) && fc == 10)) - { - if(++selected > mSize) - selected = 0; - - change = true; - } - else if(down & HidNpadButton_AnyLeft) - { - selected -= mL; - if(selected < 0) - selected = 0; - - change = true; - } - else if(down & HidNpadButton_AnyRight) - { - selected += mL; - if(selected > mSize) - selected = mSize; - - change = true; - } - - ++hoverCount; - - if(!change && hoverCount >= 90) - hover = true; - else if(change) - { - hoverCount = 0; - hover = false; - } - - if(down && !opt[selected].events.empty()) - { - for(ui::menuOptEvent& e : opt[selected].events) - { - if((down & e.button) && e.func) - (*e.func)(e.args); - } - } - - if(selected <= mL) - tY = mY; - else if(selected >= (mSize - mL) && mSize > mL * 2) - tY = mY + -(rH * (mSize - (mL * 2))); - else if(selected > mL && selected < (mSize - mL)) - tY = -(rH * (selected - mL)); - - if(selected != oldSel && onChange) - (*onChange)(NULL); - - if(callback) - (*callback)(callbackArgs); -} - -void ui::menu::draw(SDL_Texture *target, const SDL_Color *textClr, bool drawText) -{ - if(opt.size() < 1) - return; - - int tH = 0; - SDL_QueryTexture(target, NULL, NULL, NULL, &tH); - - if(y != tY) - { - float add = (float)((float)tY - (float)y) / ui::animScale; - y += ceil(add); - } - - if(clrAdd) - { - clrSh += 6; - if(clrSh >= 0x72) - clrAdd = false; - } - else - { - clrSh -= 3; - if(clrSh <= 0x00) - clrAdd = true; - } - - for(int i = 0, tY = y; i < (int)opt.size(); i++, tY += rH) - { - if(tY < -rH || tY > tH) - continue; - - gfx::clearTarget(optTex, &ui::transparent); - - //Todo: Clean this up maybe - if(i == selected && drawText) - { - 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); - - static int dX = 0; - if(hover && opt[i].txtWidth > rW - 24) - { - 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()); - } - 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::texDraw(target, optTex, x + 20, (y + 4 + (i * rH))); - } - 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) - 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::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) - { - 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) - { - 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; - gfx::texDrawStretch(optTex, opt[i].icn, 0, ((rH - 8) / 2) - fSize / 2, dW, dH); - gfx::texDraw(target, optTex, x + 20, (y + 4 + (i * rH))); - } - } -} - -void ui::menu::reset() -{ - opt.clear(); - selected = 0; - y = mY; - tY = mY; - fc = 0; -} - -void ui::menu::setActive(bool _set) -{ - isActive = _set; -} - -void ui::progBar::update(const uint64_t& _prog) -{ - prog = _prog; - - if(max > 0) - width = (float)((float)prog / (float)max) * 648.0f; -} - -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); - int progX = 640 - (gfx::getTextWidth(progStr, 18) / 2); - - ui::drawTextbox(NULL, 280, 262, 720, 256); - gfx::drawLine(NULL, &ui::rectSh, 280, 454, 999, 454); - gfx::drawRect(NULL, &fillBack, 312, 471, 648, 32); - gfx::drawRect(NULL, &fillBar, 312, 471, (int)width, 32); - gfx::drawTextf(NULL, 18, progX, 478, &ui::txtCont, progStr); - gfx::drawTextfWrap(NULL, 16, 312, 288, 648, &ui::txtCont, text.c_str()); -} - -ui::popMessageMngr::~popMessageMngr() -{ - message.clear(); -} - -void ui::popMessageMngr::update() -{ - //Anything that needs graphics/text must be processed on main thread - 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++) - { - message[i].frames--; - if(message[i].frames <= 0) - message.erase(message.begin() + i); - } -} - -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) - lastMessage = message[lastMessageIndex].message; - if(lastMessage != mess) - { - ui::popMessage newPop = {mess, 0, frameTime}; - popQueue.push_back(newPop); - } -} - -void ui::popMessageMngr::draw() -{ - int y = 640; - for(auto& p : message) - { - y -= 48; - if(p.y != y) - p.y += (y - p.y) / ui::animScale; - - gfx::drawRect(NULL, &ui::tboxClr, 64, p.y, p.rectWidth, 40); - gfx::drawTextf(NULL, 24, 80, p.y + 8, &ui::txtCont, p.message.c_str()); - } -} - -ui::confirmArgs *ui::confirmArgsCreate(bool _hold, funcPtr _confFunc, funcPtr _cancelFunc, void *_funcArgs, const char *fmt, ...) -{ - char tmp[1024]; - va_list args; - va_start(args, fmt); - vsprintf(tmp, fmt, args); - va_end(args); - - ui::confirmArgs *ret = new confirmArgs; - ret->hold = _hold; - ret->confFunc = _confFunc; - ret->cancelFunc = _cancelFunc; - ret->args = _funcArgs; - ret->text = tmp; - - return ret; -} - -//todo: more inline with original -void confirm_t(void *a) -{ - threadInfo *t = (threadInfo *)a; - ui::confirmArgs *c = (ui::confirmArgs *)t->argPtr; - - while(true) - { - uint64_t down = ui::padKeysDown(); - uint64_t held = ui::padKeysHeld(); - - if((down & HidNpadButton_A) && !c->hold) - { - ui::newThread(c->confFunc, c->args, NULL); - break; - } - else if((held & HidNpadButton_A) && c->hold) - { - if(c->frameCount % 4 == 0 && ++c->lgFrame > 7) - c->lgFrame = 0; - - if(c->frameCount >= 120) - { - ui::newThread(c->confFunc, c->args, NULL); - break; - } - } - else if(down & HidNpadButton_B) - { - if(c->cancelFunc) - (*c->cancelFunc)(c->args); - break; - } - - svcSleepThread(1e+9 / 60);//Close enough - } - delete c; - t->finished = true; -} - -//For now this will work -void confirmDrawFunc(void *a) -{ - threadInfo *t = (threadInfo *)a; - ui::confirmArgs *c = (ui::confirmArgs *)t->argPtr; - if(!t->finished) - { - std::string yesTxt = ui::getUIString("dialogYes", 0); - unsigned yesX = 0; - - if((ui::padKeysHeld() & HidNpadButton_A) && c->hold) - { - c->frameCount++; - if(c->frameCount <= 40) - yesTxt = ui::getUIString("holdingText", 0); - else if(c->frameCount <= 80) - yesTxt = ui::getUIString("holdingText", 1); - else if(c->frameCount <= 120) - yesTxt = ui::getUICString("holdingText", 2); - - yesTxt += ui::loadGlyphArray[c->lgFrame]; - } - else - c->frameCount = 0; - - - //I positioned these by eye. They're probably off. - yesX = 458 - (gfx::getTextWidth(yesTxt.c_str(), 18) / 2); - ui::drawTextbox(NULL, 280, 262, 720, 256); - gfx::drawTextfWrap(NULL, 16, 312, 288, 656, &ui::txtCont, c->text.c_str()); - gfx::drawLine(NULL, &ui::rectSh, 280, 454, 999, 454); - gfx::drawLine(NULL, &ui::rectSh, 640, 454, 640, 518); - gfx::drawTextf(NULL, 18, yesX, 478, &ui::txtCont, yesTxt.c_str()); - gfx::drawTextf(NULL, 18, 782, 478, &ui::txtCont, ui::getUICString("dialogNo", 0)); - } -} - -void ui::confirm(void *a) -{ - ui::newThread(confirm_t, a, confirmDrawFunc); -} - -static void showMessage_t(void *a) -{ - threadInfo *t = (threadInfo *)a; - std::string *text = (std::string *)t->argPtr; - while(true) - { - if(ui::padKeysDown() & HidNpadButton_A) - break; - - svcSleepThread(1e+9 / 60); - } - delete text; - t->finished = true; -} - -static void showMessageDrawFunc(void *a) -{ - threadInfo *t = (threadInfo *)a; - std::string *text = (std::string *)t->argPtr; - if(!t->finished) - { - unsigned okX = 640 - (gfx::getTextWidth(ui::getUICString("dialogOK", 0), 18) / 2); - ui::drawTextbox(NULL, 280, 262, 720, 256); - gfx::drawLine(NULL, &ui::rectSh, 280, 454, 999, 454); - gfx::drawTextfWrap(NULL, 16, 312, 288, 656, &ui::txtCont, text->c_str()); - gfx::drawTextf(NULL, 18, okX, 478, &ui::txtCont, ui::getUICString("dialogOK", 0)); - } -} - -void ui::showMessage(const char *fmt, ...) -{ - char tmp[1024]; - va_list args; - va_start(args, fmt); - vsprintf(tmp, fmt, args); - va_end(args); - - std::string *send = new std::string(tmp); - ui::newThread(showMessage_t, send, showMessageDrawFunc); -} - -void ui::drawTextbox(SDL_Texture *target, int x, int y, int w, int h) -{ - //Top - gfx::texDraw(target, ui::cornerTopLeft, x, y); - gfx::drawRect(target, &ui::tboxClr, x + 32, y, w - 64, 32); - gfx::texDraw(target, ui::cornerTopRight, (x + w) - 32, y); - - //middle - gfx::drawRect(target, &ui::tboxClr, x, y + 32, w, h - 64); - - //bottom - gfx::texDraw(target, ui::cornerBottomLeft, x, (y + h) - 32); - gfx::drawRect(target, &ui::tboxClr, x + 32, (y + h) - 32, w - 64, 32); - gfx::texDraw(target, ui::cornerBottomRight, (x + w) - 32, (y + h) - 32); -} - -void ui::drawBoundBox(SDL_Texture *target, int x, int y, int w, int h, uint8_t clrSh) -{ - SDL_SetRenderTarget(gfx::render, target); - SDL_Color rectClr; - - if(ui::thmID == ColorSetId_Light) - rectClr = {0xFD, 0xFD, 0xFD, 0xFF}; - else - rectClr = {0x21, 0x22, 0x21, 0xFF}; - - gfx::drawRect(target, &rectClr, x + 4, y + 4, w - 8, h - 8); - - rectClr = {0x00, (uint8_t)(0x88 + clrSh), (uint8_t)(0xC5 + (clrSh / 2)), 0xFF}; - - SDL_SetTextureColorMod(mnuTopLeft, rectClr.r, rectClr.g, rectClr.b); - SDL_SetTextureColorMod(mnuTopRight, rectClr.r, rectClr.g, rectClr.b); - SDL_SetTextureColorMod(mnuBotLeft, rectClr.r, rectClr.g, rectClr.b); - SDL_SetTextureColorMod(mnuBotRight, rectClr.r, rectClr.g, rectClr.b); - - //top - gfx::texDraw(target, mnuTopLeft, x, y); - gfx::drawRect(target, &rectClr, x + 4, y, w - 8, 4); - gfx::texDraw(target, mnuTopRight, (x + w) - 4, y); - - //mid - gfx::drawRect(target, &rectClr, x, y + 4, 4, h - 8); - gfx::drawRect(target, &rectClr, (x + w) - 4, y + 4, 4, h - 8); - - //bottom - gfx::texDraw(target, mnuBotLeft, x, (y + h) - 4); - gfx::drawRect(target, &rectClr, x + 4, (y + h) - 4, w - 8, 4); - gfx::texDraw(target, mnuBotRight, (x + w) - 4, (y + h) - 4); -} diff --git a/src/ui/sett.cpp b/src/ui/sett.cpp deleted file mode 100644 index 6acb246..0000000 --- a/src/ui/sett.cpp +++ /dev/null @@ -1,297 +0,0 @@ -#include -#include - -#include "ui.h" -#include "file.h" -#include "sett.h" -#include "cfg.h" -#include "util.h" - -ui::menu *ui::settMenu; -static ui::slideOutPanel *blEditPanel; -static ui::menu *blEditMenu; - -//This is the name of strings used here -static const char *settMenuStr = "settingsMenu"; - -static unsigned optHelpX = 0; - -static inline std::string getBoolText(const bool& b) -{ - return b ? ui::getUIString("settingsOn", 0) : ui::getUIString("settingsOff", 0); -} - -static inline void toggleBool(bool& b) -{ - if(b) - b = false; - else - b = true; -} - -//Declaration, implementation further down -static void blEditMenuPopulate(); - -static void settMenuCallback(void *a) -{ - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - ui::usrMenu->setActive(true); - ui::settMenu->setActive(false); - ui::changeState(USR_SEL); - break; - - case HidNpadButton_X: - cfg::resetConfig(); - break; - } -} - -static void settMenuDeleteAllBackups_t(void *a) -{ - threadInfo *t = (threadInfo *)a; - t->status->setStatus(ui::getUICString("threadStatusDeletingFile", 0)); - - fs::dirList *jksvDir = new fs::dirList(fs::getWorkDir()); - for(unsigned i = 0; i < jksvDir->getCount(); i++) - { - if(jksvDir->isDir(i) && jksvDir->getItem(i) != "svi") - { - std::string delTarget = fs::getWorkDir() + jksvDir->getItem(i) + "/"; - fs::delDir(delTarget); - } - } - delete jksvDir; - t->finished = true; -} - -static void settMenuDeleteAllBackups(void *a) -{ - ui::confirmArgs *send = ui::confirmArgsCreate(true, settMenuDeleteAllBackups_t, NULL, NULL, ui::getUICString("confirmDeleteBackupsAll", 0)); - ui::confirm(send); -} - -static void blEditDrawFunc(void *a) -{ - SDL_Texture *target = (SDL_Texture *)a; - blEditMenu->draw(target, &ui::txtCont, true); -} - -static void blEditMenuCallback(void *a) -{ - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - ui::updateInput(); - blEditMenu->setActive(false); - ui::settMenu->setActive(true); - blEditPanel->closePanel(); - break; - } -} - -static void blEditMenuRemoveTitle(void *a) -{ - uint64_t remTID = cfg::blacklist[blEditMenu->getSelected()]; - cfg::removeTitleFromBlacklist(remTID); - if(cfg::blacklist.size() > 0) - blEditMenuPopulate(); - else - { - blEditMenu->setActive(false); - ui::settMenu->setActive(true); - blEditPanel->closePanel(); - } -} - -static void blEditMenuPopulate() -{ - blEditMenu->reset(); - for(unsigned i = 0; i < cfg::blacklist.size(); i++) - { - blEditMenu->addOpt(NULL, data::getTitleNameByTID(cfg::blacklist[i])); - blEditMenu->optAddButtonEvent(i, HidNpadButton_A, blEditMenuRemoveTitle, NULL); - } -} - -//Todo: this different -static void toggleOpt(void *a) -{ - switch(ui::settMenu->getSelected()) - { - case 0: - fs::delDir(fs::getWorkDir() + "_TRASH_/"); - mkdir(std::string(fs::getWorkDir() + "_TRASH_").c_str(), 777); - ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popTrashEmptied", 0)); - break; - - case 1: - ui::newThread(util::checkForUpdate, NULL, NULL); - break; - - case 2: - { - std::string oldWD = fs::getWorkDir(); - std::string getWD = util::getStringInput(SwkbdType_QWERTY, fs::getWorkDir(), ui::getUIString("swkbdSetWorkDir", 0), 64, 0, NULL); - if(!getWD.empty()) - { - if(getWD[getWD.length() - 1] != '/') - getWD += "/"; - - rename(oldWD.c_str(), getWD.c_str()); - fs::setWorkDir(getWD); - } - } - break; - - case 3: - if(cfg::blacklist.size() > 0) - { - blEditMenuPopulate(); - ui::settMenu->setActive(false); - blEditMenu->setActive(true); - blEditPanel->openPanel(); - } - break; - - case 4: - settMenuDeleteAllBackups(NULL); - break; - - case 5: - toggleBool(cfg::config["incDev"]); - break; - - case 6: - toggleBool(cfg::config["autoBack"]); - break; - - case 7: - toggleBool(cfg::config["autoName"]); - break; - - case 8: - toggleBool(cfg::config["ovrClk"]); - break; - - case 9: - toggleBool(cfg::config["holdDel"]); - break; - - case 10: - toggleBool(cfg::config["holdRest"]); - break; - - case 11: - toggleBool(cfg::config["holdOver"]); - break; - - case 12: - toggleBool(cfg::config["forceMount"]); - break; - - case 13: - toggleBool(cfg::config["accSysSave"]); - break; - - case 14: - toggleBool(cfg::config["sysSaveWrite"]); - break; - - case 15: - toggleBool(cfg::config["directFsCmd"]); - break; - - case 16: - toggleBool(cfg::config["zip"]); - break; - - case 17: - toggleBool(cfg::config["langOverride"]); - break; - - case 18: - toggleBool(cfg::config["trashBin"]); - break; - - case 19: - if(++cfg::sortType > 2) - cfg::sortType = 0; - data::loadUsersTitles(false); - ui::ttlRefresh(); - break; - - case 20: - ui::animScale += 0.5f; - if(ui::animScale > 8) - ui::animScale = 1; - break; - } - cfg::saveConfig(); -} - -static void updateMenuText() -{ - ui::settMenu->editOpt(5, NULL, ui::getUIString(settMenuStr, 5) + getBoolText(cfg::config["incDev"])); - ui::settMenu->editOpt(6, NULL, ui::getUIString(settMenuStr, 6) + getBoolText(cfg::config["autoBack"])); - ui::settMenu->editOpt(7, NULL, ui::getUIString(settMenuStr, 7) + getBoolText(cfg::config["autoName"])); - ui::settMenu->editOpt(8, NULL, ui::getUIString(settMenuStr, 8) + getBoolText(cfg::config["ovrClk"])); - ui::settMenu->editOpt(9, NULL, ui::getUIString(settMenuStr, 9) + getBoolText(cfg::config["holdDel"])); - ui::settMenu->editOpt(10, NULL, ui::getUIString(settMenuStr, 10) + getBoolText(cfg::config["holdRest"])); - ui::settMenu->editOpt(11, NULL, ui::getUIString(settMenuStr, 11) + getBoolText(cfg::config["holdOver"])); - ui::settMenu->editOpt(12, NULL, ui::getUIString(settMenuStr, 12) + getBoolText(cfg::config["forceMount"])); - ui::settMenu->editOpt(13, NULL, ui::getUIString(settMenuStr, 13) + getBoolText(cfg::config["accSysSave"])); - ui::settMenu->editOpt(14, NULL, ui::getUIString(settMenuStr, 14) + getBoolText(cfg::config["sysSaveWrite"])); - ui::settMenu->editOpt(15, NULL, ui::getUIString(settMenuStr, 15) + getBoolText(cfg::config["directFsCmd"])); - ui::settMenu->editOpt(16, NULL, ui::getUIString(settMenuStr, 16) + getBoolText(cfg::config["zip"])); - ui::settMenu->editOpt(17, NULL, ui::getUIString(settMenuStr, 17) + getBoolText(cfg::config["langOverride"])); - ui::settMenu->editOpt(18, NULL, ui::getUIString(settMenuStr, 18) + getBoolText(cfg::config["trashBin"])); - ui::settMenu->editOpt(19, NULL, ui::getUIString(settMenuStr, 19) + ui::getUICString("sortType", cfg::sortType)); - - char tmp[16]; - sprintf(tmp, "%.1f", ui::animScale); - ui::settMenu->editOpt(20, NULL, ui::getUIString(settMenuStr, 20) + std::string(tmp)); -} - -void ui::settInit() -{ - ui::settMenu = new ui::menu(200, 24, 1002, 24, 4); - ui::settMenu->setCallback(settMenuCallback, NULL); - ui::settMenu->setActive(false); - - blEditPanel = new ui::slideOutPanel(512, 720, 0, ui::SLD_RIGHT, blEditDrawFunc); - blEditMenu = new ui::menu(8, 32, 492, 20, 6); - blEditMenu->setCallback(blEditMenuCallback, NULL); - blEditMenu->setActive(false); - ui::registerPanel(blEditPanel); - - optHelpX = 1220 - gfx::getTextWidth(ui::getUICString("helpSettings", 0), 18); - - for(unsigned i = 0; i < 21; i++) - { - ui::settMenu->addOpt(NULL, ui::getUIString("settingsMenu", i)); - ui::settMenu->optAddButtonEvent(i, HidNpadButton_A, toggleOpt, NULL); - } -} - -void ui::settExit() -{ - delete ui::settMenu; - delete blEditMenu; - delete blEditPanel; -} - -void ui::settUpdate() -{ - blEditMenu->update(); - ui::settMenu->update(); -} - -void ui::settDraw(SDL_Texture *target) -{ - updateMenuText(); - ui::settMenu->draw(target, &ui::txtCont, true); - if(ui::mstate == OPT_MNU) - gfx::drawTextf(NULL, 18, optHelpX, 673, &ui::txtCont, ui::getUICString("helpSettings", 0)); -} diff --git a/src/ui/sldpanel.cpp b/src/ui/sldpanel.cpp deleted file mode 100644 index b0ae7c7..0000000 --- a/src/ui/sldpanel.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include - -#include "ui.h" -#include "gfx.h" - -ui::slideOutPanel::slideOutPanel(int _w, int _h, int _y, ui::slidePanelOrientation _side, funcPtr _draw) -{ - w = _w; - h = _h; - y = _y; - sldSide = _side; - if(_side == ui::SLD_LEFT) - x = -w; - else - x = 1280; - - drawFunc = _draw; - panel = gfx::texMgr->textureCreate(w, h); -} - -void ui::slideOutPanel::resizePanel(int _w, int _h, int _y) -{ - w = _w; - h = _h; - y = _y; - if(sldSide == ui::SLD_LEFT) - x = -w; - - gfx::texMgr->textureResize(&panel, w, h); -} - -void ui::slideOutPanel::update() -{ - if(open && callback) - (*callback)(cbArgs); -} - -void ui::slideOutPanel::draw(const SDL_Color *backCol) -{ - gfx::clearTarget(panel, backCol); - - if(open && sldSide == ui::SLD_LEFT && x < 0) - { - float add = (float)x / ui::animScale; - x -= ceil(add); - } - else if(open && sldSide == ui::SLD_RIGHT && x > 1280 - w) - { - float add = ((1280.0f - (float)w) - (float)x) / ui::animScale; - x += ceil(add); - } - else if(!open && sldSide == ui::SLD_LEFT && x > -w) - { - float sub = ((float)w - (float)x) / ui::animScale; - x -= ceil(sub); - } - else if(!open && sldSide == ui::SLD_RIGHT && x < 1280) - { - float add = (1280.0f - (float)x) / ui::animScale; - x += ceil(add); - } - - if((sldSide == ui::SLD_LEFT && x > -w) || (sldSide == ui::SLD_RIGHT && x < 1280)) - { - (*drawFunc)(panel); - gfx::texDraw(NULL, panel, x, y); - } -} diff --git a/src/ui/thrdProc.cpp b/src/ui/thrdProc.cpp deleted file mode 100644 index b882cec..0000000 --- a/src/ui/thrdProc.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include - -#include "ui.h" -#include "gfx.h" - -#include "file.h" - -//Thread status screen always uses white -static const SDL_Color white = {0xFF, 0xFF, 0xFF, 0xFF}; -static const SDL_Color darkenBack = {0x00, 0x00, 0x00, 0xBB}; - -ui::threadProcMngr::~threadProcMngr() -{ - for(threadInfo *t : threads) - { - threadWaitForExit(&t->thrd); - threadClose(&t->thrd); - delete t->status; - delete t; - } -} - -threadInfo *ui::threadProcMngr::newThread(ThreadFunc func, void *args, funcPtr _drawfunc) -{ - threadInfo *t = new threadInfo; - t->status = new threadStatus; - t->running = false; - t->finished = false; - t->thrdFunc = func; - t->drawFunc = _drawfunc; - t->argPtr = args; - - mutexLock(&threadLock); - threads.push_back(t); - mutexUnlock(&threadLock); - return threads[threads.size() - 1]; -} - -void ui::threadProcMngr::update() -{ - if(!threads.empty()) - { - Result res = 0; - threadInfo *t = threads[0]; - if(!t->running && R_SUCCEEDED((res = threadCreate(&t->thrd, t->thrdFunc, t, NULL, 0x80000, 0x2B, 1)))) - { - threadStart(&t->thrd); - t->running = true; - } - else if(!t->running && R_FAILED(res))//Should kill the thread that failed. - t->finished = true; - else if(t->finished) - { - threadWaitForExit(&t->thrd); - threadClose(&t->thrd); - delete t->status; - delete t; - mutexLock(&threadLock); - threads.erase(threads.begin()); - mutexUnlock(&threadLock); - } - } -} - -void ui::threadProcMngr::draw() -{ - if(!threads.empty()) - { - if(++frameCount % 4 == 0 && ++lgFrame > 7) - lgFrame = 0; - - if(clrAdd && (clrShft += 6) >= 0x72) - clrAdd = false; - else if(!clrAdd && (clrShft -= 3) <= 0x00) - clrAdd = true; - - - SDL_Color glyphCol = {0x00, (uint8_t)(0x88 + clrShft), (uint8_t)(0xC5 + (clrShft / 2)), 0xFF}; - - gfx::drawRect(NULL, &darkenBack, 0, 0, 1280, 720); - gfx::drawTextf(NULL, 32, 56, 673, &glyphCol, loadGlyphArray[lgFrame].c_str()); - - threadInfo *t = threads[0]; - if(t->drawFunc) - (*(t->drawFunc))(t); - else - { - std::string gStatus; - t->status->getStatus(gStatus); - - int statX = 640 - (gfx::getTextWidth(gStatus.c_str(), 18) / 2); - gfx::drawTextf(NULL, 18, statX, 387, &white, gStatus.c_str()); - } - } -} diff --git a/src/ui/ttl.cpp b/src/ui/ttl.cpp deleted file mode 100644 index 8d88505..0000000 --- a/src/ui/ttl.cpp +++ /dev/null @@ -1,414 +0,0 @@ -#include - -#include "ui.h" -#include "ttl.h" -#include "file.h" -#include "util.h" -#include "cfg.h" - -static int ttlHelpX = 0; -static std::vector ttlViews; -static ui::menu *ttlOpts; -ui::slideOutPanel *ui::ttlOptsPanel; -static ui::slideOutPanel *infoPanel; -static Mutex ttlViewLock = 0; - -void ui::ttlRefresh() -{ - mutexLock(&ttlViewLock); - for(int i = 0; i < (int)data::users.size(); i++) - ttlViews[i]->refresh(); - mutexUnlock(&ttlViewLock); -} - -static void ttlViewCallback(void *a) -{ - unsigned curUserIndex = data::getCurrentUserIndex(); - unsigned setUserTitleIndex = ttlViews[curUserIndex]->getSelected(); - data::setTitleIndex(setUserTitleIndex); - data::userTitleInfo *d = data::getCurrentUserTitleInfo(); - uint64_t tid = d->tid; - - switch(ui::padKeysDown()) - { - case HidNpadButton_A: - if(fs::mountSave(d->saveInfo)) - { - ttlViews[curUserIndex]->setActive(false, true); - ui::fldPopulateMenu(); - } - break; - - case HidNpadButton_B: - ttlViews[curUserIndex]->setActive(false, false); - ui::usrMenu->setActive(true); - ui::changeState(USR_SEL); - break; - - case HidNpadButton_X: - ttlViews[curUserIndex]->setActive(false, true); - ttlOpts->setActive(true); - ui::ttlOptsPanel->openPanel(); - break; - - case HidNpadButton_Y: - { - cfg::addTitleToFavorites(tid); - int newSel = data::getTitleIndexInUser(data::users[curUserIndex], tid); - ttlViews[curUserIndex]->setSelected(newSel); - } - break; - } -} - -static void ttlOptsCallback(void *a) -{ - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - { - int curUserIndex = data::getCurrentUserIndex(); - ttlOpts->setActive(false); - ui::ttlOptsPanel->closePanel(); - ttlViews[curUserIndex]->setActive(true, true); - ui::updateInput(); - } - break; - } -} - -static void ttlOptsPanelDraw(void *a) -{ - SDL_Texture *panel = (SDL_Texture *)a; - ttlOpts->draw(panel, &ui::txtCont, true); -} - -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(); - infoPanel->openPanel(); -} - -static void ttlOptsBlacklistTitle(void *a) -{ - std::string title = data::getTitleNameByTID(data::getCurrentUserTitleInfo()->tid); - ui::confirmArgs *conf = ui::confirmArgsCreate(false, cfg::addTitleToBlacklist, NULL, NULL, ui::getUICString("confirmBlacklist", 0), title.c_str()); - ui::confirm(conf); -} - -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, 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)) - { - 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); - } -} - -static void ttlOptsDeleteAllBackups_t(void *a) -{ - threadInfo *t = (threadInfo *)a; - t->status->setStatus(ui::getUICString("threadStatusDeletingFile", 0)); - 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++) - { - std::string delPath = targetPath + backupList->getItem(i); - if(backupList->isDir(i)) - { - delPath += "/"; - fs::delDir(delPath); - } - else - fs::delfile(delPath); - } - delete backupList; - t->finished = true; -} - -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, NULL, ui::getUICString("confirmDeleteBackupsTitle", 0), currentTitle.c_str()); - ui::confirm(send); -} - -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)); - - fs::mountSave(d->saveInfo); - fs::delDir("sv:/"); - fsdevCommitDevice("sv:/"); - fs::unmountSave(); - ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataResetSuccess", 0), title.c_str()); - t->finished = true; -} - -static void ttlOptsResetSaveData(void *a) -{ - data::userTitleInfo *d = data::getCurrentUserTitleInfo(); - 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, NULL, ui::getUICString("confirmResetSaveData", 0), title.c_str()); - ui::confirm(conf); - } -} - -static void ttlOptsDeleteSaveData_t(void *a) -{ - threadInfo *t = (threadInfo *)a; - data::user *u = data::getCurrentUser(); - data::userTitleInfo *d = data::getCurrentUserTitleInfo(); - unsigned userIndex = data::getCurrentUserIndex(); - - 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))) - { - data::loadUsersTitles(false); - if(u->titleInfo.size() == 0) - { - //Kick back to user - ui::ttlOptsPanel->closePanel();//JIC - ttlOpts->setActive(false); - ttlViews[userIndex]->setActive(false, false); - ui::usrMenu->setActive(true); - ui::changeState(USR_SEL); - } - ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataDeleteSuccess", 0), title.c_str()); - ttlViews[userIndex]->refresh(); - } - t->finished = true; -} - -static void ttlOptsDeleteSaveData(void *a) -{ - data::userTitleInfo *d = data::getCurrentUserTitleInfo(); - 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, NULL, ui::getUICString("confirmDeleteSaveData", 0), title.c_str()); - ui::confirm(conf); - } -} - -static void ttlOptsExtendSaveData(void *a) -{ - data::userTitleInfo *d = data::getCurrentUserTitleInfo(); - 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) -{ - data::userTitleInfo *ut = data::getCurrentUserTitleInfo(); - data::titleInfo *t = data::getTitleInfoByTID(ut->tid); - std::string out = fs::getWorkDir() + "svi/"; - fs::mkDir(out.substr(0, out.length() - 1)); - out += util::getIDStr(ut->tid) + ".svi"; - FILE *svi = fopen(out.c_str(), "wb"); - if(svi) - { - //Grab icon - NsApplicationControlData *ctrlData = new NsApplicationControlData; - uint64_t ctrlSize = 0; - nsGetApplicationControlData(NsApplicationControlSource_Storage, ut->tid, ctrlData, sizeof(NsApplicationControlData), &ctrlSize); - size_t jpegSize = ctrlSize - sizeof(ctrlData->nacp); - - fwrite(&ut->tid, sizeof(uint64_t), 1, svi); - fwrite(&t->nacp, sizeof(NacpStruct), 1, svi); - fwrite(ctrlData->icon, 1, jpegSize, svi); - fclose(svi); - delete ctrlData; - 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; - 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()) - { - case HidNpadButton_B: - infoPanel->closePanel(); - ui::ttlOptsPanel->openPanel(); - ttlOpts->setActive(true); - ui::updateInput(); - break; - } -} - -void ui::ttlInit() -{ - ttlHelpX = 1220 - gfx::getTextWidth(ui::getUICString("helpTitle", 0), 18); - - 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); - - ttlOptsPanel = new ui::slideOutPanel(410, 720, 0, ui::SLD_RIGHT, ttlOptsPanelDraw); - ui::registerPanel(ttlOptsPanel); - - infoPanel = new ui::slideOutPanel(410, 720, 0, ui::SLD_RIGHT, infoPanelDraw); - ui::registerPanel(infoPanel); - infoPanel->setCallback(infoPanelCallback, NULL); - - ttlOpts->setActive(false); - for(int i = 0; i < 9; i++) - ttlOpts->addOpt(NULL, ui::getUIString("titleOptions", i)); - - //Information - ttlOpts->optAddButtonEvent(0, HidNpadButton_A, ttlOptsShowInfoPanel, NULL); - //Blacklist - ttlOpts->optAddButtonEvent(1, HidNpadButton_A, ttlOptsBlacklistTitle, NULL); - //Title Define - ttlOpts->optAddButtonEvent(2, HidNpadButton_A, ttlOptsDefinePath, NULL); - //File Mode - ttlOpts->optAddButtonEvent(3, HidNpadButton_A, ttlOptsToFileMode, NULL); - //Delete all backups - ttlOpts->optAddButtonEvent(4, HidNpadButton_A, ttlOptsDeleteAllBackups, NULL); - //Reset Save - ttlOpts->optAddButtonEvent(5, HidNpadButton_A, ttlOptsResetSaveData, NULL); - //Delete Save - 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) - delete t; - - delete ttlOptsPanel; - delete ttlOpts; - delete infoPanel; -} - -void ui::ttlSetActive(int usr, bool _set, bool _showSel) -{ - ttlViews[usr]->setActive(_set, _showSel); -} - -void ui::ttlUpdate() -{ - unsigned curUserIndex = data::getCurrentUserIndex(); - - ttlOpts->update(); - infoPanel->update(); - ui::fldUpdate(); - ttlViews[curUserIndex]->update(); -} - -void ui::ttlDraw(SDL_Texture *target) -{ - unsigned curUserIndex = data::getCurrentUserIndex(); - - mutexLock(&ttlViewLock); - ttlViews[curUserIndex]->draw(target); - mutexUnlock(&ttlViewLock); - if(ui::mstate == TTL_SEL && !fldPanel->isOpen()) - gfx::drawTextf(NULL, 18, ttlHelpX, 673, &ui::txtCont, ui::getUICString("helpTitle", 0)); -} diff --git a/src/ui/ttlview.cpp b/src/ui/ttlview.cpp deleted file mode 100644 index 03f2d11..0000000 --- a/src/ui/ttlview.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include "ui.h" -#include "ui/ttlview.h" -#include "cfg.h" - -//Todo make less hardcoded -void ui::titleTile::draw(SDL_Texture *target, int x, int y, bool sel) -{ - unsigned xScale = w * 1.28, yScale = h * 1.28; - if(sel) - { - if(wS < xScale) - wS += 18; - if(hS < yScale) - hS += 18; - } - else - { - if(wS > w) - wS -= 9; - if(hS > h) - hS -= 9; - } - - int dX = x - ((wS - w) / 2); - int dY = y - ((hS - h) / 2); - gfx::texDrawStretch(target, icon, dX, dY, wS, hS); - if(fav) - gfx::drawTextf(target, 20, dX + 8, dY + 8, &ui::heartColor, "♥"); -} - -ui::titleview::titleview(const data::user& _u, int _iconW, int _iconH, int _horGap, int _vertGap, int _rowCount, funcPtr _callback) -{ - iconW = _iconW; - iconH = _iconH; - horGap = _horGap; - vertGap = _vertGap; - rowCount = _rowCount; - callback = _callback; - u = &_u; - - for(const data::userTitleInfo& t : u->titleInfo) - tiles.emplace_back(new ui::titleTile(_iconW, _iconH, cfg::isFavorite(t.tid), data::getTitleIconByTID(t.tid))); -} - -ui::titleview::~titleview() -{ - for(ui::titleTile *t : tiles) - delete t; -} - -void ui::titleview::refresh() -{ - for(ui::titleTile *t : tiles) - delete t; - - tiles.clear(); - for(const data::userTitleInfo& t : u->titleInfo) - tiles.emplace_back(new ui::titleTile(iconW, iconH, cfg::isFavorite(t.tid), data::getTitleIconByTID(t.tid))); - - if(selected > (int)tiles.size() - 1 && selected > 0) - selected = tiles.size() - 1; -} - -void ui::titleview::update() -{ - if(selected > (int)tiles.size() - 1 && selected > 0) - selected = tiles.size() - 1; - - if(!active) - return; - - switch(ui::padKeysDown()) - { - case HidNpadButton_StickLUp: - case HidNpadButton_StickRUp: - case HidNpadButton_Up: - if((selected -= rowCount) < 0) - selected = 0; - break; - - case HidNpadButton_StickLDown: - case HidNpadButton_StickRDown: - case HidNpadButton_Down: - if((selected += rowCount) > (int)(tiles.size() - 1)) - selected = tiles.size() - 1; - break; - - case HidNpadButton_StickLLeft: - case HidNpadButton_StickRLeft: - case HidNpadButton_Left: - if(selected > 0) - --selected; - break; - - case HidNpadButton_StickLRight: - case HidNpadButton_StickRRight: - case HidNpadButton_Right: - if(selected < (int)(tiles.size() - 1)) - ++selected; - break; - - case HidNpadButton_L: - if((selected -= rowCount * 3) < 0) - selected = 0; - break; - - case HidNpadButton_R: - if((selected += rowCount * 3) > (int)(tiles.size() - 1)) - selected = tiles.size() - 1; - break; - } - - if(callback) - (*callback)(this); -} - -void ui::titleview::draw(SDL_Texture *target) -{ - if(tiles.size() <= 0) - return; - - int tH = 0, tY = 0; - SDL_QueryTexture(target, NULL, NULL, NULL, &tH); - tY = tH - 214; - if(selRectY > tY) - { - float add = ((float)tY - (float)selRectY) / ui::animScale; - y += ceil(add); - } - else if(selRectY < 38) - { - float add = (38.0f - (float)selRectY) / ui::animScale; - y += ceil(add); - } - - if(clrAdd) - { - clrShft += 6; - if(clrShft >= 0x72) - clrAdd = false; - } - else - { - clrShft -= 3; - if(clrShft <= 0) - clrAdd = true; - } - - int totalTitles = tiles.size(), selX = 32, selY = 64; - for(int tY = y, i = 0; i < totalTitles; tY += iconH + vertGap) - { - int endRow = i + rowCount; - for(int tX = x; i < endRow; tX += iconW + horGap, i++) - { - if(i >= totalTitles) - break; - - if(i == selected) - { - //save x and y for later so it's draw over top - selX = tX; - selY = tY; - selRectX = tX - 24; - selRectY = tY - 24; - } - else - tiles[i]->draw(target, tX, tY, false); - } - } - - if(showSel) - { - ui::drawBoundBox(target, selRectX, selRectY, 176, 176, clrShft); - tiles[selected]->draw(target, selX, selY, true); - } - else - tiles[selected]->draw(target, selX, selY, false); -} diff --git a/src/ui/uistr.cpp b/src/ui/uistr.cpp deleted file mode 100644 index 1da1748..0000000 --- a/src/ui/uistr.cpp +++ /dev/null @@ -1,391 +0,0 @@ -#include -#include - -#include "file.h" -#include "cfg.h" -#include "type.h" -#include "util.h" -#include "uistr.h" - -std::map, std::string> ui::strings; - -static void addUIString(const std::string& _name, int ind, const std::string& _str) -{ - 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 [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 [ZR] Upload [B] Close"); - addUIString("helpSettings", 0, "[A] Toggle [X] Defaults [B] Back"); - - //Y/N On/Off - addUIString("dialogYes", 0, "Yes [A]"); - addUIString("dialogNo", 0, "No [B]"); - addUIString("dialogOK", 0, "OK [A]"); - addUIString("settingsOn", 0, ">On>"); - addUIString("settingsOff", 0, "Off"); - addUIString("holdingText", 0, "(Hold) "); - addUIString("holdingText", 1, "(Keep Holding) "); - addUIString("holdingText", 2, "(Almost There!) "); - - //Confirmation Strings - addUIString("confirmBlacklist", 0, "Are you sure you want to add #%s# to your blacklist?"); - addUIString("confirmOverwrite", 0, "Are you sure you want to overwrite #%s#?"); - addUIString("confirmRestore", 0, "Are you sure you want to restore #%s#?"); - addUIString("confirmDelete", 0, "Are you sure you want to delete #%s#? *This is permanent*!"); - addUIString("confirmCopy", 0, "Are you sure you want to copy #%s# to #%s#?"); - addUIString("confirmDeleteSaveData", 0, "*WARNING*: This *will* erase the save data for #%s# *from your system*. Are you sure you want to do this?"); - addUIString("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?"); - 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#!"); - addUIString("saveDataCreatedForUser", 0, "Save data created for %s!"); - addUIString("saveDataCreationFailed", 0, "Save data creation failed!"); - addUIString("saveDataResetSuccess", 0, "Save for #%s# reset!"); - addUIString("saveDataDeleteSuccess", 0, "Save data for #%s# deleted!"); - addUIString("saveDataExtendSuccess", 0, "Save data for #%s# extended!"); - addUIString("saveDataExtendFailed", 0, "Failed to extend save data."); - 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!"); - addUIString("onlineNoUpdates", 0, "No Updates Available."); - - //File mode menu strings - addUIString("fileModeMenu", 0, "Copy To "); - addUIString("fileModeMenu", 1, "Delete"); - addUIString("fileModeMenu", 2, "Rename"); - addUIString("fileModeMenu", 3, "Make Dir"); - 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"); - addUIString("fileModeFolderProperties", 0, "Path: %s\nSub Folders: %u\nFile Count: %u\nTotal Size: %s"); - - //Settings menu - addUIString("settingsMenu", 0, "Empty Trash Bin"); - addUIString("settingsMenu", 1, "Check for Updates"); - addUIString("settingsMenu", 2, "Set JKSV Save Output Folder"); - addUIString("settingsMenu", 3, "Edit Blacklisted Titles"); - addUIString("settingsMenu", 4, "Delete All Save Backups"); - addUIString("settingsMenu", 5, "Include Device Saves With Users: "); - addUIString("settingsMenu", 6, "Auto Backup On Restore: "); - addUIString("settingsMenu", 7, "Auto-Name Backups: "); - addUIString("settingsMenu", 8, "Overclock/CPU Boost: "); - addUIString("settingsMenu", 9, "Hold To Delete: "); - addUIString("settingsMenu", 10, "Hold To Restore: "); - addUIString("settingsMenu", 11, "Hold To Overwrite: "); - addUIString("settingsMenu", 12, "Force Mount: "); - addUIString("settingsMenu", 13, "Account System Saves: "); - addUIString("settingsMenu", 14, "Enable Writing to System Saves: "); - addUIString("settingsMenu", 15, "Use FS Commands Directly: "); - addUIString("settingsMenu", 16, "Export Saves to ZIP: "); - addUIString("settingsMenu", 17, "Force English To Be Used: "); - 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"); - addUIString("sortType", 1, "Time Played"); - addUIString("sortType", 2, "Last Played"); - - //Extras - addUIString("extrasMenu", 0, "SD to SD Browser"); - addUIString("extrasMenu", 1, "BIS: ProdInfoF"); - addUIString("extrasMenu", 2, "BIS: Safe"); - addUIString("extrasMenu", 3, "BIS: System"); - addUIString("extrasMenu", 4, "BIS: User"); - addUIString("extrasMenu", 5, "Remove Pending Update"); - addUIString("extrasMenu", 6, "Terminate Process"); - addUIString("extrasMenu", 7, "Mount System Save"); - addUIString("extrasMenu", 8, "Rescan Titles"); - addUIString("extrasMenu", 9, "Mount Process RomFS"); - addUIString("extrasMenu", 10, "Backup JKSV Folder"); - addUIString("extrasMenu", 11, "*[DEV]* Output en-US"); - - //User Options - addUIString("userOptions", 0, "Dump All For "); - addUIString("userOptions", 1, "Create Save Data"); - addUIString("userOptions", 2, "Create All Save Data"); - addUIString("userOptions", 3, "Delete All User Saves"); - - //Title Options - addUIString("titleOptions", 0, "Information"); - addUIString("titleOptions", 1, "Blacklist"); - addUIString("titleOptions", 2, "Change Output Folder"); - addUIString("titleOptions", 3, "Open in File Mode"); - addUIString("titleOptions", 4, "Delete All Save Backups"); - 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#..."); - addUIString("threadStatusCopyingFile", 0, "Copying '#%s#'..."); - addUIString("threadStatusDeletingFile", 0, "Deleting..."); - addUIString("threadStatusOpeningFolder", 0, "Opening '#%s#'..."); - addUIString("threadStatusAddingFileToZip", 0, "Adding '#%s#' to ZIP..."); - addUIString("threadStatusDecompressingFile", 0, "Decompressing '#%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#..."); - addUIString("threadStatusResettingSaveData", 0, "Resetting save data..."); - addUIString("threadStatusDeletingUpdate", 0, "Deleting pending update..."); - addUIString("threadStatusCheckingForUpdate", 0, "Checking for updates..."); - 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."); - addUIString("popErrorCommittingFile", 0, "Error committing file to save!"); - addUIString("popZipIsEmpty", 0, "ZIP file is empty!"); - addUIString("popFolderIsEmpty", 0, "Folder is empty!"); - 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"); - addUIString("swkbdSaveIndex", 0, "Enter Cache Index"); - addUIString("swkbdSetWorkDir", 0, "Enter a new Output Path"); - addUIString("swkbdProcessID", 0, "Enter Process ID"); - 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() -{ - 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 - loadTranslationFile(translationFile); - - 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::saveTranslationFiles(void *a) -{ - threadInfo *t = (threadInfo *)a; - t->status->setStatus(ui::getUICString("infoStatus", 9)); - - 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) - { - 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; -} diff --git a/src/ui/usr.cpp b/src/ui/usr.cpp deleted file mode 100644 index 345d90b..0000000 --- a/src/ui/usr.cpp +++ /dev/null @@ -1,491 +0,0 @@ -#include -#include -#include -#include - -#include "file.h" -#include "data.h" -#include "ui.h" -#include "util.h" -#include "type.h" - -#include "usr.h" -#include "ttl.h" - -static const char *settText = ui::getUICString("mainMenuSettings", 0), *extText = ui::getUICString("mainMenuExtras", 0); - -//Main menu/Users + options, folder -ui::menu *ui::usrMenu; -ui::slideOutPanel *ui::usrSelPanel; - -static ui::menu *usrOptMenu, *saveCreateMenu, *deviceSaveMenu, *bcatSaveMenu, *cacheSaveMenu; -//All save types have different entries. -static ui::slideOutPanel *usrOptPanel, *saveCreatePanel, *deviceSavePanel, *bcatSavePanel, *cacheSavePanel; - -//Icons for settings + extras -static SDL_Texture *sett, *ext; - -//This stores save ids to match with saveCreateMenu -//Probably needs/should be changed -static std::vector accSids, devSids, bcatSids, cacheSids; - -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(); ) - { - 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]); - - pointA = tolower(pointA), pointB = tolower(pointB); - if(pointA != pointB) - return pointA < pointB; - - i += tid1Cnt, j += tid2Cnt; - } - return false; - } -} sortCreateTIDs; - -static void onMainChange(void *a) -{ - if(ui::usrMenu->getSelected() < (int)data::users.size()) - { - unsigned setUser = ui::usrMenu->getSelected(); - data::setUserIndex(setUser); - } -} - -static void _usrSelPanelDraw(void *a) -{ - SDL_Texture *target = (SDL_Texture *)a; - gfx::texDraw(target, ui::sideBar, 0, 0); - ui::usrMenu->draw(target, &ui::txtCont, false); -} - -static void toOPT(void *a) -{ - ui::changeState(OPT_MNU); - ui::usrMenu->setActive(false); - ui::settMenu->setActive(true); -} - -static void toEXT(void *a) -{ - ui::changeState(EX_MNU); - ui::usrMenu->setActive(false); - ui::extMenu->setActive(true); -} - -static void usrOptCallback(void *a) -{ - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - usrOptPanel->closePanel(); - ui::usrMenu->setActive(true); - usrOptMenu->setActive(false); - break; - } -} - -static void saveCreateCallback(void *a) -{ - switch(ui::padKeysDown()) - { - case HidNpadButton_B: - usrOptMenu->setActive(true); - usrOptPanel->openPanel(); - saveCreateMenu->setActive(false); - saveCreatePanel->closePanel(); - deviceSaveMenu->setActive(false); - deviceSavePanel->closePanel(); - bcatSaveMenu->setActive(false); - bcatSavePanel->closePanel(); - cacheSaveMenu->setActive(false); - cacheSavePanel->closePanel(); - break; - } -} - -static void usrOptSaveCreate(void *a) -{ - ui::menu *m = (ui::menu *)a; - 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) - { - deviceSaveMenu->setActive(true); - deviceSavePanel->openPanel(); - closeUsrOpt = true; - } - else if(sel == bcatPos && bcatSaveMenu->getOptCount() > 0) - { - bcatSaveMenu->setActive(true); - bcatSavePanel->openPanel(); - closeUsrOpt = true; - } - else if(sel == cachePos && cacheSaveMenu->getOptCount() > 0) - { - cacheSaveMenu->setActive(true); - cacheSavePanel->openPanel(); - closeUsrOpt = true; - } - else if(sel < devPos) - { - saveCreateMenu->setActive(true); - saveCreatePanel->openPanel(); - closeUsrOpt = true; - } - - if(closeUsrOpt) - { - usrOptMenu->setActive(false); - usrOptPanel->closePanel(); - } -} - -//nsDeleteUserSaveDataAll only works if the user is selected at system level -static void usrOptDeleteAllUserSaves_t(void *a) -{ - threadInfo *t = (threadInfo *)a; - data::user *u = data::getCurrentUser(); - int curUserIndex = data::getCurrentUserIndex(); - int devUser = ui::usrMenu->getOptPos(ui::getUICString("saveTypeMainMenu", 0)); - - for(data::userTitleInfo& tinf : u->titleInfo) - { - 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()); - fsDeleteSaveDataFileSystemBySaveDataSpaceId(FsSaveDataSpaceId_User, tinf.saveInfo.save_data_id); - } - } - data::loadUsersTitles(false); - ui::ttlRefresh(); - t->finished = true; -} - -static void usrOptDeleteAllUserSaves(void *a) -{ - data::user *u = data::getCurrentUser(); - ui::confirmArgs *conf = ui::confirmArgsCreate(true, usrOptDeleteAllUserSaves_t, NULL, NULL, ui::getUICString("saveDataDeleteAllUser", 0), u->getUsername().c_str()); - ui::confirm(conf); -} - -static void usrOptPanelDraw(void *a) -{ - SDL_Texture *panel = (SDL_Texture *)a; - usrOptMenu->draw(panel, &ui::txtCont, true); -} - -static void saveCreatePanelDraw(void *a) -{ - SDL_Texture *panel = (SDL_Texture *)a; - saveCreateMenu->draw(panel, &ui::txtCont, true); -} - -static void deviceSavePanelDraw(void *a) -{ - SDL_Texture *panel = (SDL_Texture *)a; - deviceSaveMenu->draw(panel, &ui::txtCont, true); -} - -static void bcatSavePanelDraw(void *a) -{ - SDL_Texture *panel = (SDL_Texture *)a; - bcatSaveMenu->draw(panel, &ui::txtCont, true); -} - -static void cacheSavePanelDraw(void *a) -{ - SDL_Texture *panel = (SDL_Texture *)a; - cacheSaveMenu->draw(panel, &ui::txtCont, true); -} - -static void usrOptDumpAllUserSaves(void *a) -{ - ui::newThread(fs::dumpAllUserSaves, NULL, fs::fileDrawFunc); -} - -static void createSaveData(void *a) -{ - data::user *u = data::getCurrentUser(); - u128 uid = u->getUID128(); - - //Device, BCAT, and Cache are hardcoded user IDs in JKSV's data - uint64_t tid = 0; - switch(uid) - { - case 0://This is system - break; - - case 2: - tid = bcatSids[bcatSaveMenu->getSelected()]; - fs::createSaveDataThreaded(FsSaveDataType_Bcat, tid, util::u128ToAccountUID(0)); - break; - - case 3: - tid = devSids[deviceSaveMenu->getSelected()]; - fs::createSaveDataThreaded(FsSaveDataType_Device, tid, util::u128ToAccountUID(0)); - break; - - case 5: - tid = cacheSids[cacheSaveMenu->getSelected()]; - fs::createSaveDataThreaded(FsSaveDataType_Cache, tid, util::u128ToAccountUID(0)); - break; - - default: - tid = accSids[saveCreateMenu->getSelected()]; - fs::createSaveDataThreaded(FsSaveDataType_Account, tid, u->getUID()); - break; - } -} - -static void usrOptCreateAllSaves_t(void *a) -{ - threadInfo *t = (threadInfo *)a; - data::user *u = data::getCurrentUser(); - 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) - { - AccountUid uid = u->getUID(); - for(unsigned i = 0; i < accSids.size(); i++) - fs::createSaveData(FsSaveDataType_Account, accSids[i], uid, t); - } - else if(sel == devPos) - { - for(unsigned i = 0; i < devSids.size(); i++) - fs::createSaveData(FsSaveDataType_Device, devSids[i], util::u128ToAccountUID(0), t); - } - else if(sel == bcatPos) - { - for(unsigned i = 0; i < bcatSids.size(); i++) - fs::createSaveData(FsSaveDataType_Bcat, bcatSids[i], util::u128ToAccountUID(0), t); - } - t->finished = true; -} - -static void usrOptCreateAllSaves(void *a) -{ - data::user *u = data::getCurrentUser(); - 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 -static void initSaveCreateMenus() -{ - saveCreateMenu->reset(); - deviceSaveMenu->reset(); - bcatSaveMenu->reset(); - cacheSaveMenu->reset(); - - accSids.clear(); - devSids.clear(); - bcatSids.clear(); - cacheSids.clear(); - - //Group into vectors to match - for(auto& t : data::titles) - { - NacpStruct *nacp = &t.second.nacp; - - if(nacp->user_account_save_data_size > 0) - accSids.push_back(t.first); - - if(nacp->device_save_data_size > 0) - devSids.push_back(t.first); - - 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) - cacheSids.push_back(t.first); - } - - //Sort them alphabetically - std::sort(accSids.begin(), accSids.end(), sortCreateTIDs); - std::sort(devSids.begin(), devSids.end(), sortCreateTIDs); - std::sort(bcatSids.begin(), bcatSids.end(), sortCreateTIDs); - std::sort(cacheSids.begin(), cacheSids.end(), sortCreateTIDs); - - 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++) - { - deviceSaveMenu->addOpt(NULL, data::getTitleNameByTID(devSids[i])); - deviceSaveMenu->optAddButtonEvent(i, HidNpadButton_A, createSaveData, NULL); - } - - 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++) - { - cacheSaveMenu->addOpt(NULL, data::getTitleNameByTID(cacheSids[i])); - cacheSaveMenu->optAddButtonEvent(i, HidNpadButton_A, createSaveData, NULL); - } -} - -void ui::usrInit() -{ - usrMenu = new ui::menu(50, 16, 0, 112, 1); - usrOptMenu = new ui::menu(8, 32, 390, 20, 6); - saveCreateMenu = new ui::menu(8, 32, 492, 20, 6); - deviceSaveMenu = new ui::menu(8, 32, 492, 20, 6); - bcatSaveMenu = new ui::menu(8, 32, 492, 20, 6); - cacheSaveMenu = new ui::menu(8, 32, 492, 20, 6); - - usrOptMenu->setCallback(usrOptCallback, NULL); - - saveCreateMenu->setActive(false); - saveCreateMenu->setCallback(saveCreateCallback, NULL); - - deviceSaveMenu->setActive(false); - deviceSaveMenu->setCallback(saveCreateCallback, NULL); - - bcatSaveMenu->setActive(false); - bcatSaveMenu->setCallback(saveCreateCallback, NULL); - - cacheSaveMenu->setActive(false); - cacheSaveMenu->setCallback(saveCreateCallback, NULL); - - for(data::user u : data::users) - { - int usrPos = usrMenu->addOpt(u.getUserIcon(), u.getUsername()); - usrMenu->optAddButtonEvent(usrPos, HidNpadButton_A, toTTL, NULL); - } - - sett = util::createIconGeneric(settText, 48, false); - int pos = usrMenu->addOpt(sett, settText); - usrMenu->optAddButtonEvent(pos, HidNpadButton_A, toOPT, NULL); - - ext = util::createIconGeneric(extText, 48, false); - pos = usrMenu->addOpt(ext, extText); - usrMenu->optAddButtonEvent(pos, HidNpadButton_A, toEXT, NULL); - - usrMenu->setOnChangeFunc(onMainChange); - usrMenu->editParam(MENU_RECT_WIDTH, 142); - usrMenu->editParam(MENU_RECT_HEIGHT, 130); - - usrSelPanel = new ui::slideOutPanel(200, 559, 89, ui::SLD_LEFT, _usrSelPanelDraw); - usrSelPanel->setX(0); - ui::registerPanel(usrSelPanel); - usrSelPanel->openPanel(); - - usrOptPanel = new ui::slideOutPanel(410, 720, 0, ui::SLD_RIGHT, usrOptPanelDraw); - ui::registerPanel(usrOptPanel); - - for(int i = 0; i < 4; i++) - usrOptMenu->addOpt(NULL, ui::getUIString("userOptions", i)); - - //Dump All User Saves - usrOptMenu->optAddButtonEvent(0, HidNpadButton_A, usrOptDumpAllUserSaves, NULL); - //Create Save Data - usrOptMenu->optAddButtonEvent(1, HidNpadButton_A, usrOptSaveCreate, usrMenu); - //Create All - usrOptMenu->optAddButtonEvent(2, HidNpadButton_A, usrOptCreateAllSaves, NULL); - //Delete All - usrOptMenu->optAddButtonEvent(3, HidNpadButton_A, usrOptDeleteAllUserSaves, NULL); - usrOptMenu->setActive(false); - - saveCreatePanel = new ui::slideOutPanel(512, 720, 0, ui::SLD_RIGHT, saveCreatePanelDraw); - ui::registerPanel(saveCreatePanel); - - deviceSavePanel = new ui::slideOutPanel(512, 720, 0, ui::SLD_RIGHT, deviceSavePanelDraw); - ui::registerPanel(deviceSavePanel); - - bcatSavePanel = new ui::slideOutPanel(512, 720, 0, ui::SLD_RIGHT, bcatSavePanelDraw); - ui::registerPanel(bcatSavePanel); - - cacheSavePanel = new ui::slideOutPanel(512, 720, 0, ui::SLD_RIGHT, cacheSavePanelDraw); - ui::registerPanel(cacheSavePanel); - - initSaveCreateMenus(); - - usrHelpX = 1220 - gfx::getTextWidth(ui::getUICString("helpUser", 0), 18); -} - -void ui::usrExit() -{ - delete usrSelPanel; - delete usrOptPanel; - delete saveCreatePanel; - delete deviceSavePanel; - delete bcatSavePanel; - delete cacheSavePanel; - - delete usrMenu; - delete usrOptMenu; - delete saveCreateMenu; - delete deviceSaveMenu; - delete bcatSaveMenu; - delete cacheSaveMenu; -} - -void ui::usrRefresh() -{ - initSaveCreateMenus(); -} - -void ui::usrUpdate() -{ - if(usrMenu->getActive()) - { - switch(ui::padKeysDown()) - { - 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(); - usrOptMenu->update(); - saveCreateMenu->update(); - deviceSaveMenu->update(); - bcatSaveMenu->update(); - cacheSaveMenu->update(); -} - -void ui::usrDraw(SDL_Texture *target) -{ - if(ui::mstate == USR_SEL) - gfx::drawTextf(NULL, 18, usrHelpX, 673, &ui::txtCont, ui::getUICString("helpUser", 0)); -} diff --git a/src/util.cpp b/src/util.cpp deleted file mode 100644 index faaefd0..0000000 --- a/src/util.cpp +++ /dev/null @@ -1,420 +0,0 @@ -#include -#include -#include -#include -#include - -#include "file.h" -#include "data.h" -#include "gfx.h" -#include "util.h" -#include "ui.h" -#include "curlfuncs.h" -#include "type.h" -#include "cfg.h" - -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) -{ - for(unsigned i = 0; i < 13; i++) - { - if(t == verboten[i]) - return true; - } - - return false; -} - -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) - _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) -{ - _month = strtoul(tag.substr(0, 2).c_str(), NULL, 10); - _day = strtoul(tag.substr(3, 5).c_str(), NULL, 10); - _year = strtoul(tag.substr(6, 10).c_str(), NULL, 10); -} - -//Missing swkbd config funcs for now -typedef enum -{ - SwkbdPosStart = 0, - SwkbdPosEnd = 1 -} SwkbdInitPos; - -typedef struct -{ - 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) -{ - memset(w, 0, sizeof(*w)); - - utf8_to_utf16(w->read, (uint8_t *)read, (sizeof(w->read) / sizeof(uint16_t)) - 1); - utf8_to_utf16(w->word, (uint8_t *)word, (sizeof(w->word) / sizeof(uint16_t)) - 1); -} - -uint32_t replaceChar(uint32_t c) -{ - switch(c) - { - case L'é': - return 'e'; - break; - } - - return c; -} - -static inline void replaceCharCStr(char *_s, char _find, char _rep) -{ - size_t strLength = strlen(_s); - for(unsigned i = 0; i < strLength; i++) - { - if(_s[i] == _find) - _s[i] = _rep; - } -} - -std::string util::getDateTime(int fmt) -{ - char ret[128]; - - time_t raw; - time(&raw); - tm *Time = localtime(&raw); - - 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); - 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); - break; - - case DATE_FMT_HOYSTE: - sprintf(ret, "%02d.%02d.%04d", Time->tm_mday, Time->tm_mon + 1, Time->tm_year + 1900); - 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); - break; - - case DATE_FMT_ASC: - strcpy(ret, asctime(Time)); - replaceCharCStr(ret, ':', '_'); - replaceCharCStr(ret, '\n', 0x00); - break; - } - - return std::string(ret); -} - -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++) - { - 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) -{ - unsigned last = _path.find_last_of('/', _path.length() - 2); - _path.erase(last + 1, _path.length()); -} - -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) - ++ret; - return ret; -} - -void util::trimPath(std::string& _path, uint8_t _places) -{ - size_t pos = _path.find_first_of('/'); - 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 ret = ""; - for(unsigned i = 0; i < s.length(); ) - { - uint32_t tmpChr = 0; - ssize_t untCnt = decode_utf8(&tmpChr, (uint8_t *)&s.data()[i]); - - i += untCnt; - - tmpChr = replaceChar(tmpChr); - - if(isVerboten(tmpChr)) - ret += ' '; - 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] == '.') - ret.erase(ret.length() - 1, 1); - - return ret; -} - -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::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); - swkbdConfigSetBlurBackground(&swkbd, true); - swkbdConfigSetInitialText(&swkbd, def.c_str()); - swkbdConfigSetHeaderText(&swkbd, head.c_str()); - swkbdConfigSetGuideText(&swkbd, head.c_str()); - swkbdConfigSetInitialCursorPos(&swkbd, SwkbdPosEnd); - swkbdConfigSetType(&swkbd, _type); - swkbdConfigSetStringLenMax(&swkbd, maxLength); - swkbdConfigSetKeySetDisableBitmask(&swkbd, SwkbdKeyDisableBitmask_Backslash | SwkbdKeyDisableBitmask_Percent); - swkbdConfigSetDicFlag(&swkbd, 1); - - if(dictCnt > 0) - { - dictWord words[dictCnt]; - for(unsigned i = 0; i < dictCnt; i++) - swkbdDictWordCreate(&words[i], dictWords[i].c_str(), dictWords[i].c_str()); - - swkbdConfigSetDictionary(&swkbd, (SwkbdDictWord *)words, dictCnt); - } - - char out[maxLength + 1]; - memset(out, 0, maxLength + 1); - swkbdShow(&swkbd, out, maxLength + 1); - swkbdClose(&swkbd); - - return std::string(out); -} - -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(const uint64_t& tid) -{ - data::titleInfo *tmp = data::getTitleInfoByTID(tid); - size_t titleLength = tmp->safeTitle.length(); - - char temp[titleLength + 1]; - memset(temp, 0, titleLength + 1); - memcpy(temp, tmp->safeTitle.c_str(), titleLength); - - std::string ret; - char *tok = strtok(temp, " "); - while(tok) - { - if(isASCII(tok[0])) - ret += tok[0]; - tok = strtok(NULL, " "); - } - return ret; -} - -void util::stripChar(char _c, std::string& _s) -{ - size_t pos = 0; - while((pos = _s.find(_c)) != _s.npos) - _s.erase(pos, 1); -} - -void util::replaceButtonsInString(std::string& rep) -{ - replaceStr(rep, "[A]", "\ue0e0"); - replaceStr(rep, "[B]", "\ue0e1"); - replaceStr(rep, "[X]", "\ue0e2"); - replaceStr(rep, "[Y]", "\ue0e3"); - replaceStr(rep, "[L]", "\ue0e4"); - replaceStr(rep, "[R]", "\ue0e5"); - replaceStr(rep, "[ZL]", "\ue0e6"); - replaceStr(rep, "[ZR]", "\ue0e7"); - replaceStr(rep, "[SL]", "\ue0e8"); - replaceStr(rep, "[SR]", "\ue0e9"); - replaceStr(rep, "[DPAD]", "\ue0ea"); - replaceStr(rep, "[DUP]", "\ue0eb"); - replaceStr(rep, "[DDOWN]", "\ue0ec"); - replaceStr(rep, "[DLEFT]", "\ue0ed"); - replaceStr(rep, "[DRIGHT]", "\ue0ee"); - replaceStr(rep, "[+]", "\ue0ef"); - replaceStr(rep, "[-]", "\ue0f0"); -} - -SDL_Texture *util::createIconGeneric(const char *txt, int fontSize, bool clearBack) -{ - SDL_Texture *ret = gfx::texMgr->textureCreate(256, 256); - SDL_SetRenderTarget(gfx::render, ret); - if(clearBack) - { - SDL_SetRenderDrawColor(gfx::render, ui::rectLt.r, ui::rectLt.g, ui::rectLt.b, ui::rectLt.a); - SDL_RenderClear(gfx::render); - } - else - gfx::clearTarget(ret, &ui::transparent); - - unsigned int x = 128 - (gfx::getTextWidth(txt, fontSize) / 2); - unsigned int y = 128 - (fontSize / 2); - gfx::drawTextf(ret, fontSize, x, y, &ui::txtCont, txt); - SDL_SetRenderTarget(gfx::render, NULL); - SDL_SetTextureBlendMode(ret, SDL_BLENDMODE_BLEND); - return ret; -} - -void util::sysBoost() -{ - if(R_FAILED(clkrstInitialize())) - return; - - ClkrstSession cpu, gpu, ram; - clkrstOpenSession(&cpu, PcvModuleId_CpuBus, 3); - 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(); - -} - -void util::checkForUpdate(void *a) -{ - threadInfo *t = (threadInfo *)a; - t->status->setStatus(ui::getUICString("threadStatusCheckingForUpdate", 0)); - 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; - return; - } - - std::string tagStr; - unsigned month, day, year; - json_object *jobj = json_tokener_parse(gitJson.c_str()), *tag; - json_object_object_get_ex(jobj, "tag_name", &tag); - 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) - { - t->status->setStatus(ui::getUICString("threadStatusDownloadingUpdate", 0)); - //dunno about NSP yet... - json_object *assets, *asset0, *dlUrl; - json_object_object_get_ex(jobj, "assets", &assets); - asset0 = json_object_array_get_idx(assets, 0); - json_object_object_get_ex(asset0, "browser_download_url", &dlUrl); - - std::vector jksvBuff; - std::string url = json_object_get_string(dlUrl); - curlFuncs::getBinURL(&jksvBuff, url); - FILE *jksvOut = fopen("sdmc:/switch/JKSV.nro", "wb"); - fwrite(jksvBuff.data(), 1, jksvBuff.size(), jksvOut); - fclose(jksvOut); - } - else - ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("onlineNoUpdates", 0)); - - json_object_put(jobj); - t->finished = true; -} - -std::string util::getSizeString(const uint64_t& _size) -{ - char sizeStr[32]; - if(_size >= 0x40000000) - sprintf(sizeStr, "%.2fGB", (float)_size / 1024.0f / 1024.0f / 1024.0f); - else if(_size >= 0x100000) - sprintf(sizeStr, "%.2fMB", (float)_size / 1024.0f / 1024.0f); - 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); -} diff --git a/src/webdav.cpp b/src/webdav.cpp deleted file mode 100644 index da9b5f0..0000000 --- a/src/webdav.cpp +++ /dev/null @@ -1,339 +0,0 @@ -#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::WebDav::getListWithParent(const std::string& _parentId) { - std::vector 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 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::WebDav::parseXMLResponse(const std::string& xml) { - std::vector 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; -}