From 4874d0c120b4982aba45d775ead45a19b12f3c6f Mon Sep 17 00:00:00 2001 From: J-D-K Date: Sun, 20 Jul 2025 15:26:03 -0400 Subject: [PATCH] More refactoring. Add error:: --- include/JKSV.hpp | 25 +- include/appstates/BackupMenuState.hpp | 32 ++- include/appstates/BaseTask.hpp | 7 +- include/appstates/ProgressState.hpp | 7 +- include/appstates/SettingsState.hpp | 25 +- include/appstates/TaskState.hpp | 8 +- include/data/TitleInfo.hpp | 18 +- include/data/User.hpp | 68 ++--- include/data/accountUID.hpp | 4 +- include/data/data.hpp | 1 + include/error.hpp | 12 + include/strings.hpp | 59 ++-- include/system/ProgressTask.hpp | 5 +- include/tasks/backup.hpp | 29 ++ romfs/Text/ENUS.json | 382 ++++++++++++------------- source/JKSV.cpp | 197 ++++++------- source/appstates/BackupMenuState.cpp | 279 ++++++++---------- source/appstates/BaseTask.cpp | 22 +- source/appstates/ExtrasMenuState.cpp | 6 +- source/appstates/ProgressState.cpp | 38 +-- source/appstates/SaveCreateState.cpp | 41 +-- source/appstates/SettingsState.cpp | 239 ++++++---------- source/appstates/TaskState.cpp | 29 +- source/appstates/TitleInfoState.cpp | 129 +++------ source/appstates/TitleOptionState.cpp | 128 ++++----- source/appstates/UserOptionState.cpp | 108 +++---- source/data/TitleInfo.cpp | 392 +++++++------------------- source/data/User.cpp | 347 ++++++++--------------- source/data/data.cpp | 320 ++++++++------------- source/error.cpp | 50 ++++ source/fs/io.cpp | 29 +- source/fs/zip.cpp | 80 ++---- source/remote/GoogleDrive.cpp | 247 +++++----------- source/remote/remote.cpp | 26 +- source/strings.cpp | 62 ++-- source/tasks/backup.cpp | 9 + 36 files changed, 1408 insertions(+), 2052 deletions(-) create mode 100644 include/error.hpp create mode 100644 include/tasks/backup.hpp create mode 100644 source/error.cpp create mode 100644 source/tasks/backup.cpp diff --git a/include/JKSV.hpp b/include/JKSV.hpp index cfadab3..c916323 100644 --- a/include/JKSV.hpp +++ b/include/JKSV.hpp @@ -27,11 +27,30 @@ class JKSV private: /// @brief Whether or not initialization was successful and JKSV is still running. - bool m_isRunning = false; + bool m_isRunning{}; /// @brief Whether or not to print the translation credits. - bool m_showTranslationInfo = false; + bool m_showTranslationInfo{}; /// @brief JKSV icon in upper left corner. - sdl::SharedTexture m_headerIcon = nullptr; + sdl::SharedTexture m_headerIcon{}; + + /// @brief These are pointers to the translation info. + const char *m_translation{}; + const char *m_author{}; + + /// @brief Initializes fslib and takes care of a few other things. + bool initialize_filesystem(); + + /// @brief Initializes the services JKSV uses. + bool initialize_services(); + + // Creates the needed directories on SD. + bool create_directories(); + + /// @brief Adds the text color changing characters. + void add_color_chars(); + + /// @brief Exits all services. + void exit_services(); }; diff --git a/include/appstates/BackupMenuState.hpp b/include/appstates/BackupMenuState.hpp index 0476f4e..59d15f6 100644 --- a/include/appstates/BackupMenuState.hpp +++ b/include/appstates/BackupMenuState.hpp @@ -36,6 +36,19 @@ class BackupMenuState final : public BaseState void save_data_written(); // clang-format off + enum class MenuEntryType + { + Null, + Local, + Remote + }; + + struct MenuEntry + { + MenuEntryType type; + int index; + }; + struct DataStruct { data::User *user{}; @@ -45,6 +58,9 @@ class BackupMenuState final : public BaseState }; // clang-format on + // This makes some things elsewhere easier to type. + using TaskData = std::shared_ptr; + private: /// @brief Pointer to current user. data::User *m_user{}; @@ -70,6 +86,9 @@ class BackupMenuState final : public BaseState /// @brief Data struct passed to functions. std::shared_ptr m_dataStruct{}; + /// @brief This keeps track of the properties of the entries in the menu. + std::vector m_menuEntries{}; + /// @brief This is a pointer to the control guide string. const char *m_controlGuide{}; @@ -89,7 +108,7 @@ class BackupMenuState final : public BaseState void name_and_create_backup(); /// @brief This is the function called when a backup is selected to be overwritten. - void confirm_backup_overwrite(); + void confirm_overwrite(); /// @brief This function is called to confirm restoring a backup. void confirm_restore(); @@ -97,6 +116,12 @@ class BackupMenuState final : public BaseState /// @brief Function called to confirm deleting a backup. void confirm_delete(); + /// @brief Uploads the currently selected backup to the remote storage. + void upload_backup(); + + /// @brief Just creates the pop-up that says Save is empty or w/e. + void pop_save_empty(); + /// @brief Initializes the static members all instances share if they haven't been already. void initialize_static_members(); @@ -111,4 +136,9 @@ class BackupMenuState final : public BaseState /// @brief Checks to see if the save data is empty. void save_data_check(); + + inline bool is_system_save_data() + { + return m_saveType == FsSaveDataType_System || m_saveType == FsSaveDataType_SystemBcat; + } }; diff --git a/include/appstates/BaseTask.hpp b/include/appstates/BaseTask.hpp index df248d2..6f4d7bc 100644 --- a/include/appstates/BaseTask.hpp +++ b/include/appstates/BaseTask.hpp @@ -1,10 +1,11 @@ #pragma once #include "appstates/BaseState.hpp" +#include "system/Task.hpp" #include "system/Timer.hpp" #include "ui/ColorMod.hpp" #include -#include +#include /// @brief Normally, I wouldn't do this, but this holds a single function both TaskState and ProgressState share... class BaseTask : public BaseState @@ -27,6 +28,10 @@ class BaseTask : public BaseState /// @note This is mostly just so users don't think JKSV has frozen when operations take a long time. void render_loading_glyph(); + protected: + /// @brief Underlying system task. This needs to be allocated by the derived classes. + std::unique_ptr m_task{}; + private: /// @brief This is the current frame of the loading glyph animation. int m_currentFrame{}; diff --git a/include/appstates/ProgressState.hpp b/include/appstates/ProgressState.hpp index 907cfa2..09ede15 100644 --- a/include/appstates/ProgressState.hpp +++ b/include/appstates/ProgressState.hpp @@ -16,7 +16,9 @@ class ProgressState final : public BaseTask template ProgressState(void (*function)(sys::ProgressTask *, Args...), Args... args) : BaseTask() - , m_task(function, std::forward(args)...){}; + { + m_task = std::make_unique(function, std::forward(args)...); + } /// @brief Required destructor. ~ProgressState() {}; @@ -28,9 +30,6 @@ class ProgressState final : public BaseTask void render() override; private: - /// @brief Underlying task that has extra methods for tracking the progress of a task. - sys::ProgressTask m_task; - /// @brief Progress which is saved as a rounded whole number. size_t m_progress{}; diff --git a/include/appstates/SettingsState.hpp b/include/appstates/SettingsState.hpp index 29fa30d..501f3a8 100644 --- a/include/appstates/SettingsState.hpp +++ b/include/appstates/SettingsState.hpp @@ -23,15 +23,36 @@ class SettingsState final : public BaseState /// @brief Menu for selecting and toggling settings. ui::Menu m_settingsMenu; - /// @brief Render target to render to. - sdl::SharedTexture m_renderTarget{}; + /// @brief Pointer to the control guide string. + const char *m_controlGuide{}; + + // These are pointers to strings this state uses constantly. + const char *m_onOff[2]{}; + const char *m_sortTypes[3]{}; /// @brief X coordinate of the control guide in the bottom right corner. int m_controlGuideX{}; + /// @brief Render target to render to. + sdl::SharedTexture m_renderTarget{}; + /// @brief Runs a routine to update the menu strings for the menu. void update_menu_options(); /// @brief Toggles or executes the code to changed the selected menu option. void toggle_options(); + + void cycle_zip_level(); + + void cycle_sort_type(); + + void toggle_jksm_mode(); + + void cycle_anim_scaling(); + + // Returns On/Off depending on the value passed. + const char *get_status_text(uint8_t value); + + /// @brief Returns the sort type depending on the value passed. + const char *get_sort_type_text(uint8_t value); }; diff --git a/include/appstates/TaskState.hpp b/include/appstates/TaskState.hpp index e2b343a..7ecd991 100644 --- a/include/appstates/TaskState.hpp +++ b/include/appstates/TaskState.hpp @@ -15,7 +15,9 @@ class TaskState final : public BaseTask template TaskState(void (*function)(sys::Task *, Args...), Args... args) : BaseTask() - , m_task(function, std::forward(args)...){}; + { + m_task = std::make_unique(function, std::forward(args)...); + } /// @brief Required destructor. ~TaskState() {}; @@ -26,8 +28,4 @@ class TaskState final : public BaseTask /// @brief Run render routine. Prints m_task's status string to screen, basically. /// @param void render() override; - - private: - /// @brief Underlying task. - sys::Task m_task; }; diff --git a/include/data/TitleInfo.hpp b/include/data/TitleInfo.hpp index c913dba..80e5151 100644 --- a/include/data/TitleInfo.hpp +++ b/include/data/TitleInfo.hpp @@ -1,5 +1,6 @@ #pragma once #include "sdl.hpp" + #include #include #include @@ -19,6 +20,13 @@ namespace data /// @param controlData Reference to the control data to init from. TitleInfo(uint64_t applicationID, NsApplicationControlData &controlData); + /// @brief Move constructor and operator. + TitleInfo(TitleInfo &&titleInfo); + TitleInfo &operator=(TitleInfo &&TitleInfo); + + // None of this nonesense around these parts.TitleInfo(const TitleInfo &) = delete; + TitleInfo &operator=(const TitleInfo &) = delete; + /// @brief Returns the application ID of the title. /// @return Title's application ID. uint64_t get_application_id() const; @@ -85,19 +93,19 @@ namespace data static inline constexpr size_t SIZE_PATH_SAFE = 0x200; /// @brief Stores application ID for easier grabbing since JKSV is all pointers. - uint64_t m_applicationID = 0; + uint64_t m_applicationID{}; /// @brief This contains the NACP and the icon. - NsApplicationControlData m_data; + std::unique_ptr m_data{}; /// @brief Saves whether or not the title has control data. - bool m_hasData = false; + bool m_hasData{}; /// @brief This is the path safe version of the title. - char m_pathSafeTitle[TitleInfo::SIZE_PATH_SAFE] = {0}; + char m_pathSafeTitle[TitleInfo::SIZE_PATH_SAFE]{}; /// @brief Shared icon texture. - sdl::SharedTexture m_icon = nullptr; + sdl::SharedTexture m_icon{}; /// @brief Private function to get/create the path safe title. void get_create_path_safe_title(); diff --git a/include/data/User.hpp b/include/data/User.hpp index 7cad2a6..8f4f6f6 100644 --- a/include/data/User.hpp +++ b/include/data/User.hpp @@ -1,6 +1,7 @@ #pragma once #include "fslib.hpp" #include "sdl.hpp" + #include #include #include @@ -8,7 +9,8 @@ namespace data { - /// @brief Type used to store save info and play statistics in the vector. Vector is used to preserve the order since I can't use a map without having to extra heap allocate it. + /// @brief Type used to store save info and play statistics in the vector. Vector is used to preserve the order since I + /// can't use a map without having to extra heap allocate it. using UserDataEntry = std::pair>; /// @brief Type definition for the user save info/play stats vector. @@ -28,10 +30,15 @@ namespace data /// @param pathSafeNickname The path safe version of the save data since JKSV is in everything the Switch supports. /// @param iconPath Path to the icon to load for account. /// @param saveType Save data type of user. - User(AccountUid accountID, - std::string_view nickname, - std::string_view pathSafeNickname, - FsSaveDataType saveType); + User(AccountUid accountID, std::string_view nickname, std::string_view pathSafeNickname, FsSaveDataType saveType); + + /// @brief Move constructor and operator. + User(User &&user); + User &operator=(User &&user); + + // Non of this around these parts. + User(const User &) = delete; + User &operator=(const User &) = delete; /// @brief Pushes data to m_userData /// @param saveInfo SaveDataInfo. @@ -48,88 +55,63 @@ namespace data /// @brief Runs the sort algo on the vector. void sort_data(); - /// @brief Returns the account ID of the user. - /// @return AccountID AccountUid get_account_id() const; - /// @brief Returns the save data type the account uses. - /// @return Save data type of the account. FsSaveDataType get_account_save_type() const; - /// @brief Returns the account's nickname. - /// @return Account nickname. const char *get_nickname() const; - /// @brief Returns the path safe version of the nickname. - /// @return Path safe nickname. const char *get_path_safe_nickname() const; - /// @brief Returns the total number of entries in the data vector. - /// @return Total number of entries. size_t get_total_data_entries() const; /// @brief Returns the application ID of the title at index. - /// @param index Index of title. - /// @return Application ID if index is valid. 0 if not. uint64_t get_application_id_at(int index) const; /// @brief Returns a pointer to the save data info at index. - /// @param index Index of data to fetch. - /// @return Pointer to info if valid. nullptr if out-of-bounds. FsSaveDataInfo *get_save_info_at(int index); /// @brief Returns a pointer to the play statistics at index. - /// @param index Index of play statistics to fetch. - /// @return Pointer to play statistics if index is value. nullptr if it's out of bounds. PdmPlayStatistics *get_play_stats_at(int index); /// @brief Returns a pointer to the save info of applicationID. - /// @param applicationID Application ID to search and fetch for. - /// @return Pointer to save info if found. nullptr if not. FsSaveDataInfo *get_save_info_by_id(uint64_t applicationID); - /// @brief Returns a reference to the user save data info vector. - /// @return Reference to the user save info vector. data::UserSaveInfoList &get_user_save_info_list(); /// @brief Returns a pointer to the play statistics of applicationID - /// @param applicationID Application ID to search and fetch. - /// @return Pointer to play statistics if index is valid. nullptr if it isn't. PdmPlayStatistics *get_play_stats_by_id(uint64_t applicationID); - /// @brief Returns raw SDL_Texture pointer of icon. - /// @return SDL_Texture of icon. SDL_Texture *get_icon(); - /// @brief Returns the shared texture of icon. Increasing reference count of it. - /// @return Shared icon texture. sdl::SharedTexture get_shared_icon(); /// @brief Erases a UserDataEntry according to the application ID passed. /// @param applicationID ID of the save to erase. void erase_save_info_by_id(uint64_t applicationID); - /// @brief Loads the save data info and play statistics for the current user using the information passed to the constructor. + /// @brief Loads the save data info and play statistics for the current user using the information passed to the + /// constructor. void load_user_data(); private: /// @brief Account's ID - AccountUid m_accountID; + AccountUid m_accountID{}; /// @brief Type of save data account uses. - FsSaveDataType m_saveType; + FsSaveDataType m_saveType{}; /// @brief User's nickname. - char m_nickname[0x20] = {0}; + char m_nickname[0x20]{}; /// @brief Path safe version of nickname. - char m_pathSafeNickname[0x20] = {0}; + char m_pathSafeNickname[0x20]{}; /// @brief User's icon. - sdl::SharedTexture m_icon = nullptr; + sdl::SharedTexture m_icon{}; /// @brief Vector containing save info and play statistics. - data::UserSaveInfoList m_userData; + data::UserSaveInfoList m_userData{}; /// @brief Loads account structs from system. /// @param profile AccountProfile struct to write to. @@ -139,10 +121,10 @@ namespace data /// @brief Creates a placeholder since something went wrong. void create_account(); - /// @brief Opens a save data info reader according to the data passed to the user. - /// @param spaceID The FsSaveDataSpaceId to use when opening the reader. - /// @param reader Reference to the reader to use to open. - /// @note I added this so the load_user_data function would be easier to follow. - bool open_save_info_reader(FsSaveDataSpaceId spaceID, fslib::SaveInfoReader &reader); + /// @brief Attempts to locate the data associated with applicationID + data::UserSaveInfoList::iterator find_title_by_id(uint64_t applicationID); + + /// @brief Returns whether or not the index is within bounds. + inline bool index_check(int index) const { return index >= 0 && index < static_cast(m_userData.size()); } }; } // namespace data diff --git a/include/data/accountUID.hpp b/include/data/accountUID.hpp index d8d8fb1..03e051e 100644 --- a/include/data/accountUID.hpp +++ b/include/data/accountUID.hpp @@ -7,7 +7,6 @@ namespace data static constexpr AccountUid BLANK_ACCOUNT_ID = {0}; } // namespace data - /// @brief Allows comparison of AccountUids since devkitpro decided a struct with two uint64_t's is better than u128 /// @param accountIDA First account to compare. /// @param accountIDB Second account to compare. @@ -21,7 +20,8 @@ static inline bool operator==(AccountUid accountIDA, AccountUid accountIDB) /// @param accountIDA AccountUid to compare. /// @param accountIDB Number to compare. /// @return True if they match. False if they don't. -/// @note I'm not 100% sure which uint64_t in the AccountUid struct comes first. I don't know if it's [0][1] or [1][0]. To do: Figure that out. +/// @note I'm not 100% sure which uint64_t in the AccountUid struct comes first. I don't know if it's [0][1] or [1][0]. To do: +/// Figure that out. static inline bool operator==(AccountUid accountIDA, u128 accountIDB) { return accountIDA.uid[0] == (accountIDB >> 64 & 0xFFFFFFFFFFFFFFFF) && diff --git a/include/data/data.hpp b/include/data/data.hpp index af2da14..5f0cff2 100644 --- a/include/data/data.hpp +++ b/include/data/data.hpp @@ -2,6 +2,7 @@ #include "data/TitleInfo.hpp" #include "data/User.hpp" #include "data/accountUID.hpp" + #include #include diff --git a/include/error.hpp b/include/error.hpp new file mode 100644 index 0000000..4769005 --- /dev/null +++ b/include/error.hpp @@ -0,0 +1,12 @@ +#pragma once +#include +#include + +namespace error +{ + /// @brief Logs and returns if a call from libnx fails. + bool libnx(Result code, const std::source_location &location = std::source_location::current()); + + /// @brief Logs and returns if an fslib function fails. + bool fslib(bool result, const std::source_location &location = std::source_location::current()); +} diff --git a/include/strings.hpp b/include/strings.hpp index 699a528..2d8a741 100644 --- a/include/strings.hpp +++ b/include/strings.hpp @@ -12,35 +12,34 @@ namespace strings // Names of strings to prevent typos. namespace names { - static constexpr std::string_view TRANSLATION_INFO = "TranslationInfo"; - static constexpr std::string_view CONTROL_GUIDES = "ControlGuides"; - static constexpr std::string_view SAVE_DATA_TYPES = "SaveDataTypes"; - static constexpr std::string_view SETTINGS_MENU = "SettingsMenu"; - static constexpr std::string_view EXTRAS_MENU = "ExtrasMenu"; - static constexpr std::string_view EXTRAS_POP_MESSAGES = "ExtrasPopMessages"; - static constexpr std::string_view YES_NO = "YesNo"; - static constexpr std::string_view HOLDING_STRINGS = "HoldingStrings"; - static constexpr std::string_view ON_OFF = "OnOff"; - static constexpr std::string_view BACKUP_MENU = "BackupMenu"; - static constexpr std::string_view COPYING_FILES = "CopyingFiles"; - static constexpr std::string_view BACKUPMENU_CONFIRMATIONS = "BackupMenuConfirmations"; - static constexpr std::string_view BACKUPMENU_STATUS = "BackupMenuStatus"; - static constexpr std::string_view BACKUPMENU_POPS = "BackupMenuPops"; - static constexpr std::string_view DELETING_FILES = "DeletingFiles"; - static constexpr std::string_view KEYBOARD_STRINGS = "KeyboardStrings"; - static constexpr std::string_view USER_OPTIONS = "UserOptions"; - static constexpr std::string_view USER_OPTION_CONFIRMATIONS = "UserOptionConfirmations"; - static constexpr std::string_view USER_OPTION_STATUS = "UserOptionStatus"; - static constexpr std::string_view TITLE_OPTIONS = "TitleOptions"; - static constexpr std::string_view TITLE_OPTION_STATUS = "TitleOptionStatus"; - static constexpr std::string_view TITLE_OPTION_POPS = "TitleOptionPops"; - static constexpr std::string_view TITLE_OPTION_CONFIRMATIONS = "TitleOptionConfirmations"; - static constexpr std::string_view TITLE_INFO_STRINGS = "TitleInfo"; - static constexpr std::string_view POP_MESSAGES_GENERAL = "PopMessagesGeneral"; - static constexpr std::string_view POP_MESSAGES_BACKUP_MENU = "PopMessagesBackupMenu"; - static constexpr std::string_view POP_MESSAGES_SAVE_CREATE = "PopMessagesSaveCreate"; - static constexpr std::string_view POP_MESSAGES_TITLE_OPTIONS = "PopMessagesTitleOptions"; - static constexpr std::string_view GOOGLE_DRIVE_STRINGS = "GoogleDriveStrings"; - static constexpr std::string_view WEBDAV_STRINGS = "WebDavStrings"; + static constexpr std::string_view BACKUPMENU_MENU = "BackupMenu"; + static constexpr std::string_view BACKUPMENU_CONFS = "BackupMenuConfirmations"; + static constexpr std::string_view BACKUPMENU_POPS = "BackupMenuPops"; + static constexpr std::string_view BACKUPMENU_STATUS = "BackupMenuStatus"; + static constexpr std::string_view CONTROL_GUIDES = "ControlGuides"; + static constexpr std::string_view EXTRASMENU_MENU = "ExtrasMenu"; + static constexpr std::string_view EXTRASMENU_POPS = "ExtrasPops"; + static constexpr std::string_view GENERAL_POPS = "GeneralPops"; + static constexpr std::string_view GOOGLE_DRIVE = "GoogleDriveStrings"; + static constexpr std::string_view HOLDING_STRINGS = "HoldingStrings"; + static constexpr std::string_view IO_STATUSES = "IOStatuses"; + static constexpr std::string_view KEYBOARD = "KeyboardStrings"; + static constexpr std::string_view ON_OFF = "OnOff"; + static constexpr std::string_view SAVECREATE_POPS = "SaveCreatePops"; + static constexpr std::string_view SAVE_DATA_TYPES = "SaveDataTypes"; + static constexpr std::string_view SETTINGS_DESCRIPTIONS = "SettingsDescriptions"; + static constexpr std::string_view SETTINGS_MENU = "SettingsMenu"; + static constexpr std::string_view SORT_TYPES = "SortTypes"; + static constexpr std::string_view TITLEINFO = "TitleInfo"; + static constexpr std::string_view TITLEOPTION_CONFS = "TitleOptionConfirmations"; + static constexpr std::string_view TITLEOPTION_POPS = "TitleOptionPops"; + static constexpr std::string_view TITLEOPTION_STATUS = "TitleOptionStatus"; + static constexpr std::string_view TITLEOPTION = "TitleOptions"; + static constexpr std::string_view TRANSLATION = "TranslationInfo"; + static constexpr std::string_view USEROPTION_CONFS = "UserOptionConfirmations"; + static constexpr std::string_view USEROPTION_STATUS = "UserOptionStatus"; + static constexpr std::string_view USEROPTION_MENU = "UserOptions"; + static constexpr std::string_view WEBDAV = "WebDavStrings"; + static constexpr std::string_view YES_NO = "YesNo"; } // namespace names } // namespace strings diff --git a/include/system/ProgressTask.hpp b/include/system/ProgressTask.hpp index 7bd001e..6af2361 100644 --- a/include/system/ProgressTask.hpp +++ b/include/system/ProgressTask.hpp @@ -4,7 +4,7 @@ namespace sys { /// @brief Derived class of Task that has methods for tracking progress. - class ProgressTask : public sys::Task + class ProgressTask final : public sys::Task { public: /// @brief Contstructs a new ProgressTask @@ -33,6 +33,7 @@ namespace sys private: // Current value and goal - double m_current, m_goal; + double m_current{}; + double m_goal{}; }; } // namespace sys diff --git a/include/tasks/backup.hpp b/include/tasks/backup.hpp new file mode 100644 index 0000000..6573a83 --- /dev/null +++ b/include/tasks/backup.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "appstates/BackupMenuState.hpp" +#include "system/ProgressTask.hpp" +#include "system/Task.hpp" + +namespace tasks +{ + namespace backup + { + /// @brief Task/thread function executed when a new backup is created. + void create_new_backup(sys::ProgressTask *task, + data::User *user, + data::TitleInfo *titleInfo, + fslib::Path target, + BackupMenuState *spawningState); + + /// @brief Overwrites a pre-existing backup. + void overwrite_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData); + + /// @brief Restores a backup + void restore_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData); + + /// @brief Deletes a backup + void delete_backup(sys::Task *task, BackupMenuState::TaskData taskData); + + /// @brief Uploads a backup + void upload_backup(sys::ProgressTask *task, BackupMenuState::TaskData taskData); + } +} diff --git a/romfs/Text/ENUS.json b/romfs/Text/ENUS.json index a90bbfb..04d2745 100644 --- a/romfs/Text/ENUS.json +++ b/romfs/Text/ENUS.json @@ -1,208 +1,208 @@ { - "TranslationInfo": [ - "Translated By: %s", - "NULL" - ], - "ControlGuides": [ - "[A] Select [Y] Dump All Saves [X] User Options", - "[A] Select [L] [R] Jump [Y] Favorite [X] Title Options [B] Back", - "[A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close", - "[A] Toggle [X] Defaults [B] Back" - ], - "SaveDataTypes": [ - "System", - "Account", - "BCAT", - "Device", - "Temporary", - "Cache", - "System BCAT" - ], - "SettingsMenu": [ - "Set JKSV output folder.", - "Edit Blacklist", - "Include Device Saves with users: %s", - "Auto-backup on restore: %s", - "Auto-name backups: %s", - "Auto-upload backups to remote storage: %s", - "Hold to delete backups: %s", - "Hold to restore backups: %s", - "Hold to overwrite backups: %s", - "Only list mountable titles: %s", - "Show account system saves: %s", - "Enable writing to system saves and NAND: %s", - "Export saves to ZIP: %s", - "Zip compression level: %u", - "Title sort type: %s", - "Text menu (JKSM) mode: %s", - "Force English: %s", - "Enable trash bin: %s", - "Animation scaling: %.02f" - ], - "SettingsDescriptions": [ - "Sets the working directory for JKSV. The default value for this is `sdmc:/JKSV`.", - "Allows you to remove titles from the blacklist.", - "Includes device, or shared saves, with users.", - "Auto-names backups and skips the keyboard.", - "Automatically uploads backups to Google Drive or WebDav and deletes them locally.", - "Whether or not holding [A] for three seconds is required to delete backups.", - "Whether or not holding [A] for three seconds is required to restore backups.", - "Whether or not holding [A] for three seconds is required to overwrite backups.", - "Only shows save data JKSV can successfully open.", - "Shows system saves that have an account ID tied to them.", - "Enables restoring system saves and writing to NAND partitions.", - "Exports save data to ZIP archives instead of unpacked folders.", - "Compression or deflate level used when writing to ZIP. The default value is 6. Lower values are faster, but offer less compression and space savings. Zero is store, or no compression.", - "Controls the way titles are sorted and displayed.", - "Displays titles as text menus like the original JKSM on 3DS instead of icon grids.", - "Forces English to be used as the language instead of the detected system language.", - "Moves deleted backups to the _TRASH_ folder instead of permanently deleting them.", - "Sets the speed at which transitions and animations occur. Lower is faster." - ], - "ExtrasMenu": [ - "Reinitialize Data", - "SD to SD Browser", - "BIS: ProdInfoF", - "BIS: Safe", - "BIS: System", - "BIS: User", - "Terminate Process" - ], - "ExtrasPopMessages": [ - "Data reinitialized!", - "Data reinitialization failed" - ], - "YesNo": [ - "Yes [A]", - "No [B]" - ], - "HoldingStrings": [ - "Hold [A]", - "Keep Holding [A]", - "Almost There! [A]" - ], - "OnOff": [ - "Off", - ">On>" - ], "BackupMenu": [ - "New Backup" - ], - "CopyingFiles": [ - "Copying #%s#...", - "Compressing #%s# to ZIP...", - "Decompressing #%s# from ZIP..." + "0: New Backup" ], "BackupMenuConfirmations": [ - "Are you sure you really want to overwrite #%s#?", - "Are you sure you really want to restore #%s#?", - "Are you sure you really want to delete #%s#?" - ], - "BackupMenuStatus": [ - "Processing save data meta file...", - "Uploading #%s# to remote storage..." + "0: Are you sure you really want to overwrite #%s#?", + "1: Are you sure you really want to restore #%s#?", + "2: Are you sure you really want to delete #%s#?" ], "BackupMenuPops": [ - "Writing to system data is disabled!" + "0: Save data is empty!", + "1: Backup is empty!", + "2: Error resetting save data!", + "3: Error opening ZIP file for reading!", + "4: Error occurred deleting backup!", + "5: Error creating backup!", + "6: Writing to system is disabled!" ], - "DeletingFiles": [ - "Deleting #%s#..." + "BackupMenuStatus": [ + "0: Processing save data meta file...", + "1: Uploading #%s# to remote storage..." ], - "KeyboardStrings": [ - "Enter a new backup name.", - "Enter cache index.", - "Enter a new output path for JKSV", - "Enter process ID to terminate.", - "Enter a system save ID", - "Enter a new name for the target item.", - "Enter a name for the new folder.", - "Enter a new output folder name for %s.", - "Enter how much to expand (in MB)." + "ControlGuides": [ + "0: [A] Select [Y] Dump All Saves [X] User Options", + "1: [A] Select [L] [R] Jump [Y] Favorite [X] Title Options [B] Back", + "2: [A] Select [Y] Restore [X] Delete [ZR] Upload [B] Close", + "3: [A] Toggle [X] Defaults [B] Back" ], - "UserOptions": [ - "Dump all for `%s`", - "Create Save Data for `%s`", - "Create All Save Data for `%s`", - "Delete All Save Data for `%s`" + "ExtrasMenu": [ + "0: Reinitialize Data", + "1: SD to SD Browser", + "2: ProdInfoF", + "3: Safe", + "4: System", + "5: User", + "6: Terminate Process" ], - "UserOptionConfirmations": [ - "Are you sure you want to backup the save data for every title found for `%s`? This can take a while.", - "Are you sure you want to create save data for all titles found on your system for `%s`? This can take a while.", - "Are you sure you want to delete all of the save data for `%s`? This is *PERMANENT* and can't be undone." + "ExtrasPops": [ + "0: Data reinitialized!", + "1: Data reinitialization failed" ], - "UserOptionStatus": [ - "Creating save data for #%s#...", - "Deleting save data for #%s#..." - ], - "TitleOptions": [ - "Information", - "Blacklist Title", - "Change Output folder", - "Open in File Mode", - "Delete all save backups", - "Reset save data.", - "Delete save data from system", - "Extend save data", - "Export SVI file" - ], - "TitleOptionStatus": [ - "Deleting all backups for #%s#.", - "Resetting save data for #%s#.", - "Deleting #%s#'s save data for #%s#...", - "Extending `%s`'s save data for #%s#..." - ], - "TitleOptionPops": [ - "All backups deleted for `%s`!", - "Failed to delete all backups!", - "Error resetting save data!", - "Save data successfully reset!", - "SVI file exported successfully!", - "Error exporting SVI file!", - "This option is unavailable for system saves!" - ], - "TitleOptionConfirmations": [ - "Are you sure you want to add #%s# to your blacklist? Once you do this, it will no longer appear on any title list or selection.", - "Are you sure you would like to delete all of the current save backups for #%s#? *This cannot be undone!*", - "Are you sure you would like to reset the save data for #%s#? *This will delete the current save data for the title as if it were never run!*", - "Are you sure you want to delete `%s`'s save data for #%s#? *This will permanently delete it from the system.*" - ], - "TitleInfo": [ - "App ID: %016lX", - "Save ID: %016lx", - "First Played: %x - %X", - "Last Played: %x - %X", - "Play Time: %02d:%02d:%02d", - "Launches: %i", - "Save Type: %s" - ], - "PopMessagesGeneral": [ - "Unable to exit JKSV while tasks are running!" - ], - "PopMessagesBackupMenu": [ - "Save data is empty!", - "Backup is empty!", - "Error resetting save data!", - "Error opening ZIP file for reading!", - "Error occurred deleting backup!", - "Error creating backup!" - ], - "PopMessagesSaveCreate": [ - "Save data created for #%s#!", - "Error creating save data!", - "Error deleting save data!" - ], - "PopMessagesTitleOptions": [ - "Could not sanitize path for use!", - "Output folder set to #%s#.", - "Error setting new output path!" + "GeneralPops": [ + "0: Unable to exit JKSV while tasks are running!" ], "GoogleDriveStrings": [ - "To continue, go to #%s# and enter >%s>!", - "Successfully signed in to Google Drive!", - "Google Drive sign in failed!" + "0: To continue, go to #%s# and enter >%s>!", + "1: Successfully signed in to Google Drive!", + "2: Google Drive sign in failed!" + ], + "HoldingStrings": [ + "0: Hold [A]", + "1: Keep Holding [A]", + "2: Almost There! [A]" + ], + "IOStatuses": [ + "0: Copying #%s#...", + "1: Compressing #%s# to ZIP...", + "2: Decompressing #%s# from ZIP...", + "3: Deleting #%s#..." + ], + "KeyboardStrings": [ + "0: Enter a new backup name.", + "1: Enter cache index.", + "2: Enter a new output path for JKSV", + "3: Enter process ID to terminate.", + "4: Enter a system save ID", + "5: Enter a new name for the target item.", + "6: Enter a name for the new folder.", + "7: Enter a new output folder name for %s.", + "8: Enter how much to expand (in MB)." + ], + "OnOff": [ + "0: Off", + "1: >On>" + ], + "SaveCreatePops": [ + "0: Save data created for #%s#!", + "1: Error creating save data!", + "2: Error deleting save data!" + ], + "SaveDataTypes": [ + "0: System", + "1: Account", + "2: BCAT", + "3: Device", + "4: Temporary", + "5: Cache", + "6: System BCAT" + ], + "SettingsDescriptions": [ + "0: Sets the working directory for JKSV. The default value for this is `sdmc:\/JKSV`.", + "1: Allows you to remove titles from the blacklist.", + "2: Includes device, or shared saves, with users.", + "3: Creates a backup automatically when restoring another.", + "4: Auto-names backups and skips the keyboard.", + "5: Automatically uploads backups to Google Drive or WebDav and deletes them locally.", + "6: Whether or not holding [A] for three seconds is required to delete backups.", + "7: Whether or not holding [A] for three seconds is required to restore backups.", + "8: Whether or not holding [A] for three seconds is required to overwrite backups.", + "9: Only shows save data JKSV can successfully open.", + "10: Shows system saves that have an account ID tied to them.", + "11: Enables restoring system saves and writing to NAND partitions.", + "12: Exports save data to ZIP archives instead of unpacked folders.", + "13: Compression or deflate level used when writing to ZIP. The default value is 6. Lower values are faster, but offer less compression and space savings. Zero is store, or no compression.", + "14: Controls the way titles are sorted and displayed.", + "15: Displays titles as text menus like the original JKSM on 3DS instead of icon grids.", + "16: Forces English to be used as the language instead of the detected system language.", + "17: Moves deleted backups to the _TRASH_ folder instead of permanently deleting them.", + "18: Sets the speed at which transitions and animations occur. Lower is faster." + ], + "SettingsMenu": [ + "0: Set JKSV output folder.", + "1: Edit Blacklist", + "2: Include Device Saves with users: %s", + "3: Auto-backup on restore: %s", + "4: Auto-name backups: %s", + "5: Auto-upload backups to remote storage: %s", + "6: Hold to delete backups: %s", + "7: Hold to restore backups: %s", + "8: Hold to overwrite backups: %s", + "9: Only list mountable titles: %s", + "10: Show account system saves: %s", + "11: Enable writing to system saves and NAND: %s", + "12: Export saves to ZIP: %s", + "13: Zip compression level: %u", + "14: Title sort type: %s", + "15: Text menu (JKSM) mode: %s", + "16: Force English: %s", + "17: Enable trash bin: %s", + "18: Animation scaling: %.02f" + ], + "SortTypes": [ + "0: Alphabetically", + "1: Most Played", + "2: Last Played" + ], + "TitleInfo": [ + "0: App ID: %016lX", + "1: Save ID: %016lx", + "2: First Played: %x - %X", + "3: Last Played: %x - %X", + "4: Play Time: %02d:%02d:%02d", + "5: Launches: %i", + "6: Save Type: %s" + ], + "TitleOptionConfirmations": [ + "0: Are you sure you want to add #%s# to your blacklist? Once you do this, it will no longer appear on any title list or selection.", + "1: Are you sure you would like to delete all of the current save backups for #%s#? *This cannot be undone!*", + "2: Are you sure you would like to reset the save data for #%s#? *This will delete the current save data for the title as if it were never run!*", + "3: Are you sure you want to delete `%s`'s save data for #%s#? *This will permanently delete it from the system.*" + ], + "TitleOptionPops": [ + "0: All backups deleted for `%s`!", + "1: Failed to delete all backups!", + "2: Error resetting save data!", + "3: Save data successfully reset!", + "4: SVI file exported successfully!", + "5: Error exporting SVI file!", + "6: This option is unavailable for system saves!", + "7: Could not sanitize path for use!", + "8: Output folder set to #%s#.", + "9: Error setting new output path!" + ], + "TitleOptionStatus": [ + "0: Deleting all backups for #%s#.", + "1: Resetting save data for #%s#.", + "2: Deleting #%s#'s save data for #%s#...", + "3: Extending `%s`'s save data for #%s#..." + ], + "TitleOptions": [ + "0: Information", + "1: Blacklist Title", + "2: Change Output folder", + "3: Open in File Mode", + "4: Delete all save backups", + "5: Reset save data.", + "6: Delete save data from system", + "7: Extend save data", + "8: Export SVI file" + ], + "TranslationInfo": [ + "0: Translated by: %s", + "1: NULL" + ], + "UserOptionConfirmations": [ + "0: Are you sure you want to backup the save data for every title found for `%s`? This can take a while.", + "1: Are you sure you want to create save data for all titles found on your system for `%s`? This can take a while.", + "2: Are you sure you want to delete all of the save data for `%s`? This is *PERMANENT* and can't be undone." + ], + "UserOptionStatus": [ + "0: Creating save data for #%s#...", + "1: Deleting save data for #%s#..." + ], + "UserOptions": [ + "0: Dump all for `%s`", + "1: Create Save Data for `%s`", + "2: Create All Save Data for `%s`", + "3: Delete All Save Data for `%s`" ], "WebDavStrings": [ - "WebDav successfully started!", - "WebDav failed!" + "0: WebDav successfully started!", + "1: WebDav failed!" + ], + "YesNo": [ + "0: Yes [A]", + "1: No [B]" ] } diff --git a/source/JKSV.cpp b/source/JKSV.cpp index bb05354..6a609f7 100644 --- a/source/JKSV.cpp +++ b/source/JKSV.cpp @@ -1,4 +1,5 @@ #include "JKSV.hpp" + #include "StateManager.hpp" #include "appstates/MainMenuState.hpp" #include "colors.hpp" @@ -12,14 +13,12 @@ #include "sdl.hpp" #include "strings.hpp" #include "ui/PopMessageManager.hpp" + #include // Normally I try to avoid C macros in C++, but this cleans stuff up nicely. -#define ABORT_ON_FAILURE(x) \ - if (!x) \ - { \ - return; \ - } +#define ABORT_ON_FAILURE(x) \ + if (!x) { return; } namespace { @@ -43,136 +42,71 @@ static bool initialize_service(Result (*function)(Args...), const char *serviceN return true; } +// This can't really have an initializer list since it sets everything up. JKSV::JKSV() { // Start with this. appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad); - // FsLib - ABORT_ON_FAILURE(fslib::initialize()); + ABORT_ON_FAILURE(JKSV::initialize_filesystem()); - // This doesn't rely on stdio or anything. + ABORT_ON_FAILURE(JKSV::initialize_services()); logger::initialize(); - // Need to init RomFS here for now until I update FsLib to take care of this. Never mind. That isn't going to happen. - ABORT_ON_FAILURE(initialize_service(romfsInit, "RomFS")); - - // Let FsLib take care of calls to SDMC instead of fs_dev - ABORT_ON_FAILURE(fslib::dev::initialize_sdmc()); - // SDL ABORT_ON_FAILURE(sdl::initialize("JKSV", 1280, 720)); ABORT_ON_FAILURE(sdl::text::initialize()); + m_headerIcon = sdl::TextureManager::create_load_texture("HeaderIcon", "romfs:/Textures/HeaderIcon.png"); + JKSV::add_color_chars(); - // Services. - // Using administrator so JKSV can still run in Applet mode, barely. - ABORT_ON_FAILURE(initialize_service(accountInitialize, "Account", AccountServiceType_Administrator)); - ABORT_ON_FAILURE(initialize_service(nsInitialize, "NS")); - ABORT_ON_FAILURE(initialize_service(pdmqryInitialize, "PDMQry")); - ABORT_ON_FAILURE(initialize_service(plInitialize, "PL", PlServiceType_User)); - ABORT_ON_FAILURE(initialize_service(pmshellInitialize, "PMShell")); - ABORT_ON_FAILURE(initialize_service(setInitialize, "Set")); - ABORT_ON_FAILURE(initialize_service(setsysInitialize, "SetSys")); - ABORT_ON_FAILURE(initialize_service(socketInitializeDefault, "Socket")); ABORT_ON_FAILURE(curl::initialize()); + ABORT_ON_FAILURE(strings::initialize()); // This is fatal now. + m_translation = strings::get_by_name(strings::names::TRANSLATION, 0); + m_author = strings::get_by_name(strings::names::TRANSLATION, 1); + m_showTranslationInfo = std::char_traits::compare(m_author, "NULL", 4) != 0; // This is whether or not to show. - // Input doesn't have anything to return. input::initialize(); - - // Neither does config. config::initialize(); - // Get and create working directory. There isn't much of an FS anymore. - fslib::Path workingDirectory = config::get_working_directory(); - if (!fslib::directory_exists(workingDirectory) && !fslib::create_directories_recursively(workingDirectory)) - { - logger::log("Error creating working directory: %s", fslib::error::get_string()); - return; - } + // This needs the config init'd or read to work. + JKSV::create_directories(); - // I'd rather this be here than checked every time one is exported. - fslib::Path sviDir = config::get_working_directory() / "svi"; - if (!fslib::directory_exists(sviDir) && !fslib::create_directories_recursively(sviDir)) - { - // This one isn't fatal, but it can be super fatal later if this fails. - logger::log("Error creating svi directory: %s", fslib::error::get_string()); - } - - // JKSV also has no internal strings anymore. This is FATAL now. - ABORT_ON_FAILURE(strings::initialize()); - - if (!data::initialize(false)) - { - return; - } - - // Install/setup our color changing characters. - sdl::text::add_color_character(L'#', colors::BLUE); - sdl::text::add_color_character(L'*', colors::RED); - sdl::text::add_color_character(L'<', colors::YELLOW); - sdl::text::add_color_character(L'>', colors::GREEN); - sdl::text::add_color_character(L'`', colors::BLUE_GREEN); - sdl::text::add_color_character(L'^', colors::PINK); - - // This is to check whether the author wanted credit for their work. - m_showTranslationInfo = - std::char_traits::compare(strings::get_by_name(strings::names::TRANSLATION_INFO, 1), "NULL", 4) != 0; - - // This can't be in an initializer list because it needs SDL initialized. - m_headerIcon = sdl::TextureManager::create_load_texture("HeaderIcon", "romfs:/Textures/HeaderIcon.png"); + // Data loading depends on the config being read or init'd. + ABORT_ON_FAILURE(data::initialize(false)); // Push initial main menu state. - StateManager::push_state(std::make_shared()); - if (fslib::file_exists(remote::PATH_GOOGLE_DRIVE_CONFIG)) - { - remote::initialize_google_drive(); - } - else if (fslib::file_exists(remote::PATH_WEBDAV_CONFIG)) - { - remote::initialize_webdav(); - } + auto mainMenu = std::make_shared(); + StateManager::push_state(mainMenu); + + // Init drive or webdav. + if (fslib::file_exists(remote::PATH_GOOGLE_DRIVE_CONFIG)) { remote::initialize_google_drive(); } + else if (fslib::file_exists(remote::PATH_WEBDAV_CONFIG)) { remote::initialize_webdav(); } m_isRunning = true; } JKSV::~JKSV() { - // Try to save config first. config::save(); - curl::exit(); - socketExit(); - setsysExit(); - setExit(); - pmshellExit(); - plExit(); - pdmqryExit(); - nsExit(); - accountExit(); + JKSV::exit_services(); sdl::text::exit(); sdl::exit(); fslib::exit(); appletSetCpuBoostMode(ApmCpuBoostMode_Normal); } -bool JKSV::is_running() const -{ - return m_isRunning; -} +bool JKSV::is_running() const { return m_isRunning; } void JKSV::update() { input::update(); - if (input::button_pressed(HidNpadButton_Plus) && StateManager::back_is_closable()) - { - m_isRunning = false; - } + const bool plusPressed = input::button_pressed(HidNpadButton_Plus); + const bool isClosable = StateManager::back_is_closable(); + if (plusPressed && isClosable) { m_isRunning = false; } - // State update. StateManager::update(); - - // Update pop messages. ui::PopMessageManager::update(); } @@ -193,14 +127,7 @@ void JKSV::render() // Translation info in bottom left. if (m_showTranslationInfo) { - sdl::text::render(NULL, - 8, - 680, - 14, - sdl::text::NO_TEXT_WRAP, - colors::WHITE, - strings::get_by_name(strings::names::TRANSLATION_INFO, 0), - strings::get_by_name(strings::names::TRANSLATION_INFO, 1)); + sdl::text::render(NULL, 8, 680, 14, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_translation, m_author); } // Build date @@ -215,11 +142,71 @@ void JKSV::render() BUILD_DAY, BUILD_YEAR); - // State render. StateManager::render(); - - // Render messages. ui::PopMessageManager::render(); sdl::frame_end(); } + +bool JKSV::initialize_filesystem() +{ + // This needs to be in this specific order + const bool fslib = fslib::initialize(); + const bool romfs = initialize_service(romfsInit, "RomFS"); + const bool fslibDev = fslib && fslib::dev::initialize_sdmc(); + if (!fslib || !romfs || !fslibDev) { return false; } + return true; +} + +bool JKSV::initialize_services() +{ + // This looks cursed, but it works. + bool serviceInit = initialize_service(accountInitialize, "Account", AccountServiceType_Administrator); + serviceInit = serviceInit && initialize_service(nsInitialize, "NS"); + serviceInit = serviceInit && initialize_service(pdmqryInitialize, "PDMQry"); + serviceInit = serviceInit && initialize_service(plInitialize, "PL", PlServiceType_User); + serviceInit = serviceInit && initialize_service(pmshellInitialize, "PMShell"); + serviceInit = serviceInit && initialize_service(setInitialize, "Set"); + serviceInit = serviceInit && initialize_service(setsysInitialize, "SetSys"); + serviceInit = serviceInit && initialize_service(socketInitializeDefault, "Socket"); + return serviceInit; +} + +bool JKSV::create_directories() +{ + // Working directory creation. + const fslib::Path workDir = config::get_working_directory(); + const bool needsWorkDir = !fslib::directory_exists(workDir); + const bool workDirCreated = needsWorkDir && fslib::create_directories_recursively(workDir); + if (needsWorkDir && !workDirCreated) { return false; } + + // SVI folder. + const fslib::Path sviDir = workDir / "svi"; + const bool needsSviDir = !fslib::directory_exists(sviDir); + const bool sviDirCreated = needsSviDir && fslib::create_directory(sviDir); + if (needsSviDir && !sviDirCreated) { return false; } + + return true; +} + +void JKSV::add_color_chars() +{ + sdl::text::add_color_character(L'#', colors::BLUE); + sdl::text::add_color_character(L'*', colors::RED); + sdl::text::add_color_character(L'<', colors::YELLOW); + sdl::text::add_color_character(L'>', colors::GREEN); + sdl::text::add_color_character(L'`', colors::BLUE_GREEN); + sdl::text::add_color_character(L'^', colors::PINK); +} + +void JKSV::exit_services() +{ + socketExit(); + setsysExit(); + setExit(); + pmshellExit(); + plExit(); + pdmqryExit(); + nsExit(); + accountExit(); +} diff --git a/source/appstates/BackupMenuState.cpp b/source/appstates/BackupMenuState.cpp index 7745354..81361a4 100644 --- a/source/appstates/BackupMenuState.cpp +++ b/source/appstates/BackupMenuState.cpp @@ -23,10 +23,14 @@ namespace { /// @brief This is the length allotted for naming backups. - constexpr size_t SIZE_BACKUP_NAME_LENGTH = 0x80; + constexpr size_t SIZE_NAME_LENGTH = 0x80; /// @brief This is just so there isn't random .zip comparisons everywhere. const char *STRING_ZIP_EXTENSION = ".zip"; + + // These make some things cleaner and easier to type. + using TaskConfirm = ConfirmState; + using ProgressConfirm = ConfirmState; } // namespace // Declarations here. Definitions after class. @@ -64,97 +68,62 @@ BackupMenuState::BackupMenuState(data::User *user, data::TitleInfo *titleInfo) BackupMenuState::~BackupMenuState() { - // Close the save. fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); - // Close the panel. - sm_slidePanel->clear_elements(); - // Return the remote to root. + sm_slidePanel->clear_elements(); + sm_slidePanel->reset(); + remote::Storage *remote = remote::get_remote_storage(); if (remote && remote->is_initialized()) { remote->return_to_root(); } } void BackupMenuState::update() { - bool hasFocus = BaseState::has_focus(); + const bool hasFocus = BaseState::has_focus(); + const int selected = sm_backupMenu->get_selected(); + const bool aPressed = input::button_pressed(HidNpadButton_A); + const bool bPressed = input::button_pressed(HidNpadButton_B); + const bool xPressed = input::button_pressed(HidNpadButton_X); + const bool yPressed = input::button_pressed(HidNpadButton_Y); + const bool zrPressed = input::button_pressed(HidNpadButton_ZR); - if (input::button_pressed(HidNpadButton_A) && sm_backupMenu->get_selected() == 0 && m_saveHasData) - { - BackupMenuState::name_and_create_backup(); - } - else if (input::button_pressed(HidNpadButton_A) && sm_backupMenu->get_selected() == 0 && !m_saveHasData) - { - // This just makes the little no save data found pop up. - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 0)); - } - else if (input::button_pressed(HidNpadButton_A) && m_saveHasData && sm_backupMenu->get_selected() > 0) - { - BackupMenuState::confirm_backup_overwrite(); - } - else if (input::button_pressed(HidNpadButton_A) && !m_saveHasData && sm_backupMenu->get_selected() > 0) - { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 0)); - } - else if (input::button_pressed(HidNpadButton_Y) && sm_backupMenu->get_selected() > 0) - { - BackupMenuState::confirm_restore(); - } - else if (input::button_pressed(HidNpadButton_X) && sm_backupMenu->get_selected() > 0) { BackupMenuState::confirm_delete(); } - else if (input::button_pressed(HidNpadButton_ZR) && sm_backupMenu->get_selected() > 0) - { - int selected = sm_backupMenu->get_selected() - 1; - m_dataStruct->path = m_directoryPath / m_directoryListing[selected]; - auto upload = std::make_shared(upload_backup, m_dataStruct); - StateManager::push_state(upload); - } + const bool newSelected = selected == 0; + const bool newBackup = aPressed && newSelected && m_saveHasData; + const bool overwriteBackup = aPressed && !newSelected && m_saveHasData; + const bool restoreBackup = yPressed && !newSelected; + const bool deleteBackup = xPressed && !newSelected; + const bool uploadBackup = zrPressed && !newSelected; + const bool popEmpty = aPressed && !m_saveHasData; - else if (input::button_pressed(HidNpadButton_B)) { sm_slidePanel->close(); } - else if (sm_slidePanel->is_closed()) - { - sm_slidePanel->reset(); - BaseState::deactivate(); - } + if (newBackup) { BackupMenuState::name_and_create_backup(); } + else if (overwriteBackup) { BackupMenuState::confirm_overwrite(); } + else if (restoreBackup) { BackupMenuState::confirm_restore(); } + else if (deleteBackup) { BackupMenuState::confirm_delete(); } + else if (uploadBackup) { BackupMenuState::upload_backup(); } + else if (popEmpty) { BackupMenuState::pop_save_empty(); } + else if (bPressed) { sm_slidePanel->close(); } + else if (sm_slidePanel->is_closed()) { BaseState::deactivate(); } - // Update title scrolling. m_titleScroll.update(hasFocus); - // Update panel. sm_slidePanel->update(hasFocus); - // This state bypasses the Slideout panel's normal behavior because it kind of has to. sm_backupMenu->update(hasFocus); } void BackupMenuState::render() { - // Save this locally. - bool hasFocus = BaseState::has_focus(); + const bool hasFocus = BaseState::has_focus(); + SDL_Texture *target = sm_slidePanel->get_target(); - // Clear panel target. sm_slidePanel->clear_target(); + m_titleScroll.render(target, hasFocus); - // Grab the render target. - SDL_Texture *slideTarget = sm_slidePanel->get_target(); + sdl::render_line(target, 10, 42, sm_panelWidth - 10, 42, colors::WHITE); + sdl::render_line(target, 10, 648, sm_panelWidth - 10, 648, colors::WHITE); + sdl::text::render(target, 32, 673, 22, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_controlGuide); - // Start with [Name] - [Title] - m_titleScroll.render(slideTarget, hasFocus); - - sdl::render_line(slideTarget, 10, 42, sm_panelWidth - 10, 42, colors::WHITE); - sdl::render_line(slideTarget, 10, 648, sm_panelWidth - 10, 648, colors::WHITE); - sdl::text::render(slideTarget, - 32, - 673, - 22, - sdl::text::NO_TEXT_WRAP, - colors::WHITE, - strings::get_by_name(strings::names::CONTROL_GUIDES, 2)); - - // Clear menu target. sm_menuRenderTarget->clear(colors::TRANSPARENT); - // render menu to it. sm_backupMenu->render(sm_menuRenderTarget->get(), hasFocus); - // render it to panel target. - sm_menuRenderTarget->render(slideTarget, 0, 43); + sm_menuRenderTarget->render(target, 0, 43); sm_slidePanel->render(NULL, hasFocus); } @@ -165,8 +134,15 @@ void BackupMenuState::refresh() if (!m_directoryListing) { return; } sm_backupMenu->reset(); - sm_backupMenu->add_option(strings::get_by_name(strings::names::BACKUP_MENU, 0)); - for (int64_t i = 0; i < m_directoryListing.get_count(); i++) { sm_backupMenu->add_option(m_directoryListing[i]); } + m_menuEntries.clear(); + + sm_backupMenu->add_option(strings::get_by_name(strings::names::BACKUPMENU_MENU, 0)); + m_menuEntries.push_back({MenuEntryType::Null, 0}); + for (int64_t i = 0; i < m_directoryListing.get_count(); i++) + { + sm_backupMenu->add_option(m_directoryListing[i]); + m_menuEntries.push_back({MenuEntryType::Local, static_cast(i)}); + } } void BackupMenuState::save_data_written() @@ -176,134 +152,107 @@ void BackupMenuState::save_data_written() void BackupMenuState::name_and_create_backup() { - static const char *STRING_ERROR_CREATE_NEW = "Error creating new backup: %s"; + const bool autoName = config::get_by_key(config::keys::AUTO_NAME_BACKUPS); + const bool exportZip = config::get_by_key(config::keys::EXPORT_TO_ZIP); + const bool autoUpload = config::get_by_key(config::keys::AUTO_UPLOAD); + const bool zrHeld = input::button_held(HidNpadButton_ZR); + const char *keyboardHeader = strings::get_by_name(strings::names::KEYBOARD, 0); + const bool autoNamed = (autoName || zrHeld); // This can be eval'd here. + char name[SIZE_NAME_LENGTH + 1] = {0}; - bool zip = config::get_by_key(config::keys::EXPORT_TO_ZIP); - char backupName[SIZE_BACKUP_NAME_LENGTH + 1] = {0}; + std::snprintf(name, SIZE_NAME_LENGTH, "%s - %s", m_user->get_path_safe_nickname(), stringutil::get_date_string().c_str()); - std::snprintf(backupName, - SIZE_BACKUP_NAME_LENGTH, - "%s - %s", - m_user->get_path_safe_nickname(), - stringutil::get_date_string().c_str()); + const bool named = autoNamed || keyboard::get_input(SwkbdType_QWERTY, name, keyboardHeader, name, SIZE_NAME_LENGTH); + if (!named) { return; } - // ZR is a shortcut to skip the keyboard popping up. - if (!input::button_held(HidNpadButton_ZR) && !keyboard::get_input(SwkbdType_QWERTY, - backupName, - strings::get_by_name(strings::names::KEYBOARD_STRINGS, 0), - backupName, - SIZE_BACKUP_NAME_LENGTH)) + fslib::Path target{m_directoryPath / name}; + const bool hasZipExt = std::strstr(target.full_path(), ".zip"); // This might not be the best check. + if ((exportZip || autoUpload) && !hasZipExt) { target += ".zip"; } + else if (!exportZip && !autoUpload && !hasZipExt) { - return; + const bool targetExists = fslib::directory_exists(target); + const bool targetCreated = !targetExists && fslib::create_directory(target); } - - if (zip && !std::strstr(backupName, STRING_ZIP_EXTENSION)) - { - // This could have potential consequences... - std::strncat(backupName, STRING_ZIP_EXTENSION, SIZE_BACKUP_NAME_LENGTH); - } - else if (!zip && !std::strstr(backupName, STRING_ZIP_EXTENSION) && !fslib::directory_exists(m_directoryPath / backupName) && - !fslib::create_directory(m_directoryPath / backupName)) - { - logger::log(STRING_ERROR_CREATE_NEW, "Error creating export directory."); - return; - } - - // We should now be able to create the final target path. - fslib::Path targetPath = m_directoryPath / backupName; - - auto newBackupTask = std::make_shared(create_new_backup, m_user, m_titleInfo, targetPath, this); + auto newBackupTask = std::make_shared(create_new_backup, m_user, m_titleInfo, target, this); StateManager::push_state(newBackupTask); } -void BackupMenuState::confirm_backup_overwrite() +void BackupMenuState::confirm_overwrite() { - // This has one subtracted to account for New Backup. - int selected = sm_backupMenu->get_selected() - 1; - - std::string confirmationString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::BACKUPMENU_CONFIRMATIONS, 0), - m_directoryListing[selected]); - - // This needs a new target path to pass. - m_dataStruct->path = m_directoryPath / m_directoryListing[selected]; - - auto confirm = std::make_shared>( - confirmationString, - config::get_by_key(config::keys::HOLD_FOR_OVERWRITE), - overwrite_backup, - m_dataStruct); + const int selected = sm_backupMenu->get_selected(); + const MenuEntry &entry = m_menuEntries.at(selected); + const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_OVERWRITE); + const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 0); + m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; + const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); + auto confirm = std::make_shared(query, holdRequired, overwrite_backup, m_dataStruct); StateManager::push_state(confirm); } void BackupMenuState::confirm_restore() { - if ((m_saveType == FsSaveDataType_System || m_saveType == FsSaveDataType_SystemBcat) && - !config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM)) + const int selected = sm_backupMenu->get_selected(); + const MenuEntry &entry = m_menuEntries.at(selected); + const int popTicks = ui::PopMessageManager::DEFAULT_MESSAGE_TICKS; + const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_RESTORATION); + const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 1); + const char *popBackupEmpty = strings::get_by_name(strings::names::BACKUPMENU_POPS, 1); + const char *popSysNotAllowed = strings::get_by_name(strings::names::BACKUPMENU_POPS, 6); + + const bool isSystem = BackupMenuState::is_system_save_data(); + const bool allowSystem = config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM); + const bool isValidRestore = !isSystem || allowSystem; + if (!isValidRestore) { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::BACKUPMENU_POPS, 0)); + ui::PopMessageManager::push_message(popTicks, popSysNotAllowed); return; } - int selected = sm_backupMenu->get_selected() - 1; - - fslib::Path target = m_directoryPath / m_directoryListing[selected]; - - if (fslib::directory_exists(target) && !fs::directory_has_contents(target)) + const fslib::Path target = m_directoryPath / m_directoryListing[entry.index]; + const bool targetIsDirectory = fslib::directory_exists(target); + const bool backupIsGood = targetIsDirectory ? fs::directory_has_contents(target) : fs::zip_has_contents(target); + if (!backupIsGood) { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 1)); - return; - } - else if (fslib::file_exists(target) && std::strcmp("zip", target.get_extension()) == 0 && !fs::zip_has_contents(target)) - { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 1)); + ui::PopMessageManager::push_message(popTicks, popBackupEmpty); return; } - m_dataStruct->path = m_directoryPath / m_directoryListing[selected]; - - std::string confirmationString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::BACKUPMENU_CONFIRMATIONS, 1), - m_directoryListing[selected]); - - auto confirm = std::make_shared>( - confirmationString, - config::get_by_key(config::keys::HOLD_FOR_RESTORATION), - restore_backup, - m_dataStruct); - + m_dataStruct->path = target; + const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); + auto confirm = std::make_shared(query, holdRequired, restore_backup, m_dataStruct); StateManager::push_state(confirm); } void BackupMenuState::confirm_delete() { - int selected = sm_backupMenu->get_selected() - 1; + const int selected = sm_backupMenu->get_selected(); + const MenuEntry &entry = m_menuEntries.at(selected); + const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_DELETION); + const char *confirmTemplate = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 2); + m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index]; - m_dataStruct->path = m_directoryPath / m_directoryListing[selected]; - - std::string confirmationString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::BACKUPMENU_CONFIRMATIONS, 2), - m_directoryListing[selected]); - - auto confirm = std::make_shared>( - confirmationString, - config::get_by_key(config::keys::HOLD_FOR_DELETION), - delete_backup, - m_dataStruct); + const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]); + auto confirm = std::make_shared(query, holdRequired, delete_backup, m_dataStruct); StateManager::push_state(confirm); } +void BackupMenuState::upload_backup() {} + +void BackupMenuState::pop_save_empty() +{ + const int ticks = ui::PopMessageManager::DEFAULT_MESSAGE_TICKS; + const char *popEmpty = strings::get_by_name(strings::names::BACKUPMENU_POPS, 0); + ui::PopMessageManager::push_message(ticks, popEmpty); +} + void BackupMenuState::initialize_static_members() { constexpr int SDL_TEX_FLAGS = SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET; if (sm_backupMenu && sm_slidePanel && sm_menuRenderTarget && sm_panelWidth) { return; } - sm_panelWidth = sdl::text::get_width(22, m_controlGuide); + sm_panelWidth = sdl::text::get_width(22, m_controlGuide) + 64; sm_backupMenu = std::make_shared(8, 8, sm_panelWidth - 14, 24, 600); sm_slidePanel = std::make_unique(sm_panelWidth, ui::SlideOutPanel::Side::Right); sm_menuRenderTarget = sdl::TextureManager::create_load_texture("backupMenuTarget", sm_panelWidth, 600, SDL_TEX_FLAGS); @@ -502,7 +451,7 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptrfinished(); return; } @@ -531,7 +480,7 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptrfinished(); return; @@ -562,19 +511,19 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptr dataStruct) { - if (task) { task->set_status(strings::get_by_name(strings::names::DELETING_FILES, 0), dataStruct->path.full_path()); } + if (task) { task->set_status(strings::get_by_name(strings::names::IO_STATUSES, 3), dataStruct->path.full_path()); } if (fslib::directory_exists(dataStruct->path) && !fslib::delete_directory_recursively(dataStruct->path)) { logger::log("Error deleting folder backup: %s", fslib::error::get_string()); ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 4)); + strings::get_by_name(strings::names::BACKUPMENU_POPS, 4)); } else if (fslib::file_exists(dataStruct->path) && !fslib::delete_file(dataStruct->path)) { logger::log("Error deleting backup: %s", fslib::error::get_string()); ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 4)); + strings::get_by_name(strings::names::BACKUPMENU_POPS, 4)); } dataStruct->spawningState->refresh(); task->finished(); diff --git a/source/appstates/BaseTask.cpp b/source/appstates/BaseTask.cpp index 9482860..04cde67 100644 --- a/source/appstates/BaseTask.cpp +++ b/source/appstates/BaseTask.cpp @@ -1,4 +1,5 @@ #include "appstates/BaseTask.hpp" + #include "colors.hpp" namespace @@ -7,31 +8,28 @@ namespace constexpr uint64_t TICKS_GLYPH_TRIGGER = 50; } // namespace -BaseTask::BaseTask() : BaseState(false) +BaseTask::BaseTask() + : BaseState(false) { m_frameTimer.start(TICKS_GLYPH_TRIGGER); } void BaseTask::update() { - // Just bail if the timer wasn't triggered yet. - if (!m_frameTimer.is_triggered()) + if (!m_task->is_running()) { + BaseState::deactivate(); return; } - // Reset to 0 here. - if (++m_currentFrame >= 8) - { - m_currentFrame = 0; - } - - // Update the color pulse. + // Just bail if the timer wasn't triggered yet. + if (!m_frameTimer.is_triggered()) { return; } + if (++m_currentFrame >= 8) { m_currentFrame = 0; } m_colorMod.update(); } void BaseTask::render_loading_glyph() { - // This assumes it's being called after the background was dimmed. - sdl::text::render(NULL, 56, 673, 32, sdl::text::NO_TEXT_WRAP, m_colorMod, sm_glyphArray.at(m_currentFrame).data()); + const char *currentFrame = sm_glyphArray.at(m_currentFrame).data(); + sdl::text::render(NULL, 56, 673, 32, sdl::text::NO_TEXT_WRAP, m_colorMod, currentFrame); } diff --git a/source/appstates/ExtrasMenuState.cpp b/source/appstates/ExtrasMenuState.cpp index 1bbec15..022a294 100644 --- a/source/appstates/ExtrasMenuState.cpp +++ b/source/appstates/ExtrasMenuState.cpp @@ -67,7 +67,7 @@ void ExtrasMenuState::render() void ExtrasMenuState::initialize_menu() { - for (int i = 0; const char *option = strings::get_by_name(strings::names::EXTRAS_MENU, i); i++) + for (int i = 0; const char *option = strings::get_by_name(strings::names::EXTRASMENU_MENU, i); i++) { m_extrasMenu.add_option(option); } @@ -76,8 +76,8 @@ void ExtrasMenuState::initialize_menu() void ExtrasMenuState::reinitialize_data() { const int popTicks = ui::PopMessageManager::DEFAULT_MESSAGE_TICKS; - const char *popSuccess = strings::get_by_name(strings::names::EXTRAS_POP_MESSAGES, 0); - const char *popFailure = strings::get_by_name(strings::names::EXTRAS_POP_MESSAGES, 1); + const char *popSuccess = strings::get_by_name(strings::names::EXTRASMENU_POPS, 0); + const char *popFailure = strings::get_by_name(strings::names::EXTRASMENU_POPS, 1); // Call data and make in reinit and delete the cache first. const bool initSuccess = data::initialize(true); diff --git a/source/appstates/ProgressState.cpp b/source/appstates/ProgressState.cpp index a04e6e5..d8659a9 100644 --- a/source/appstates/ProgressState.cpp +++ b/source/appstates/ProgressState.cpp @@ -1,4 +1,5 @@ #include "appstates/ProgressState.hpp" + #include "colors.hpp" #include "input.hpp" #include "sdl.hpp" @@ -6,48 +7,35 @@ #include "stringutil.hpp" #include "ui/PopMessageManager.hpp" #include "ui/render_functions.hpp" + #include void ProgressState::update() { + sys::ProgressTask *task = static_cast(m_task.get()); + const double current = task->get_current(); + // Base routine. BaseTask::update(); - if (m_task.is_running() && input::button_pressed(HidNpadButton_Plus)) - { - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 0)); - } - else if (!m_task.is_running()) - { - BaseState::deactivate(); - } - - m_progressBarWidth = std::ceil(656.0f * m_task.get_current()); - m_progress = std::ceil(m_task.get_current() * 100); + m_progressBarWidth = std::ceil(656.0f * current); + m_progress = std::ceil(current * 100); m_percentageString = stringutil::get_formatted_string("%u", m_progress); - m_percentageX = 640 - (sdl::text::get_width(18, m_percentageString.c_str())); + m_percentageX = 640 - (sdl::text::get_width(18, m_percentageString.c_str())); } void ProgressState::render() { - // This will dim the background. + const std::string status = m_task->get_status(); + const char *percentage = m_percentageString.c_str(); + sdl::render_rect_fill(NULL, 0, 0, 1280, 720, colors::DIM_BACKGROUND); - // Render the dialog and little loading bar thingy. ui::render_dialog_box(NULL, 280, 262, 720, 256); - sdl::text::render(NULL, 312, 288, 18, 648, colors::WHITE, m_task.get_status().c_str()); + sdl::text::render(NULL, 312, 288, 18, 648, colors::WHITE, status.c_str()); sdl::render_rect_fill(NULL, 312, 462, 656, 32, colors::BLACK); sdl::render_rect_fill(NULL, 312, 462, m_progressBarWidth, 32, colors::GREEN); - sdl::text::render(NULL, - m_percentageX, - 468, - 18, - sdl::text::NO_TEXT_WRAP, - colors::WHITE, - "%s%%", - m_percentageString.c_str()); + sdl::text::render(NULL, m_percentageX, 468, 18, sdl::text::NO_TEXT_WRAP, colors::WHITE, "%s%%", percentage); - // Glyph in the corner. BaseTask::render_loading_glyph(); } diff --git a/source/appstates/SaveCreateState.cpp b/source/appstates/SaveCreateState.cpp index 0020cfa..dc0ef1a 100644 --- a/source/appstates/SaveCreateState.cpp +++ b/source/appstates/SaveCreateState.cpp @@ -1,4 +1,5 @@ #include "appstates/SaveCreateState.hpp" + #include "StateManager.hpp" #include "appstates/TaskState.hpp" #include "data/data.hpp" @@ -8,6 +9,7 @@ #include "strings.hpp" #include "system/Task.hpp" #include "ui/PopMessageManager.hpp" + #include #include #include @@ -23,7 +25,9 @@ static void create_save_data(sys::Task *task, static bool compare_info(data::TitleInfo *infoA, data::TitleInfo *infoB); SaveCreateState::SaveCreateState(data::User *user, TitleSelectCommon *titleSelect) - : m_user(user), m_titleSelect(titleSelect), m_saveMenu(8, 8, 624, 22, 720) + : m_user(user) + , m_titleSelect(titleSelect) + , m_saveMenu(8, 8, 624, 22, 720) { // If the panel is null, create it. if (!sm_slidePanel) @@ -38,10 +42,7 @@ SaveCreateState::SaveCreateState(data::User *user, TitleSelectCommon *titleSelec // Sort it by alpha std::sort(m_titleInfoVector.begin(), m_titleInfoVector.end(), compare_info); - for (size_t i = 0; i < m_titleInfoVector.size(); i++) - { - m_saveMenu.add_option(m_titleInfoVector.at(i)->get_title()); - } + for (size_t i = 0; i < m_titleInfoVector.size(); i++) { m_saveMenu.add_option(m_titleInfoVector.at(i)->get_title()); } } void SaveCreateState::update() @@ -66,10 +67,7 @@ void SaveCreateState::update() data::TitleInfo *targetTitle = m_titleInfoVector.at(m_saveMenu.get_selected()); StateManager::push_state(std::make_shared(create_save_data, m_user, targetTitle, this)); } - else if (input::button_pressed(HidNpadButton_B)) - { - sm_slidePanel->close(); - } + else if (input::button_pressed(HidNpadButton_B)) { sm_slidePanel->close(); } else if (sm_slidePanel->is_closed()) { sm_slidePanel->reset(); @@ -87,10 +85,7 @@ void SaveCreateState::render() sm_slidePanel->render(NULL, hasFocus); } -void SaveCreateState::data_and_view_refresh_required() -{ - m_refreshRequired = true; -} +void SaveCreateState::data_and_view_refresh_required() { m_refreshRequired = true; } static void create_save_data(sys::Task *task, data::User *targetUser, @@ -98,18 +93,18 @@ static void create_save_data(sys::Task *task, SaveCreateState *spawningState) { // Set status. We'll just borrow the string from the other group. - task->set_status(strings::get_by_name(strings::names::USER_OPTION_STATUS, 0), titleInfo->get_title()); + task->set_status(strings::get_by_name(strings::names::USEROPTION_STATUS, 0), titleInfo->get_title()); if (fs::create_save_data_for(targetUser, titleInfo)) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_SAVE_CREATE, 0), + strings::get_by_name(strings::names::SAVECREATE_POPS, 0), titleInfo->get_title()); } else { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_SAVE_CREATE, 1)); + strings::get_by_name(strings::names::SAVECREATE_POPS, 1)); } spawningState->data_and_view_refresh_required(); @@ -123,8 +118,8 @@ static bool compare_info(data::TitleInfo *infoA, data::TitleInfo *infoB) const char *titleA = infoA->get_title(); const char *titleB = infoB->get_title(); - size_t titleALength = std::char_traits::length(titleA); - size_t titleBLength = std::char_traits::length(titleB); + size_t titleALength = std::char_traits::length(titleA); + size_t titleBLength = std::char_traits::length(titleB); size_t shortestTitle = titleALength < titleBLength ? titleALength : titleBLength; // To do: This doesn't take into account which is the shortest title. This can still go out-of-bounds. for (size_t i = 0, j = 0; i < shortestTitle;) @@ -135,15 +130,9 @@ static bool compare_info(data::TitleInfo *infoA, data::TitleInfo *infoB) ssize_t unitCountA = decode_utf8(&codepointA, reinterpret_cast(&titleA[i])); ssize_t unitCountB = decode_utf8(&codepointB, reinterpret_cast(&titleB[j])); - if (unitCountA <= 0 || unitCountB <= 0) - { - return false; - } + if (unitCountA <= 0 || unitCountB <= 0) { return false; } - if (codepointA != codepointB) - { - return codepointA < codepointB; - } + if (codepointA != codepointB) { return codepointA < codepointB; } i += unitCountA; j += unitCountB; diff --git a/source/appstates/SettingsState.cpp b/source/appstates/SettingsState.cpp index 19c33f4..ff5cbb4 100644 --- a/source/appstates/SettingsState.cpp +++ b/source/appstates/SettingsState.cpp @@ -1,4 +1,5 @@ #include "appstates/SettingsState.hpp" + #include "appstates/MainMenuState.hpp" #include "colors.hpp" #include "config.hpp" @@ -9,6 +10,7 @@ #include "logger.hpp" #include "strings.hpp" #include "stringutil.hpp" + #include namespace @@ -38,44 +40,38 @@ namespace "NULL"}; } // namespace -// Declarations. Definitions after class members. -static const char *get_value_text(uint8_t value); -static const char *get_sort_type_text(uint8_t value); - SettingsState::SettingsState() - : m_settingsMenu(32, 8, 1000, 24, 555), - m_renderTarget(sdl::TextureManager::create_load_texture(SECONDARY_TARGET, + : m_settingsMenu(32, 8, 1000, 24, 555) + , m_controlGuide(strings::get_by_name(strings::names::CONTROL_GUIDES, 3)) + , m_controlGuideX(1220 - sdl::text::get_width(22, m_controlGuide)) + , m_renderTarget(sdl::TextureManager::create_load_texture(SECONDARY_TARGET, 1080, 555, - SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET)), - m_controlGuideX(1220 - sdl::text::get_width(22, strings::get_by_name(strings::names::CONTROL_GUIDES, 3))) + SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET)) { - // Loop and allocate the strings and menu options. - int currentString = 0; - const char *settingsString = nullptr; - while ((settingsString = strings::get_by_name(strings::names::SETTINGS_MENU, currentString++)) != nullptr) + + for (int i = 0; const char *setting = strings::get_by_name(strings::names::SETTINGS_MENU, i); i++) { - m_settingsMenu.add_option(settingsString); + m_settingsMenu.add_option(setting); } - // Run the update routine so they're right. + for (int i = 0; const char *onOff = strings::get_by_name(strings::names::ON_OFF, i); i++) { m_onOff[i] = onOff; } + for (int i = 0; const char *sortType = strings::get_by_name(strings::names::SORT_TYPES, i); i++) + { + m_sortTypes[i] = sortType; + } SettingsState::update_menu_options(); } void SettingsState::update() { const bool hasFocus = BaseState::has_focus(); + const bool aPressed = input::button_pressed(HidNpadButton_A); + const bool bPressed = input::button_pressed(HidNpadButton_B); m_settingsMenu.update(hasFocus); - - if (input::button_pressed(HidNpadButton_A)) - { - SettingsState::toggle_options(); - } - else if (input::button_pressed(HidNpadButton_B)) - { - BaseState::deactivate(); - } + if (aPressed) { SettingsState::toggle_options(); } + else if (bPressed) { BaseState::deactivate(); } } void SettingsState::render() @@ -86,156 +82,99 @@ void SettingsState::render() m_settingsMenu.render(m_renderTarget->get(), hasFocus); m_renderTarget->render(NULL, 201, 91); - if (hasFocus) - { - sdl::text::render(NULL, - m_controlGuideX, - 673, - 22, - sdl::text::NO_TEXT_WRAP, - colors::WHITE, - strings::get_by_name(strings::names::CONTROL_GUIDES, 3)); - } + if (hasFocus) { sdl::text::render(NULL, m_controlGuideX, 673, 22, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_controlGuide); } } void SettingsState::update_menu_options() { - // These can be looped, so screw it. - for (int i = 2; i < 13; i++) + for (int i : {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17}) { - std::string updatedOption = - stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, i), - get_value_text(config::get_by_key(CONFIG_KEY_ARRAY[i]))); - m_settingsMenu.edit_option(i, updatedOption); + const char *optionTemplate = strings::get_by_name(strings::names::SETTINGS_MENU, i); + const uint8_t value = config::get_by_key(CONFIG_KEY_ARRAY[i]); + const char *status = SettingsState::get_status_text(value); + const std::string option = stringutil::get_formatted_string(optionTemplate, status); + m_settingsMenu.edit_option(i, option); } - // This just displays the value. The config value is offset to account for the first two settings options. - m_settingsMenu.edit_option(13, - stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, 13), - config::get_by_key(CONFIG_KEY_ARRAY[13]))); - - // This gets the type according to the value. - m_settingsMenu.edit_option( - 14, - stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, 14), - get_sort_type_text(config::get_by_key(CONFIG_KEY_ARRAY[14])))); - - // Loop again. - for (int i = 15; i < 18; i++) { - std::string updatedOption = - stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, i), - get_value_text(config::get_by_key(CONFIG_KEY_ARRAY[i]))); - m_settingsMenu.edit_option(i, updatedOption); + const char *zipCompTemplate = strings::get_by_name(strings::names::SETTINGS_MENU, 13); + const uint8_t zipLevel = config::get_by_key(CONFIG_KEY_ARRAY[13]); + const std::string zipOption = stringutil::get_formatted_string(zipCompTemplate, zipLevel); + m_settingsMenu.edit_option(13, zipOption); } - // Animating scaling. - m_settingsMenu.edit_option(18, - stringutil::get_formatted_string(strings::get_by_name(strings::names::SETTINGS_MENU, 18), - config::get_animation_scaling())); + { + const char *titleSortTemplate = strings::get_by_name(strings::names::SETTINGS_MENU, 14); + const uint8_t sortType = config::get_by_key(CONFIG_KEY_ARRAY[14]); + const char *typeText = SettingsState::get_sort_type_text(sortType); + const std::string sortTypeOption = stringutil::get_formatted_string(titleSortTemplate, typeText); + m_settingsMenu.edit_option(14, sortTypeOption); + } + + { + const char *scalingTemplate = strings::get_by_name(strings::names::SETTINGS_MENU, 18); + const double scaling = config::get_animation_scaling(); + const std::string scalingOption = stringutil::get_formatted_string(scalingTemplate, scaling); + m_settingsMenu.edit_option(18, scalingOption); + } } void SettingsState::toggle_options() { - int selected = m_settingsMenu.get_selected(); - + const int selected = m_settingsMenu.get_selected(); switch (selected) { - // Zip level. - case 13: - { - uint8_t zipLevel = config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL); - if (++zipLevel > 9) - { - zipLevel = 0; - } - config::set_by_key(config::keys::ZIP_COMPRESSION_LEVEL, zipLevel); - } - break; - - // Title sorting. - case 14: - { - // Change the sorting type. - uint8_t sortType = config::get_by_key(config::keys::TITLE_SORT_TYPE); - if (++sortType >= 3) - { - sortType = 0; - } - config::set_by_key(config::keys::TITLE_SORT_TYPE, sortType); - - // Grab the users and resort their data. - data::UserList list; - data::get_users(list); - for (data::User *user : list) - { - user->sort_data(); - } - - // Main the main menu refresh everything. - MainMenuState::refresh_view_states(); - } - break; - - // Text mode. This is handled beyond a toggle. - case 15: - { - config::toggle_by_key(CONFIG_KEY_ARRAY[selected]); - - // This will reinit all the views according the key toggled. - MainMenuState::initialize_view_states(); - } - break; - - // UI transition scaling. - case 18: - { - double scaling = config::get_animation_scaling(); - if ((scaling += 0.25f) > 4.0f) - { - scaling = 1.0f; - } - config::set_animation_scaling(scaling); - } - break; - - default: - { - config::toggle_by_key(CONFIG_KEY_ARRAY[selected]); - } - break; + case 13: SettingsState::cycle_zip_level(); break; + case 14: SettingsState::cycle_sort_type(); break; + case 15: SettingsState::toggle_jksm_mode(); break; + case 18: SettingsState::cycle_anim_scaling(); break; + default: config::toggle_by_key(CONFIG_KEY_ARRAY[selected]); } - - // Toggle the update routine. + config::save(); SettingsState::update_menu_options(); } -static const char *get_value_text(uint8_t value) +void SettingsState::cycle_zip_level() { - return value ? strings::get_by_name(strings::names::ON_OFF, 1) : strings::get_by_name(strings::names::ON_OFF, 0); + uint8_t zipLevel = config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL); + if (++zipLevel % 10 == 0) { zipLevel = 0; } + config::set_by_key(config::keys::ZIP_COMPRESSION_LEVEL, zipLevel); } -static const char *get_sort_type_text(uint8_t value) +void SettingsState::cycle_sort_type() { - switch (value) - { - case 0: - { - return "Alphabetically"; - } - break; + uint8_t sortType = config::get_by_key(config::keys::TITLE_SORT_TYPE); + if (++sortType % 3 == 0) { sortType = 0; } + config::set_by_key(config::keys::TITLE_SORT_TYPE, sortType); - case 1: - { - return "Most Played"; - } - break; - - case 2: - { - return "Last Played"; - } - break; - } - return nullptr; + data::UserList users{}; + data::get_users(users); + for (data::User *user : users) { user->sort_data(); } + MainMenuState::refresh_view_states(); +} + +void SettingsState::toggle_jksm_mode() +{ + config::toggle_by_key(config::keys::JKSM_TEXT_MODE); + MainMenuState::initialize_view_states(); +} + +void SettingsState::cycle_anim_scaling() +{ + double scaling = config::get_animation_scaling(); + if ((scaling += 0.25f) > 4.0f) { scaling = 1.0f; } + config::set_animation_scaling(scaling); +} + +const char *SettingsState::get_status_text(uint8_t value) +{ + if (value > 1) { return nullptr; } + return m_onOff[value]; +} + +const char *SettingsState::get_sort_type_text(uint8_t value) +{ + if (value > 2) { return nullptr; } + logger::log("return string: %s", m_sortTypes[value]); + return m_sortTypes[value]; } diff --git a/source/appstates/TaskState.cpp b/source/appstates/TaskState.cpp index 0508052..eb78840 100644 --- a/source/appstates/TaskState.cpp +++ b/source/appstates/TaskState.cpp @@ -1,37 +1,20 @@ #include "appstates/TaskState.hpp" + #include "colors.hpp" #include "input.hpp" #include "sdl.hpp" #include "strings.hpp" #include "ui/PopMessageManager.hpp" -void TaskState::update() -{ - // Run the base update routine. - BaseTask::update(); - - if (m_task.is_running() && input::button_pressed(HidNpadButton_Plus)) - { - // Throw the message. - ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_GENERAL, 0)); - } - if (!m_task.is_running()) - { - BaseState::deactivate(); - } -} +void TaskState::update() { BaseTask::update(); } void TaskState::render() { - // Grab task string. - std::string status = m_task.get_status(); - // Center so it looks perty - int statusX = 640 - (sdl::text::get_width(24, status.c_str()) / 2); - // Dim the background states. + const std::string status = m_task->get_status(); + const int statusX = 640 - (sdl::text::get_width(24, status.c_str()) / 2); + sdl::render_rect_fill(NULL, 0, 0, 1280, 720, colors::DIM_BACKGROUND); - // Render the status. sdl::text::render(NULL, statusX, 351, 24, sdl::text::NO_TEXT_WRAP, colors::WHITE, status.c_str()); - // Render the loading glyph + BaseTask::render_loading_glyph(); } diff --git a/source/appstates/TitleInfoState.cpp b/source/appstates/TitleInfoState.cpp index 50d8e66..2d756e6 100644 --- a/source/appstates/TitleInfoState.cpp +++ b/source/appstates/TitleInfoState.cpp @@ -1,9 +1,11 @@ #include "appstates/TitleInfoState.hpp" + #include "colors.hpp" #include "input.hpp" #include "sdl.hpp" #include "strings.hpp" #include "stringutil.hpp" + #include namespace @@ -22,7 +24,9 @@ namespace } // namespace TitleInfoState::TitleInfoState(data::User *user, data::TitleInfo *titleInfo) - : m_user(user), m_titleInfo(titleInfo), m_icon(m_titleInfo->get_icon()) + : m_user(user) + , m_titleInfo(titleInfo) + , m_icon(m_titleInfo->get_icon()) { // This needs to be checked. All title information panels share these members. if (!sm_initialized) @@ -37,61 +41,54 @@ TitleInfoState::TitleInfoState(data::User *user, data::TitleInfo *titleInfo) SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET); // This is the render target for the publisher. - sm_publisherTarget = - sdl::TextureManager::create_load_texture("infoPublisherTarget", - SIZE_PANEL_WIDTH - SIZE_PANEL_SUB, - SIZE_TEXT_TARGET_HEIGHT, - SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET); - sm_initialized = true; + sm_publisherTarget = sdl::TextureManager::create_load_texture("infoPublisherTarget", + SIZE_PANEL_WIDTH - SIZE_PANEL_SUB, + SIZE_TEXT_TARGET_HEIGHT, + SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET); + sm_initialized = true; } // Do this instead of calling the function everytime. uint64_t applicationID = m_titleInfo->get_application_id(); // These is needed at some point. - FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(applicationID); + FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(applicationID); PdmPlayStatistics *playStats = m_user->get_play_stats_by_id(applicationID); // Title text. - m_titleScroll - .create(m_titleInfo->get_title(), SIZE_FONT, SIZE_PANEL_WIDTH - SIZE_PANEL_SUB, 6, false, colors::WHITE); + m_titleScroll.create(m_titleInfo->get_title(), SIZE_FONT, SIZE_PANEL_WIDTH - SIZE_PANEL_SUB, 6, false, colors::WHITE); // Publisher. m_publisherScroll .create(m_titleInfo->get_publisher(), SIZE_FONT, SIZE_PANEL_WIDTH - SIZE_PANEL_SUB, 6, false, colors::WHITE); // Grab the application ID string. - m_applicationID = - stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 0), applicationID); + m_applicationID = stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEINFO, 0), applicationID); // Same here. I think these use lower case characters on the NAND? - m_saveDataID = stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 1), - saveInfo->save_data_id); + m_saveDataID = stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEINFO, 1), saveInfo->save_data_id); // This is simple. - m_totalLaunches = stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 5), - playStats->total_launches); + m_totalLaunches = + stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEINFO, 5), playStats->total_launches); // This should be semi-simple. - m_saveDataType = stringutil::get_formatted_string( - strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 6), - strings::get_by_name(strings::names::SAVE_DATA_TYPES, saveInfo->save_data_type)); + m_saveDataType = + stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEINFO, 6), + strings::get_by_name(strings::names::SAVE_DATA_TYPES, saveInfo->save_data_type)); // Going to "cheat" with the next two. This works, so screw it. char playBuffer[0x40] = {0}; - std::tm *firstPlayed = std::localtime(reinterpret_cast(&playStats->first_timestamp_user)); - std::strftime(playBuffer, 0x40, strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 2), firstPlayed); + std::tm *firstPlayed = std::localtime(reinterpret_cast(&playStats->first_timestamp_user)); + std::strftime(playBuffer, 0x40, strings::get_by_name(strings::names::TITLEINFO, 2), firstPlayed); m_firstPlayed.assign(playBuffer); std::tm *lastPlayed = std::localtime(reinterpret_cast(&playStats->last_timestamp_user)); - std::strftime(playBuffer, 0x40, strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 3), lastPlayed); + std::strftime(playBuffer, 0x40, strings::get_by_name(strings::names::TITLEINFO, 3), lastPlayed); m_lastPlayed.assign(playBuffer); // Calculate play time. We're going to use non-floating point to truncate the remainders. int64_t seconds = playStats->playtime / static_cast(1e+9); - int64_t hours = seconds / 3600; + int64_t hours = seconds / 3600; int64_t minutes = (seconds % 3600) / 60; seconds %= 60; - m_playTime = stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 4), - hours, - minutes, - seconds); + m_playTime = stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEINFO, 4), hours, minutes, seconds); } TitleInfoState::~TitleInfoState() @@ -112,10 +109,7 @@ void TitleInfoState::update() m_titleScroll.update(hasFocus); m_publisherScroll.update(hasFocus); - if (input::button_pressed(HidNpadButton_B)) - { - sm_slidePanel->close(); - } + if (input::button_pressed(HidNpadButton_B)) { sm_slidePanel->close(); } else if (sm_slidePanel->is_closed()) { sm_slidePanel->reset(); @@ -159,86 +153,33 @@ void TitleInfoState::render() sm_publisherTarget->render(panelTarget, 8, (y += SIZE_VERT_GAP)); // Application ID. - sdl::render_rect_fill(panelTarget, - 8, - (y += SIZE_VERT_GAP), - SIZE_RECT_WIDTH, - SIZE_TEXT_TARGET_HEIGHT, - colors::DIALOG_BOX); - sdl::text::render(panelTarget, - 16, - y + 6, - SIZE_FONT, - sdl::text::NO_TEXT_WRAP, - colors::WHITE, - m_applicationID.c_str()); + sdl::render_rect_fill(panelTarget, 8, (y += SIZE_VERT_GAP), SIZE_RECT_WIDTH, SIZE_TEXT_TARGET_HEIGHT, colors::DIALOG_BOX); + sdl::text::render(panelTarget, 16, y + 6, SIZE_FONT, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_applicationID.c_str()); // Save data ID. - sdl::render_rect_fill(panelTarget, - 8, - (y += SIZE_VERT_GAP), - SIZE_RECT_WIDTH, - SIZE_TEXT_TARGET_HEIGHT, - colors::CLEAR_COLOR); + sdl::render_rect_fill(panelTarget, 8, (y += SIZE_VERT_GAP), SIZE_RECT_WIDTH, SIZE_TEXT_TARGET_HEIGHT, colors::CLEAR_COLOR); // Text needs to be aligned like the scrolling text. sdl::text::render(panelTarget, 16, y + 6, SIZE_FONT, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_saveDataID.c_str()); // First played. - sdl::render_rect_fill(panelTarget, - 8, - (y += SIZE_VERT_GAP), - SIZE_RECT_WIDTH, - SIZE_TEXT_TARGET_HEIGHT, - colors::DIALOG_BOX); + sdl::render_rect_fill(panelTarget, 8, (y += SIZE_VERT_GAP), SIZE_RECT_WIDTH, SIZE_TEXT_TARGET_HEIGHT, colors::DIALOG_BOX); sdl::text::render(panelTarget, 16, y + 6, SIZE_FONT, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_firstPlayed.c_str()); // Last played. - sdl::render_rect_fill(panelTarget, - 8, - (y += SIZE_VERT_GAP), - SIZE_RECT_WIDTH, - SIZE_TEXT_TARGET_HEIGHT, - colors::CLEAR_COLOR); + sdl::render_rect_fill(panelTarget, 8, (y += SIZE_VERT_GAP), SIZE_RECT_WIDTH, SIZE_TEXT_TARGET_HEIGHT, colors::CLEAR_COLOR); sdl::text::render(panelTarget, 16, y + 6, SIZE_FONT, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_lastPlayed.c_str()); // Play time. - sdl::render_rect_fill(panelTarget, - 8, - (y += SIZE_VERT_GAP), - SIZE_RECT_WIDTH, - SIZE_TEXT_TARGET_HEIGHT, - colors::DIALOG_BOX); + sdl::render_rect_fill(panelTarget, 8, (y += SIZE_VERT_GAP), SIZE_RECT_WIDTH, SIZE_TEXT_TARGET_HEIGHT, colors::DIALOG_BOX); sdl::text::render(panelTarget, 16, y + 6, SIZE_FONT, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_playTime.c_str()); // Total launches. - sdl::render_rect_fill(panelTarget, - 8, - (y += SIZE_VERT_GAP), - SIZE_RECT_WIDTH, - SIZE_TEXT_TARGET_HEIGHT, - colors::CLEAR_COLOR); - sdl::text::render(panelTarget, - 16, - y + 6, - SIZE_FONT, - sdl::text::NO_TEXT_WRAP, - colors::WHITE, - m_totalLaunches.c_str()); + sdl::render_rect_fill(panelTarget, 8, (y += SIZE_VERT_GAP), SIZE_RECT_WIDTH, SIZE_TEXT_TARGET_HEIGHT, colors::CLEAR_COLOR); + sdl::text::render(panelTarget, 16, y + 6, SIZE_FONT, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_totalLaunches.c_str()); // Save data type. - sdl::render_rect_fill(panelTarget, - 8, - (y += SIZE_VERT_GAP), - SIZE_RECT_WIDTH, - SIZE_TEXT_TARGET_HEIGHT, - colors::DIALOG_BOX); - sdl::text::render(panelTarget, - 16, - y + 6, - SIZE_FONT, - sdl::text::NO_TEXT_WRAP, - colors::WHITE, - m_saveDataType.c_str()); + sdl::render_rect_fill(panelTarget, 8, (y += SIZE_VERT_GAP), SIZE_RECT_WIDTH, SIZE_TEXT_TARGET_HEIGHT, colors::DIALOG_BOX); + sdl::text::render(panelTarget, 16, y + 6, SIZE_FONT, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_saveDataType.c_str()); sm_slidePanel->render(NULL, hasFocus); } diff --git a/source/appstates/TitleOptionState.cpp b/source/appstates/TitleOptionState.cpp index 381d7cf..99f37e5 100644 --- a/source/appstates/TitleOptionState.cpp +++ b/source/appstates/TitleOptionState.cpp @@ -1,4 +1,5 @@ #include "appstates/TitleOptionState.hpp" + #include "StateManager.hpp" #include "appstates/ConfirmState.hpp" #include "appstates/MainMenuState.hpp" @@ -14,6 +15,7 @@ #include "stringutil.hpp" #include "system/system.hpp" #include "ui/PopMessageManager.hpp" + #include namespace @@ -46,20 +48,22 @@ static void extend_save_data(sys::Task *task, std::shared_ptr()) + : m_user(user) + , m_titleInfo(titleInfo) + , m_titleSelect(titleSelect) + , m_dataStruct(std::make_shared()) { // Create panel if needed. if (!sm_initialized) { // Allocate static members. - sm_slidePanel = std::make_unique(480, ui::SlideOutPanel::Side::Right); + sm_slidePanel = std::make_unique(480, ui::SlideOutPanel::Side::Right); sm_titleOptionMenu = std::make_unique(8, 8, 460, 22, 720); // Populate menu. - int stringIndex = 0; + int stringIndex = 0; const char *currentString = nullptr; - while ((currentString = strings::get_by_name(strings::names::TITLE_OPTIONS, stringIndex++)) != nullptr) + while ((currentString = strings::get_by_name(strings::names::TITLEOPTION, stringIndex++)) != nullptr) { sm_titleOptionMenu->add_option(currentString); } @@ -69,10 +73,10 @@ TitleOptionState::TitleOptionState(data::User *user, data::TitleInfo *titleInfo, } // Fill this out. - m_dataStruct->m_user = m_user; - m_dataStruct->m_titleInfo = m_titleInfo; + m_dataStruct->m_user = m_user; + m_dataStruct->m_titleInfo = m_titleInfo; m_dataStruct->m_spawningState = this; - m_dataStruct->m_titleSelect = m_titleSelect; + m_dataStruct->m_titleSelect = m_titleSelect; } void TitleOptionState::update() @@ -86,10 +90,7 @@ void TitleOptionState::update() // Return so nothing else happens. Not sure I like this, but w/e. return; } - if (m_exitRequired) - { - sm_slidePanel->close(); - } + if (m_exitRequired) { sm_slidePanel->close(); } // Update panel and menu. sm_slidePanel->update(BaseState::has_focus()); @@ -111,9 +112,9 @@ void TitleOptionState::update() case BLACKLIST: { // Get the string. - std::string confirmString = stringutil::get_formatted_string( - strings::get_by_name(strings::names::TITLE_OPTION_CONFIRMATIONS, 0), - m_titleInfo->get_title()); + std::string confirmString = + stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEOPTION_CONFS, 0), + m_titleInfo->get_title()); // The actual state. auto confirm = @@ -141,11 +142,12 @@ void TitleOptionState::update() case DELETE_ALL_BACKUPS: { // String - std::string confirmString = stringutil::get_formatted_string( - strings::get_by_name(strings::names::TITLE_OPTION_CONFIRMATIONS, 1), - m_titleInfo->get_title()); + std::string confirmString = + stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEOPTION_CONFS, 1), + m_titleInfo->get_title()); - // State. This always requires holding because I hate people complaining to me about how it's my fault they don't read things first. + // State. This always requires holding because I hate people complaining to me about how it's my fault they + // don't read things first. auto confirm = std::make_shared>( confirmString, true, @@ -163,14 +165,14 @@ void TitleOptionState::update() if (fs::is_system_save_data(saveInfo) && !config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM)) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLE_OPTION_POPS, 6)); + strings::get_by_name(strings::names::TITLEOPTION_POPS, 6)); return; } // String - std::string confirmString = stringutil::get_formatted_string( - strings::get_by_name(strings::names::TITLE_OPTION_CONFIRMATIONS, 2), - m_titleInfo->get_title()); + std::string confirmString = + stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEOPTION_CONFS, 2), + m_titleInfo->get_title()); auto confirm = std::make_shared>(confirmString, @@ -188,15 +190,15 @@ void TitleOptionState::update() if (fs::is_system_save_data(saveInfo) && !config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM)) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLE_OPTION_POPS, 6)); + strings::get_by_name(strings::names::TITLEOPTION_POPS, 6)); return; } // String - std::string confirmString = stringutil::get_formatted_string( - strings::get_by_name(strings::names::TITLE_OPTION_CONFIRMATIONS, 3), - m_user->get_nickname(), - m_titleInfo->get_title()); + std::string confirmString = + stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLEOPTION_CONFS, 3), + m_user->get_nickname(), + m_titleInfo->get_title()); // Confirmation. auto confirm = std::make_shared>( @@ -215,7 +217,7 @@ void TitleOptionState::update() if (fs::is_system_save_data(saveInfo) && !config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM)) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLE_OPTION_POPS, 6)); + strings::get_by_name(strings::names::TITLEOPTION_POPS, 6)); return; } @@ -228,19 +230,13 @@ void TitleOptionState::update() { // This type of save data can't have this exported anyway. FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(m_titleInfo->get_application_id()); - if (fs::is_system_save_data(saveInfo)) - { - return; - } + if (fs::is_system_save_data(saveInfo)) { return; } export_svi_file(m_titleInfo); } break; } } - else if (input::button_pressed(HidNpadButton_B)) - { - sm_slidePanel->close(); - } + else if (input::button_pressed(HidNpadButton_B)) { sm_slidePanel->close(); } else if (sm_slidePanel->is_closed()) { // Reset static members. @@ -258,15 +254,9 @@ void TitleOptionState::render() sm_slidePanel->render(NULL, BaseState::has_focus()); } -void TitleOptionState::close_on_update() -{ - m_exitRequired = true; -} +void TitleOptionState::close_on_update() { m_exitRequired = true; } -void TitleOptionState::refresh_required() -{ - m_refreshRequired = true; -} +void TitleOptionState::refresh_required() { m_refreshRequired = true; } static void blacklist_title(sys::Task *task, std::shared_ptr dataStruct) { @@ -279,10 +269,7 @@ static void blacklist_title(sys::Task *task, std::shared_ptrerase_save_info_by_id(applicationID); - } + for (data::User *user : userList) { user->erase_save_info_by_id(applicationID); } // This will tell the main thread a refresh is required on the next update call. dataStruct->m_spawningState->refresh_required(); @@ -298,20 +285,16 @@ static void change_output_path(data::TitleInfo *targetTitle) // Header string. std::string headerString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::KEYBOARD_STRINGS, 7), - targetTitle->get_title()); + stringutil::get_formatted_string(strings::get_by_name(strings::names::KEYBOARD, 7), targetTitle->get_title()); // Try to get input. - if (!keyboard::get_input(SwkbdType_QWERTY, targetTitle->get_path_safe_title(), headerString, pathBuffer, 0x200)) - { - return; - } + if (!keyboard::get_input(SwkbdType_QWERTY, targetTitle->get_path_safe_title(), headerString, pathBuffer, 0x200)) { return; } // Try to make sure it will work. if (!stringutil::sanitize_string_for_path(pathBuffer, pathBuffer, 0x200)) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_TITLE_OPTIONS, 0)); + strings::get_by_name(strings::names::TITLEOPTION_POPS, 0)); return; } @@ -331,7 +314,7 @@ static void change_output_path(data::TitleInfo *targetTitle) // Pop so we know stuff happened. ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_TITLE_OPTIONS, 1), + strings::get_by_name(strings::names::TITLEOPTION_POPS, 1), pathBuffer); } @@ -341,19 +324,18 @@ static void delete_all_backups_for_title(sys::Task *task, std::shared_ptrm_titleInfo->get_path_safe_title(); // Set the status. - task->set_status(strings::get_by_name(strings::names::TITLE_OPTION_STATUS, 0), - dataStruct->m_titleInfo->get_title()); + task->set_status(strings::get_by_name(strings::names::TITLEOPTION_STATUS, 0), dataStruct->m_titleInfo->get_title()); // Just call this and nuke the folder. if (!fslib::delete_directory_recursively(titlePath)) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLE_OPTION_POPS, 1)); + strings::get_by_name(strings::names::TITLEOPTION_POPS, 1)); } else { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLE_OPTION_POPS, 0), + strings::get_by_name(strings::names::TITLEOPTION_POPS, 0), dataStruct->m_titleInfo->get_title()); } task->finished(); @@ -369,7 +351,7 @@ static void reset_save_data(sys::Task *task, std::shared_ptrfinished(); return; } @@ -380,7 +362,7 @@ static void reset_save_data(sys::Task *task, std::shared_ptrfinished(); return; } @@ -391,7 +373,7 @@ static void reset_save_data(sys::Task *task, std::shared_ptrfinished(); return; } @@ -399,19 +381,19 @@ static void reset_save_data(sys::Task *task, std::shared_ptrfinished(); } static void delete_save_data_from_system(sys::Task *task, std::shared_ptr dataStruct) { // Set the status in case this takes a little while. - task->set_status(strings::get_by_name(strings::names::TITLE_OPTION_STATUS, 2), + task->set_status(strings::get_by_name(strings::names::TITLEOPTION_STATUS, 2), dataStruct->m_user->get_nickname(), dataStruct->m_titleInfo->get_title()); // Grab the save data info pointer. - uint64_t applicationID = dataStruct->m_titleInfo->get_application_id(); + uint64_t applicationID = dataStruct->m_titleInfo->get_application_id(); FsSaveDataInfo *saveInfo = dataStruct->m_user->get_save_info_by_id(applicationID); if (saveInfo == nullptr) { @@ -444,7 +426,7 @@ static void extend_save_data(sys::Task *task, std::shared_ptrm_titleInfo; - FsSaveDataInfo *saveInfo = dataStruct->m_user->get_save_info_by_id(titleInfo->get_application_id()); + FsSaveDataInfo *saveInfo = dataStruct->m_user->get_save_info_by_id(titleInfo->get_application_id()); if (!saveInfo) { @@ -454,12 +436,12 @@ static void extend_save_data(sys::Task *task, std::shared_ptrset_status(strings::get_by_name(strings::names::TITLE_OPTION_STATUS, 3), + task->set_status(strings::get_by_name(strings::names::TITLEOPTION_STATUS, 3), dataStruct->m_user->get_nickname(), dataStruct->m_titleInfo->get_title()); // This is the header string. - std::string_view keyboardString = strings::get_by_name(strings::names::KEYBOARD_STRINGS, 8); + std::string_view keyboardString = strings::get_by_name(strings::names::KEYBOARD, 8); // Get how much to extend. char buffer[5] = {0}; @@ -497,7 +479,7 @@ static void export_svi_file(data::TitleInfo *titleInfo) logger::log("SVI for %016llX already exists!", titleInfo->get_application_id()); // Just show this and bail. ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLE_OPTION_POPS, 5)); + strings::get_by_name(strings::names::TITLEOPTION_POPS, 5)); return; } @@ -507,7 +489,7 @@ static void export_svi_file(data::TitleInfo *titleInfo) { logger::log("Error exporting SVI file: %s", fslib::error::get_string()); ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLE_OPTION_POPS, 5)); + strings::get_by_name(strings::names::TITLEOPTION_POPS, 5)); } // Ok. Letsa go~ @@ -520,5 +502,5 @@ static void export_svi_file(data::TitleInfo *titleInfo) // Show this so we know things happened.jpg ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::TITLE_OPTION_POPS, 4)); + strings::get_by_name(strings::names::TITLEOPTION_POPS, 4)); } diff --git a/source/appstates/UserOptionState.cpp b/source/appstates/UserOptionState.cpp index 60ba52b..ce3def8 100644 --- a/source/appstates/UserOptionState.cpp +++ b/source/appstates/UserOptionState.cpp @@ -1,4 +1,5 @@ #include "appstates/UserOptionState.hpp" + #include "StateManager.hpp" #include "appstates/ConfirmState.hpp" #include "appstates/MainMenuState.hpp" @@ -37,24 +38,23 @@ static void create_all_save_data_for_user(sys::Task *task, std::shared_ptr dataStruct); UserOptionState::UserOptionState(data::User *user, TitleSelectCommon *titleSelect) - : m_user(user), m_titleSelect(titleSelect), m_userOptionMenu(8, 8, 460, 22, 720), - m_dataStruct(std::make_shared()) + : m_user(user) + , m_titleSelect(titleSelect) + , m_userOptionMenu(8, 8, 460, 22, 720) + , m_dataStruct(std::make_shared()) { // Check if panel needs to be created. It's shared by all instances. - if (!m_menuPanel) - { - m_menuPanel = std::make_unique(480, ui::SlideOutPanel::Side::Right); - } + if (!m_menuPanel) { m_menuPanel = std::make_unique(480, ui::SlideOutPanel::Side::Right); } - int currentStringIndex = 0; + int currentStringIndex = 0; const char *currentString = nullptr; - while ((currentString = strings::get_by_name(strings::names::USER_OPTIONS, currentStringIndex++)) != nullptr) + while ((currentString = strings::get_by_name(strings::names::USEROPTION_MENU, currentStringIndex++)) != nullptr) { m_userOptionMenu.add_option(stringutil::get_formatted_string(currentString, m_user->get_nickname())); } // Fill this is. - m_dataStruct->m_user = m_user; + m_dataStruct->m_user = m_user; m_dataStruct->m_spawningState = this; } @@ -79,7 +79,7 @@ void UserOptionState::update() { // This is broken down to make it easier to read. std::string queryString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::USER_OPTION_CONFIRMATIONS, 0), + stringutil::get_formatted_string(strings::get_by_name(strings::names::USEROPTION_CONFS, 0), m_user->get_nickname()); // State to push @@ -106,15 +106,14 @@ void UserOptionState::update() case CREATE_ALL_SAVE: { std::string queryString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::USER_OPTION_CONFIRMATIONS, 1), + stringutil::get_formatted_string(strings::get_by_name(strings::names::USEROPTION_CONFS, 1), m_user->get_nickname()); - auto confirmCreateAll = - std::make_shared>( - queryString, - true, - create_all_save_data_for_user, - m_dataStruct); + auto confirmCreateAll = std::make_shared>( + queryString, + true, + create_all_save_data_for_user, + m_dataStruct); // Done? StateManager::push_state(confirmCreateAll); @@ -124,25 +123,21 @@ void UserOptionState::update() case DELETE_ALL_SAVE: { std::string queryString = - stringutil::get_formatted_string(strings::get_by_name(strings::names::USER_OPTION_CONFIRMATIONS, 2), + stringutil::get_formatted_string(strings::get_by_name(strings::names::USEROPTION_CONFS, 2), m_user->get_nickname()); - auto confirmDeleteAll = - std::make_shared>( - queryString, - true, - delete_all_save_data_for_user, - m_dataStruct); + auto confirmDeleteAll = std::make_shared>( + queryString, + true, + delete_all_save_data_for_user, + m_dataStruct); StateManager::push_state(confirmDeleteAll); } break; } } - else if (input::button_pressed(HidNpadButton_B)) - { - m_menuPanel->close(); - } + else if (input::button_pressed(HidNpadButton_B)) { m_menuPanel->close(); } else if (m_menuPanel->is_closed()) { BaseState::deactivate(); @@ -163,10 +158,7 @@ void UserOptionState::render() m_menuPanel->render(NULL, BaseState::has_focus()); } -void UserOptionState::data_and_view_refresh_required() -{ - m_refreshRequired = true; -} +void UserOptionState::data_and_view_refresh_required() { m_refreshRequired = true; } static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptr dataStruct) { @@ -176,23 +168,16 @@ static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptrget_save_info_at(i); - data::TitleInfo *currentTitle = data::get_title_info_by_id(currentSaveInfo->application_id); + data::TitleInfo *currentTitle = data::get_title_info_by_id(currentSaveInfo->application_id); - if (!currentSaveInfo || !currentTitle) - { - continue; - } + if (!currentSaveInfo || !currentTitle) { continue; } // Try to create target game folder. fslib::Path gameFolder = config::get_working_directory() / currentTitle->get_path_safe_title(); - if (!fslib::directory_exists(gameFolder) && !fslib::create_directory(gameFolder)) - { - continue; - } + if (!fslib::directory_exists(gameFolder) && !fslib::create_directory(gameFolder)) { continue; } // Try to mount save data. - bool saveMounted = - fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *targetUser->get_save_info_at(i)); + bool saveMounted = fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *targetUser->get_save_info_at(i)); // Check to make sure the save actually has data to avoid blanks. { @@ -201,7 +186,7 @@ static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptrget_path_safe_title() / - targetUser->get_path_safe_nickname() + - " - " + stringutil::get_date_string() + ".zip"; + fslib::Path targetPath = + config::get_working_directory() / currentTitle->get_path_safe_title() / targetUser->get_path_safe_nickname() + + " - " + stringutil::get_date_string() + ".zip"; zipFile targetZip = zipOpen64(targetPath.full_path(), APPEND_STATUS_CREATE); if (!targetZip) @@ -224,9 +209,9 @@ static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptrget_path_safe_title() / - targetUser->get_path_safe_nickname() + - " - " + stringutil::get_date_string(); + fslib::Path targetPath = + config::get_working_directory() / currentTitle->get_path_safe_title() / targetUser->get_path_safe_nickname() + + " - " + stringutil::get_date_string(); if (!fslib::create_directory(targetPath)) { @@ -236,10 +221,7 @@ static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptrfinished(); } @@ -254,19 +236,16 @@ static void create_all_save_data_for_user(sys::Task *task, std::shared_ptrget_account_save_type())) - { - continue; - } + if (!titleInfo.has_save_data_type(targetUser->get_account_save_type())) { continue; } // Set status. - task->set_status(strings::get_by_name(strings::names::USER_OPTION_STATUS, 0), titleInfo.get_title()); + task->set_status(strings::get_by_name(strings::names::USEROPTION_STATUS, 0), titleInfo.get_title()); if (!fs::create_save_data_for(targetUser, &titleInfo)) { // Function should log error. ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_SAVE_CREATE, 0)); + strings::get_by_name(strings::names::SAVECREATE_POPS, 0)); } } @@ -290,7 +269,7 @@ static void delete_all_save_data_for_user(sys::Task *task, std::shared_ptrget_application_id_at(i))->get_title(); // Update thread task. - task->set_status(strings::get_by_name(strings::names::USER_OPTION_STATUS, 1), targetTitle); + task->set_status(strings::get_by_name(strings::names::USEROPTION_STATUS, 1), targetTitle); // Grab a pointer quick. FsSaveDataInfo *saveInfo = targetUser->get_save_info_at(i); @@ -299,7 +278,7 @@ static void delete_all_save_data_for_user(sys::Task *task, std::shared_ptrsave_data_type != FsSaveDataType_System && !fs::delete_save_data(targetUser->get_save_info_at(i))) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::POP_MESSAGES_SAVE_CREATE, 2)); + strings::get_by_name(strings::names::SAVECREATE_POPS, 2)); continue; } @@ -308,10 +287,7 @@ static void delete_all_save_data_for_user(sys::Task *task, std::shared_ptrerase_save_info_by_id(applicationID); - } + for (uint64_t &applicationID : applicationIDs) { targetUser->erase_save_info_by_id(applicationID); } // Signal the main thread to update~ dataStruct->m_spawningState->data_and_view_refresh_required(); diff --git a/source/data/TitleInfo.cpp b/source/data/TitleInfo.cpp index 9eea39b..774e270 100644 --- a/source/data/TitleInfo.cpp +++ b/source/data/TitleInfo.cpp @@ -1,389 +1,205 @@ #include "data/TitleInfo.hpp" + #include "colors.hpp" #include "config.hpp" +#include "error.hpp" #include "gfxutil.hpp" #include "logger.hpp" #include "stringutil.hpp" + #include -data::TitleInfo::TitleInfo(uint64_t applicationID) : m_applicationID(applicationID) +data::TitleInfo::TitleInfo(uint64_t applicationID) + : m_applicationID(applicationID) + , m_data(std::make_unique()) { - // Used to calculate icon size. - uint64_t nsAppControlSize = 0; - // Language entry - NacpLanguageEntry *languageEntry = nullptr; + static constexpr size_t SIZE_CTRL_DATA = sizeof(NsApplicationControlData); + static constexpr size_t SIZE_NACP = sizeof(NacpStruct); - Result nsError = nsGetApplicationControlData(NsApplicationControlSource_Storage, - applicationID, - &m_data, - sizeof(NsApplicationControlData), - &nsAppControlSize); + uint64_t controlSize{}; + NacpLanguageEntry *entry{}; + NsApplicationControlData *data = m_data.get(); - if (R_FAILED(nsError) || nsAppControlSize < sizeof(m_data.nacp)) + // This will filter from even trying to fetch control data for system titles. + const bool isSystem = applicationID & 0x8000000000000000; + const bool getError = !isSystem && error::libnx(nsGetApplicationControlData(NsApplicationControlSource_Storage, + m_applicationID, + data, + SIZE_CTRL_DATA, + &controlSize)); + const bool entryError = !isSystem && !getError && error::libnx(nacpGetLanguageEntry(&data->nacp, &entry)); + if (isSystem || getError) { - // This is the lowest four hex values of the title. - std::string applicationIDHex = stringutil::get_formatted_string("%04X", m_applicationID & 0xFFFF); + const std::string appIDHex = stringutil::get_formatted_string("%04X", m_applicationID & 0xFFFF); + char *name = data->nacp.lang[SetLanguage_ENUS].name; // I'm hoping this is enough? - // Blank the nacp just to be sure. - std::memset(&m_data, 0x00, sizeof(NsApplicationControlData)); - - // Sprintf title ids to language entries for safety. - snprintf(m_data.nacp.lang[SetLanguage_ENUS].name, TitleInfo::SIZE_PATH_SAFE, "%016lX", m_applicationID); - - // Path safe version of title. + std::memset(data, 0x00, SIZE_CTRL_DATA); + std::snprintf(name, TitleInfo::SIZE_PATH_SAFE, "%016lX", m_applicationID); + m_icon = gfxutil::create_generic_icon(appIDHex, 48, colors::DIALOG_BOX, colors::WHITE); TitleInfo::get_create_path_safe_title(); - - // Create the placeholder icon. - m_icon = gfxutil::create_generic_icon(applicationIDHex, 48, colors::DIALOG_BOX, colors::WHITE); } - else if (R_SUCCEEDED(nsError) && R_SUCCEEDED(nacpGetLanguageEntry(&m_data.nacp, &languageEntry))) + else if (!getError && !entryError) { - // Make sure title knows it has a valid control data struct. - m_hasData = true; + const size_t iconSize = controlSize - SIZE_NACP; + m_hasData = true; - // Get a path safe version of the title. TitleInfo::get_create_path_safe_title(); - - // Load the icon. - m_icon = sdl::TextureManager::create_load_texture(languageEntry->name, - m_data.icon, - nsAppControlSize - sizeof(NacpStruct)); + m_icon = sdl::TextureManager::create_load_texture(entry->name, m_data->icon, iconSize); } } // To do: Make this safer... data::TitleInfo::TitleInfo(uint64_t applicationID, NsApplicationControlData &controlData) : m_applicationID(applicationID) + , m_data(std::make_unique()) { - // Start by making a copy of this. - std::memcpy(&m_data, &controlData, sizeof(NsApplicationControlData)); + NsApplicationControlData *data = m_data.get(); - // Grab the language entry for the texture name. - NacpLanguageEntry *entry = nullptr; - if (R_FAILED(nacpGetLanguageEntry(&m_data.nacp, &entry))) - { - // sprintf the title ID to it. This buffer is the same as the path safe buffer so I'm using that. - std::snprintf(entry->name, TitleInfo::SIZE_PATH_SAFE, "%016lX", m_applicationID); - } + std::memcpy(data, &controlData, sizeof(NsApplicationControlData)); - // Oops. Need this. + NacpLanguageEntry *entry{}; + const bool entryError = error::libnx(nacpGetLanguageEntry(&data->nacp, &entry)); + if (entryError) { std::snprintf(entry->name, TitleInfo::SIZE_PATH_SAFE, "%016lX", m_applicationID); } TitleInfo::get_create_path_safe_title(); - - // Load the icon to a texture. We're going to be lazy with the size here since it works anyway. - m_icon = sdl::TextureManager::create_load_texture(entry->name, m_data.icon, sizeof(m_data.icon)); + m_icon = sdl::TextureManager::create_load_texture(entry->name, m_data->icon, sizeof(m_data->icon)); } -uint64_t data::TitleInfo::get_application_id() const +data::TitleInfo::TitleInfo(data::TitleInfo &&titleInfo) { *this = std::move(titleInfo); } + +data::TitleInfo &data::TitleInfo::operator=(data::TitleInfo &&titleInfo) { - return m_applicationID; + m_applicationID = titleInfo.m_applicationID; + m_data = std::move(titleInfo.m_data); + m_hasData = titleInfo.m_hasData; + std::memcpy(m_pathSafeTitle, titleInfo.m_pathSafeTitle, TitleInfo::SIZE_PATH_SAFE); + m_icon = titleInfo.m_icon; + + titleInfo.m_applicationID = 0; + titleInfo.m_data = nullptr; + titleInfo.m_hasData = false; + std::memset(titleInfo.m_pathSafeTitle, 0x00, TitleInfo::SIZE_PATH_SAFE); + titleInfo.m_icon = nullptr; + + return *this; } -NsApplicationControlData *data::TitleInfo::get_control_data() -{ - return &m_data; -} +uint64_t data::TitleInfo::get_application_id() const { return m_applicationID; } -bool data::TitleInfo::has_control_data() const -{ - return m_hasData; -} +NsApplicationControlData *data::TitleInfo::get_control_data() { return m_data.get(); } + +bool data::TitleInfo::has_control_data() const { return m_hasData; } const char *data::TitleInfo::get_title() { - NacpLanguageEntry *entry = nullptr; - if (R_FAILED(nacpGetLanguageEntry(&m_data.nacp, &entry))) - { - return nullptr; - } + NacpLanguageEntry *entry{}; + const bool entryError = error::libnx(nacpGetLanguageEntry(&m_data->nacp, &entry)); + if (entryError) { return nullptr; } return entry->name; } -const char *data::TitleInfo::get_path_safe_title() const -{ - return m_pathSafeTitle; -} +const char *data::TitleInfo::get_path_safe_title() const { return m_pathSafeTitle; } const char *data::TitleInfo::get_publisher() { - NacpLanguageEntry *Entry = nullptr; - if (R_FAILED(nacpGetLanguageEntry(&m_data.nacp, &Entry))) - { - return nullptr; - } - return Entry->author; + NacpLanguageEntry *entry{}; + const bool entryError = error::libnx(nacpGetLanguageEntry(&m_data->nacp, &entry)); + if (entryError) { return nullptr; } + return entry->author; } -uint64_t data::TitleInfo::get_save_data_owner_id() const -{ - return m_data.nacp.save_data_owner_id; -} +uint64_t data::TitleInfo::get_save_data_owner_id() const { return m_data->nacp.save_data_owner_id; } int64_t data::TitleInfo::get_save_data_size(uint8_t saveType) const { - // Create pointer to NACP since I don't feel like typing too much. - const NacpStruct *nacp = &m_data.nacp; - + const NacpStruct &nacp = m_data->nacp; switch (saveType) { - case FsSaveDataType_Account: - { - return nacp->user_account_save_data_size; - } - break; - - case FsSaveDataType_Bcat: - { - return nacp->bcat_delivery_cache_storage_size; - } - break; - - case FsSaveDataType_Device: - { - return nacp->device_save_data_size; - } - break; - - case FsSaveDataType_Temporary: - { - return nacp->temporary_storage_size; - } - break; - - case FsSaveDataType_Cache: - { - return nacp->cache_storage_size; - } - break; - - default: - { - return 0; - } - break; + case FsSaveDataType_Account: return nacp.user_account_save_data_size; + case FsSaveDataType_Bcat: return nacp.bcat_delivery_cache_storage_size; + case FsSaveDataType_Device: return nacp.device_save_data_size; + case FsSaveDataType_Temporary: return nacp.temporary_storage_size; + case FsSaveDataType_Cache: return nacp.cache_storage_size; } return 0; } int64_t data::TitleInfo::get_save_data_size_max(uint8_t saveType) const { - const NacpStruct *nacp = &m_data.nacp; - + const NacpStruct &nacp = m_data->nacp; switch (saveType) { - case FsSaveDataType_Account: - { - return nacp->user_account_save_data_size_max > nacp->user_account_save_data_size - ? nacp->user_account_save_data_size_max - : nacp->user_account_save_data_size; - } - break; - - case FsSaveDataType_Bcat: - { - return nacp->bcat_delivery_cache_storage_size; - } - break; - - case FsSaveDataType_Device: - { - return nacp->device_save_data_size_max > nacp->device_save_data_size ? nacp->device_save_data_size_max - : nacp->device_save_data_size; - } - break; - - case FsSaveDataType_Temporary: - { - return nacp->temporary_storage_size; - } - break; - - case FsSaveDataType_Cache: - { - return nacp->cache_storage_data_and_journal_size_max > nacp->cache_storage_size - ? nacp->cache_storage_data_and_journal_size_max - : nacp->cache_storage_size; - } - break; - - default: - { - return 0; - } - break; + case FsSaveDataType_Account: return std::max(nacp.user_account_save_data_size, nacp.user_account_save_data_size_max); + case FsSaveDataType_Bcat: return nacp.bcat_delivery_cache_storage_size; + case FsSaveDataType_Device: return std::max(nacp.device_save_data_size, nacp.device_save_data_size_max); + case FsSaveDataType_Temporary: return nacp.temporary_storage_size; + case FsSaveDataType_Cache: return std::max(nacp.cache_storage_size, nacp.cache_storage_data_and_journal_size_max); } return 0; } int64_t data::TitleInfo::get_journal_size(uint8_t saveType) const { - const NacpStruct *nacp = &m_data.nacp; - + const NacpStruct &nacp = m_data->nacp; switch (saveType) { - case FsSaveDataType_Account: - { - return nacp->user_account_save_data_journal_size; - } - break; - - case FsSaveDataType_Bcat: - { - // I'm just assuming this is right... - return nacp->bcat_delivery_cache_storage_size; - } - break; - - case FsSaveDataType_Device: - { - return nacp->device_save_data_journal_size; - } - break; - - case FsSaveDataType_Temporary: - { - // Again, just assuming. - return nacp->temporary_storage_size; - } - break; - - case FsSaveDataType_Cache: - { - return nacp->cache_storage_journal_size; - } - break; - - default: - { - return 0; - } - break; + case FsSaveDataType_Account: return nacp.user_account_save_data_journal_size; + case FsSaveDataType_Bcat: return nacp.bcat_delivery_cache_storage_size; + case FsSaveDataType_Device: return nacp.device_save_data_journal_size; + case FsSaveDataType_Temporary: return nacp.temporary_storage_size; + case FsSaveDataType_Cache: return nacp.cache_storage_journal_size; } return 0; } int64_t data::TitleInfo::get_journal_size_max(uint8_t saveType) const { - const NacpStruct *nacp = &m_data.nacp; - + const NacpStruct &nacp = m_data->nacp; switch (saveType) { case FsSaveDataType_Account: - { - return nacp->user_account_save_data_journal_size_max > nacp->user_account_save_data_journal_size - ? nacp->user_account_save_data_journal_size_max - : nacp->user_account_save_data_journal_size; - } - break; - - case FsSaveDataType_Bcat: - { - return nacp->bcat_delivery_cache_storage_size; - } - break; - - case FsSaveDataType_Device: - { - return nacp->device_save_data_journal_size_max > nacp->device_save_data_journal_size - ? nacp->device_save_data_journal_size_max - : nacp->device_save_data_journal_size; - } - break; - - case FsSaveDataType_Temporary: - { - return nacp->temporary_storage_size; - } - break; - + return std::max(nacp.user_account_save_data_journal_size, nacp.user_account_save_data_journal_size_max); + case FsSaveDataType_Bcat: return nacp.bcat_delivery_cache_storage_size; + case FsSaveDataType_Device: return std::max(nacp.device_save_data_journal_size, nacp.device_save_data_journal_size_max); + case FsSaveDataType_Temporary: return nacp.temporary_storage_size; case FsSaveDataType_Cache: - { - return nacp->cache_storage_data_and_journal_size_max > nacp->cache_storage_journal_size - ? nacp->cache_storage_data_and_journal_size_max - : nacp->cache_storage_journal_size; - } - break; - - default: - { - return 0; - } - break; + return std::max(nacp.cache_storage_journal_size, nacp.cache_storage_data_and_journal_size_max); } return 0; } bool data::TitleInfo::has_save_data_type(uint8_t saveType) const { - const NacpStruct *nacp = &m_data.nacp; - - // I'm not 100% sure this is the best way to test for this. + const NacpStruct &nacp = m_data->nacp; switch (saveType) { - case FsSaveDataType_Account: - { - return nacp->user_account_save_data_size > 0 || nacp->user_account_save_data_size_max > 0; - } - break; - - case FsSaveDataType_Bcat: - { - return nacp->bcat_delivery_cache_storage_size > 0; - } - break; - - case FsSaveDataType_Device: - { - return nacp->device_save_data_size > 0 || nacp->device_save_data_size_max > 0; - } - break; - - case FsSaveDataType_Cache: - { - return nacp->cache_storage_size > 0 || nacp->cache_storage_data_and_journal_size_max > 0; - } - break; - - default: - { - return false; - } - break; + case FsSaveDataType_Account: return nacp.user_account_save_data_size > 0 || nacp.user_account_save_data_size_max > 0; + case FsSaveDataType_Bcat: return nacp.bcat_delivery_cache_storage_size > 0; + case FsSaveDataType_Device: return nacp.device_save_data_size > 0 || nacp.device_save_data_size_max > 0; + case FsSaveDataType_Cache: return nacp.cache_storage_size > 0 || nacp.cache_storage_data_and_journal_size_max > 0; } return false; } -sdl::SharedTexture data::TitleInfo::get_icon() const -{ - return m_icon; -} +sdl::SharedTexture data::TitleInfo::get_icon() const { return m_icon; } void data::TitleInfo::set_path_safe_title(const char *newPathSafe, size_t newPathLength) { - if (newPathLength >= TitleInfo::SIZE_PATH_SAFE) - { - return; - } - - // Need to memset just in case the new title is shorter than the old one. + if (newPathLength >= TitleInfo::SIZE_PATH_SAFE) { return; } std::memset(m_pathSafeTitle, 0x00, TitleInfo::SIZE_PATH_SAFE); std::memcpy(m_pathSafeTitle, newPathSafe, newPathLength); } void data::TitleInfo::get_create_path_safe_title() { - // Avoid calling this function over and over. - uint64_t applicationID = TitleInfo::get_application_id(); + const uint64_t applicationID = TitleInfo::get_application_id(); + NacpLanguageEntry *entry{}; - // Attempt to grab the language entry. - NacpLanguageEntry *entry = nullptr; - - // If it has a custom path defined, use that. Else, try to make it path safe. - if (config::has_custom_path(applicationID)) - { - config::get_custom_path(applicationID, m_pathSafeTitle, TitleInfo::SIZE_PATH_SAFE); - } - else if (R_FAILED(nacpGetLanguageEntry(&m_data.nacp, &entry)) || - !stringutil::sanitize_string_for_path(entry->name, m_pathSafeTitle, TitleInfo::SIZE_PATH_SAFE)) + const bool hasCustomPath = config::has_custom_path(applicationID); + const bool entryError = !hasCustomPath && error::libnx(nacpGetLanguageEntry(&m_data->nacp, &entry)); + const bool sanitizeError = !hasCustomPath && !entryError && + !stringutil::sanitize_string_for_path(entry->name, m_pathSafeTitle, TitleInfo::SIZE_PATH_SAFE); + if (hasCustomPath) { config::get_custom_path(applicationID, m_pathSafeTitle, TitleInfo::SIZE_PATH_SAFE); } + else if (entryError || sanitizeError) { std::snprintf(m_pathSafeTitle, TitleInfo::SIZE_PATH_SAFE, "%016lX", m_applicationID); } diff --git a/source/data/User.cpp b/source/data/User.cpp index 6ba21df..dfd9478 100644 --- a/source/data/User.cpp +++ b/source/data/User.cpp @@ -1,12 +1,15 @@ #include "data/User.hpp" + #include "colors.hpp" #include "config.hpp" #include "data/data.hpp" +#include "error.hpp" #include "fs/save_mount.hpp" #include "gfxutil.hpp" #include "logger.hpp" #include "sdl.hpp" #include "stringutil.hpp" + #include #include @@ -16,7 +19,7 @@ namespace constexpr int SIZE_ICON_FONT = 50; /// @brief This is the number of FsSaveDataInfo entries to allocate and try to read. - constexpr size_t SIZE_SAVE_INFO_BUFFER = 128; + constexpr size_t SIZE_SAVE_INFO_BUFFER = 256; // Array of SaveDataSpaceIDs - SaveDataSpaceAll doesn't seem to work as it should... constexpr std::array SAVE_DATA_SPACE_ORDER = {FsSaveDataSpaceId_System, @@ -30,326 +33,213 @@ namespace // Function used to sort user data. Definition at the bottom. static bool sort_user_data(const data::UserDataEntry &entryA, const data::UserDataEntry &entryB); -data::User::User(AccountUid accountID, FsSaveDataType saveType) : m_accountID(accountID), m_saveType(saveType) +data::User::User(AccountUid accountID, FsSaveDataType saveType) + : m_accountID(accountID) + , m_saveType(saveType) { - AccountProfile profile; - AccountProfileBase profileBase = {0}; + AccountProfile profile{}; + AccountProfileBase profileBase{}; - // Whoever named these needs some help. What the hell? - Result profileError = accountGetProfile(&profile, m_accountID); - Result profileBaseError = accountProfileGet(&profile, NULL, &profileBase); - if (R_FAILED(profileError) || R_FAILED(profileBaseError)) - { - User::create_account(); - } - else - { - User::load_account(profile, profileBase); - } + const bool profileError = error::libnx(accountGetProfile(&profile, m_accountID)); + const bool baseError = !profileError && error::libnx(accountProfileGet(&profile, nullptr, &profileBase)); + if (profileError || baseError) { User::create_account(); } + else { User::load_account(profile, profileBase); } accountProfileClose(&profile); } -data::User::User(AccountUid accountID, - std::string_view nickname, - std::string_view pathSafeNickname, - FsSaveDataType saveType) - : m_accountID(accountID), m_saveType(saveType) +data::User::User(AccountUid accountID, std::string_view nickname, std::string_view pathSafeNickname, FsSaveDataType saveType) + : m_accountID(accountID) + , m_saveType(saveType) { - // Generate icon. m_icon = gfxutil::create_generic_icon(nickname, 48, colors::DIALOG_BOX, colors::WHITE); - - // We're just gonna use this for both. std::memcpy(m_nickname, nickname.data(), nickname.length()); std::memcpy(m_pathSafeNickname, pathSafeNickname.data(), pathSafeNickname.length()); } +data::User::User(data::User &&user) { *this = std::move(user); } + +data::User &data::User::operator=(data::User &&user) +{ + static constexpr size_t SIZE_NICKNAME = 0x20; + + m_accountID = user.m_accountID; + m_saveType = user.m_saveType; + std::strncpy(m_nickname, user.m_nickname, SIZE_NICKNAME); + std::strncpy(m_pathSafeNickname, user.m_pathSafeNickname, SIZE_NICKNAME); + m_icon = user.m_icon; + m_userData = std::move(user.m_userData); + + user.m_accountID = {0}; + user.m_saveType = static_cast(0); + std::memset(user.m_nickname, 0x00, SIZE_NICKNAME); + std::memset(user.m_pathSafeNickname, 0x00, SIZE_NICKNAME); + user.m_icon = nullptr; + + return *this; +} + void data::User::add_data(const FsSaveDataInfo *saveInfo, const PdmPlayStatistics *playStats) { - uint64_t applicationID = saveInfo->application_id == 0 ? saveInfo->system_save_data_id : saveInfo->application_id; + const uint64_t saveInfoAppID = saveInfo->application_id; + const uint64_t saveInfoSysID = saveInfo->system_save_data_id; + const uint64_t applicationID = saveInfoAppID != 0 ? saveInfoAppID : saveInfoSysID; - m_userData.push_back(std::make_pair(applicationID, std::make_pair(*saveInfo, *playStats))); + auto dataPair = std::make_pair(*saveInfo, *playStats); + auto vectorPair = std::make_pair(applicationID, std::move(dataPair)); + m_userData.push_back(std::move(vectorPair)); } -void data::User::clear_data_entries() -{ - m_userData.clear(); -} +void data::User::clear_data_entries() { m_userData.clear(); } -void data::User::erase_data(int index) -{ - m_userData.erase(m_userData.begin() + index); -} +void data::User::erase_data(int index) { m_userData.erase(m_userData.begin() + index); } -void data::User::sort_data() -{ - std::sort(m_userData.begin(), m_userData.end(), sort_user_data); -} +void data::User::sort_data() { std::sort(m_userData.begin(), m_userData.end(), sort_user_data); } -AccountUid data::User::get_account_id() const -{ - return m_accountID; -} +AccountUid data::User::get_account_id() const { return m_accountID; } -FsSaveDataType data::User::get_account_save_type() const -{ - return m_saveType; -} +FsSaveDataType data::User::get_account_save_type() const { return m_saveType; } -const char *data::User::get_nickname() const -{ - return m_nickname; -} +const char *data::User::get_nickname() const { return m_nickname; } -const char *data::User::get_path_safe_nickname() const -{ - return m_pathSafeNickname; -} +const char *data::User::get_path_safe_nickname() const { return m_pathSafeNickname; } -size_t data::User::get_total_data_entries() const -{ - return m_userData.size(); -} +size_t data::User::get_total_data_entries() const { return m_userData.size(); } uint64_t data::User::get_application_id_at(int index) const { - if (index < 0 || index >= static_cast(m_userData.size())) - { - return 0; - } + if (!User::index_check(index)) { return 0; } return m_userData.at(index).first; } FsSaveDataInfo *data::User::get_save_info_at(int index) { - if (index < 0 || index >= static_cast(m_userData.size())) - { - return nullptr; - } + if (!User::index_check(index)) { return nullptr; } return &m_userData.at(index).second.first; } PdmPlayStatistics *data::User::get_play_stats_at(int index) { - if (index < 0 || index >= static_cast(m_userData.size())) - { - return nullptr; - } + if (!User::index_check(index)) { return nullptr; } return &m_userData.at(index).second.second; } FsSaveDataInfo *data::User::get_save_info_by_id(uint64_t applicationID) { - auto findTitle = std::find_if(m_userData.begin(), m_userData.end(), [applicationID](data::UserDataEntry &entry) { - return entry.first == applicationID; - }); - - if (findTitle == m_userData.end()) - { - return nullptr; - } - return &findTitle->second.first; + auto target = User::find_title_by_id(applicationID); + if (target == m_userData.end()) { return nullptr; } + return &target->second.first; } -data::UserSaveInfoList &data::User::get_user_save_info_list() -{ - return m_userData; -} +data::UserSaveInfoList &data::User::get_user_save_info_list() { return m_userData; } PdmPlayStatistics *data::User::get_play_stats_by_id(uint64_t applicationID) { - auto findTitle = std::find_if(m_userData.begin(), m_userData.end(), [applicationID](data::UserDataEntry &entry) { - return entry.first == applicationID; - }); - - if (findTitle == m_userData.end()) - { - return nullptr; - } - return &findTitle->second.second; + auto target = User::find_title_by_id(applicationID); + if (target == m_userData.end()) { return nullptr; } + return &target->second.second; } -SDL_Texture *data::User::get_icon() -{ - return m_icon->get(); -} +SDL_Texture *data::User::get_icon() { return m_icon->get(); } -sdl::SharedTexture data::User::get_shared_icon() -{ - return m_icon; -} +sdl::SharedTexture data::User::get_shared_icon() { return m_icon; } void data::User::erase_save_info_by_id(uint64_t applicationID) { - auto targetEntry = - std::find_if(m_userData.begin(), m_userData.end(), [applicationID](const data::UserDataEntry &entry) { - return entry.second.first.application_id == applicationID; - }); - - if (targetEntry == m_userData.end()) - { - // Do not pass go. Do not collect $200. - return; - } - - m_userData.erase(targetEntry); + auto target = User::find_title_by_id(applicationID); + if (target == m_userData.end()) { return; } + m_userData.erase(target); } void data::User::load_user_data() { - // Pull these from config quick. - bool accountSystemSaves = config::get_by_key(config::keys::LIST_ACCOUNT_SYS_SAVES); - bool enforceMountable = config::get_by_key(config::keys::ONLY_LIST_MOUNTABLE); + if (!m_userData.empty()) { m_userData.clear(); } + const bool accountSys = config::get_by_key(config::keys::LIST_ACCOUNT_SYS_SAVES); + const bool enforceMount = config::get_by_key(config::keys::ONLY_LIST_MOUNTABLE); + const bool isAccountUser = m_saveType != FsSaveDataType_System && m_saveType != FsSaveDataType_SystemBcat; - // Clear the vector if there's anything there first. - if (!m_userData.empty()) - { - m_userData.clear(); - } - - // Loop through the save data space IDs. for (int i = 0; i < 6; i++) { - // Open the save reader according to the save type of the account. - fslib::SaveInfoReader infoReader; - if (!User::open_save_info_reader(SAVE_DATA_SPACE_ORDER[i], infoReader)) + fslib::SaveInfoReader infoReader{}; + if (m_saveType == FsSaveDataType_Account) { - continue; + infoReader.open(SAVE_DATA_SPACE_ORDER[i], m_accountID, SIZE_SAVE_INFO_BUFFER); } + else { infoReader.open(SAVE_DATA_SPACE_ORDER[i], m_saveType, SIZE_SAVE_INFO_BUFFER); } + if (!infoReader.is_open()) { continue; } - // Loop until the reader can't be read anymore. while (infoReader.read()) { - // Grab the count cause I don't remember if the compiler optimizes this away or not. - int64_t readCount = infoReader.get_read_count(); + const int64_t readCount = infoReader.get_read_count(); - // Loop through that too. Loopy loop loop. for (int64_t i = 0; i < readCount; i++) { - // Grab the target save data info struct. - FsSaveDataInfo &saveInfo = infoReader[i]; + const FsSaveDataInfo &saveInfo = infoReader[i]; + const uint64_t saveInfoAppID = saveInfo.application_id; + const uint64_t saveInfoSysID = saveInfo.system_save_data_id; + const uint64_t applicationID = saveInfoAppID != 0 ? saveInfoAppID : saveInfoSysID; + const uint8_t saveDataType = saveInfo.save_data_type; + const bool isSystemSave = saveDataType == FsSaveDataType_System || saveDataType == FsSaveDataType_SystemBcat; - // Since system saves have no application ID... - uint64_t applicationID = - saveInfo.application_id == 0 ? saveInfo.system_save_data_id : saveInfo.application_id; + const bool isBlacklisted = config::is_blacklisted(applicationID); + const bool systemFilter = (!accountSys && isAccountUser && isSystemSave); + const bool mounted = !isBlacklisted && !systemFilter && enforceMount && + fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, saveInfo); + if (isBlacklisted || systemFilter || (enforceMount && !mounted)) { continue; } + if (mounted) { fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); } - // Make sure we shouldn't filter it. - if ((!accountSystemSaves && saveInfo.save_data_type == FsSaveDataType_System && saveInfo.uid != 0) || - config::is_blacklisted(applicationID) || - (enforceMountable && !fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, saveInfo))) - { - // Just skip this stuff. - continue; - } - // Make sure to clean up the mounting. - fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); + const bool titleFound = data::title_exists_in_map(applicationID); + if (!titleFound) { data::load_title_to_map(applicationID); } - if (!data::title_exists_in_map(applicationID)) - { - data::load_title_to_map(applicationID); - } - - // Try to read the play stats. I don't really care if this fails. - PdmPlayStatistics playStats = {0}; + // I don't really care about this failing. + PdmPlayStatistics playStats{}; pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(saveInfo.application_id, m_accountID, false, &playStats); - User::add_data(&saveInfo, &playStats); } } } - // Sort User::sort_data(); } void data::User::load_account(AccountProfile &profile, AccountProfileBase &profileBase) { - // Try to load icon. - uint32_t iconSize = 0; - Result accError = accountProfileGetImageSize(&profile, &iconSize); - if (R_FAILED(accError)) + // We're going to use this for the icon buffer size since it should be pretty safe to use. + static constexpr size_t SIZE_ICON = sizeof(NsApplicationControlData::icon); + static constexpr size_t NICKNAME_BUFFER = 0x20; + + uint32_t iconSize{}; + auto iconBuffer = std::make_unique(SIZE_ICON); + const bool loadError = error::libnx(accountProfileLoadImage(&profile, iconBuffer.get(), SIZE_ICON, &iconSize)); + if (loadError) { - logger::log("Error getting user icon size: 0x%X.", accError); User::create_account(); return; } - std::unique_ptr iconBuffer(new unsigned char[iconSize]); - accError = accountProfileLoadImage(&profile, iconBuffer.get(), iconSize, &iconSize); - if (R_FAILED(accError)) - { - logger::log("Error loading user icon: 0x%08X.", accError); - User::create_account(); - return; - } - - // We should be good at this point. + std::strncpy(m_nickname, profileBase.nickname, NICKNAME_BUFFER); m_icon = sdl::TextureManager::create_load_texture(profileBase.nickname, iconBuffer.get(), iconSize); - // Memcpy the nickname. - std::memcpy(m_nickname, &profileBase.nickname, 0x20); - - if (!stringutil::sanitize_string_for_path(m_nickname, m_pathSafeNickname, 0x20)) + const bool sanitizeError = !stringutil::sanitize_string_for_path(m_nickname, m_pathSafeNickname, NICKNAME_BUFFER); + if (sanitizeError) { - std::string accountIDString = stringutil::get_formatted_string("Account_%08X", m_accountID.uid[0] & 0xFFFFFFFF); - std::memcpy(m_pathSafeNickname, accountIDString.c_str(), accountIDString.length()); + const std::string idString = stringutil::get_formatted_string("Acc_%04X", m_accountID.uid[0] & 0xFFFF); + std::memcpy(m_pathSafeNickname, idString.c_str(), idString.length()); } } void data::User::create_account() { - // This is needed a lot here. - std::string accountIDString = stringutil::get_formatted_string("Acc_%08X", m_accountID.uid[0] & 0xFFFFFFFF); - - // Create icon - m_icon = gfxutil::create_generic_icon(accountIDString, SIZE_ICON_FONT, colors::DIALOG_BOX, colors::WHITE); - - // Memcpy the id string for both nicknames - std::memcpy(m_nickname, accountIDString.c_str(), accountIDString.length()); - std::memcpy(m_pathSafeNickname, accountIDString.c_str(), accountIDString.length()); + const std::string idString = stringutil::get_formatted_string("Acc_%04X", m_accountID.uid[0] & 0xFFFF); + m_icon = gfxutil::create_generic_icon(idString, SIZE_ICON_FONT, colors::DIALOG_BOX, colors::WHITE); + std::memcpy(m_nickname, idString.c_str(), idString.length()); + std::memcpy(m_pathSafeNickname, idString.c_str(), idString.length()); } -bool data::User::open_save_info_reader(FsSaveDataSpaceId spaceID, fslib::SaveInfoReader &reader) +data::UserSaveInfoList::iterator data::User::find_title_by_id(uint64_t applicationID) { - switch (m_saveType) - { - case FsSaveDataType_System: - { - reader.open(spaceID, m_saveType, SIZE_SAVE_INFO_BUFFER); - } - break; - - case FsSaveDataType_Account: - { - reader.open(spaceID, m_accountID, SIZE_SAVE_INFO_BUFFER); - } - break; - - case FsSaveDataType_Bcat: - { - reader.open(spaceID, m_saveType, SIZE_SAVE_INFO_BUFFER); - } - break; - - case FsSaveDataType_Device: - { - reader.open(spaceID, m_saveType, SIZE_SAVE_INFO_BUFFER); - } - break; - - case FsSaveDataType_Cache: - { - reader.open(spaceID, m_saveType, SIZE_SAVE_INFO_BUFFER); - } - break; - - default: - { - } - break; - } - return reader.is_open(); + return std::find_if(m_userData.begin(), m_userData.end(), [&](const auto &entry) { return entry.first == applicationID; }); } static bool sort_user_data(const data::UserDataEntry &entryA, const data::UserDataEntry &entryB) @@ -378,8 +268,8 @@ static bool sort_user_data(const data::UserDataEntry &entryA, const data::UserDa const char *titleB = titleInfoB->get_title(); // Get the shortest of the two. - size_t titleALength = std::char_traits::length(titleA); - size_t titleBLength = std::char_traits::length(titleB); + size_t titleALength = std::char_traits::length(titleA); + size_t titleBLength = std::char_traits::length(titleB); size_t shortestTitle = titleALength < titleBLength ? titleALength : titleBLength; // Loop and compare codepoints. for (size_t i = 0, j = 0; i < shortestTitle;) @@ -387,16 +277,13 @@ static bool sort_user_data(const data::UserDataEntry &entryA, const data::UserDa // Decode UTF-8 uint32_t codepointA = 0; uint32_t codepointB = 0; - ssize_t unitCountA = decode_utf8(&codepointA, reinterpret_cast(&titleA[i])); - ssize_t unitCountB = decode_utf8(&codepointB, reinterpret_cast(&titleB[j])); + ssize_t unitCountA = decode_utf8(&codepointA, reinterpret_cast(&titleA[i])); + ssize_t unitCountB = decode_utf8(&codepointB, reinterpret_cast(&titleB[j])); // Lower so case doesn't screw with it. int charA = std::tolower(codepointA); int charB = std::tolower(codepointB); - if (charA != charB) - { - return charA < charB; - } + if (charA != charB) { return charA < charB; } i += unitCountA; j += unitCountB; diff --git a/source/data/data.cpp b/source/data/data.cpp index 35cb4c0..db6b705 100644 --- a/source/data/data.cpp +++ b/source/data/data.cpp @@ -1,9 +1,12 @@ #include "data/data.hpp" + #include "config.hpp" +#include "error.hpp" #include "fs/fs.hpp" #include "fslib.hpp" #include "logger.hpp" #include "strings.hpp" + #include #include #include @@ -17,11 +20,13 @@ namespace using UserIDPair = std::pair; /// @brief Struct used for reading the cache from file. + // clang-format off typedef struct { - uint64_t m_applicationID; - NsApplicationControlData m_data; + uint64_t applicationID; + NsApplicationControlData data; } CacheEntry; + // clang-format on // User vector to preserve order. std::vector s_userVector; @@ -34,9 +39,9 @@ namespace // These are the ID's used for system type users. constexpr AccountUid ID_SYSTEM_USER = {FsSaveDataType_System}; - constexpr AccountUid ID_BCAT_USER = {FsSaveDataType_Bcat}; + constexpr AccountUid ID_BCAT_USER = {FsSaveDataType_Bcat}; constexpr AccountUid ID_DEVICE_USER = {FsSaveDataType_Device}; - constexpr AccountUid ID_CACHE_USER = {FsSaveDataType_Cache}; + constexpr AccountUid ID_CACHE_USER = {FsSaveDataType_Cache}; } // namespace // Declarations here. Definitions at bottom. These should appear in the order called. @@ -58,89 +63,49 @@ static void create_cache_file(); bool data::initialize(bool clearCache) { - // Convert this to an fslib::Path right off the bat so we don't call the path constructor twice. - fslib::Path cachePath = PATH_CACHE_PATH; + const fslib::Path cachePath{PATH_CACHE_PATH}; + const bool cacheExists = fslib::file_exists(cachePath); + const bool cacheClearFailed = clearCache && cacheExists && error::fslib(fslib::delete_file(cachePath)); + if (clearCache && cacheClearFailed) { return false; } - // Nuke the cache file if we're supposed to. - if (clearCache && fslib::file_exists(cachePath) && !fslib::delete_file(cachePath)) - { - // I don't really think this should be fatal. It's not good that it happens, but not fatal. - logger::log("data::initialize failed to remove existing cache file from SD: %s", fslib::error::get_string()); - } + const bool needsUsers = s_userVector.empty(); + const bool usersLoaded = needsUsers && load_create_user_accounts(); + if (needsUsers && !usersLoaded) { return false; } - // Load user accounts if not done previously. Bail if the load fails cause that is fatal. - if (s_userVector.empty() && !load_create_user_accounts()) + const bool cacheRead = read_cache_file(); + if (!cacheRead) { - return false; - } - - // If the cacheWas nuked, the map is empty or we fail to read the cache file... - if ((clearCache || s_titleInfoMap.empty()) && !read_cache_file()) - { - // Clear the map out first. s_titleInfoMap.clear(); - // Load the application records. load_application_records(); + import_svi_files(); } - // Load these now if needed. - import_svi_files(); + for (auto &[accountID, user] : s_userVector) { user.load_user_data(); } - // Load the save data. - // data::load_save_data_info(); - // Loop users and make them load their data. - for (auto &[accountID, user] : s_userVector) - { - user.load_user_data(); - } + const bool needsCacheBuild = !fslib::file_exists(cachePath); + if (needsCacheBuild) { create_cache_file(); } - - // If the cache file doesn't exist at this point, create it. - if (!fslib::file_exists(PATH_CACHE_PATH)) - { - create_cache_file(); - } - - // Wew return true; } void data::get_users(data::UserList &userList) { - // Clear the vector passed just in case. userList.clear(); - - // Loop and push the pointers to the vector. - for (auto &[accountID, userData] : s_userVector) - { - userList.push_back(&userData); - } + for (auto &[accountID, userData] : s_userVector) { userList.push_back(&userData); } } data::TitleInfo *data::get_title_info_by_id(uint64_t applicationID) { - if (s_titleInfoMap.find(applicationID) == s_titleInfoMap.end()) - { - return nullptr; - } - - return &s_titleInfoMap.at(applicationID); + auto findTitle = s_titleInfoMap.find(applicationID); + if (findTitle == s_titleInfoMap.end()) { return nullptr; } + return &findTitle->second; } -void data::load_title_to_map(uint64_t applicationID) -{ - s_titleInfoMap.emplace(applicationID, applicationID); -} +void data::load_title_to_map(uint64_t applicationID) { s_titleInfoMap.emplace(applicationID, applicationID); } -bool data::title_exists_in_map(uint64_t applicationID) -{ - return s_titleInfoMap.find(applicationID) != s_titleInfoMap.end(); -} +bool data::title_exists_in_map(uint64_t applicationID) { return s_titleInfoMap.find(applicationID) != s_titleInfoMap.end(); } -std::unordered_map &data::get_title_info_map() -{ - return s_titleInfoMap; -} +std::unordered_map &data::get_title_info_map() { return s_titleInfoMap; } void data::get_title_info_by_type(FsSaveDataType saveType, std::vector &vectorOut) { @@ -150,208 +115,143 @@ void data::get_title_info_by_type(FsSaveDataType saveType, std::vector 0) - { - // s_titleInfoMap.emplace(record.application_id, data::TitleInfo(record.application_id)); + bool listError{}; + do { + listError = error::libnx(nsListApplicationRecord(&record, 1, offset++, &count)) || count <= 0; + if (listError) { break; } s_titleInfoMap.emplace(record.application_id, record.application_id); - } + } while (!listError); } static void import_svi_files() { - // Path. - fslib::Path sviPath = config::get_working_directory() / "svi"; + static constexpr size_t SIZE_UINT64 = sizeof(uint64_t); + static constexpr size_t SIZE_CTRL_DATA = sizeof(NsApplicationControlData); + static constexpr size_t SIZE_SVI = SIZE_UINT64 + SIZE_CTRL_DATA; - // Try opening it. - fslib::Directory sviDir(sviPath); - if (!sviDir) + const fslib::Path sviPath = config::get_working_directory() / "svi"; + const fslib::Directory sviDir{sviPath}; + if (error::fslib(sviDir)) { return; } + + const int64_t sviCount = sviDir.get_count(); + for (int64_t i = 0; i < sviCount; i++) { - return; - } + const fslib::Path target = sviPath / sviDir[i]; + fslib::File sviFile{target, FsOpenMode_Read}; - // Loop through the directory and load these things. - for (int64_t i = 0; i < sviDir.get_count(); i++) - { - // Full path. - fslib::Path fullPath = sviPath / sviDir[i]; + const bool goodFile = sviFile.is_open() && sviFile.get_size() == SIZE_SVI; + if (!goodFile) { continue; } - // Try opening it. - fslib::File sviFile(fullPath, FsOpenMode_Read); - if (!sviFile || sviFile.get_size() != sizeof(uint64_t) + sizeof(NsApplicationControlData)) - { - logger::log("Error importing \"%s\": File couldn't be opened or is invalid!"); - continue; - } + uint64_t applicationID{}; + NsApplicationControlData controlData{}; + const bool idReadGood = sviFile.read(&applicationID, SIZE_UINT64) == SIZE_UINT64; + const bool dataReadGood = sviFile.read(&controlData, SIZE_CTRL_DATA) == SIZE_CTRL_DATA; + if (!idReadGood || !dataReadGood) { continue; } - // First read the ID so we can check if it's even worth bothering with the rest. - // To do. This could be accomplished with just the file name when I have time. - uint64_t applicationID = 0; - if (sviFile.read(&applicationID, sizeof(uint64_t)) != sizeof(uint64_t) || - s_titleInfoMap.find(applicationID) != s_titleInfoMap.end()) - { - // Not worth continuing with this because it was already loaded somewhere else. - continue; - } - - // Good to go and read this now. - NsApplicationControlData controlData = {0}; - if (sviFile.read(&controlData, sizeof(NsApplicationControlData)) != sizeof(NsApplicationControlData)) - { - logger::log("Error reading SVI file!"); - continue; - } - - s_titleInfoMap.emplace(applicationID, std::move(data::TitleInfo(applicationID, controlData))); + data::TitleInfo newInfo{applicationID, controlData}; + s_titleInfoMap.emplace(applicationID, std::move(newInfo)); } } static bool read_cache_file() { - // Try opening the cache file. - fslib::File cache(PATH_CACHE_PATH, FsOpenMode_Read); - if (!cache) - { - logger::log("No cache file found!"); - return false; - } + constexpr size_t SIZE_UNSIGNED = sizeof(unsigned int); + constexpr size_t SIZE_CACHE_ENTRY = sizeof(CacheEntry); - // Title cache count. - unsigned int titleCount = 0; - // Read it - cache.read(&titleCount, sizeof(unsigned int)); + const fslib::Path cachePath{PATH_CACHE_PATH}; + const bool cacheExists = fslib::file_exists(cachePath); - // Allocate buffer for reading the rest. - std::unique_ptr entryBuffer = std::make_unique(titleCount); - if (!entryBuffer) - { - logger::log("Error allocating memory to read cache!"); - return false; - } + fslib::File cache{PATH_CACHE_PATH, FsOpenMode_Read}; + if (error::fslib(cache.is_open())) { return false; } - if (cache.read(entryBuffer.get(), sizeof(CacheEntry) * titleCount) != - static_cast(sizeof(CacheEntry) * titleCount)) - { - logger::log("Error reading cache file! Size mismatch!"); - return false; - } + unsigned int titleCount{}; + const bool countRead = cache.read(&titleCount, SIZE_UNSIGNED) == SIZE_UNSIGNED; + if (!countRead) { return false; } + + auto entryBuffer = std::make_unique(titleCount); // I've read there might not be any point in error checking. + const bool cacheRead = cache.read(entryBuffer.get(), SIZE_CACHE_ENTRY * titleCount) == SIZE_CACHE_ENTRY * titleCount; + if (!cacheRead) { return false; } // Loop through the cache entries and emplace them to the map. for (unsigned int i = 0; i < titleCount; i++) { - s_titleInfoMap.emplace(entryBuffer[i].m_applicationID, - std::move(data::TitleInfo(entryBuffer[i].m_applicationID, entryBuffer[i].m_data))); - } + CacheEntry &entry = entryBuffer[i]; + data::TitleInfo newInfo{entry.applicationID, entry.data}; + s_titleInfoMap.emplace(entry.applicationID, std::move(newInfo)); + } return true; } static void create_cache_file() { - // First try creating and opening the file. - fslib::File cache(PATH_CACHE_PATH, FsOpenMode_Create | FsOpenMode_Write); - if (!cache) - { - // Don't bother trying to continue. - return; - } + static constexpr size_t SIZE_UNSIGNED = sizeof(unsigned int); + static constexpr size_t SIZE_UINT64 = sizeof(uint64_t); + static constexpr size_t SIZE_CTRL_DATA = sizeof(NsApplicationControlData); - // This is to keep track of how many titles we actually write to the file. - unsigned int titleCount = 0; + fslib::File cache{PATH_CACHE_PATH, FsOpenMode_Create | FsOpenMode_Write}; + if (error::fslib(cache.is_open())) { return; } - // Start by writing that. We'll come back to it later. - cache.write(&titleCount, sizeof(unsigned int)); + // This will make more sense later. I promise. + unsigned int titleCount{}; + const bool countWrite = cache.write(&titleCount, SIZE_UNSIGNED) == SIZE_UNSIGNED; + if (!countWrite) { return; } - // Loop through the title map and write what we need to. for (auto &[applicationID, titleInfo] : s_titleInfoMap) { - // Check if the title has valid control data before continuing. - if (!titleInfo.has_control_data()) - { - continue; - } + if (!titleInfo.has_control_data()) { continue; } - // Grab the control data pointer. - NsApplicationControlData *data = titleInfo.get_control_data(); - - // Write the application ID first. - cache.write(&applicationID, sizeof(uint64_t)); - // Write it to the file. - cache.write(data, sizeof(NsApplicationControlData)); - - // Increment the counter. + const NsApplicationControlData *data = titleInfo.get_control_data(); + const bool idWrite = cache.write(&applicationID, SIZE_UINT64) == SIZE_UINT64; + const bool dataWrite = cache.write(data, SIZE_CTRL_DATA) == SIZE_CTRL_DATA; + if (!idWrite || !dataWrite) { return; } ++titleCount; } - // Go back to the beginning. + // Go back to beginning and write the final count. cache.seek(0, cache.BEGINNING); - - // Write the count. - cache.write(&titleCount, sizeof(unsigned int)); - // fslib::File cleans up on destruction. + cache.write(&titleCount, SIZE_UNSIGNED); } diff --git a/source/error.cpp b/source/error.cpp new file mode 100644 index 0000000..819ad57 --- /dev/null +++ b/source/error.cpp @@ -0,0 +1,50 @@ +#include "error.hpp" + +#include "fslib.hpp" +#include "logger.hpp" + +#include +#include + +/// @brief Prepares and makes sure the strings match the format I actually want! +static void prep_locations(std::string_view &file, std::string_view &function, const std::source_location &location); + +bool error::libnx(Result code, const std::source_location &location) +{ + if (code == 0) { return false; } + + std::string_view file{}, function{}; + prep_locations(file, function, location); + + logger::log("%s::%s::%u::%u:%X", file.data(), function.data(), location.line(), location.column()); + return true; +} + +bool error::fslib(bool result, const std::source_location &location) +{ + if (result) { return false; } + + std::string_view file{}, function{}; + prep_locations(file, function, location); + + logger::log("%s::%s::%u::%u::%s", + file.data(), + function.data(), + location.line(), + location.column(), + fslib::error::get_string()); + + return true; +} + +static void prep_locations(std::string_view &file, std::string_view &function, const std::source_location &location) +{ + file = location.file_name(); + function = location.function_name(); + + size_t fileBegin = file.find_last_of('/'); + if (fileBegin != file.npos) { file = file.substr(fileBegin + 1); } + + size_t functionBegin = function.find_first_of(' '); + if (functionBegin != function.npos) { function = function.substr(functionBegin + 1); } +} diff --git a/source/fs/io.cpp b/source/fs/io.cpp index 1f78320..22d7385 100644 --- a/source/fs/io.cpp +++ b/source/fs/io.cpp @@ -1,6 +1,8 @@ #include "fs/io.hpp" + #include "logger.hpp" #include "strings.hpp" + #include #include #include @@ -44,8 +46,7 @@ static void readThreadFunction(fslib::File &sourceFile, std::shared_ptrm_bufferCondition.notify_one(); // Wait for other thread to signal buffer is empty. Lock is released immediately, but it works and that's what matters. std::unique_lock m_bufferLock(sharedData->m_bufferLock); - sharedData->m_bufferCondition.wait(m_bufferLock, - [&sharedData]() { return sharedData->m_bufferIsFull == false; }); + sharedData->m_bufferCondition.wait(m_bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull == false; }); } } @@ -64,10 +65,7 @@ void fs::copy_file(const fslib::Path &source, } // Set status if task pointer was passed. - if (task) - { - task->set_status(strings::get_by_name(strings::names::COPYING_FILES, 0), source.full_path()); - } + if (task) { task->set_status(strings::get_by_name(strings::names::IO_STATUSES, 0), source.full_path()); } // Shared struct both threads use std::shared_ptr sharedData(new FileTransferStruct); @@ -81,10 +79,7 @@ void fs::copy_file(const fslib::Path &source, // Get file size for loop and set goal. int64_t fileSize = sourceFile.get_size(); - if (task) - { - task->reset(static_cast(fileSize)); - } + if (task) { task->reset(static_cast(fileSize)); } for (int64_t writeCount = 0, readCount = 0, journalCount = 0; writeCount < fileSize;) { @@ -132,20 +127,14 @@ void fs::copy_file(const fslib::Path &source, journalCount += readCount; // Update task if passed. - if (task) - { - task->update_current(static_cast(writeCount)); - } + if (task) { task->update_current(static_cast(writeCount)); } } // Close the destination for committing. destinationFile.close(); // One last commit for good luck. - if (!fslib::commit_data_to_file_system(commitDevice)) - { - logger::log(fslib::error::get_string()); - } + if (!fslib::commit_data_to_file_system(commitDevice)) { logger::log(fslib::error::get_string()); } // Wait for read thread and free it. readThread.join(); @@ -168,7 +157,7 @@ void fs::copy_directory(const fslib::Path &source, { if (sourceDir.is_directory(i)) { - fslib::Path newSource = source / sourceDir[i]; + fslib::Path newSource = source / sourceDir[i]; fslib::Path newDestination = destination / sourceDir[i]; // Try to create new destination folder and continue loop on failure. if (!fslib::directory_exists(newDestination) && !fslib::create_directory(newDestination)) @@ -181,7 +170,7 @@ void fs::copy_directory(const fslib::Path &source, } else { - fslib::Path fullSource = source / sourceDir[i]; + fslib::Path fullSource = source / sourceDir[i]; fslib::Path fullDestination = destination / sourceDir[i]; fs::copy_file(fullSource, fullDestination, journalSize, commitDevice, task); } diff --git a/source/fs/zip.cpp b/source/fs/zip.cpp index 1582568..2e4ca75 100644 --- a/source/fs/zip.cpp +++ b/source/fs/zip.cpp @@ -1,8 +1,10 @@ #include "fs/zip.hpp" + #include "config.hpp" #include "fs/SaveMetaData.hpp" #include "logger.hpp" #include "strings.hpp" + #include #include #include @@ -117,7 +119,7 @@ void fs::copy_directory_to_zip(const fslib::Path &source, zipFile destination, s // Create new file in zip const char *zipNameBegin = std::strchr(fullSource.get_path(), '/') + 1; - int zipError = zipOpenNewFileInZip64(destination, + int zipError = zipOpenNewFileInZip64(destination, zipNameBegin, &fileInfo, NULL, @@ -144,7 +146,7 @@ void fs::copy_directory_to_zip(const fslib::Path &source, zipFile destination, s // Update task if passed. if (task) { - task->set_status(strings::get_by_name(strings::names::COPYING_FILES, 1), fullSource.full_path()); + task->set_status(strings::get_by_name(strings::names::IO_STATUSES, 1), fullSource.full_path()); task->reset(static_cast(sourceFile.get_size())); } @@ -157,8 +159,7 @@ void fs::copy_directory_to_zip(const fslib::Path &source, zipFile destination, s { // Wait for buffer signal std::unique_lock m_bufferLock(sharedData->m_bufferLock); - sharedData->m_bufferCondition.wait(m_bufferLock, - [&sharedData]() { return sharedData->m_bufferIsFull; }); + sharedData->m_bufferCondition.wait(m_bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull; }); // Save read count, copy shared to local. readCount = sharedData->m_readCount; @@ -171,17 +172,11 @@ void fs::copy_directory_to_zip(const fslib::Path &source, zipFile destination, s // Write zipError = zipWriteInFileInZip(destination, localBuffer.get(), readCount); - if (zipError != ZIP_OK) - { - logger::log("Error writing data to zip: %i.", zipError); - } + if (zipError != ZIP_OK) { logger::log("Error writing data to zip: %i.", zipError); } // Update count and status writeCount += readCount; - if (task) - { - task->update_current(static_cast(writeCount)); - } + if (task) { task->update_current(static_cast(writeCount)); } } // Wait for thread readThread.join(); @@ -205,8 +200,7 @@ void fs::copy_zip_to_directory(unzFile source, return; } - do - { + do { // Get file information. unz_file_info64 currentFileInfo; char filename[FS_MAX_PATH] = {0}; @@ -219,10 +213,7 @@ void fs::copy_zip_to_directory(unzFile source, } // Save meta file filter. - if (filename == fs::NAME_SAVE_META) - { - continue; - } + if (filename == fs::NAME_SAVE_META) { continue; } // Create full path to item, make sure directories are created if needed. fslib::Path fullDestination = destination / filename; @@ -236,9 +227,7 @@ void fs::copy_zip_to_directory(unzFile source, continue; } - fslib::File destinationFile(fullDestination, - FsOpenMode_Create | FsOpenMode_Write, - currentFileInfo.uncompressed_size); + fslib::File destinationFile(fullDestination, FsOpenMode_Create | FsOpenMode_Write, currentFileInfo.uncompressed_size); if (!destinationFile) { logger::log("Error creating file from zip: %s", fslib::error::get_string()); @@ -258,7 +247,7 @@ void fs::copy_zip_to_directory(unzFile source, // Set status if (task) { - task->set_status(strings::get_by_name(strings::names::COPYING_FILES, 2), filename); + task->set_status(strings::get_by_name(strings::names::IO_STATUSES, 2), filename); task->reset(static_cast(currentFileInfo.uncompressed_size)); } @@ -308,10 +297,7 @@ void fs::copy_zip_to_directory(unzFile source, journalCount += readCount; // Update status - if (task) - { - task->update_current(writeCount); - } + if (task) { task->update_current(writeCount); } } // Join the read thread. @@ -336,13 +322,13 @@ void fs::create_zip_fileinfo(zip_fileinfo &info) std::tm *localTime = std::localtime(¤tTime); // Create struct to return. - info = {.tmz_date = {.tm_sec = localTime->tm_sec, - .tm_min = localTime->tm_min, - .tm_hour = localTime->tm_hour, - .tm_mday = localTime->tm_mday, - .tm_mon = localTime->tm_mon, - .tm_year = localTime->tm_year + 1900}, - .dosDate = 0, + info = {.tmz_date = {.tm_sec = localTime->tm_sec, + .tm_min = localTime->tm_min, + .tm_hour = localTime->tm_hour, + .tm_mday = localTime->tm_mday, + .tm_mon = localTime->tm_mon, + .tm_year = localTime->tm_year + 1900}, + .dosDate = 0, .internal_fa = 0, .external_fa = 0}; } @@ -350,10 +336,7 @@ void fs::create_zip_fileinfo(zip_fileinfo &info) bool fs::zip_has_contents(const fslib::Path &zipPath) { unzFile testZip = unzOpen(zipPath.full_path()); - if (!testZip) - { - return false; - } + if (!testZip) { return false; } int zipError = unzGoToFirstFile(testZip); if (zipError != UNZ_OK) @@ -381,18 +364,11 @@ bool fs::locate_file_in_zip(unzFile zip, std::string_view name) unz_file_info64 fileinfo = {0}; // Loop through files. If minizip has a better way of doing this, I couldn't find it. - do - { + do { // Grab this stuff. zipError = unzGetCurrentFileInfo64(zip, &fileinfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0); - if (zipError != UNZ_OK) - { - continue; - } - else if (filename == name) - { - return true; - } + if (zipError != UNZ_OK) { continue; } + else if (filename == name) { return true; } } while (unzGoToNextFile(zip) != UNZ_END_OF_LIST_OF_FILE); // Guess it wasn't found? @@ -414,15 +390,11 @@ uint64_t fs::get_zip_total_size(unzFile zip) // File's name and info buffers. char filename[FS_MAX_PATH] = {0}; - unz_file_info64 fileinfo = {0}; + unz_file_info64 fileinfo = {0}; - do - { + do { zipError = unzGetCurrentFileInfo64(zip, &fileinfo, filename, 0, NULL, 0, NULL, 0); - if (zipError != UNZ_OK) - { - continue; - } + if (zipError != UNZ_OK) { continue; } // Add zipSize += fileinfo.uncompressed_size; diff --git a/source/remote/GoogleDrive.cpp b/source/remote/GoogleDrive.cpp index 7a1e527..744b6b4 100644 --- a/source/remote/GoogleDrive.cpp +++ b/source/remote/GoogleDrive.cpp @@ -1,10 +1,12 @@ #include "remote/GoogleDrive.hpp" + #include "logger.hpp" #include "remote/Form.hpp" #include "remote/URL.hpp" #include "remote/remote.hpp" #include "strings.hpp" #include "stringutil.hpp" + #include #include @@ -22,28 +24,29 @@ namespace // These are API endpoints used in multiple request calls. const char *URL_OAUTH2_TOKEN_URL = "https://oauth2.googleapis.com/token"; - const char *URL_DRIVE_FILE_API = "https://www.googleapis.com/drive/v3/files"; + const char *URL_DRIVE_FILE_API = "https://www.googleapis.com/drive/v3/files"; const char *URL_DRIVE_UPLOAD_API = "https://www.googleapis.com/upload/drive/v3/files"; // These are json keys that are used for various requests. - const char *JSON_KEY_ACCESS_TOKEN = "access_token"; - const char *JSON_KEY_CLIENT_ID = "client_id"; + const char *JSON_KEY_ACCESS_TOKEN = "access_token"; + const char *JSON_KEY_CLIENT_ID = "client_id"; const char *JSON_KEY_CLIENT_SECRET = "client_secret"; - const char *JSON_KEY_DEVICE_CODE = "device_code"; - const char *JSON_KEY_EXPIRES_IN = "expires_in"; - const char *JSON_KEY_GRANT_TYPE = "grant_type"; - const char *JSON_KEY_ID = "id"; - const char *JSON_KEY_INSTALLED = "installed"; - const char *JSON_KEY_MIMETYPE = "mimeType"; - const char *JSON_KEY_NAME = "name"; - const char *JSON_KEY_PARENTS = "parents"; + const char *JSON_KEY_DEVICE_CODE = "device_code"; + const char *JSON_KEY_EXPIRES_IN = "expires_in"; + const char *JSON_KEY_GRANT_TYPE = "grant_type"; + const char *JSON_KEY_ID = "id"; + const char *JSON_KEY_INSTALLED = "installed"; + const char *JSON_KEY_MIMETYPE = "mimeType"; + const char *JSON_KEY_NAME = "name"; + const char *JSON_KEY_PARENTS = "parents"; const char *JSON_KEY_REFRESH_TOKEN = "refresh_token"; /// @brief Folder mimetype string. const char *MIME_TYPE_DIRECTORY = "application/vnd.google-apps.folder"; } // namespace -remote::GoogleDrive::GoogleDrive() : Storage() +remote::GoogleDrive::GoogleDrive() + : Storage() { static const char *STRING_ERROR_READING_CONFIG = "Error reading Google Drive config: %s"; @@ -68,7 +71,7 @@ remote::GoogleDrive::GoogleDrive() : Storage() return; } - json_object *clientId = json_object_object_get(installed, JSON_KEY_CLIENT_ID); + json_object *clientId = json_object_object_get(installed, JSON_KEY_CLIENT_ID); json_object *clientSecret = json_object_object_get(installed, JSON_KEY_CLIENT_SECRET); json_object *refreshToken = json_object_object_get(installed, JSON_KEY_REFRESH_TOKEN); if (!clientId || !clientSecret) @@ -77,14 +80,11 @@ remote::GoogleDrive::GoogleDrive() : Storage() return; } // Grab them. - m_clientId = json_object_get_string(clientId); + m_clientId = json_object_get_string(clientId); m_clientSecret = json_object_get_string(clientSecret); // Returning here will make is_initialized return false. - if (!refreshToken) - { - return; - } + if (!refreshToken) { return; } m_refreshToken = json_object_get_string(refreshToken); if (!GoogleDrive::refresh_token()) @@ -92,33 +92,27 @@ remote::GoogleDrive::GoogleDrive() : Storage() // If refreshing the token failed, this will cause is_initialized to return false and force a re-signin. m_refreshToken.clear(); } - else if (GoogleDrive::get_root_id() && GoogleDrive::request_listing()) - { - m_isInitialized = true; - } + else if (GoogleDrive::get_root_id() && GoogleDrive::request_listing()) { m_isInitialized = true; } } bool remote::GoogleDrive::create_directory(std::string_view name) { - if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) - { - return false; - } + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } curl::HeaderList headers = curl::new_header_list(); curl::append_header(headers, m_authHeader); curl::append_header(headers, HEADER_CONTENT_TYPE_JSON); - json::Object postJson = json::new_object(json_object_new_object); + json::Object postJson = json::new_object(json_object_new_object); json_object *directoryName = json_object_new_string(name.data()); - json_object *mimeType = json_object_new_string(MIME_TYPE_DIRECTORY); + json_object *mimeType = json_object_new_string(MIME_TYPE_DIRECTORY); json::add_object(postJson, JSON_KEY_NAME, directoryName); json::add_object(postJson, JSON_KEY_MIMETYPE, mimeType); if (!m_parent.empty()) { // I don't understand why this is an array. json_object *parentArray = json_object_new_array(); - json_object *parentId = json_object_new_string(m_parent.c_str()); + json_object *parentId = json_object_new_string(m_parent.c_str()); json_object_array_add(parentArray, parentId); json::add_object(postJson, JSON_KEY_PARENTS, parentArray); } @@ -131,10 +125,7 @@ bool remote::GoogleDrive::create_directory(std::string_view name) curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); curl::set_option(m_curl, CURLOPT_POSTFIELDS, json_object_get_string(postJson.get())); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } json::Object parser = json::new_object(json_tokener_parse, response.c_str()); if (!parser) @@ -160,10 +151,7 @@ bool remote::GoogleDrive::create_directory(std::string_view name) bool remote::GoogleDrive::upload_file(const fslib::Path &source) { - if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) - { - return false; - } + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } fslib::File sourceFile(source, FsOpenMode_Read); if (!sourceFile) @@ -181,14 +169,14 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) url.append_parameter("uploadType", "resumable"); // Json to post. - json::Object postJson = json::new_object(json_object_new_object); + json::Object postJson = json::new_object(json_object_new_object); json_object *driveName = json_object_new_string(source.get_filename()); json::add_object(postJson, JSON_KEY_NAME, driveName); // Append the parent. if (!m_parent.empty()) { json_object *parentArray = json_object_new_array(); - json_object *parentId = json_object_new_string(m_parent.c_str()); + json_object *parentId = json_object_new_string(m_parent.c_str()); json_object_array_add(parentArray, parentId); json::add_object(postJson, JSON_KEY_PARENTS, parentArray); } @@ -203,10 +191,7 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) curl::set_option(m_curl, CURLOPT_URL, url.get()); curl::set_option(m_curl, CURLOPT_POSTFIELDS, json_object_get_string(postJson.get())); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } // Extract the location from the headers. std::string location; @@ -225,10 +210,7 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } json::Object responseParser = json::new_object(json_tokener_parse, response.c_str()); if (!responseParser) @@ -237,10 +219,10 @@ bool remote::GoogleDrive::upload_file(const fslib::Path &source) return false; } - json_object *id = json::get_object(responseParser, JSON_KEY_ID); + json_object *id = json::get_object(responseParser, JSON_KEY_ID); json_object *filename = json::get_object(responseParser, JSON_KEY_NAME); json_object *mimeType = json::get_object(responseParser, JSON_KEY_MIMETYPE); - json_object *size = json::get_object(responseParser, "size"); + json_object *size = json::get_object(responseParser, "size"); // All of these are needed for the emplace_back. if (!id || !filename || !mimeType) { @@ -262,10 +244,7 @@ bool remote::GoogleDrive::patch_file(remote::Item *file, const fslib::Path &sour { static const char *STRING_PATCH_ERROR = "Error patching file: %s"; - if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) - { - return false; - } + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } fslib::File sourceFile(source, FsOpenMode_Read); if (!sourceFile) @@ -291,10 +270,7 @@ bool remote::GoogleDrive::patch_file(remote::Item *file, const fslib::Path &sour curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } // This is the target location to upload to. std::string location; @@ -310,10 +286,7 @@ bool remote::GoogleDrive::patch_file(remote::Item *file, const fslib::Path &sour curl::set_option(m_curl, CURLOPT_READFUNCTION, curl::read_data_from_file); curl::set_option(m_curl, CURLOPT_READDATA, &sourceFile); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } // Update the file size with the source file size. file->set_size(sourceFile.get_size()); @@ -323,10 +296,7 @@ bool remote::GoogleDrive::patch_file(remote::Item *file, const fslib::Path &sour bool remote::GoogleDrive::download_file(const remote::Item *file, const fslib::Path &destination) { - if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) - { - return false; - } + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } // Try to open the file too before continuing. Using a starting size speeds up write calls later. fslib::File destinationFile(destination, FsOpenMode_Create | FsOpenMode_Write, file->get_size()); @@ -350,25 +320,19 @@ bool remote::GoogleDrive::download_file(const remote::Item *file, const fslib::P curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_data_to_file); curl::set_option(m_curl, CURLOPT_WRITEDATA, &destinationFile); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } return true; } bool remote::GoogleDrive::delete_item(const remote::Item *item) { - if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) - { - return false; - } + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } // Iterator is needed to remove it from the list. - auto findItem = std::find_if(m_list.begin(), m_list.end(), [item](const Item &listItem) { - return item->get_id() == listItem.get_id(); - }); + auto findItem = std::find_if(m_list.begin(), + m_list.end(), + [item](const Item &listItem) { return item->get_id() == listItem.get_id(); }); if (findItem == m_list.end()) { @@ -387,10 +351,7 @@ bool remote::GoogleDrive::delete_item(const remote::Item *item) curl::set_option(m_curl, CURLOPT_URL, url.get()); curl::set_option(m_curl, CURLOPT_HTTPHEADER, header.get()); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } // This might be a better way to check? long code = curl::get_response_code(m_curl); @@ -406,10 +367,7 @@ bool remote::GoogleDrive::delete_item(const remote::Item *item) return true; } -bool remote::GoogleDrive::sign_in_required() const -{ - return !m_isInitialized || m_refreshToken.empty(); -} +bool remote::GoogleDrive::sign_in_required() const { return !m_isInitialized || m_refreshToken.empty(); } bool remote::GoogleDrive::get_sign_in_data(std::string &message, std::string &code, std::time_t &expiration, int &wait) { @@ -433,22 +391,16 @@ bool remote::GoogleDrive::get_sign_in_data(std::string &message, std::string &co curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); curl::set_option(m_curl, CURLOPT_POSTFIELDS, post.get()); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } json::Object parser = json::new_object(json_tokener_parse, response.c_str()); - if (!parser || GoogleDrive::error_occurred(parser)) - { - return false; - } + if (!parser || GoogleDrive::error_occurred(parser)) { return false; } - json_object *deviceCode = json::get_object(parser, JSON_KEY_DEVICE_CODE); - json_object *userCode = json::get_object(parser, "user_code"); + json_object *deviceCode = json::get_object(parser, JSON_KEY_DEVICE_CODE); + json_object *userCode = json::get_object(parser, "user_code"); json_object *verificationUrl = json::get_object(parser, "verification_url"); - json_object *expiresIn = json::get_object(parser, "expires_in"); - json_object *interval = json::get_object(parser, "interval"); + json_object *expiresIn = json::get_object(parser, "expires_in"); + json_object *interval = json::get_object(parser, "interval"); // These are required and fatal. if (!deviceCode || !userCode || !verificationUrl || !expiresIn || !interval) { @@ -457,7 +409,7 @@ bool remote::GoogleDrive::get_sign_in_data(std::string &message, std::string &co } // I hate how this looks, but whatever. - message = stringutil::get_formatted_string(strings::get_by_name(strings::names::GOOGLE_DRIVE_STRINGS, 0), + message = stringutil::get_formatted_string(strings::get_by_name(strings::names::GOOGLE_DRIVE, 0), json_object_get_string(verificationUrl), json_object_get_string(userCode)); @@ -496,20 +448,14 @@ bool remote::GoogleDrive::poll_sign_in(std::string_view code) curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); curl::set_option(m_curl, CURLOPT_POSTFIELDS, post.get()); - if (!curl::perform(m_curl) || response.empty()) - { - return false; - } + if (!curl::perform(m_curl) || response.empty()) { return false; } json::Object parser = json::new_object(json_tokener_parse, response.c_str()); // This error isn't logged, because that's how you know if the user logged in or not. - if (!parser || GoogleDrive::error_occurred(parser, false)) - { - return false; - } + if (!parser || GoogleDrive::error_occurred(parser, false)) { return false; } - json_object *accessToken = json::get_object(parser, JSON_KEY_ACCESS_TOKEN); - json_object *expiresIn = json::get_object(parser, JSON_KEY_EXPIRES_IN); + json_object *accessToken = json::get_object(parser, JSON_KEY_ACCESS_TOKEN); + json_object *expiresIn = json::get_object(parser, JSON_KEY_EXPIRES_IN); json_object *refreshToken = json::get_object(parser, JSON_KEY_REFRESH_TOKEN); // All of these are required. if (!accessToken || !expiresIn || !refreshToken) @@ -518,7 +464,7 @@ bool remote::GoogleDrive::poll_sign_in(std::string_view code) return false; } - m_token = json_object_get_string(accessToken); + m_token = json_object_get_string(accessToken); m_refreshToken = json_object_get_string(refreshToken); m_tokenExpires = std::time(NULL) + json_object_get_uint64(expiresIn); @@ -529,22 +475,16 @@ bool remote::GoogleDrive::poll_sign_in(std::string_view code) { // We're going to attach the refresh token to the installed object so nothing bad can happen to it and I don't // have to deal with Git issues about it. - json_object *installed = json::get_object(config, JSON_KEY_INSTALLED); + json_object *installed = json::get_object(config, JSON_KEY_INSTALLED); json_object *refreshToken = json_object_new_string(m_refreshToken.c_str()); json_object_object_add(installed, JSON_KEY_REFRESH_TOKEN, refreshToken); fslib::File configFile(remote::PATH_GOOGLE_DRIVE_CONFIG, FsOpenMode_Create | FsOpenMode_Write); - if (configFile) - { - configFile << json_object_get_string(config.get()); - } + if (configFile) { configFile << json_object_get_string(config.get()); } } // Not sure where else to really put this. - if (!GoogleDrive::get_root_id()) - { - return false; - } + if (!GoogleDrive::get_root_id()) { return false; } m_isInitialized = true; @@ -556,10 +496,7 @@ bool remote::GoogleDrive::get_root_id() // This is the only place this is used. V3 of the API doesn't allow you to retrieve this for some reason? static const char *API_URL_ABOUT_ROOT_ID = "https://www.googleapis.com/drive/v2/about?fields=rootFolderId"; - if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) - { - return false; - } + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } curl::HeaderList header = curl::new_header_list(); curl::append_header(header, m_authHeader); @@ -571,16 +508,10 @@ bool remote::GoogleDrive::get_root_id() curl::set_option(m_curl, CURLOPT_WRITEFUNCTION, curl::write_response_string); curl::set_option(m_curl, CURLOPT_WRITEDATA, &response); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } json::Object parser = json::new_object(json_tokener_parse, response.c_str()); - if (!parser) - { - return false; - } + if (!parser) { return false; } json_object *rootId = json::get_object(parser, "rootFolderId"); if (!rootId) @@ -589,7 +520,7 @@ bool remote::GoogleDrive::get_root_id() return false; } - m_root = json_object_get_string(rootId); + m_root = json_object_get_string(rootId); m_parent = m_root; return true; @@ -606,7 +537,6 @@ bool remote::GoogleDrive::refresh_token() curl::HeaderList header = curl::new_header_list(); curl::append_header(header, HEADER_CONTENT_TYPE_FORM); - remote::Form post{}; post.append_parameter(JSON_KEY_CLIENT_ID, m_clientId) .append_parameter(JSON_KEY_CLIENT_SECRET, m_clientSecret) @@ -622,24 +552,15 @@ bool remote::GoogleDrive::refresh_token() curl::set_option(m_curl, CURLOPT_POSTFIELDS, post.get()); curl::set_option(m_curl, CURLOPT_POSTFIELDSIZE, post.length()); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } json::Object parser = json::new_object(json_tokener_parse, response.c_str()); - if (!parser || GoogleDrive::error_occurred(parser)) - { - return false; - } + if (!parser || GoogleDrive::error_occurred(parser)) { return false; } // These are the only things I care about. json_object *accessToken = json::get_object(parser, JSON_KEY_ACCESS_TOKEN); - json_object *expiresIn = json::get_object(parser, JSON_KEY_EXPIRES_IN); - if (!accessToken || !expiresIn) - { - return false; - } + json_object *expiresIn = json::get_object(parser, JSON_KEY_EXPIRES_IN); + if (!accessToken || !expiresIn) { return false; } // Got our new access token. m_token = json_object_get_string(accessToken); @@ -653,10 +574,7 @@ bool remote::GoogleDrive::refresh_token() bool remote::GoogleDrive::request_listing() { - if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) - { - return false; - } + if (!GoogleDrive::token_is_valid() && !GoogleDrive::refresh_token()) { return false; } curl::HeaderList header = curl::new_header_list(); curl::append_header(header, m_authHeader.c_str()); @@ -676,15 +594,11 @@ bool remote::GoogleDrive::request_listing() // This is used as the loop condition. json_object *nextPageToken = nullptr; - do - { + do { // This needs to be cleared for every request. response.clear(); - if (!curl::perform(m_curl)) - { - return false; - } + if (!curl::perform(m_curl)) { return false; } json::Object parser = json::new_object(json_tokener_parse, response.c_str()); if (!parser || GoogleDrive::error_occurred(parser) || !GoogleDrive::process_listing(parser)) @@ -710,25 +624,19 @@ bool remote::GoogleDrive::process_listing(json::Object &json) static const char *STRING_ERROR_PROCESSING = "Error processing Google Drive listing: %s"; json_object *files = json::get_object(json, "files"); - if (!files) - { - return false; - } + if (!files) { return false; } size_t arrayLength = json_object_array_length(files); for (size_t i = 0; i < arrayLength; i++) { json_object *currentFile = json_object_array_get_idx(files, i); - if (!currentFile) - { - return false; - } + if (!currentFile) { return false; } json_object *mimeType = json_object_object_get(currentFile, JSON_KEY_MIMETYPE); - json_object *parents = json_object_object_get(currentFile, JSON_KEY_PARENTS); - json_object *id = json_object_object_get(currentFile, JSON_KEY_ID); - json_object *name = json_object_object_get(currentFile, JSON_KEY_NAME); - json_object *size = json_object_object_get(currentFile, "size"); + json_object *parents = json_object_object_get(currentFile, JSON_KEY_PARENTS); + json_object *id = json_object_object_get(currentFile, JSON_KEY_ID); + json_object *name = json_object_object_get(currentFile, JSON_KEY_NAME); + json_object *size = json_object_object_get(currentFile, "size"); // All of these are REQUIRED! Size doesn't exist for folders. if (!mimeType || !parents || !id || !name) { @@ -756,15 +664,12 @@ bool remote::GoogleDrive::process_listing(json::Object &json) bool remote::GoogleDrive::error_occurred(json::Object &json, bool log) { json_object *error = json::get_object(json, "error"); - if (!error) - { - return false; - } + if (!error) { return false; } // Google has so many different error response structures. I'm covering two here. Technically, // I could grab the code too from the second response, that makes this even more of a headache. json_object *description = json::get_object(json, "error_description"); - json_object *message = json_object_object_get(error, "message"); + json_object *message = json_object_object_get(error, "message"); if (log && (description || message)) { logger::log("Google Drive error: %s.", diff --git a/source/remote/remote.cpp b/source/remote/remote.cpp index 144cfec..a7302a0 100644 --- a/source/remote/remote.cpp +++ b/source/remote/remote.cpp @@ -1,4 +1,5 @@ #include "remote/remote.hpp" + #include "StateManager.hpp" #include "appstates/TaskState.hpp" #include "logger.hpp" @@ -6,6 +7,7 @@ #include "remote/WebDav.hpp" #include "strings.hpp" #include "ui/PopMessageManager.hpp" + #include #include #include @@ -17,7 +19,7 @@ namespace const char *STRING_JKSV_DIR = "JKSV"; /// @brief This is the single (for now) instance of a storage class. - std::unique_ptr s_storage = nullptr; + std::unique_ptr s_storage{}; } // namespace // Declarations here. Definitions at bottom. @@ -45,14 +47,11 @@ void remote::initialize_google_drive() } // To do: Handle this better. Maybe retry somehow? - if (!drive->is_initialized()) - { - return; - } + if (!drive->is_initialized()) { return; } // Can't forget this. drive_set_jksv_root(drive); ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::GOOGLE_DRIVE_STRINGS, 1)); + strings::get_by_name(strings::names::GOOGLE_DRIVE, 1)); } void remote::initialize_webdav() @@ -62,21 +61,18 @@ void remote::initialize_webdav() if (s_storage->is_initialized()) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::WEBDAV_STRINGS, 0)); + strings::get_by_name(strings::names::WEBDAV, 0)); } else { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::WEBDAV_STRINGS, 1)); + strings::get_by_name(strings::names::WEBDAV, 1)); } } remote::Storage *remote::get_remote_storage() { - if (!s_storage || !s_storage->is_initialized()) - { - return nullptr; - } + if (!s_storage || !s_storage->is_initialized()) { return nullptr; } return s_storage.get(); } @@ -86,7 +82,7 @@ static void drive_sign_in(sys::Task *task, remote::GoogleDrive *drive) std::string message{}, deviceCode{}; std::time_t expiration = 0; - int pollingInterval = 0; + int pollingInterval = 0; if (!drive->get_sign_in_data(message, deviceCode, expiration, pollingInterval)) { @@ -108,12 +104,12 @@ static void drive_sign_in(sys::Task *task, remote::GoogleDrive *drive) drive_set_jksv_root(drive); // Show everyone I did it! ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::GOOGLE_DRIVE_STRINGS, 1)); + strings::get_by_name(strings::names::GOOGLE_DRIVE, 1)); } else { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, - strings::get_by_name(strings::names::GOOGLE_DRIVE_STRINGS, 2)); + strings::get_by_name(strings::names::GOOGLE_DRIVE, 2)); } task->finished(); diff --git a/source/strings.cpp b/source/strings.cpp index 004b9c3..2058568 100644 --- a/source/strings.cpp +++ b/source/strings.cpp @@ -1,7 +1,9 @@ #include "strings.hpp" + #include "JSON.hpp" #include "fslib.hpp" #include "stringutil.hpp" + #include #include #include @@ -37,18 +39,12 @@ static fslib::Path get_file_path() fslib::Path returnPath = "romfs:/Text"; uint64_t languageCode = 0; - Result setError = setGetLanguageCode(&languageCode); - if (R_FAILED(setError)) - { - return returnPath / s_fileMap.at(SetLanguage_ENUS); - } + 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); - } + if (R_FAILED(setError)) { return returnPath / s_fileMap.at(SetLanguage_ENUS); } return returnPath / s_fileMap.at(language); } @@ -75,46 +71,46 @@ static void replace_buttons_in_string(std::string &target) bool strings::initialize() { - fslib::Path filePath = get_file_path(); - - json::Object stringJSON = json::new_object(json_object_from_file, filePath.full_path()); - if (!stringJSON) - { - return false; - } + const fslib::Path filePath = get_file_path(); + json::Object stringJSON = json::new_object(json_object_from_file, filePath.full_path()); + if (!stringJSON) { return false; } json_object_iterator stringIterator = json_object_iter_begin(stringJSON.get()); - json_object_iterator stringEnd = json_object_iter_end(stringJSON.get()); + json_object_iterator stringEnd = json_object_iter_end(stringJSON.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); + const char *name = json_object_iter_peek_name(&stringIterator); + json_object *array = 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++) + // Loop through array and add them to map so I can be lazier and not have to edit code or do anything to add more + // strings. + size_t length = json_object_array_length(array); + for (size_t i = 0; i < length; 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 *string = json_object_array_get_idx(array, i); + std::string_view slicer = json_object_get_string(string); + const int mapIndex = i; + const auto mapPair = std::make_pair(name, mapIndex); + const size_t begin = slicer.find(": "); + if (begin != slicer.npos) { slicer = slicer.substr(begin + 2); } + + s_stringMap[mapPair] = slicer; } json_object_iter_next(&stringIterator); } // Loop through entire map and replace the buttons. - for (auto &[key, string] : s_stringMap) - { - replace_buttons_in_string(string); - } + for (auto &[key, string] : s_stringMap) { replace_buttons_in_string(string); } return true; } const char *strings::get_by_name(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(); + const auto mapPair = std::make_pair(name.data(), index); + const auto findPair = s_stringMap.find(mapPair); + + if (findPair == s_stringMap.end()) { return nullptr; } + return s_stringMap.at(mapPair).c_str(); } diff --git a/source/tasks/backup.cpp b/source/tasks/backup.cpp new file mode 100644 index 0000000..9df7cc4 --- /dev/null +++ b/source/tasks/backup.cpp @@ -0,0 +1,9 @@ +#include "tasks/backup.hpp" + +void tasks::backup::create_new_backup(sys::ProgressTask *task, + data::User *user, + data::TitleInfo *titleInfo, + fslib::Path target, + BackupMenuState *spawningState) +{ +}