mirror of
https://github.com/J-D-K/JKSV.git
synced 2026-04-25 16:15:11 -05:00
Emergency push.
This commit is contained in:
parent
e894b39c63
commit
88e66992ed
|
|
@ -1 +1 @@
|
|||
Subproject commit 2fec69606748149dc6048ef4d1b27f4baf37dda2
|
||||
Subproject commit 77517f2d5f0473113e63795b45f134b2a262f893
|
||||
|
|
@ -37,4 +37,6 @@ class JKSV
|
|||
sdl::SharedTexture m_headerIcon = nullptr;
|
||||
/// @brief Vector of states to update and render.
|
||||
static inline std::vector<std::shared_ptr<AppState>> sm_stateVector;
|
||||
/// @brief Purges and updates states in sm_stateVector.
|
||||
static void updateStateVector(void);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class BackupMenuState : public AppState
|
|||
BackupMenuState(data::User *user, data::TitleInfo *titleInfo, FsSaveDataType saveType);
|
||||
|
||||
/// @brief Destructor. This is required even if it doesn't free or do anything.
|
||||
~BackupMenuState() {};
|
||||
~BackupMenuState();
|
||||
|
||||
/// @brief Required. Inherited virtual function from AppState.
|
||||
void update(void);
|
||||
|
|
@ -30,6 +30,9 @@ class BackupMenuState : public AppState
|
|||
/// @brief Refreshes the directory listing and menu.
|
||||
void refresh(void);
|
||||
|
||||
/// @brief Allows a spawned task to tell this class that it wrote save data to the system.
|
||||
void saveDataWritten(void);
|
||||
|
||||
private:
|
||||
/// @brief Pointer to current user.
|
||||
data::User *m_user = nullptr;
|
||||
|
|
@ -41,30 +44,17 @@ class BackupMenuState : public AppState
|
|||
fslib::Path m_directoryPath;
|
||||
/// @brief Directory listing of the above.
|
||||
fslib::Directory m_directoryListing;
|
||||
/// @brief The width of the current title in pixels.
|
||||
int m_titleWidth = 0;
|
||||
/// @brief X coordinate to render the games's title at.
|
||||
int m_titleX = 0;
|
||||
/// @brief Whether or not the above is too long to be displayed at once and needs to be scrolled.
|
||||
bool m_titleScrolling = false;
|
||||
/// @brief Whether or not the scrolling timer was triggered and we should scroll the title string.
|
||||
bool m_titleScrollTriggered = false;
|
||||
/// @brief Timer for scrolling the title text if it's too long.
|
||||
sys::Timer m_titleScrollTimer;
|
||||
/// @brief Variable that saves whether or not the filesystem has data in it.
|
||||
bool m_saveHasData = false;
|
||||
|
||||
/// @brief Whether or not anything beyond this point needs to be init'd. Everything here is static and shared by all instances.
|
||||
static inline bool sm_isInitialized = false;
|
||||
/// @brief The menu used by all instances of BackupMenuState.
|
||||
static inline std::unique_ptr<ui::Menu> m_backupMenu = nullptr;
|
||||
static inline std::shared_ptr<ui::Menu> sm_backupMenu = nullptr;
|
||||
/// @brief The slide out panel used by all instances of BackupMenuState.
|
||||
static inline std::unique_ptr<ui::SlideOutPanel> m_slidePanel = nullptr;
|
||||
static inline std::unique_ptr<ui::SlideOutPanel> sm_slidePanel = nullptr;
|
||||
/// @brief Inner render target so the menu only renders to a certain area.
|
||||
static inline sdl::SharedTexture m_menuRenderTarget = nullptr;
|
||||
static inline sdl::SharedTexture sm_menuRenderTarget = nullptr;
|
||||
/// @brief The width of the panels. This is set according to the control guide text.
|
||||
static inline int m_panelWidth = 0;
|
||||
|
||||
/// @brief Renders the title of the currently targetted game.
|
||||
void renderTitle(void);
|
||||
static inline int sm_panelWidth = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -50,18 +50,24 @@ class ConfirmState : public AppState
|
|||
/// @brief Just updates the ConfirmState.
|
||||
void update(void)
|
||||
{
|
||||
if (input::buttonPressed(HidNpadButton_A) && !m_hold)
|
||||
// This is to guard against the dialog being triggered right away. To do: Maybe figure out a better way to accomplish this?
|
||||
if (input::buttonPressed(HidNpadButton_A) && !m_triggerGuard)
|
||||
{
|
||||
m_triggerGuard = true;
|
||||
}
|
||||
|
||||
if (m_triggerGuard && input::buttonPressed(HidNpadButton_A) && !m_hold)
|
||||
{
|
||||
AppState::deactivate();
|
||||
JKSV::pushState(std::make_shared<StateType>(m_function, m_dataStruct));
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_A) && m_hold)
|
||||
else if (m_triggerGuard && input::buttonPressed(HidNpadButton_A) && m_hold)
|
||||
{
|
||||
// Get the starting tick count and change the Yes string to the first holding string.
|
||||
m_startingTickCount = SDL_GetTicks64();
|
||||
m_yesString = strings::getByName(strings::names::HOLDING_STRINGS, 0);
|
||||
}
|
||||
else if (input::buttonHeld(HidNpadButton_A) && m_hold)
|
||||
else if (m_triggerGuard && input::buttonHeld(HidNpadButton_A) && m_hold)
|
||||
{
|
||||
uint64_t TickCount = SDL_GetTicks64() - m_startingTickCount;
|
||||
|
||||
|
|
@ -112,20 +118,22 @@ class ConfirmState : public AppState
|
|||
}
|
||||
|
||||
private:
|
||||
// Query string
|
||||
/// @brief String displayed
|
||||
std::string m_queryString;
|
||||
// Yes string.
|
||||
/// @brief Yes or [X] [A]
|
||||
std::string m_yesString;
|
||||
// X coordinate to render the Yes [A] string to,
|
||||
/// @brief X coordinate to render the Yes [A]
|
||||
int m_yesX = 0;
|
||||
// X coordinate to render the No [B] string to.
|
||||
/// @brief Position of No.
|
||||
int m_noX = 0;
|
||||
// Whether or not holding is required to confirm.
|
||||
/// @brief This is to prevent the dialog from triggering immediately.
|
||||
bool m_triggerGuard = false;
|
||||
/// @brief Whether or not holding [A] to confirm is required.
|
||||
bool m_hold;
|
||||
// For tick counting/holding
|
||||
/// @brief Keep track of the ticks/time needed to confirm.
|
||||
uint64_t m_startingTickCount = 0;
|
||||
// Function
|
||||
/// @brief Function to execute if action is confirmed.
|
||||
TaskFunction m_function;
|
||||
// Shared ptr to data to send to confirmation function.
|
||||
/// @brief Pointer to data struct passed to ^
|
||||
std::shared_ptr<StructType> m_dataStruct;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -26,4 +26,8 @@ class SettingsState : public AppState
|
|||
sdl::SharedTexture m_renderTarget = nullptr;
|
||||
/// @brief X coordinate of the control guide in the bottom right corner.
|
||||
int m_controlGuideX = 0;
|
||||
/// @brief Runs a routine to update the menu strings for the menu.
|
||||
void updateMenuOptions(void);
|
||||
/// @brief Toggles or executes the code to changed the selected menu option.
|
||||
void toggleOptions(void);
|
||||
};
|
||||
|
|
|
|||
44
include/appstates/TitleInfoState.hpp
Normal file
44
include/appstates/TitleInfoState.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
#include "appstates/AppState.hpp"
|
||||
#include "data/data.hpp"
|
||||
#include "system/Timer.hpp"
|
||||
#include "ui/SlideOutPanel.hpp"
|
||||
#include <memory>
|
||||
|
||||
class TitleInfoState : public AppState
|
||||
{
|
||||
public:
|
||||
/// @brief Constructs a new title info state.
|
||||
/// @param user User to display info for.
|
||||
/// @param titleInfo Title to display info for.
|
||||
TitleInfoState(data::User *user, data::TitleInfo *titleInfo);
|
||||
|
||||
/// @brief Required destructor.
|
||||
~TitleInfoState() {};
|
||||
|
||||
/// @brief Runs update routine.
|
||||
void update(void);
|
||||
|
||||
/// @brief Runs render routine.
|
||||
void render(void);
|
||||
|
||||
private:
|
||||
/// @brief Pointer to user.
|
||||
data::User *m_user;
|
||||
/// @brief Pointer to title info.
|
||||
data::TitleInfo *m_titleInfo;
|
||||
/// @brief Width of titles in pixels.
|
||||
int m_titleWidth = 0;
|
||||
/// @brief X coordinate of title text.
|
||||
int m_titleX = 0;
|
||||
/// @brief Whether or not the title is too long to fit in the panel and needs to be scrolled.
|
||||
bool m_titleScrolling = false;
|
||||
/// @brief Whether or not a scroll has been triggered.
|
||||
bool m_titleScrollTriggered = false;
|
||||
/// @brief Timer to scroll title if needed.
|
||||
sys::Timer m_titleScrollTimer;
|
||||
/// @brief Bool to tell whether or not static members are initialized.
|
||||
static inline bool sm_initialized = false;
|
||||
/// @brief Slide panel.
|
||||
static inline std::unique_ptr<ui::SlideOutPanel> sm_slidePanel = nullptr;
|
||||
};
|
||||
36
include/appstates/TitleOptionState.hpp
Normal file
36
include/appstates/TitleOptionState.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
#include "appstates/AppState.hpp"
|
||||
#include "data/data.hpp"
|
||||
#include "ui/Menu.hpp"
|
||||
#include "ui/SlideOutPanel.hpp"
|
||||
#include <memory>
|
||||
|
||||
class TitleOptionState : public AppState
|
||||
{
|
||||
public:
|
||||
/// @brief Constructs a new title option state.
|
||||
/// @param user Target user.
|
||||
/// @param titleInfo Target title.
|
||||
TitleOptionState(data::User *user, data::TitleInfo *titleInfo);
|
||||
|
||||
/// @brief Required destructor.
|
||||
~TitleOptionState() {};
|
||||
|
||||
/// @brief Runs update routine.
|
||||
void update(void);
|
||||
|
||||
/// @brief Runs the render routine.
|
||||
void render(void);
|
||||
|
||||
private:
|
||||
/// @brief This is just in case the option should only apply to the current user.
|
||||
data::User *m_targetUser = nullptr;
|
||||
/// @brief This is the target title.
|
||||
data::TitleInfo *m_titleInfo = nullptr;
|
||||
/// @brief This is so it's known whether or not to initialize the static members of this class.
|
||||
static inline bool sm_initialized = false;
|
||||
/// @brief Menu used and shared by all instances.
|
||||
static inline std::unique_ptr<ui::Menu> sm_titleOptionMenu = nullptr;
|
||||
/// @brief This is shared by all instances of this class.
|
||||
static inline std::unique_ptr<ui::SlideOutPanel> sm_slidePanel = nullptr;
|
||||
};
|
||||
|
|
@ -18,10 +18,28 @@ namespace config
|
|||
/// @return Key's value if found. 0 if it is not.
|
||||
uint8_t getByKey(std::string_view key);
|
||||
|
||||
/// @brief Toggles the key. This is only for basic true or false settings.
|
||||
/// @param key Key to toggle.
|
||||
void toggleByKey(std::string_view key);
|
||||
|
||||
/// @brief Sets the key according
|
||||
/// @param key Key to set.
|
||||
/// @param value Value to set the key to.
|
||||
void setByKey(std::string_view key, uint8_t value);
|
||||
|
||||
/// @brief Retrieves value of config at index.
|
||||
/// @param index Index of value to retrieve.
|
||||
uint8_t getByIndex(int index);
|
||||
|
||||
/// @brief Toggles the key at index from 1 to 0 or vice-versa.
|
||||
/// @param index Index of key to toggle.
|
||||
void toggleByIndex(int index);
|
||||
|
||||
/// @brief Sets the config value at index to value.
|
||||
/// @param index Index of value to set.
|
||||
/// @param value Value to set index to.
|
||||
void setByIndex(int index, uint8_t value);
|
||||
|
||||
/// @brief Returns the working directory.
|
||||
/// @return Working directory.
|
||||
fslib::Path getWorkingDirectory(void);
|
||||
|
|
@ -30,6 +48,10 @@ namespace config
|
|||
/// @return Scaling variable.
|
||||
double getAnimationScaling(void);
|
||||
|
||||
/// @brief Sets the UI animation scaling.
|
||||
/// @param newScale New value to set the scaling to.
|
||||
void setAnimationScaling(double newScale);
|
||||
|
||||
/// @brief Adds or removes a title from the favorites list.
|
||||
/// @param applicationID Application ID of title to add or remove.
|
||||
void addRemoveFavorite(uint64_t applicationID);
|
||||
|
|
@ -48,6 +70,22 @@ namespace config
|
|||
/// @return True if found. False if not.
|
||||
bool isBlacklisted(uint64_t applicationID);
|
||||
|
||||
/// @brief Adds a custom output path for the title.
|
||||
/// @param applicationID Application ID of title to add a path for.
|
||||
/// @param customPath Path to assign to the output.
|
||||
void addCustomPath(uint64_t applicationID, std::string_view customPath);
|
||||
|
||||
/// @brief Searches to see if the application ID passed has a custom output path.
|
||||
/// @param applicationID Application ID to check.
|
||||
/// @return True if it does. False if it doesn't.
|
||||
bool hasCustomPath(uint64_t applicationID);
|
||||
|
||||
/// @brief Gets the custom, defined path for the title.
|
||||
/// @param applicationID Application ID of title to get.
|
||||
/// @param pathOut Buffer to write the path to.
|
||||
/// @param pathOutSize Size of the buffer to write the path to.
|
||||
void getCustomPath(uint64_t applicationID, char *pathOut, size_t pathOutSize);
|
||||
|
||||
// Names of keys. Note: Not all of these are retrievable with GetByKey. Some of these are purely for config reading and writing.
|
||||
namespace keys
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ namespace data
|
|||
/// @return Path safe version of the title.
|
||||
const char *getPathSafeTitle(void);
|
||||
|
||||
/// @brief Allows the path safe title to be set to a new path.
|
||||
/// @param newPathSafe Buffer containing the new safe path to use.
|
||||
/// @param newPathLength Size of the buffer passed.
|
||||
void setPathSafeTitle(const char *newPathSafe, size_t newPathLength);
|
||||
|
||||
/// @brief Returns the publisher of the title.
|
||||
/// @return Publisher string from NACP.
|
||||
const char *getPublisher(void);
|
||||
|
|
@ -36,27 +41,27 @@ namespace data
|
|||
/// @brief Returns the save data container's base size.
|
||||
/// @param saveType Type of save data to return.
|
||||
/// @return Size of baseline save data if applicable. If not, 0.
|
||||
int64_t getSaveDataSize(FsSaveDataType saveType) const;
|
||||
int64_t getSaveDataSize(uint8_t saveType) const;
|
||||
|
||||
/// @brief Returns the maximum size of the save data container.
|
||||
/// @param saveType Type of save data to return.
|
||||
/// @return Maximum size of the save container if applicable. If not, 0.
|
||||
int64_t getSaveDataSizeMax(FsSaveDataType saveType) const;
|
||||
int64_t getSaveDataSizeMax(uint8_t saveType) const;
|
||||
|
||||
/// @brief Returns the journaling size for the save type passed.
|
||||
/// @param saveType Save type to return.
|
||||
/// @return Journal size if applicable. If not, 0.
|
||||
int64_t getJournalSize(FsSaveDataType saveType) const;
|
||||
int64_t getJournalSize(uint8_t saveType) const;
|
||||
|
||||
/// @brief Returns the maximum journal size for the save type passed.
|
||||
/// @param saveType Save type to return.
|
||||
/// @return Maximum journal size if applicable. If not, 0.
|
||||
int64_t getJournalSizeMax(FsSaveDataType saveType) const;
|
||||
int64_t getJournalSizeMax(uint8_t saveType) const;
|
||||
|
||||
/// @brief Returns if a title uses the save type passed.
|
||||
/// @param saveType Save type to check for.
|
||||
/// @return True on success. False on failure.
|
||||
bool hasSaveDataType(FsSaveDataType saveType);
|
||||
bool hasSaveDataType(uint8_t saveType);
|
||||
|
||||
/// @brief Returns a pointer to the icon texture.
|
||||
/// @return Icon
|
||||
|
|
|
|||
|
|
@ -24,13 +24,22 @@ 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 pathSafeNickname, std::string_view iconPath, FsSaveDataType saveType);
|
||||
User(AccountUid accountID,
|
||||
std::string_view nickname,
|
||||
std::string_view pathSafeNickname,
|
||||
std::string_view iconPath,
|
||||
FsSaveDataType saveType);
|
||||
|
||||
/// @brief Pushes data to m_userData
|
||||
/// @param saveInfo SaveDataInfo.
|
||||
/// @param playStats Play statistics.
|
||||
void addData(const FsSaveDataInfo &saveInfo, const PdmPlayStatistics &playStats);
|
||||
|
||||
|
||||
/// @brief Erases data at index.
|
||||
/// @param index Index of save data info to erase.
|
||||
void eraseData(int index);
|
||||
|
||||
/// @brief Runs the sort algo on the vector.
|
||||
void sortData(void);
|
||||
|
||||
|
|
|
|||
35
include/fs/SaveMetaData.hpp
Normal file
35
include/fs/SaveMetaData.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
#include "data/TitleInfo.hpp"
|
||||
#include <cstdint>
|
||||
#include <switch.h>
|
||||
|
||||
namespace fs
|
||||
{
|
||||
/// @brief This is the magic value written to the beginning.
|
||||
constexpr uint64_t SAVE_META_MAGIC = 0x56534B4A;
|
||||
|
||||
/// @brief This struct is for storing the data necessary to restore saves to a different console.
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
/// @brief Magic.
|
||||
uint32_t m_magic;
|
||||
/// @brief Application ID.
|
||||
uint64_t m_applicationID;
|
||||
/// @brief Save data type.
|
||||
uint8_t m_saveType;
|
||||
/// @brief Save data rank.
|
||||
uint8_t m_saveRank;
|
||||
/// @brief Save data space id.
|
||||
uint8_t m_saveSpaceID;
|
||||
/// @brief Base save data size.
|
||||
int64_t m_saveDataSize;
|
||||
/// @brief Maximum save size "allowed".
|
||||
int64_t m_saveDataSizeMax;
|
||||
/// @brief Base journaling size.
|
||||
int64_t m_journalSize;
|
||||
/// @brief Maximum journaling size.
|
||||
int64_t m_journalSizeMax;
|
||||
/// @brief Total size of the files in the backup. For ZIP, this is uncompressed.
|
||||
uint64_t m_totalSaveSize;
|
||||
} SaveMetaData;
|
||||
} // namespace fs
|
||||
14
include/fs/directoryFunctions.hpp
Normal file
14
include/fs/directoryFunctions.hpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
#include "fslib.hpp"
|
||||
|
||||
namespace fs
|
||||
{
|
||||
/// @brief Retrieves the total size of the contents of the directory at targetPath.
|
||||
/// @param targetPath Directory to calculate.
|
||||
uint64_t getDirectoryTotalSize(const fslib::Path &targetPath);
|
||||
|
||||
/// @brief Checks if directory is empty. Didn't feel like this needs its own source file.
|
||||
/// @param directoryPath Path to directory to check.
|
||||
/// @return True if directory has files inside.
|
||||
bool directoryHasContents(const fslib::Path &directoryPath);
|
||||
} // namespace fs
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
#pragma once
|
||||
#include "fs/SaveMetaData.hpp"
|
||||
#include "fs/directoryFunctions.hpp"
|
||||
#include "fs/io.hpp"
|
||||
#include "fs/saveDataFunctions.hpp"
|
||||
#include "fs/saveMount.hpp"
|
||||
#include "fs/zip.hpp"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "data/data.hpp"
|
||||
#include <switch.h>
|
||||
|
||||
namespace fs
|
||||
{
|
||||
|
|
@ -8,4 +9,9 @@ namespace fs
|
|||
/// @param titleInfo Title to create save data for.
|
||||
/// @return True on success. False on failure.
|
||||
bool createSaveDataFor(data::User *targetUser, data::TitleInfo *titleInfo);
|
||||
|
||||
/// @brief Deletes the save data of the FsSaveDataInfo passed.
|
||||
/// @param saveInfo Save data to delete.
|
||||
/// @return True on success. False on failure.
|
||||
bool deleteSaveData(const FsSaveDataInfo &saveInfo);
|
||||
} // namespace fs
|
||||
|
|
@ -25,4 +25,9 @@ namespace fs
|
|||
uint64_t journalSize,
|
||||
std::string_view commitDevice,
|
||||
sys::ProgressTask *Task = nullptr);
|
||||
|
||||
/// @brief Returns whether or not zip has files inside.
|
||||
/// @param zipPath Path to zip to check.
|
||||
/// @return True if at least one file is found. False if none.
|
||||
bool zipHasContents(const fslib::Path &zipPath);
|
||||
} // namespace fs
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ namespace strings
|
|||
{
|
||||
// Attempts to load strings from file in RomFS.
|
||||
bool initialize(void);
|
||||
|
||||
// Returns string with name and index. Returns nullptr if string doesn't exist.
|
||||
const char *getByName(std::string_view name, int index);
|
||||
|
||||
// Names of strings to prevent typos.
|
||||
namespace names
|
||||
{
|
||||
|
|
@ -25,7 +27,14 @@ namespace strings
|
|||
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 CREATING_SAVE_DATA_FOR = "CreatingSaveDataFor";
|
||||
static constexpr std::string_view POP_MESSAGES = "PopMessages";
|
||||
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 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";
|
||||
} // namespace names
|
||||
} // namespace strings
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace stringutil
|
|||
/// @param format Format of string.
|
||||
/// @param arguments Arguments for string.
|
||||
/// @return Formatted C++ string.
|
||||
std::string getFormattedString(const char *Format, ...);
|
||||
std::string getFormattedString(const char *format, ...);
|
||||
|
||||
/// @brief Replaces and sequence of characters in a string.
|
||||
/// @param target Target string.
|
||||
|
|
@ -33,7 +33,11 @@ namespace ui
|
|||
|
||||
/// @brief Adds and option to the menu.
|
||||
/// @param newOption Option to add to menu.
|
||||
void addOption(std::string_view NewOption);
|
||||
void addOption(std::string_view newOption);
|
||||
|
||||
/// @brief Allows updating and editing the option.
|
||||
/// @param newOption Option to change text to.
|
||||
void editOption(int index, std::string_view newOption);
|
||||
|
||||
/// @brief Returns the index of the currently selected menu option.
|
||||
/// @return Index of currently selected option.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "system/Timer.hpp"
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -57,5 +58,7 @@ namespace ui
|
|||
std::vector<std::pair<int, std::string>> m_messageQueue;
|
||||
// Actual vector of messages
|
||||
std::vector<ui::PopMessage> m_messages;
|
||||
// Mutex to attempt to make this thread safe.
|
||||
std::mutex m_messageMutex;
|
||||
};
|
||||
} // namespace ui
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ namespace ui
|
|||
/// @param newElement New element to push.
|
||||
void pushNewElement(std::shared_ptr<ui::Element> newElement);
|
||||
|
||||
/// @brief Clears the element vector, freeing them in the process.
|
||||
void clearElements(void);
|
||||
|
||||
/// @brief Returns a pointer to the render target of the panel.
|
||||
/// @return Raw SDL_Texture pointer to target.
|
||||
SDL_Texture *get(void);
|
||||
|
|
@ -66,6 +69,8 @@ namespace ui
|
|||
double m_x;
|
||||
/// @brief Width of the panel in pixels.
|
||||
int m_width;
|
||||
/// @brief Target X position of panel.
|
||||
double m_targetX;
|
||||
/// @brief Which side the panel is on.
|
||||
SlideOutPanel::Side m_side;
|
||||
/// @brief Render target if panel.
|
||||
|
|
|
|||
57
include/ui/TextScroll.hpp
Normal file
57
include/ui/TextScroll.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
#include "system/Timer.hpp"
|
||||
#include "ui/Element.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
/// @brief This is used in multiple places and rewriting it over and over is a waste of time.
|
||||
class TextScroll : public ui::Element
|
||||
{
|
||||
public:
|
||||
/// @brief This is only here so I can get around the backup menu having static members.
|
||||
TextScroll(void) = default;
|
||||
|
||||
/// @brief Constructs a new TextScroll.
|
||||
/// @param text Text to be scrolled.
|
||||
/// @param availableWidth Maximum width. If this is not exceeded,text is just rendered centered according to it.
|
||||
TextScroll(std::string_view text, int fontSize, int availableWidth, int y, sdl::Color color);
|
||||
|
||||
/// @brief Required destructor.
|
||||
~TextScroll() {};
|
||||
|
||||
/// @brief Same as the default constructor. To do: Figure this out better sometime.
|
||||
/// @param textScroll TextScroll to copy.
|
||||
/// @return Reference to itself?
|
||||
TextScroll &operator=(const TextScroll &textScroll);
|
||||
|
||||
/// @brief Runs the update routine.
|
||||
/// @param hasFocus Whether or not the calling state has focus.
|
||||
void update(bool hasFocus);
|
||||
|
||||
/// @brief Runs the render routine.
|
||||
/// @param target Target to render to.
|
||||
/// @param hasFocus Whether or not the calling state has focus.
|
||||
void render(SDL_Texture *target, bool hasFocus);
|
||||
|
||||
private:
|
||||
/// @brief Text to display.
|
||||
std::string m_text;
|
||||
/// @brief X coordinate to render text at.
|
||||
int m_x;
|
||||
/// @brief Y coordinate to render text at.
|
||||
int m_y;
|
||||
/// @brief Font size used to calculate and render text.
|
||||
int m_fontSize = 0;
|
||||
/// @brief Color used to render the text.
|
||||
sdl::Color m_textColor;
|
||||
/// @brief Width of text in pixels.
|
||||
int m_textWidth = 0;
|
||||
/// @brief Whether or not text is too wide to fit into the availableWidth passed to the constructor.
|
||||
bool m_textScrolling = false;
|
||||
/// @brief Whether or not a scroll was triggered.
|
||||
bool m_textScrollTriggered = false;
|
||||
/// @brief Timer for scrolling text.
|
||||
sys::Timer m_scrollTimer;
|
||||
};
|
||||
} // namespace ui
|
||||
|
|
@ -39,10 +39,30 @@
|
|||
"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": [
|
||||
"Reload Titles",
|
||||
"SD to SD Browser",
|
||||
"BIS: ProfInfoF",
|
||||
"BIS: ProdInfoF",
|
||||
"BIS: Safe",
|
||||
"BIS: System",
|
||||
"BIS: User",
|
||||
|
|
@ -71,6 +91,7 @@
|
|||
"Decompressing #%s# from ZIP..."
|
||||
],
|
||||
"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#?"
|
||||
],
|
||||
|
|
@ -89,10 +110,19 @@
|
|||
"Enter how much to expand (in MB)."
|
||||
],
|
||||
"UserOptions": [
|
||||
"Dump all for #%s#",
|
||||
"Create Save Data for #%s#",
|
||||
"Create All Save Data for #%s#",
|
||||
"Delete All Save Data for #%s#"
|
||||
"Dump all for `%s`",
|
||||
"Create Save Data for `%s`",
|
||||
"Create All Save Data for `%s`",
|
||||
"Delete All Save Data for `%s`"
|
||||
],
|
||||
"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."
|
||||
],
|
||||
"UserOptionStatus": [
|
||||
"Creating save data for `%s`...",
|
||||
"Deleting save data for `%s`..."
|
||||
],
|
||||
"TitleOptions": [
|
||||
"Information",
|
||||
|
|
@ -102,14 +132,41 @@
|
|||
"Delete all save backups",
|
||||
"Reset save data.",
|
||||
"Delete save data from system",
|
||||
"Extend save data container",
|
||||
"Extend save data",
|
||||
"Export SVI file"
|
||||
],
|
||||
"CreatingSaveDataFor": [
|
||||
"Creating save data for #%s#..."
|
||||
"TitleOptionStatus": [
|
||||
"Deleting all backups for #%s#.",
|
||||
"Resetting save data for #%s#."
|
||||
],
|
||||
"PopMessages": [
|
||||
"TitleOptionPops": [
|
||||
"All backups deleted for `%s`!",
|
||||
"Failed to delete all backups!",
|
||||
"Error resetting save data!",
|
||||
"Save data successfully reset!"
|
||||
],
|
||||
"TitleOptionConfirmations": [
|
||||
"Are you sure you want to add #%s# to you blacklist? Once you do this, it will no longer appear on any title list or selection.",
|
||||
"Are you sure you want to delete #%s#'s save data for #%s#? This will permanently delete it from the system."
|
||||
],
|
||||
"PopMessagesGeneral": [
|
||||
"Unable to exit JKSV while tasks are running!"
|
||||
],
|
||||
"PopMessagesBackupMenu": [
|
||||
"Save data is empty!",
|
||||
"Backup is empty!"
|
||||
"Backup is empty!",
|
||||
"Error resetting save data!",
|
||||
"Error opening ZIP file for reading!",
|
||||
"Error occurred deleting 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!"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ JKSV::JKSV(void)
|
|||
sdl::text::addColorCharacter(L'*', colors::RED);
|
||||
sdl::text::addColorCharacter(L'<', colors::YELLOW);
|
||||
sdl::text::addColorCharacter(L'>', colors::GREEN);
|
||||
sdl::text::addColorCharacter(L'`', colors::BLUE_GREEN);
|
||||
sdl::text::addColorCharacter(L'^', colors::PINK);
|
||||
|
||||
// This is to check whether the author wanted credit for their work.
|
||||
|
|
@ -108,6 +109,9 @@ JKSV::JKSV(void)
|
|||
|
||||
JKSV::~JKSV()
|
||||
{
|
||||
// Try to save config first.
|
||||
config::save();
|
||||
|
||||
socketExit();
|
||||
setsysExit();
|
||||
setExit();
|
||||
|
|
@ -135,16 +139,7 @@ void JKSV::update(void)
|
|||
m_isRunning = false;
|
||||
}
|
||||
|
||||
if (!sm_stateVector.empty())
|
||||
{
|
||||
while (!sm_stateVector.back()->isActive())
|
||||
{
|
||||
sm_stateVector.back()->takeFocus();
|
||||
sm_stateVector.pop_back();
|
||||
sm_stateVector.back()->giveFocus();
|
||||
}
|
||||
sm_stateVector.back()->update();
|
||||
}
|
||||
JKSV::updateStateVector();
|
||||
|
||||
// Update pop messages.
|
||||
ui::PopMessageManager::update();
|
||||
|
|
@ -199,3 +194,31 @@ void JKSV::pushState(std::shared_ptr<AppState> newState)
|
|||
newState->giveFocus();
|
||||
sm_stateVector.push_back(newState);
|
||||
}
|
||||
|
||||
void JKSV::updateStateVector(void)
|
||||
{
|
||||
if (sm_stateVector.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for and purge deactivated states.
|
||||
for (size_t i = 0; i < sm_stateVector.size(); i++)
|
||||
{
|
||||
if (!sm_stateVector.at(i)->isActive())
|
||||
{
|
||||
// This is a just in case thing. Some states are never actually purged.
|
||||
sm_stateVector.at(i)->takeFocus();
|
||||
sm_stateVector.erase(sm_stateVector.begin() + i);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the back has focus.
|
||||
if (!sm_stateVector.back()->hasFocus())
|
||||
{
|
||||
sm_stateVector.back()->giveFocus();
|
||||
}
|
||||
|
||||
// Only update the back most state.
|
||||
sm_stateVector.back()->update();
|
||||
}
|
||||
|
|
|
|||
70
source/TitleInfoState.cpp
Normal file
70
source/TitleInfoState.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#include "appstates/TitleInfoState.hpp"
|
||||
#include "colors.hpp"
|
||||
#include "input.hpp"
|
||||
#include "sdl.hpp"
|
||||
|
||||
TitleInfoState::TitleInfoState(data::User *user, data::TitleInfo *titleInfo) : m_user(user), m_titleInfo(titleInfo), m_titleScrollTimer(3000)
|
||||
{
|
||||
if (!sm_initialized)
|
||||
{
|
||||
// This is the slide panel everything is rendered to.
|
||||
sm_slidePanel = std::make_unique<ui::SlideOutPanel>(480, ui::SlideOutPanel::Side::Right);
|
||||
sm_initialized = true;
|
||||
}
|
||||
|
||||
// Check if the title is too large to fit within the target.
|
||||
m_titleWidth = sdl::text::getWidth(32, m_titleInfo->getTitle());
|
||||
if (m_titleWidth > 480)
|
||||
{
|
||||
// Just set this to 8 and we'll scroll the title.
|
||||
m_titleX = 8;
|
||||
m_titleScrolling = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just center it.
|
||||
m_titleX = 240 - (m_titleWidth / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void TitleInfoState::update(void)
|
||||
{
|
||||
// Update slide panel.
|
||||
sm_slidePanel->update(AppState::hasFocus());
|
||||
|
||||
if (input::buttonPressed(HidNpadButton_B))
|
||||
{
|
||||
sm_slidePanel->close();
|
||||
}
|
||||
else if (sm_slidePanel->isClosed())
|
||||
{
|
||||
sm_slidePanel->reset();
|
||||
}
|
||||
else if (m_titleScrolling && m_titleScrollTimer.isTriggered())
|
||||
{
|
||||
m_titleX -= 2;
|
||||
m_titleScrollTriggered = true;
|
||||
}
|
||||
else if (m_titleScrollTriggered && m_titleX > 8 - m_titleWidth)
|
||||
{
|
||||
m_titleX -= 2;
|
||||
}
|
||||
else if (m_titleScrollTriggered && m_titleX <= 8 - m_titleWidth)
|
||||
{
|
||||
m_titleX = 8;
|
||||
m_titleScrollTriggered = false;
|
||||
m_titleScrollTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
void TitleInfoState::render(void)
|
||||
{
|
||||
// Grab the panel's target and clear it. To do: This how I originally intended to.
|
||||
sm_slidePanel->clearTarget();
|
||||
// SDL_Texture *panelTarget = sm_slidePanel->get();
|
||||
|
||||
// If the title doesn't need to be scrolled, just render it.
|
||||
|
||||
|
||||
sdl::renderLine(sm_slidePanel->get(), 10, 42, 460, 42, colors::WHITE);
|
||||
}
|
||||
|
|
@ -10,10 +10,11 @@
|
|||
#include "keyboard.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "sdl.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "strings.hpp"
|
||||
#include "stringutil.hpp"
|
||||
#include "system/system.hpp"
|
||||
#include "ui/PopMessageManager.hpp"
|
||||
#include "ui/TextScroll.hpp"
|
||||
#include <cstring>
|
||||
|
||||
// This struct is used to pass data to Restore, Delete, and upload.
|
||||
|
|
@ -23,13 +24,24 @@ struct TargetStruct
|
|||
fslib::Path m_targetPath;
|
||||
// Journal size if commit is needed.
|
||||
uint64_t m_journalSize;
|
||||
// User
|
||||
data::User *m_user;
|
||||
// Title info
|
||||
data::TitleInfo *m_titleInfo;
|
||||
// Spawning state so refresh can be called.
|
||||
BackupMenuState *m_spawningState = nullptr;
|
||||
};
|
||||
|
||||
// Declarations here. Definitions after class.
|
||||
// Create new backup in destinationPath
|
||||
static void createnewBackup(sys::ProgressTask *task, fslib::Path destinationPath, BackupMenuState *spawningState);
|
||||
// Create new backup in targetPath
|
||||
static void createNewBackup(sys::ProgressTask *task,
|
||||
data::User *user,
|
||||
data::TitleInfo *titleInfo,
|
||||
fslib::Path targetPath,
|
||||
BackupMenuState *spawningState);
|
||||
|
||||
// Overwrites and existing backup.
|
||||
static void overwriteBackup(sys::ProgressTask *task, std::shared_ptr<TargetStruct> dataStruct);
|
||||
// Restores a backup and requires confirmation to do so. Takes a shared_ptr to a TargetStruct.
|
||||
static void restoreBackup(sys::ProgressTask *task, std::shared_ptr<TargetStruct> dataStruct);
|
||||
// Deletes a backup and requires confirmation to do so. Takes a shared_ptr to a TargetStruct.
|
||||
|
|
@ -37,44 +49,40 @@ static void deleteBackup(sys::Task *task, std::shared_ptr<TargetStruct> dataStru
|
|||
|
||||
BackupMenuState::BackupMenuState(data::User *user, data::TitleInfo *titleInfo, FsSaveDataType saveType)
|
||||
: m_user(user), m_titleInfo(titleInfo), m_saveType(saveType),
|
||||
m_directoryPath(config::getWorkingDirectory() / m_titleInfo->getPathSafeTitle()),
|
||||
m_titleWidth(sdl::text::getWidth(22, m_titleInfo->getTitle())), m_titleScrollTimer(3000)
|
||||
m_directoryPath(config::getWorkingDirectory() / m_titleInfo->getPathSafeTitle())
|
||||
{
|
||||
if (!sm_isInitialized)
|
||||
{
|
||||
m_panelWidth = sdl::text::getWidth(22, strings::getByName(strings::names::CONTROL_GUIDES, 2)) + 64;
|
||||
sm_panelWidth = sdl::text::getWidth(22, strings::getByName(strings::names::CONTROL_GUIDES, 2)) + 64;
|
||||
// To do: Give classes an alternate so they don't have to be constructed.
|
||||
m_backupMenu = std::make_unique<ui::Menu>(8, 8, m_panelWidth - 24, 24, 600);
|
||||
m_slidePanel = std::make_unique<ui::SlideOutPanel>(m_panelWidth, ui::SlideOutPanel::Side::Right);
|
||||
m_menuRenderTarget =
|
||||
sdl::TextureManager::createLoadTexture("backupMenuTarget", m_panelWidth, 600, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET);
|
||||
sm_backupMenu = std::make_shared<ui::Menu>(8, 8, sm_panelWidth - 14, 24, 600);
|
||||
sm_slidePanel = std::make_unique<ui::SlideOutPanel>(sm_panelWidth, ui::SlideOutPanel::Side::Right);
|
||||
sm_menuRenderTarget =
|
||||
sdl::TextureManager::createLoadTexture("backupMenuTarget", sm_panelWidth, 600, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET);
|
||||
sm_isInitialized = true;
|
||||
}
|
||||
|
||||
// Check if title needs to be scrolled above the menu or just set the X position.
|
||||
if (m_titleWidth >= m_panelWidth)
|
||||
{
|
||||
m_titleScrolling = true;
|
||||
m_titleX = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_titleX = (m_panelWidth / 2) - (m_titleWidth / 2);
|
||||
}
|
||||
// String for the top of the panel.
|
||||
std::string panelString = stringutil::getFormattedString("`%s` - %s", m_user->getNickname(), m_titleInfo->getTitle());
|
||||
|
||||
// Check if there's currently any data to backup to prevent blanks.
|
||||
{
|
||||
fslib::Directory saveCheck(fs::DEFAULT_SAVE_PATH);
|
||||
logger::log("Save file count: %i", saveCheck.getCount());
|
||||
m_saveHasData = saveCheck.getCount() > 0;
|
||||
}
|
||||
// This needs sm_panelWidth or it'd be in the initializer list.
|
||||
sm_slidePanel->pushNewElement(std::make_shared<ui::TextScroll>(panelString, 22, sm_panelWidth, 8, colors::WHITE));
|
||||
|
||||
|
||||
fslib::Directory saveCheck(fs::DEFAULT_SAVE_PATH);
|
||||
m_saveHasData = saveCheck.getCount() > 0;
|
||||
|
||||
BackupMenuState::refresh();
|
||||
}
|
||||
|
||||
BackupMenuState::~BackupMenuState()
|
||||
{
|
||||
sm_slidePanel->clearElements();
|
||||
}
|
||||
|
||||
void BackupMenuState::update(void)
|
||||
{
|
||||
if (input::buttonPressed(HidNpadButton_A) && m_backupMenu->getSelected() == 0 && m_saveHasData)
|
||||
if (input::buttonPressed(HidNpadButton_A) && sm_backupMenu->getSelected() == 0 && m_saveHasData)
|
||||
{
|
||||
// get name for backup.
|
||||
char backupName[0x81] = {0};
|
||||
|
|
@ -99,24 +107,64 @@ void BackupMenuState::update(void)
|
|||
return;
|
||||
}
|
||||
// Push the task.
|
||||
JKSV::pushState(std::make_shared<ProgressState>(createnewBackup, m_directoryPath / backupName, this));
|
||||
JKSV::pushState(std::make_shared<ProgressState>(createNewBackup, m_user, m_titleInfo, m_directoryPath / backupName, this));
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_A) && m_backupMenu->getSelected() == 0 && !m_saveHasData)
|
||||
else if (input::buttonPressed(HidNpadButton_A) && sm_backupMenu->getSelected() == 0 && !m_saveHasData)
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, strings::getByName(strings::names::POP_MESSAGES, 0));
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 0));
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_Y) && m_backupMenu->getSelected() > 0 &&
|
||||
else if (input::buttonPressed(HidNpadButton_A) && m_saveHasData && sm_backupMenu->getSelected() > 0)
|
||||
{
|
||||
int selected = sm_backupMenu->getSelected() - 1;
|
||||
|
||||
std::string queryString =
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::BACKUPMENU_CONFIRMATIONS, 0), m_directoryListing[selected]);
|
||||
|
||||
std::shared_ptr<TargetStruct> dataStruct(new TargetStruct);
|
||||
dataStruct->m_targetPath = m_directoryPath / m_directoryListing[selected];
|
||||
|
||||
JKSV::pushState(
|
||||
std::make_shared<ConfirmState<sys::ProgressTask, ProgressState, TargetStruct>>(queryString,
|
||||
config::getByKey(config::keys::HOLD_FOR_OVERWRITE),
|
||||
overwriteBackup,
|
||||
dataStruct));
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_A) && !m_saveHasData && sm_backupMenu->getSelected() > 0)
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 0));
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_Y) && sm_backupMenu->getSelected() > 0 &&
|
||||
(m_saveType != FsSaveDataType_System || config::getByKey(config::keys::ALLOW_WRITING_TO_SYSTEM)))
|
||||
{
|
||||
// Need to account for new at the top.
|
||||
int selected = m_backupMenu->getSelected() - 1;
|
||||
int selected = sm_backupMenu->getSelected() - 1;
|
||||
|
||||
// Gonna need to test this quick.
|
||||
fslib::Path targetPath = m_directoryPath / m_directoryListing[selected];
|
||||
|
||||
// This is a quick check to avoid restoring blanks.
|
||||
if (fslib::directoryExists(targetPath) && !fs::directoryHasContents(targetPath))
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 1));
|
||||
return;
|
||||
}
|
||||
else if (fslib::fileExists(targetPath) && std::strcmp("zip", targetPath.getExtension()) == 0 && !fs::zipHasContents(targetPath))
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 1));
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<TargetStruct> dataStruct(new TargetStruct);
|
||||
dataStruct->m_targetPath = m_directoryPath / m_directoryListing[selected];
|
||||
dataStruct->m_journalSize = m_titleInfo->getJournalSize(m_saveType);
|
||||
dataStruct->m_spawningState = this;
|
||||
|
||||
std::string queryString =
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::BACKUPMENU_CONFIRMATIONS, 0), m_directoryListing[selected]);
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::BACKUPMENU_CONFIRMATIONS, 1), m_directoryListing[selected]);
|
||||
|
||||
JKSV::pushState(
|
||||
std::make_shared<ConfirmState<sys::ProgressTask, ProgressState, TargetStruct>>(queryString,
|
||||
|
|
@ -124,10 +172,10 @@ void BackupMenuState::update(void)
|
|||
restoreBackup,
|
||||
dataStruct));
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_X) && m_backupMenu->getSelected() > 0)
|
||||
else if (input::buttonPressed(HidNpadButton_X) && sm_backupMenu->getSelected() > 0)
|
||||
{
|
||||
// Selected needs to be offset by one to account for New
|
||||
int selected = m_backupMenu->getSelected() - 1;
|
||||
int selected = sm_backupMenu->getSelected() - 1;
|
||||
|
||||
// Create struct to pass.
|
||||
std::shared_ptr<TargetStruct> dataStruct(new TargetStruct);
|
||||
|
|
@ -136,7 +184,7 @@ void BackupMenuState::update(void)
|
|||
|
||||
// get the string.
|
||||
std::string queryString =
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::BACKUPMENU_CONFIRMATIONS, 1), m_directoryListing[selected]);
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::BACKUPMENU_CONFIRMATIONS, 2), m_directoryListing[selected]);
|
||||
|
||||
// Create/push new state.
|
||||
JKSV::pushState(std::make_shared<ConfirmState<sys::Task, TaskState, TargetStruct>>(queryString,
|
||||
|
|
@ -147,123 +195,175 @@ void BackupMenuState::update(void)
|
|||
else if (input::buttonPressed(HidNpadButton_B))
|
||||
{
|
||||
fslib::closeFileSystem(fs::DEFAULT_SAVE_MOUNT);
|
||||
m_slidePanel->close();
|
||||
sm_slidePanel->close();
|
||||
}
|
||||
else if (m_slidePanel->isClosed())
|
||||
else if (sm_slidePanel->isClosed())
|
||||
{
|
||||
m_slidePanel->reset();
|
||||
sm_slidePanel->reset();
|
||||
AppState::deactivate();
|
||||
}
|
||||
|
||||
m_slidePanel->update(AppState::hasFocus());
|
||||
// Update panel.
|
||||
sm_slidePanel->update(AppState::hasFocus());
|
||||
// This state bypasses the Slideout panel's normal behavior because it kind of has to.
|
||||
m_backupMenu->update(AppState::hasFocus());
|
||||
sm_backupMenu->update(AppState::hasFocus());
|
||||
}
|
||||
|
||||
void BackupMenuState::render(void)
|
||||
{
|
||||
// Clear panel target.
|
||||
m_slidePanel->clearTarget();
|
||||
// render the current title's name.
|
||||
BackupMenuState::renderTitle();
|
||||
sdl::renderLine(m_slidePanel->get(), 10, 42, m_panelWidth - 20, 42, colors::WHITE);
|
||||
sdl::renderLine(m_slidePanel->get(), 10, 648, m_panelWidth - 20, 648, colors::WHITE);
|
||||
sdl::text::render(m_slidePanel->get(),
|
||||
32,
|
||||
673,
|
||||
22,
|
||||
sdl::text::NO_TEXT_WRAP,
|
||||
colors::WHITE,
|
||||
strings::getByName(strings::names::CONTROL_GUIDES, 2));
|
||||
sm_slidePanel->clearTarget();
|
||||
|
||||
// Grab the render target.
|
||||
SDL_Texture *slideTarget = sm_slidePanel->get();
|
||||
|
||||
sdl::renderLine(slideTarget, 10, 42, sm_panelWidth - 10, 42, colors::WHITE);
|
||||
sdl::renderLine(slideTarget, 10, 648, sm_panelWidth - 10, 648, colors::WHITE);
|
||||
sdl::text::render(slideTarget, 32, 673, 22, sdl::text::NO_TEXT_WRAP, colors::WHITE, strings::getByName(strings::names::CONTROL_GUIDES, 2));
|
||||
|
||||
// Clear menu target.
|
||||
m_menuRenderTarget->clear(colors::TRANSPARENT);
|
||||
sm_menuRenderTarget->clear(colors::TRANSPARENT);
|
||||
// render menu to it.
|
||||
m_backupMenu->render(m_menuRenderTarget->get(), AppState::hasFocus());
|
||||
sm_backupMenu->render(sm_menuRenderTarget->get(), AppState::hasFocus());
|
||||
// render it to panel target.
|
||||
m_menuRenderTarget->render(m_slidePanel->get(), 0, 43);
|
||||
m_slidePanel->render(NULL, AppState::hasFocus());
|
||||
sm_menuRenderTarget->render(sm_slidePanel->get(), 0, 43);
|
||||
|
||||
sm_slidePanel->render(NULL, AppState::hasFocus());
|
||||
}
|
||||
|
||||
void BackupMenuState::refresh(void)
|
||||
{
|
||||
m_directoryListing.open(m_directoryPath);
|
||||
if (!m_directoryListing.isOpen())
|
||||
if (!m_directoryListing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_backupMenu->reset();
|
||||
m_backupMenu->addOption(strings::getByName(strings::names::BACKUP_MENU, 0));
|
||||
sm_backupMenu->reset();
|
||||
sm_backupMenu->addOption(strings::getByName(strings::names::BACKUP_MENU, 0));
|
||||
for (int64_t i = 0; i < m_directoryListing.getCount(); i++)
|
||||
{
|
||||
m_backupMenu->addOption(m_directoryListing[i]);
|
||||
sm_backupMenu->addOption(m_directoryListing[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void BackupMenuState::renderTitle(void)
|
||||
void BackupMenuState::saveDataWritten(void)
|
||||
{
|
||||
SDL_Texture *SlidePanelTarget = m_slidePanel->get();
|
||||
|
||||
if (m_titleScrolling && m_titleScrollTriggered && m_titleX > -(m_titleWidth + 8))
|
||||
if (!m_saveHasData)
|
||||
{
|
||||
m_titleX -= 2;
|
||||
}
|
||||
else if (m_titleScrolling && m_titleScrollTriggered && m_titleX <= -(m_titleWidth + 8))
|
||||
{
|
||||
m_titleX = 8;
|
||||
m_titleScrollTriggered = false;
|
||||
m_titleScrollTimer.restart();
|
||||
}
|
||||
else if (m_titleScrolling && m_titleScrollTimer.isTriggered())
|
||||
{
|
||||
m_titleScrollTriggered = true;
|
||||
}
|
||||
|
||||
if (m_titleScrolling && m_titleScrollTriggered)
|
||||
{
|
||||
// This is just a trick, or maybe the only way to accomplish this. Either way, it works.
|
||||
// render title first time.
|
||||
sdl::text::render(SlidePanelTarget, m_titleX, 8, 22, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_titleInfo->getTitle());
|
||||
// render it again following the first.
|
||||
sdl::text::render(SlidePanelTarget,
|
||||
m_titleX + m_titleWidth + 16,
|
||||
8,
|
||||
22,
|
||||
sdl::text::NO_TEXT_WRAP,
|
||||
colors::WHITE,
|
||||
m_titleInfo->getTitle());
|
||||
}
|
||||
else
|
||||
{
|
||||
sdl::text::render(SlidePanelTarget, m_titleX, 8, 22, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_titleInfo->getTitle());
|
||||
m_saveHasData = true;
|
||||
}
|
||||
}
|
||||
|
||||
// This is the function to create new backups.
|
||||
static void createnewBackup(sys::ProgressTask *task, fslib::Path destinationPath, BackupMenuState *spawningState)
|
||||
static void createNewBackup(sys::ProgressTask *task,
|
||||
data::User *user,
|
||||
data::TitleInfo *titleInfo,
|
||||
fslib::Path targetPath,
|
||||
BackupMenuState *spawningState)
|
||||
{
|
||||
// SaveMeta
|
||||
FsSaveDataInfo *saveInfo = user->getSaveInfoByID(titleInfo->getApplicationID());
|
||||
|
||||
// I got tired of typing out the cast.
|
||||
fs::SaveMetaData saveMeta = {.m_magic = fs::SAVE_META_MAGIC,
|
||||
.m_applicationID = titleInfo->getApplicationID(),
|
||||
.m_saveType = saveInfo->save_data_type,
|
||||
.m_saveRank = saveInfo->save_data_rank,
|
||||
.m_saveSpaceID = saveInfo->save_data_space_id,
|
||||
.m_saveDataSize = titleInfo->getSaveDataSize(saveInfo->save_data_type),
|
||||
.m_saveDataSizeMax = titleInfo->getSaveDataSizeMax(saveInfo->save_data_type),
|
||||
.m_journalSize = titleInfo->getJournalSize(saveInfo->save_data_type),
|
||||
.m_journalSizeMax = titleInfo->getSaveDataSizeMax(saveInfo->save_data_type),
|
||||
.m_totalSaveSize = fs::getDirectoryTotalSize(fs::DEFAULT_SAVE_PATH)};
|
||||
|
||||
// This extension search is lazy and needs to be revised.
|
||||
if (config::getByKey(config::keys::EXPORT_TO_ZIP) || std::strstr(destinationPath.cString(), ".zip") != NULL)
|
||||
if (config::getByKey(config::keys::EXPORT_TO_ZIP) || std::strcmp("zip", targetPath.getExtension()) == 0)
|
||||
{
|
||||
zipFile newBackup = zipOpen64(destinationPath.cString(), APPEND_STATUS_CREATE);
|
||||
zipFile newBackup = zipOpen64(targetPath.cString(), APPEND_STATUS_CREATE);
|
||||
if (!newBackup)
|
||||
{
|
||||
// To do: Pop up.
|
||||
logger::log("Error opening zip for backup.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Write meta to zip.
|
||||
int zipError = zipOpenNewFileInZip64(newBackup,
|
||||
".jksv_save_meta.bin",
|
||||
NULL,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
Z_DEFLATED,
|
||||
config::getByKey(config::keys::ZIP_COMPRESSION_LEVEL),
|
||||
0);
|
||||
if (zipError == ZIP_OK)
|
||||
{
|
||||
zipWriteInFileInZip(newBackup, &saveMeta, sizeof(fs::SaveMetaData));
|
||||
zipCloseFileInZip(newBackup);
|
||||
}
|
||||
|
||||
fs::copyDirectoryToZip(fs::DEFAULT_SAVE_PATH, newBackup, task);
|
||||
zipClose(newBackup, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
fs::copyDirectory(fs::DEFAULT_SAVE_PATH, destinationPath, 0, {}, task);
|
||||
{
|
||||
// Write save meta quick.
|
||||
fslib::Path saveMetaPath = targetPath / ".jksv_save_meta.bin";
|
||||
fslib::File saveMetaOut(targetPath, FsOpenMode_Create | FsOpenMode_Write, sizeof(fs::SaveMetaData));
|
||||
if (saveMetaOut)
|
||||
{
|
||||
saveMetaOut.write(&saveMeta, sizeof(fs::SaveMetaData));
|
||||
}
|
||||
}
|
||||
|
||||
fs::copyDirectory(fs::DEFAULT_SAVE_PATH, targetPath, 0, {}, task);
|
||||
}
|
||||
spawningState->refresh();
|
||||
task->finished();
|
||||
}
|
||||
|
||||
static void overwriteBackup(sys::ProgressTask *task, std::shared_ptr<TargetStruct> dataStruct)
|
||||
{
|
||||
// DirectoryExists can also be used to check if the target is a directory.
|
||||
if (fslib::directoryExists(dataStruct->m_targetPath) && !fslib::deleteDirectoryRecursively(dataStruct->m_targetPath))
|
||||
{
|
||||
logger::log("Error overwriting backup: %s", fslib::getErrorString());
|
||||
task->finished();
|
||||
return;
|
||||
} // This has an added check for the zip extension so it can't try to overwrite files that aren't supposed to be zip.
|
||||
else if (fslib::fileExists(dataStruct->m_targetPath) && std::strcmp("zip", dataStruct->m_targetPath.getExtension()) == 0 &&
|
||||
!fslib::deleteFile(dataStruct->m_targetPath))
|
||||
{
|
||||
logger::log("Error overwriting backup: %s", fslib::getErrorString());
|
||||
task->finished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp("zip", dataStruct->m_targetPath.getExtension()))
|
||||
{
|
||||
zipFile backupZip = zipOpen64(dataStruct->m_targetPath.cString(), APPEND_STATUS_CREATE);
|
||||
fs::copyDirectoryToZip(fs::DEFAULT_SAVE_PATH, backupZip, task);
|
||||
zipClose(backupZip, NULL);
|
||||
} // I hope this check works for making sure this is a folder
|
||||
else if (dataStruct->m_targetPath.getExtension() == nullptr && fslib::createDirectory(dataStruct->m_targetPath))
|
||||
{
|
||||
fs::copyDirectory(fs::DEFAULT_SAVE_PATH, dataStruct->m_targetPath, 0, {}, task);
|
||||
}
|
||||
task->finished();
|
||||
}
|
||||
|
||||
static void restoreBackup(sys::ProgressTask *task, std::shared_ptr<TargetStruct> dataStruct)
|
||||
{
|
||||
// Wipe the save root first.
|
||||
if (!fslib::deleteDirectoryRecursively(fs::DEFAULT_SAVE_PATH))
|
||||
{
|
||||
logger::log("Error restoring save: %s", fslib::getErrorString());
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 2));
|
||||
task->finished();
|
||||
return;
|
||||
}
|
||||
|
|
@ -277,6 +377,8 @@ static void restoreBackup(sys::ProgressTask *task, std::shared_ptr<TargetStruct>
|
|||
unzFile targetZip = unzOpen64(dataStruct->m_targetPath.cString());
|
||||
if (!targetZip)
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 3));
|
||||
logger::log("Error opening zip for reading.");
|
||||
task->finished();
|
||||
return;
|
||||
|
|
@ -288,6 +390,10 @@ static void restoreBackup(sys::ProgressTask *task, std::shared_ptr<TargetStruct>
|
|||
{
|
||||
fs::copyFile(dataStruct->m_targetPath, fs::DEFAULT_SAVE_PATH, dataStruct->m_journalSize, fs::DEFAULT_SAVE_MOUNT, task);
|
||||
}
|
||||
|
||||
// Update this just in case.
|
||||
dataStruct->m_spawningState->saveDataWritten();
|
||||
|
||||
task->finished();
|
||||
}
|
||||
|
||||
|
|
@ -301,10 +407,14 @@ static void deleteBackup(sys::Task *task, std::shared_ptr<TargetStruct> dataStru
|
|||
if (fslib::directoryExists(dataStruct->m_targetPath) && !fslib::deleteDirectoryRecursively(dataStruct->m_targetPath))
|
||||
{
|
||||
logger::log("Error deleting folder backup: %s", fslib::getErrorString());
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 4));
|
||||
}
|
||||
else if (!fslib::deleteFile(dataStruct->m_targetPath))
|
||||
{
|
||||
logger::log("Error deleting backup: %s", fslib::getErrorString());
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 4));
|
||||
}
|
||||
dataStruct->m_spawningState->refresh();
|
||||
task->finished();
|
||||
|
|
|
|||
|
|
@ -2,14 +2,20 @@
|
|||
#include "colors.hpp"
|
||||
#include "input.hpp"
|
||||
#include "sdl.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "strings.hpp"
|
||||
#include "stringutil.hpp"
|
||||
#include "ui/PopMessageManager.hpp"
|
||||
#include "ui/renderFunctions.hpp"
|
||||
#include <cmath>
|
||||
|
||||
void ProgressState::update(void)
|
||||
{
|
||||
if (!m_task.isRunning())
|
||||
if (m_task.isRunning() && input::buttonPressed(HidNpadButton_Plus))
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 0));
|
||||
}
|
||||
else if (!m_task.isRunning())
|
||||
{
|
||||
AppState::deactivate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
#include "JKSV.hpp"
|
||||
#include "appstates/TaskState.hpp"
|
||||
#include "data/data.hpp"
|
||||
#include "fs/fs.hpp"
|
||||
#include "input.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "strings.hpp"
|
||||
#include "system/Task.hpp"
|
||||
#include "ui/PopMessageManager.hpp"
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <switch.h>
|
||||
|
|
@ -45,14 +47,8 @@ static bool compareInfo(data::TitleInfo *infoA, data::TitleInfo *infoB)
|
|||
return false;
|
||||
}
|
||||
|
||||
// This attempts to create the save data for the given user. It will fail if it already exists.
|
||||
static void createSaveDataFor(sys::Task *task, data::User *targetUser, data::TitleInfo *titleInfo)
|
||||
{
|
||||
// Set task status.
|
||||
task->setStatus(strings::getByName(strings::names::CREATING_SAVE_DATA_FOR, 0), titleInfo->getTitle());
|
||||
|
||||
task->finished();
|
||||
}
|
||||
// Declarations here. Definitions under class.
|
||||
static void createSaveData(sys::Task *task, data::User *targetUser, data::TitleInfo *titleInfo);
|
||||
|
||||
SaveCreateState::SaveCreateState(data::User *targetUser, TitleSelectCommon *titleSelect)
|
||||
: m_user(targetUser), m_titleSelect(titleSelect), m_saveMenu(8, 8, 624, 22, 720)
|
||||
|
|
@ -78,13 +74,13 @@ SaveCreateState::SaveCreateState(data::User *targetUser, TitleSelectCommon *titl
|
|||
|
||||
void SaveCreateState::update(void)
|
||||
{
|
||||
sm_slidePanel->update(AppState::hasFocus());
|
||||
m_saveMenu.update(AppState::hasFocus());
|
||||
sm_slidePanel->update(AppState::hasFocus());
|
||||
|
||||
if (input::buttonPressed(HidNpadButton_A))
|
||||
{
|
||||
data::TitleInfo *targetTitle = m_titleInfoVector.at(m_saveMenu.getSelected());
|
||||
JKSV::pushState(std::make_shared<TaskState>(createSaveDataFor, m_user, targetTitle));
|
||||
JKSV::pushState(std::make_shared<TaskState>(createSaveData, m_user, targetTitle));
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_B))
|
||||
{
|
||||
|
|
@ -104,3 +100,22 @@ void SaveCreateState::render(void)
|
|||
m_saveMenu.render(sm_slidePanel->get(), AppState::hasFocus());
|
||||
sm_slidePanel->render(NULL, AppState::hasFocus());
|
||||
}
|
||||
|
||||
static void createSaveData(sys::Task *task, data::User *targetUser, data::TitleInfo *titleInfo)
|
||||
{
|
||||
// Set status. We'll just borrow the string from the other group.
|
||||
task->setStatus(strings::getByName(strings::names::USER_OPTION_STATUS, 0), titleInfo->getTitle());
|
||||
|
||||
if (fs::createSaveDataFor(targetUser, titleInfo))
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_SAVE_CREATE, 0),
|
||||
titleInfo->getTitle());
|
||||
}
|
||||
else
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_SAVE_CREATE, 1));
|
||||
}
|
||||
task->finished();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
#include "appstates/SettingsState.hpp"
|
||||
#include "colors.hpp"
|
||||
#include "config.hpp"
|
||||
#include "fslib.hpp"
|
||||
#include "input.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "keyboard.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "strings.hpp"
|
||||
#include "stringutil.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
|
@ -11,36 +14,36 @@ namespace
|
|||
constexpr std::string_view SECONDARY_TARGET = "SecondaryTarget";
|
||||
} // namespace
|
||||
|
||||
static inline const char *getvalueText(uint8_t value)
|
||||
{
|
||||
return value == 1 ? strings::getByName(strings::names::ON_OFF, 0) : strings::getByName(strings::names::ON_OFF, 1);
|
||||
}
|
||||
// Declarations. Definitions after class members.
|
||||
static const char *getValueText(uint8_t value);
|
||||
static const char *getSortTypeText(uint8_t value);
|
||||
|
||||
SettingsState::SettingsState(void)
|
||||
: m_settingsMenu(32, 8, 1000, 24, 555),
|
||||
m_renderTarget(sdl::TextureManager::createLoadTexture(SECONDARY_TARGET, 1080, 555, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET)),
|
||||
m_controlGuideX(1220 - sdl::text::getWidth(22, strings::getByName(strings::names::CONTROL_GUIDES, 3)))
|
||||
{
|
||||
// Add the first two, because they don't have values to display.
|
||||
m_settingsMenu.addOption(strings::getByName(strings::names::SETTINGS_MENU, 0));
|
||||
m_settingsMenu.addOption(strings::getByName(strings::names::SETTINGS_MENU, 1));
|
||||
|
||||
int currentString = 2;
|
||||
// Loop and allocation the strings and menu options.
|
||||
int currentString = 0;
|
||||
const char *settingsString = nullptr;
|
||||
while (currentString < 16 && (settingsString = strings::getByName(strings::names::SETTINGS_MENU, currentString++)) != nullptr)
|
||||
while ((settingsString = strings::getByName(strings::names::SETTINGS_MENU, currentString++)) != nullptr)
|
||||
{
|
||||
m_settingsMenu.addOption(stringutil::getFormattedString(settingsString, getvalueText(config::getByIndex(currentString - 1))));
|
||||
m_settingsMenu.addOption(settingsString);
|
||||
}
|
||||
// Add the scaling
|
||||
m_settingsMenu.addOption(
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::SETTINGS_MENU, 17), config::getAnimationScaling()));
|
||||
|
||||
// Run the update routine so they're right.
|
||||
SettingsState::updateMenuOptions();
|
||||
}
|
||||
|
||||
void SettingsState::update(void)
|
||||
{
|
||||
m_settingsMenu.update(AppState::hasFocus());
|
||||
|
||||
if (input::buttonPressed(HidNpadButton_B))
|
||||
if (input::buttonPressed(HidNpadButton_A))
|
||||
{
|
||||
SettingsState::toggleOptions();
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_B))
|
||||
{
|
||||
AppState::deactivate();
|
||||
}
|
||||
|
|
@ -63,3 +66,108 @@ void SettingsState::render(void)
|
|||
strings::getByName(strings::names::CONTROL_GUIDES, 3));
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsState::updateMenuOptions(void)
|
||||
{
|
||||
// These can be looped, so screw it.
|
||||
for (int i = 2; i < 13; i++)
|
||||
{
|
||||
std::string updatedOption =
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::SETTINGS_MENU, i), getValueText(config::getByIndex(i - 2)));
|
||||
m_settingsMenu.editOption(i, updatedOption);
|
||||
}
|
||||
|
||||
// This just displays the value. The config value is offset to account for the first two settings options.
|
||||
m_settingsMenu.editOption(13,
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::SETTINGS_MENU, 13), config::getByIndex(11)));
|
||||
|
||||
// This gets the type according to the value.
|
||||
m_settingsMenu.editOption(
|
||||
14,
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::SETTINGS_MENU, 14), getSortTypeText(config::getByIndex(12))));
|
||||
|
||||
// Loop again.
|
||||
for (int i = 15; i < 18; i++)
|
||||
{
|
||||
std::string updatedOption =
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::SETTINGS_MENU, i), getValueText(config::getByIndex(i - 2)));
|
||||
m_settingsMenu.editOption(i, updatedOption);
|
||||
}
|
||||
|
||||
// Animating scaling.
|
||||
m_settingsMenu.editOption(
|
||||
18,
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::SETTINGS_MENU, 18), config::getAnimationScaling()));
|
||||
}
|
||||
|
||||
void SettingsState::toggleOptions(void)
|
||||
{
|
||||
int selected = m_settingsMenu.getSelected();
|
||||
// These are just true or false more or less.
|
||||
if ((selected >= 2 && selected <= 12) || (selected >= 14 && selected <= 17))
|
||||
{
|
||||
config::toggleByIndex(selected - 2);
|
||||
}
|
||||
else if (selected == 13)
|
||||
{
|
||||
// This is the zip compression level.
|
||||
uint8_t zipLevel = config::getByKey(config::keys::ZIP_COMPRESSION_LEVEL);
|
||||
if (++zipLevel > 9)
|
||||
{
|
||||
zipLevel = 0;
|
||||
}
|
||||
config::setByKey(config::keys::ZIP_COMPRESSION_LEVEL, zipLevel);
|
||||
}
|
||||
else if (selected == 14)
|
||||
{
|
||||
// This is the title sorting type.
|
||||
uint8_t sortType = config::getByKey(config::keys::TITLE_SORT_TYPE);
|
||||
if (++sortType > 2)
|
||||
{
|
||||
sortType = 0;
|
||||
}
|
||||
config::setByKey(config::keys::TITLE_SORT_TYPE, sortType);
|
||||
}
|
||||
else if (selected == 18)
|
||||
{
|
||||
// This is the animation scaling.
|
||||
double scaling = config::getAnimationScaling();
|
||||
if ((scaling += 0.25f) > 4.0f)
|
||||
{
|
||||
scaling = 1.0f;
|
||||
}
|
||||
config::setAnimationScaling(scaling);
|
||||
}
|
||||
// Toggle the update routine.
|
||||
SettingsState::updateMenuOptions();
|
||||
}
|
||||
|
||||
static const char *getValueText(uint8_t value)
|
||||
{
|
||||
return value ? strings::getByName(strings::names::ON_OFF, 1) : strings::getByName(strings::names::ON_OFF, 0);
|
||||
}
|
||||
|
||||
static const char *getSortTypeText(uint8_t value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
return "Alphabetically";
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
{
|
||||
return "Most Played";
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
{
|
||||
return "Last Played";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
#include "appstates/TaskState.hpp"
|
||||
#include "colors.hpp"
|
||||
#include "input.hpp"
|
||||
#include "sdl.hpp"
|
||||
#include "strings.hpp"
|
||||
#include "ui/PopMessageManager.hpp"
|
||||
|
||||
void TaskState::update(void)
|
||||
{
|
||||
if (m_task.isRunning() && input::buttonPressed(HidNpadButton_Plus))
|
||||
{
|
||||
// Throw the message.
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_GENERAL, 0));
|
||||
}
|
||||
if (!m_task.isRunning())
|
||||
{
|
||||
AppState::deactivate();
|
||||
|
|
|
|||
233
source/appstates/TitleOptionState.cpp
Normal file
233
source/appstates/TitleOptionState.cpp
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
#include "appstates/TitleOptionState.hpp"
|
||||
#include "colors.hpp"
|
||||
#include "config.hpp"
|
||||
#include "fs/fs.hpp"
|
||||
#include "fslib.hpp"
|
||||
#include "input.hpp"
|
||||
#include "keyboard.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "strings.hpp"
|
||||
#include "stringutil.hpp"
|
||||
#include "system/system.hpp"
|
||||
#include "ui/PopMessageManager.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Enum for menu.
|
||||
enum
|
||||
{
|
||||
INFORMATION,
|
||||
BLACKLIST,
|
||||
CHANGE_OUTPUT,
|
||||
FILE_MODE,
|
||||
DELETE_ALL_BACKUPS,
|
||||
RESET_SAVE_DATA,
|
||||
DELETE_SAVE_FROM_SYSTEM,
|
||||
EXTEND_CONTAINER,
|
||||
EXPORT_SVI
|
||||
};
|
||||
// Error string template thingies.
|
||||
const char ERROR_RESETTING_SAVE = "Error resetting save data: %s";
|
||||
} // namespace
|
||||
|
||||
// Struct to send data to functions that require confirmation.
|
||||
typedef struct
|
||||
{
|
||||
data::User *m_targetUser;
|
||||
data::TitleInfo *m_targetTitle;
|
||||
} TargetStruct;
|
||||
|
||||
// Declarations. Definitions after class.
|
||||
// I don't like this, but it needs to be like this to be usable with confirmation.
|
||||
static void blacklistTitle(sys::Task *task, std::shared_ptr<TargetStruct> dataStruct);
|
||||
static void deleteAllBackupsForTitle(sys::Task *task, std::shared_ptr<TargetStruct> dataStruct);
|
||||
static void resetSaveData(sys::Task *task, std::shared_ptr<TargetStruct> dataStruct);
|
||||
static void deleteSaveDataFromSystem(sys::Task *task, std::shared_ptr<TargetStruct> dataStruct);
|
||||
static void changeOutputPath(data::TitleInfo *targetTitle);
|
||||
|
||||
TitleOptionState::TitleOptionState(data::User *user, data::TitleInfo *titleInfo) : m_targetUser(user), m_titleInfo(titleInfo)
|
||||
{
|
||||
// Create panel if needed.
|
||||
if (!sm_initialized)
|
||||
{
|
||||
// Allocate static members.
|
||||
sm_slidePanel = std::make_unique<ui::SlideOutPanel>(480, ui::SlideOutPanel::Side::Right);
|
||||
sm_titleOptionMenu = std::make_unique<ui::Menu>(8, 8, 460, 22, 720);
|
||||
|
||||
// Populate menu.
|
||||
int stringIndex = 0;
|
||||
const char *currentString = nullptr;
|
||||
while ((currentString = strings::getByName(strings::names::TITLE_OPTIONS, stringIndex++)) != nullptr)
|
||||
{
|
||||
sm_titleOptionMenu->addOption(currentString);
|
||||
}
|
||||
// Only do this once.
|
||||
sm_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TitleOptionState::update(void)
|
||||
{
|
||||
// Update panel and menu.
|
||||
sm_slidePanel->update(AppState::hasFocus());
|
||||
sm_titleOptionMenu->update(AppState::hasFocus());
|
||||
|
||||
if (input::buttonPressed(HidNpadButton_A))
|
||||
{
|
||||
switch (sm_titleOptionMenu->getSelected())
|
||||
{
|
||||
case BLACKLIST:
|
||||
{
|
||||
}
|
||||
break;
|
||||
|
||||
case CHANGE_OUTPUT:
|
||||
{
|
||||
changeOutputPath(m_titleInfo);
|
||||
}
|
||||
break;
|
||||
|
||||
case DELETE_ALL_BACKUPS:
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_B))
|
||||
{
|
||||
sm_slidePanel->close();
|
||||
}
|
||||
else if (sm_slidePanel->isClosed())
|
||||
{
|
||||
// Reset static members.
|
||||
sm_slidePanel->reset();
|
||||
sm_titleOptionMenu->setSelected(0);
|
||||
AppState::deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
void TitleOptionState::render(void)
|
||||
{
|
||||
sm_slidePanel->clearTarget();
|
||||
sm_titleOptionMenu->render(sm_slidePanel->get(), AppState::hasFocus());
|
||||
sm_slidePanel->render(NULL, AppState::hasFocus());
|
||||
}
|
||||
|
||||
static void blacklistTitle(sys::Task *task, std::shared_ptr<TargetStruct> dataStruct)
|
||||
{
|
||||
// We're not gonna bother with a status for this. It'll flicker, but be barely noticeable.
|
||||
config::addRemoveBlacklist(dataStruct->m_targetTitle->getApplicationID());
|
||||
task->finished();
|
||||
}
|
||||
|
||||
static void deleteAllBackupsForTitle(sys::Task *task, std::shared_ptr<TargetStruct> dataStruct)
|
||||
{
|
||||
// Get the path.
|
||||
fslib::Path titlePath = config::getWorkingDirectory() / dataStruct->m_targetTitle->getPathSafeTitle();
|
||||
|
||||
// Set the status.
|
||||
task->setStatus(strings::getByName(strings::names::TITLE_OPTION_STATUS, 0), dataStruct->m_targetTitle->getTitle());
|
||||
|
||||
// Just call this and nuke the folder.
|
||||
if (!fslib::deleteDirectoryRecursively(titlePath))
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::TITLE_OPTION_POPS, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::TITLE_OPTION_POPS, 0),
|
||||
dataStruct->m_targetTitle->getTitle());
|
||||
}
|
||||
task->finished();
|
||||
}
|
||||
|
||||
static void resetSaveData(sys::Task *task, std::shared_ptr<TargetStruct> dataStruct)
|
||||
{
|
||||
// Attempt to mount save.
|
||||
if (!fslib::openSaveFileSystemWithSaveDataInfo(fs::DEFAULT_SAVE_MOUNT,
|
||||
*dataStruct->m_targetUser->getSaveInfoByID(dataStruct->m_targetTitle->getApplicationID())))
|
||||
{
|
||||
logger::log(ERROR_RESETTING_SAVE, fslib::getErrorString());
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::TITLE_OPTION_POPS, 2));
|
||||
task->finished();
|
||||
return;
|
||||
}
|
||||
|
||||
// Wipe the root.
|
||||
if (!fslib::deleteDirectoryRecursively(fs::DEFAULT_SAVE_PATH))
|
||||
{
|
||||
fslib::closeFileSystem(fs::DEFAULT_SAVE_MOUNT);
|
||||
logger::log(ERROR_RESETTING_SAVE, fslib::getErrorString());
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::TITLE_OPTION_POPS, 2));
|
||||
task->finished();
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt commit.
|
||||
if (!fslib::commitDataToFileSystem(fs::DEFAULT_SAVE_MOUNT))
|
||||
{
|
||||
fslib::closeFileSystem(fs::DEFAULT_SAVE_MOUNT);
|
||||
logger(ERROR_RESETTING_SAVE, fslib::getErrorString());
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::TITLE_OPTION_POPS, 2));
|
||||
task->finished();
|
||||
return;
|
||||
}
|
||||
|
||||
// Should be good to go.
|
||||
fslib::closeFileSystem(fs::DEFAULT_SAVE_MOUNT);
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, strings::getByName(strings::names::TITLE_OPTION_POPS, 3));
|
||||
task->finished();
|
||||
}
|
||||
|
||||
static void deleteSaveDataFromSystem(sys::Task *task, std::shared_ptr<TargetStruct> dataStruct)
|
||||
{
|
||||
// Set status. We're going to borrow this string from the other state's strings.
|
||||
task->setStatus(strings::getByName(strings::names::USER_OPTION_STATUS, 1), dataStruct->m_targetTitle->getTitle());
|
||||
}
|
||||
|
||||
static void changeOutputPath(data::TitleInfo *targetTitle)
|
||||
{
|
||||
// This is where we're writing the path.
|
||||
char pathBuffer[0x200] = {0};
|
||||
|
||||
// Header string.
|
||||
std::string headerString = stringutil::getFormattedString(strings::getByName(strings::names::KEYBOARD_STRINGS, 7), targetTitle->getTitle());
|
||||
|
||||
// Try to get input.
|
||||
if (!keyboard::getInput(SwkbdType_QWERTY, targetTitle->getPathSafeTitle(), headerString, pathBuffer, 0x200))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to make sure it will work.
|
||||
if (!stringutil::sanitizeStringForPath(pathBuffer, pathBuffer, 0x200))
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_TITLE_OPTIONS, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Rename folder to match so there are no issues.
|
||||
fslib::Path oldPath = config::getWorkingDirectory() / targetTitle->getPathSafeTitle();
|
||||
fslib::Path newPath = config::getWorkingDirectory() / pathBuffer;
|
||||
if (fslib::directoryExists(oldPath) && !fslib::renameDirectory(oldPath, newPath))
|
||||
{
|
||||
// Bail if this fails, because something is really wrong.
|
||||
logger::log("Error setting new output path: %s", fslib::getErrorString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Add it to config and set target title to use it.
|
||||
targetTitle->setPathSafeTitle(pathBuffer, std::strlen(pathBuffer));
|
||||
config::addCustomPath(targetTitle->getApplicationID(), pathBuffer);
|
||||
|
||||
// Pop so we know stuff happened.
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_TITLE_OPTIONS, 1),
|
||||
pathBuffer);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include "JKSV.hpp"
|
||||
#include "appstates/BackupMenuState.hpp"
|
||||
#include "appstates/MainMenuState.hpp"
|
||||
#include "appstates/TitleOptionState.hpp"
|
||||
#include "colors.hpp"
|
||||
#include "config.hpp"
|
||||
#include "fs/fs.hpp"
|
||||
|
|
@ -47,6 +48,13 @@ void TitleSelectState::update(void)
|
|||
logger::log("%s", fslib::getErrorString());
|
||||
}
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_X))
|
||||
{
|
||||
uint64_t applicationID = m_user->getApplicationIDAt(m_titleView.getSelected());
|
||||
data::TitleInfo *titleInfo = data::getTitleInfoByID(applicationID);
|
||||
|
||||
JKSV::pushState(std::make_shared<TitleOptionState>(m_user, titleInfo));
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_B))
|
||||
{
|
||||
// This will reset all the tiles so they're 128x128.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "appstates/UserOptionState.hpp"
|
||||
#include "JKSV.hpp"
|
||||
#include "appstates/ConfirmState.hpp"
|
||||
#include "appstates/ProgressState.hpp"
|
||||
#include "appstates/SaveCreateState.hpp"
|
||||
#include "appstates/TaskState.hpp"
|
||||
|
|
@ -9,18 +10,36 @@
|
|||
#include "fslib.hpp"
|
||||
#include "input.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "strings.hpp"
|
||||
#include "stringutil.hpp"
|
||||
#include "system/system.hpp"
|
||||
#include "ui/PopMessageManager.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Enum for menu switch case.
|
||||
enum
|
||||
{
|
||||
BACKUP_ALL,
|
||||
CREATE_SAVE,
|
||||
CREATE_ALL_SAVE,
|
||||
DELETE_ALL_SAVE
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Struct to pass data to functions that require it.
|
||||
typedef struct
|
||||
{
|
||||
data::User *m_targetUser;
|
||||
} UserStruct;
|
||||
|
||||
// Declarations here. Defintions after class.
|
||||
// Backs up all save data for the target user.
|
||||
static void backupAllForUser(sys::ProgressTask *task, data::User *targetUser);
|
||||
static void backupAllForUser(sys::ProgressTask *task, std::shared_ptr<UserStruct> dataStruct);
|
||||
// // Creates all save data for the current user.
|
||||
// static void createAllSaveDataForUser(sys::Task *task, data::User *targetUser);
|
||||
static void createAllSaveDataForUser(sys::Task *task, std::shared_ptr<UserStruct> dataStruct);
|
||||
// // Deletes all save data from the system for the target user.
|
||||
// static void deleteAllSaveDataForUser(sys::Task *task, data::User *targetUser);
|
||||
static void deleteAllSaveDataForUser(sys::Task *task, std::shared_ptr<UserStruct> dataStruct);
|
||||
|
||||
UserOptionState::UserOptionState(data::User *user, TitleSelectCommon *titleSelect)
|
||||
: m_user(user), m_titleSelect(titleSelect), m_userOptionMenu(8, 8, 460, 22, 720)
|
||||
|
|
@ -47,17 +66,64 @@ void UserOptionState::update(void)
|
|||
{
|
||||
switch (m_userOptionMenu.getSelected())
|
||||
{
|
||||
case 0:
|
||||
case BACKUP_ALL:
|
||||
{
|
||||
JKSV::pushState(std::make_shared<ProgressState>(backupAllForUser, m_user));
|
||||
// This is broken down to make it easier to read.
|
||||
std::string queryString =
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::USER_OPTION_CONFIRMATIONS, 0), m_user->getNickname());
|
||||
|
||||
// Data to send if confirmed.
|
||||
std::shared_ptr<UserStruct> dataStruct(new UserStruct);
|
||||
dataStruct->m_targetUser = m_user;
|
||||
|
||||
// State to push
|
||||
std::shared_ptr<ConfirmState<sys::ProgressTask, ProgressState, UserStruct>> confirmBackupAll =
|
||||
std::make_shared<ConfirmState<sys::ProgressTask, ProgressState, UserStruct>>(queryString,
|
||||
false,
|
||||
backupAllForUser,
|
||||
dataStruct);
|
||||
|
||||
JKSV::pushState(confirmBackupAll);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
case CREATE_SAVE:
|
||||
{
|
||||
// This just pushes the state with the menu to select.
|
||||
JKSV::pushState(std::make_shared<SaveCreateState>(m_user, m_titleSelect));
|
||||
}
|
||||
break;
|
||||
|
||||
case CREATE_ALL_SAVE:
|
||||
{
|
||||
std::string queryString =
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::USER_OPTION_CONFIRMATIONS, 1), m_user->getNickname());
|
||||
|
||||
std::shared_ptr<UserStruct> dataStruct(new UserStruct);
|
||||
dataStruct->m_targetUser = m_user;
|
||||
|
||||
std::shared_ptr<ConfirmState<sys::Task, TaskState, UserStruct>> confirmCreateAll =
|
||||
std::make_shared<ConfirmState<sys::Task, TaskState, UserStruct>>(queryString, true, createAllSaveDataForUser, dataStruct);
|
||||
|
||||
// Done?
|
||||
JKSV::pushState(confirmCreateAll);
|
||||
}
|
||||
break;
|
||||
|
||||
case DELETE_ALL_SAVE:
|
||||
{
|
||||
std::string queryString =
|
||||
stringutil::getFormattedString(strings::getByName(strings::names::USER_OPTION_CONFIRMATIONS, 2), m_user->getNickname());
|
||||
|
||||
std::shared_ptr<UserStruct> dataStruct(new UserStruct);
|
||||
dataStruct->m_targetUser = m_user;
|
||||
|
||||
std::shared_ptr<ConfirmState<sys::Task, TaskState, UserStruct>> confirmDeleteAll =
|
||||
std::make_shared<ConfirmState<sys::Task, TaskState, UserStruct>>(queryString, true, deleteAllSaveDataForUser, dataStruct);
|
||||
|
||||
JKSV::pushState(confirmDeleteAll);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (input::buttonPressed(HidNpadButton_B))
|
||||
|
|
@ -84,8 +150,10 @@ void UserOptionState::render(void)
|
|||
m_menuPanel->render(NULL, AppState::hasFocus());
|
||||
}
|
||||
|
||||
static void backupAllForUser(sys::ProgressTask *task, data::User *targetUser)
|
||||
static void backupAllForUser(sys::ProgressTask *task, std::shared_ptr<UserStruct> dataStruct)
|
||||
{
|
||||
data::User *targetUser = dataStruct->m_targetUser;
|
||||
|
||||
for (size_t i = 0; i < targetUser->getTotalDataEntries(); i++)
|
||||
{
|
||||
// This should be safe like this....
|
||||
|
|
@ -94,7 +162,6 @@ static void backupAllForUser(sys::ProgressTask *task, data::User *targetUser)
|
|||
|
||||
if (!currentSaveInfo || !currentTitle)
|
||||
{
|
||||
logger::log("One of these is nullptr?");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +169,6 @@ static void backupAllForUser(sys::ProgressTask *task, data::User *targetUser)
|
|||
fslib::Path gameFolder = config::getWorkingDirectory() / currentTitle->getPathSafeTitle();
|
||||
if (!fslib::directoryExists(gameFolder) && !fslib::createDirectory(gameFolder))
|
||||
{
|
||||
logger::log("Error creating target game folder: %s", fslib::getErrorString());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -114,8 +180,9 @@ static void backupAllForUser(sys::ProgressTask *task, data::User *targetUser)
|
|||
fslib::Directory saveCheck(fs::DEFAULT_SAVE_PATH);
|
||||
if (saveMounted && saveCheck.getCount() <= 0)
|
||||
{
|
||||
// Gonna borrow these messages. No point in repeating them.
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES, 0));
|
||||
strings::getByName(strings::names::POP_MESSAGES_BACKUP_MENU, 0));
|
||||
fslib::closeFileSystem(fs::DEFAULT_SAVE_MOUNT);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -156,21 +223,51 @@ static void backupAllForUser(sys::ProgressTask *task, data::User *targetUser)
|
|||
task->finished();
|
||||
}
|
||||
|
||||
// static void createAllSaveDataForUser(sys::Task *task, data::User *targetUser)
|
||||
// {
|
||||
// // Get title info map.
|
||||
// auto &titleInfoMap = data::getTitleInfoMap();
|
||||
static void createAllSaveDataForUser(sys::Task *task, std::shared_ptr<UserStruct> dataStruct)
|
||||
{
|
||||
data::User *targetUser = dataStruct->m_targetUser;
|
||||
|
||||
// // Iterate through it.
|
||||
// for (auto &[applicationID, titleInfo] : titleInfoMap)
|
||||
// {
|
||||
// // Only continue if the info has save data for the type the account is.
|
||||
// if (titleInfo.hasSaveDataType(targetUser->getAccountSaveType()))
|
||||
// {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Get title info map.
|
||||
auto &titleInfoMap = data::getTitleInfoMap();
|
||||
|
||||
// static void deleteAllSaveDataForUser(sys::Task *task, data::User *targetUser)
|
||||
// {
|
||||
// }
|
||||
// Iterate through it.
|
||||
for (auto &[applicationID, titleInfo] : titleInfoMap)
|
||||
{
|
||||
if (!titleInfo.hasSaveDataType(targetUser->getAccountSaveType()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set status.
|
||||
task->setStatus(strings::getByName(strings::names::USER_OPTION_STATUS, 0), titleInfo.getTitle());
|
||||
|
||||
if (!fs::createSaveDataFor(targetUser, &titleInfo))
|
||||
{
|
||||
// Function should log error too.
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_SAVE_CREATE, 2));
|
||||
}
|
||||
}
|
||||
task->finished();
|
||||
}
|
||||
|
||||
static void deleteAllSaveDataForUser(sys::Task *task, std::shared_ptr<UserStruct> dataStruct)
|
||||
{
|
||||
data::User *targetUser = dataStruct->m_targetUser;
|
||||
|
||||
for (size_t i = 0; i < targetUser->getTotalDataEntries(); i++)
|
||||
{
|
||||
// Grab title for title.
|
||||
const char *targetTitle = data::getTitleInfoByID(targetUser->getApplicationIDAt(i))->getTitle();
|
||||
|
||||
// Update thread task.
|
||||
task->setStatus(strings::getByName(strings::names::USER_OPTION_STATUS, 1), targetTitle);
|
||||
|
||||
if (!fs::deleteSaveData(*targetUser->getSaveInfoAt(i)))
|
||||
{
|
||||
ui::PopMessageManager::pushMessage(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::getByName(strings::names::POP_MESSAGES_SAVE_CREATE, 2));
|
||||
}
|
||||
}
|
||||
task->finished();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,26 @@
|
|||
#include "config.hpp"
|
||||
#include "JSON.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "stringutil.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Config path(s)
|
||||
// Makes stuff slightly easier to read.
|
||||
using ConfigPair = std::pair<std::string, uint8_t>;
|
||||
// Folder path.
|
||||
const char *CONFIG_FOLDER = "sdmc:/config/JKSV";
|
||||
// Actual config path.
|
||||
const char *CONFIG_PATH = "sdmc:/config/JKSV/JKSV.json";
|
||||
// Paths file. Sweet name too.
|
||||
const char *PATHS_PATH = "sdmc:/config/JKSV/Paths.json";
|
||||
// Vector to preserve order now.
|
||||
std::vector<std::pair<std::string, uint8_t>> s_configVector;
|
||||
std::vector<ConfigPair> s_configVector;
|
||||
// Working directory
|
||||
fslib::Path s_workingDirectory;
|
||||
// UI animation scaling.
|
||||
|
|
@ -23,6 +29,8 @@ namespace
|
|||
std::vector<uint64_t> s_favorites;
|
||||
// Vector of titles to ignore.
|
||||
std::vector<uint64_t> s_blacklist;
|
||||
// Map of paths.
|
||||
std::unordered_map<uint64_t, std::string> s_pathMap;
|
||||
} // namespace
|
||||
|
||||
static void readArrayToVector(std::vector<uint64_t> &vector, json_object *array)
|
||||
|
|
@ -89,6 +97,33 @@ void config::initialize(void)
|
|||
}
|
||||
json_object_iter_next(&configIterator);
|
||||
}
|
||||
|
||||
// Load custom output paths.
|
||||
if (!fslib::fileExists(PATHS_PATH))
|
||||
{
|
||||
// Just bail.
|
||||
return;
|
||||
}
|
||||
|
||||
json::Object pathsJSON = json::newObject(json_object_from_file, PATHS_PATH);
|
||||
if (!pathsJSON)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
json_object_iterator pathsIterator = json_object_iter_begin(pathsJSON.get());
|
||||
json_object_iterator pathsEnd = json_object_iter_end(pathsJSON.get());
|
||||
while (!json_object_iter_equal(&pathsIterator, &pathsEnd))
|
||||
{
|
||||
// Grab these
|
||||
uint64_t applicationID = std::strtoull(json_object_iter_peek_name(&pathsIterator), NULL, 16);
|
||||
json_object *path = json_object_iter_peek_value(&pathsIterator);
|
||||
|
||||
// Map em.
|
||||
s_pathMap[applicationID] = json_object_get_string(path);
|
||||
|
||||
json_object_iter_next(&pathsIterator);
|
||||
}
|
||||
}
|
||||
|
||||
void config::resetToDefault(void)
|
||||
|
|
@ -115,45 +150,73 @@ void config::resetToDefault(void)
|
|||
|
||||
void config::save(void)
|
||||
{
|
||||
json::Object configJSON = json::newObject(json_object_new_object);
|
||||
|
||||
// Add working directory first.
|
||||
json_object *workingDirectory = json_object_new_string(s_workingDirectory.cString());
|
||||
json_object_object_add(configJSON.get(), config::keys::WORKING_DIRECTORY.data(), workingDirectory);
|
||||
|
||||
// Loop through map and add it.
|
||||
for (auto &[key, value] : s_configVector)
|
||||
{
|
||||
json_object *jsonValue = json_object_new_uint64(value);
|
||||
json_object_object_add(configJSON.get(), key.c_str(), jsonValue);
|
||||
json::Object configJSON = json::newObject(json_object_new_object);
|
||||
|
||||
// Add working directory first.
|
||||
json_object *workingDirectory = json_object_new_string(s_workingDirectory.cString());
|
||||
json_object_object_add(configJSON.get(), config::keys::WORKING_DIRECTORY.data(), workingDirectory);
|
||||
|
||||
// Loop through map and add it.
|
||||
for (auto &[key, value] : s_configVector)
|
||||
{
|
||||
json_object *jsonValue = json_object_new_uint64(value);
|
||||
json_object_object_add(configJSON.get(), key.c_str(), jsonValue);
|
||||
}
|
||||
|
||||
// Add UI scaling.
|
||||
json_object *scaling = json_object_new_double(s_uiAnimationScaling);
|
||||
json_object_object_add(configJSON.get(), config::keys::UI_ANIMATION_SCALE.data(), scaling);
|
||||
|
||||
// Favorites
|
||||
json_object *favoritesArray = json_object_new_array();
|
||||
for (uint64_t &titleID : s_favorites)
|
||||
{
|
||||
// Need to do it like this or json-c does decimal instead of hex.
|
||||
json_object *newFavorite = json_object_new_string(stringutil::getFormattedString("%016lX", titleID).c_str());
|
||||
json_object_array_add(favoritesArray, newFavorite);
|
||||
}
|
||||
json_object_object_add(configJSON.get(), config::keys::FAVORITES.data(), favoritesArray);
|
||||
|
||||
// Same but blacklist
|
||||
json_object *blacklistArray = json_object_new_array();
|
||||
for (uint64_t &titleID : s_blacklist)
|
||||
{
|
||||
json_object *newBlacklist = json_object_new_string(stringutil::getFormattedString("%016lX", titleID).c_str());
|
||||
json_object_array_add(blacklistArray, newBlacklist);
|
||||
}
|
||||
json_object_object_add(configJSON.get(), config::keys::BLACKLIST.data(), blacklistArray);
|
||||
|
||||
// Write config file
|
||||
fslib::File configFile(CONFIG_PATH, FsOpenMode_Create | FsOpenMode_Write, std::strlen(json_object_get_string(configJSON.get())));
|
||||
if (configFile)
|
||||
{
|
||||
configFile << json_object_get_string(configJSON.get());
|
||||
}
|
||||
}
|
||||
|
||||
// Add UI scaling.
|
||||
json_object *scaling = json_object_new_double(s_uiAnimationScaling);
|
||||
json_object_object_add(configJSON.get(), config::keys::UI_ANIMATION_SCALE.data(), scaling);
|
||||
|
||||
// Favorites
|
||||
json_object *favoritesArray = json_object_new_array();
|
||||
for (uint64_t &titleID : s_favorites)
|
||||
if (!s_pathMap.empty())
|
||||
{
|
||||
// Need to do it like this or json-c does decimal instead of hex.
|
||||
json_object *newFavorite = json_object_new_string(stringutil::getFormattedString("%016lX", titleID).c_str());
|
||||
json_object_array_add(favoritesArray, newFavorite);
|
||||
}
|
||||
json_object_object_add(configJSON.get(), config::keys::FAVORITES.data(), favoritesArray);
|
||||
// Paths file.
|
||||
json::Object pathsJSON = json::newObject(json_object_new_object);
|
||||
// Loop through map and write stuff.
|
||||
for (auto &[applicationID, path] : s_pathMap)
|
||||
{
|
||||
// Get ID as hex string.
|
||||
std::string idHex = stringutil::getFormattedString("%016llX", applicationID);
|
||||
// path
|
||||
json_object *pathObject = json_object_new_string(path.c_str());
|
||||
|
||||
// Same but blacklist
|
||||
json_object *blacklistArray = json_object_new_array();
|
||||
for (uint64_t &titleID : s_blacklist)
|
||||
{
|
||||
json_object *newBlacklist = json_object_new_string(stringutil::getFormattedString("%016lX", titleID).c_str());
|
||||
json_object_array_add(blacklistArray, newBlacklist);
|
||||
// Add to pathsJSON object.
|
||||
json_object_object_add(pathsJSON.get(), idHex.c_str(), pathObject);
|
||||
}
|
||||
// Write it.
|
||||
fslib::File pathsFile(PATHS_PATH, FsOpenMode_Create | FsOpenMode_Write, std::strlen(json_object_get_string(pathsJSON.get())));
|
||||
if (pathsFile)
|
||||
{
|
||||
pathsFile << json_object_get_string(pathsJSON.get());
|
||||
}
|
||||
}
|
||||
json_object_object_add(configJSON.get(), config::keys::BLACKLIST.data(), blacklistArray);
|
||||
|
||||
// Write config file
|
||||
fslib::File configFile(CONFIG_PATH, FsOpenMode_Create | FsOpenMode_Write, std::strlen(json_object_get_string(configJSON.get())));
|
||||
configFile << json_object_get_string(configJSON.get());
|
||||
}
|
||||
|
||||
uint8_t config::getByKey(std::string_view key)
|
||||
|
|
@ -167,6 +230,30 @@ uint8_t config::getByKey(std::string_view key)
|
|||
return findKey->second;
|
||||
}
|
||||
|
||||
void config::toggleByKey(std::string_view key)
|
||||
{
|
||||
// Make sure the key exists first.
|
||||
auto findKey =
|
||||
std::find_if(s_configVector.begin(), s_configVector.end(), [key](const auto &configPair) { return key == configPair.first; });
|
||||
if (findKey == s_configVector.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
findKey->second = findKey->second ? 0 : 1;
|
||||
}
|
||||
|
||||
void config::setByKey(std::string_view key, uint8_t value)
|
||||
{
|
||||
auto findKey =
|
||||
std::find_if(s_configVector.begin(), s_configVector.end(), [key](const auto &configPair) { return key == configPair.first; });
|
||||
if (findKey == s_configVector.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
findKey->second = value;
|
||||
}
|
||||
|
||||
|
||||
uint8_t config::getByIndex(int index)
|
||||
{
|
||||
if (index < 0 || index >= static_cast<int>(s_configVector.size()))
|
||||
|
|
@ -176,6 +263,24 @@ uint8_t config::getByIndex(int index)
|
|||
return s_configVector.at(index).second;
|
||||
}
|
||||
|
||||
void config::toggleByIndex(int index)
|
||||
{
|
||||
if (index < 0 || index >= static_cast<int>(s_configVector.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
s_configVector[index].second = s_configVector[index].second ? 0 : 1;
|
||||
}
|
||||
|
||||
void config::setByIndex(int index, uint8_t value)
|
||||
{
|
||||
if (index < 0 || index >= static_cast<int>(s_configVector.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
s_configVector[index].second = value;
|
||||
}
|
||||
|
||||
fslib::Path config::getWorkingDirectory(void)
|
||||
{
|
||||
return s_workingDirectory;
|
||||
|
|
@ -186,6 +291,11 @@ double config::getAnimationScaling(void)
|
|||
return s_uiAnimationScaling;
|
||||
}
|
||||
|
||||
void config::setAnimationScaling(double newScale)
|
||||
{
|
||||
s_uiAnimationScaling = newScale;
|
||||
}
|
||||
|
||||
void config::addRemoveFavorite(uint64_t applicationID)
|
||||
{
|
||||
auto findTitle = std::find(s_favorites.begin(), s_favorites.end(), applicationID);
|
||||
|
|
@ -229,3 +339,26 @@ bool config::isBlacklisted(uint64_t applicationID)
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void config::addCustomPath(uint64_t applicationID, std::string_view customPath)
|
||||
{
|
||||
s_pathMap[applicationID] = customPath.data();
|
||||
}
|
||||
|
||||
bool config::hasCustomPath(uint64_t applicationID)
|
||||
{
|
||||
if (s_pathMap.find(applicationID) == s_pathMap.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void config::getCustomPath(uint64_t applicationID, char *pathOut, size_t pathOutSize)
|
||||
{
|
||||
if (s_pathMap.find(applicationID) == s_pathMap.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::memcpy(pathOut, s_pathMap[applicationID].c_str(), s_pathMap[applicationID].length());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
#include "data/TitleInfo.hpp"
|
||||
#include "colors.hpp"
|
||||
#include "config.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "stringutil.hpp"
|
||||
#include <cstring>
|
||||
|
||||
data::TitleInfo::TitleInfo(uint64_t applicationID) : m_applicationID(applicationID)
|
||||
|
|
@ -21,14 +22,23 @@ data::TitleInfo::TitleInfo(uint64_t applicationID) : m_applicationID(application
|
|||
|
||||
if (R_FAILED(nsError) || nsAppControlSize < sizeof(nsControlData.nacp))
|
||||
{
|
||||
std::string applicationIDHex = stringutil::getFormattedString("%04X", applicationID & 0xFFFF);
|
||||
std::string applicationIDHex = stringutil::getFormattedString("%04X", m_applicationID & 0xFFFF);
|
||||
|
||||
// Blank the nacp just to be sure.
|
||||
std::memset(&m_nacp, 0x00, sizeof(NacpStruct));
|
||||
|
||||
// Sprintf title ids to language entries for safety.
|
||||
snprintf(m_nacp.lang[SetLanguage_ENUS].name, 0x200, "%016lX", applicationID);
|
||||
snprintf(m_pathSafeTitle, 0x200, "%016lX", applicationID);
|
||||
snprintf(m_nacp.lang[SetLanguage_ENUS].name, 0x200, "%016lX", m_applicationID);
|
||||
|
||||
// Path safe version of title.
|
||||
if (config::hasCustomPath(m_applicationID))
|
||||
{
|
||||
config::getCustomPath(m_applicationID, m_pathSafeTitle, 0x200);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::snprintf(m_pathSafeTitle, 0x200, "%016lX", m_applicationID);
|
||||
}
|
||||
|
||||
// Create a place holder icon.
|
||||
int textX = 128 - (sdl::text::getWidth(48, applicationIDHex.c_str()) / 2);
|
||||
|
|
@ -40,11 +50,18 @@ data::TitleInfo::TitleInfo(uint64_t applicationID) : m_applicationID(application
|
|||
{
|
||||
// Memcpy the NACP since it has all the good stuff.
|
||||
std::memcpy(&m_nacp, &nsControlData.nacp, sizeof(NacpStruct));
|
||||
|
||||
|
||||
// Get a path safe version of the title.
|
||||
if (!stringutil::sanitizeStringForPath(languageEntry->name, m_pathSafeTitle, 0x200))
|
||||
if (config::hasCustomPath(m_applicationID))
|
||||
{
|
||||
std::sprintf(m_pathSafeTitle, "%016lX", applicationID);
|
||||
config::getCustomPath(m_applicationID, m_pathSafeTitle, 0x200);
|
||||
}
|
||||
else if (!stringutil::sanitizeStringForPath(languageEntry->name, m_pathSafeTitle, 0x200))
|
||||
{
|
||||
std::snprintf(m_pathSafeTitle, 0x200, "%016lX", applicationID);
|
||||
}
|
||||
|
||||
// Load the icon.
|
||||
m_icon = sdl::TextureManager::createLoadTexture(languageEntry->name, nsControlData.icon, nsAppControlSize - sizeof(NacpStruct));
|
||||
}
|
||||
|
|
@ -70,6 +87,17 @@ const char *data::TitleInfo::getPathSafeTitle(void)
|
|||
return m_pathSafeTitle;
|
||||
}
|
||||
|
||||
void data::TitleInfo::setPathSafeTitle(const char *newPathSafe, size_t newPathLength)
|
||||
{
|
||||
if (newPathLength >= 0x200)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Need to memset just in case the new title is shorter than the old one.
|
||||
std::memset(m_pathSafeTitle, 0x00, 0x200);
|
||||
std::memcpy(m_pathSafeTitle, newPathSafe, newPathLength);
|
||||
}
|
||||
|
||||
const char *data::TitleInfo::getPublisher(void)
|
||||
{
|
||||
NacpLanguageEntry *Entry = nullptr;
|
||||
|
|
@ -85,7 +113,7 @@ uint64_t data::TitleInfo::getSaveDataOwnerID(void) const
|
|||
return m_nacp.save_data_owner_id;
|
||||
}
|
||||
|
||||
int64_t data::TitleInfo::getSaveDataSize(FsSaveDataType saveType) const
|
||||
int64_t data::TitleInfo::getSaveDataSize(uint8_t saveType) const
|
||||
{
|
||||
switch (saveType)
|
||||
{
|
||||
|
|
@ -128,7 +156,7 @@ int64_t data::TitleInfo::getSaveDataSize(FsSaveDataType saveType) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
int64_t data::TitleInfo::getSaveDataSizeMax(FsSaveDataType saveType) const
|
||||
int64_t data::TitleInfo::getSaveDataSizeMax(uint8_t saveType) const
|
||||
{
|
||||
switch (saveType)
|
||||
{
|
||||
|
|
@ -174,7 +202,7 @@ int64_t data::TitleInfo::getSaveDataSizeMax(FsSaveDataType saveType) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
int64_t data::TitleInfo::getJournalSize(FsSaveDataType saveType) const
|
||||
int64_t data::TitleInfo::getJournalSize(uint8_t saveType) const
|
||||
{
|
||||
switch (saveType)
|
||||
{
|
||||
|
|
@ -219,7 +247,7 @@ int64_t data::TitleInfo::getJournalSize(FsSaveDataType saveType) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
int64_t data::TitleInfo::getJournalSizeMax(FsSaveDataType saveType) const
|
||||
int64_t data::TitleInfo::getJournalSizeMax(uint8_t saveType) const
|
||||
{
|
||||
switch (saveType)
|
||||
{
|
||||
|
|
@ -267,7 +295,7 @@ int64_t data::TitleInfo::getJournalSizeMax(FsSaveDataType saveType) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool data::TitleInfo::hasSaveDataType(FsSaveDataType saveType)
|
||||
bool data::TitleInfo::hasSaveDataType(uint8_t saveType)
|
||||
{
|
||||
switch (saveType)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#include "data/data.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "sdl.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "stringutil.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
|
|
@ -102,9 +102,15 @@ data::User::User(AccountUid accountID, FsSaveDataType saveType) : m_accountID(ac
|
|||
accountProfileClose(&profile);
|
||||
}
|
||||
|
||||
data::User::User(AccountUid accountID, std::string_view pathSafeNickname, std::string_view iconPath, FsSaveDataType saveType)
|
||||
data::User::User(AccountUid accountID,
|
||||
std::string_view nickname,
|
||||
std::string_view pathSafeNickname,
|
||||
std::string_view iconPath,
|
||||
FsSaveDataType saveType)
|
||||
: m_accountID(accountID), m_saveType(saveType), m_icon(sdl::TextureManager::createLoadTexture(pathSafeNickname, iconPath.data()))
|
||||
{
|
||||
// We're just gonna use this for both.
|
||||
std::memcpy(m_nickname, nickname.data(), nickname.length());
|
||||
std::memcpy(m_pathSafeNickname, pathSafeNickname.data(), pathSafeNickname.length());
|
||||
}
|
||||
|
||||
|
|
@ -115,6 +121,11 @@ void data::User::addData(const FsSaveDataInfo &saveInfo, const PdmPlayStatistics
|
|||
m_userData.push_back(std::make_pair(applicationID, std::make_pair(saveInfo, playStats)));
|
||||
}
|
||||
|
||||
void data::User::eraseData(int index)
|
||||
{
|
||||
m_userData.erase(m_userData.begin() + index);
|
||||
}
|
||||
|
||||
void data::User::sortData(void)
|
||||
{
|
||||
std::sort(m_userData.begin(), m_userData.end(), sortUserData);
|
||||
|
|
|
|||
|
|
@ -51,16 +51,34 @@ bool data::initialize(void)
|
|||
// Need this for save data of deleted users. It does happen. This is where they are inserted.
|
||||
// size_t userInsertPosition = s_userVector.size() - 1;
|
||||
|
||||
// "System" users.
|
||||
// "System" user IDs.
|
||||
constexpr AccountUid deviceID = {FsSaveDataType_Device};
|
||||
constexpr AccountUid bcatID = {FsSaveDataType_Bcat};
|
||||
constexpr AccountUid cacheID = {FsSaveDataType_Cache};
|
||||
constexpr AccountUid systemID = {FsSaveDataType_System};
|
||||
|
||||
s_userVector.push_back(std::make_pair(deviceID, data::User(deviceID, "Device", "romfs:/Textures/SystemSaves.png", FsSaveDataType_Device)));
|
||||
s_userVector.push_back(std::make_pair(bcatID, data::User(bcatID, "BCAT", "romfs:/Textures/BCAT.png", FsSaveDataType_Bcat)));
|
||||
s_userVector.push_back(std::make_pair(cacheID, data::User(cacheID, "Cache", "romfs:/Textures/Cache.png", FsSaveDataType_Cache)));
|
||||
s_userVector.push_back(std::make_pair(systemID, data::User(systemID, "System", "romfs:/Textures/SystemSaves.png", FsSaveDataType_System)));
|
||||
// Push them to the user vector. Clang-format makes this look weird...
|
||||
s_userVector.push_back(std::make_pair(deviceID,
|
||||
data::User(deviceID,
|
||||
strings::getByName(strings::names::SAVE_DATA_TYPES, 3),
|
||||
"Device",
|
||||
"romfs:/Textures/SystemSaves.png",
|
||||
FsSaveDataType_Device)));
|
||||
s_userVector.push_back(std::make_pair(
|
||||
bcatID,
|
||||
data::User(bcatID, strings::getByName(strings::names::SAVE_DATA_TYPES, 2), "BCAT", "romfs:/Textures/BCAT.png", FsSaveDataType_Bcat)));
|
||||
s_userVector.push_back(std::make_pair(cacheID,
|
||||
data::User(cacheID,
|
||||
strings::getByName(strings::names::SAVE_DATA_TYPES, 5),
|
||||
"Cache",
|
||||
"romfs:/Textures/Cache.png",
|
||||
FsSaveDataType_Cache)));
|
||||
s_userVector.push_back(std::make_pair(systemID,
|
||||
data::User(systemID,
|
||||
strings::getByName(strings::names::SAVE_DATA_TYPES, 0),
|
||||
"System",
|
||||
"romfs:/Textures/SystemSaves.png",
|
||||
FsSaveDataType_System)));
|
||||
|
||||
NsApplicationRecord currentRecord = {0};
|
||||
int entryCount = 0, entryOffset = 0;
|
||||
|
|
@ -72,7 +90,7 @@ bool data::initialize(void)
|
|||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
fslib::SaveInfoReader saveInfoReader(SAVE_DATA_SPACE_ORDER[i]);
|
||||
if (!saveInfoReader.isOpen())
|
||||
if (!saveInfoReader)
|
||||
{
|
||||
logger::log(fslib::getErrorString());
|
||||
continue;
|
||||
|
|
|
|||
35
source/fs/directoryFunctions.cpp
Normal file
35
source/fs/directoryFunctions.cpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#include "fs/directoryFunctions.hpp"
|
||||
|
||||
uint64_t fs::getDirectoryTotalSize(const fslib::Path &targetPath)
|
||||
{
|
||||
fslib::Directory targetDir(targetPath);
|
||||
if (!targetDir)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t directorySize = 0;
|
||||
for (int64_t i = 0; i < targetDir.getCount(); i++)
|
||||
{
|
||||
if (targetDir.isDirectory(i))
|
||||
{
|
||||
fslib::Path newTarget = targetPath / targetDir[i];
|
||||
directorySize += getDirectoryTotalSize(newTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
directorySize += targetDir.getEntrySize(i);
|
||||
}
|
||||
}
|
||||
return directorySize;
|
||||
}
|
||||
|
||||
bool fs::directoryHasContents(const fslib::Path &directoryPath)
|
||||
{
|
||||
fslib::Directory testDir(directoryPath);
|
||||
if (!testDir)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return testDir.getCount() != 0;
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ void fs::copyFile(const fslib::Path &source,
|
|||
{
|
||||
fslib::File sourceFile(source, FsOpenMode_Read);
|
||||
fslib::File destinationFile(destination, FsOpenMode_Create | FsOpenMode_Write, sourceFile.getSize());
|
||||
if (!sourceFile.isOpen() || !destinationFile.isOpen())
|
||||
if (!sourceFile || !destinationFile)
|
||||
{
|
||||
logger::log("Error opening one of the files: %s", fslib::getErrorString());
|
||||
return;
|
||||
|
|
@ -137,7 +137,7 @@ void fs::copyDirectory(const fslib::Path &source,
|
|||
sys::ProgressTask *task)
|
||||
{
|
||||
fslib::Directory sourceDir(source);
|
||||
if (!sourceDir.isOpen())
|
||||
if (!sourceDir)
|
||||
{
|
||||
logger::log("Error opening directory for reading: %s", fslib::getErrorString());
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include "fs/createSaveData.hpp"
|
||||
#include "fs/saveDataFunctions.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
bool fs::createSaveDataFor(data::User *targetUser, data::TitleInfo *titleInfo)
|
||||
|
|
@ -29,6 +29,32 @@ bool fs::createSaveDataFor(data::User *targetUser, data::TitleInfo *titleInfo)
|
|||
logger::log("Error creating save data for %016llX: 0x%X.", titleInfo->getApplicationID(), fsError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fs::deleteSaveData(const FsSaveDataInfo &saveInfo)
|
||||
{
|
||||
// I'm not allowing this at all.
|
||||
if (saveInfo.save_data_type == FsSaveDataType_System || saveInfo.save_data_type == FsSaveDataType_SystemBcat)
|
||||
{
|
||||
logger::log("Error deleting save data: Deleting system save data is not allowed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save attributes.
|
||||
FsSaveDataAttribute saveAttributes = {.application_id = saveInfo.application_id,
|
||||
.uid = saveInfo.uid,
|
||||
.system_save_data_id = saveInfo.system_save_data_id,
|
||||
.save_data_type = saveInfo.save_data_type,
|
||||
.save_data_rank = saveInfo.save_data_rank,
|
||||
.save_data_index = saveInfo.save_data_index};
|
||||
|
||||
Result fsError =
|
||||
fsDeleteSaveDataFileSystemBySaveDataAttribute(static_cast<FsSaveDataSpaceId>(saveInfo.save_data_space_id), &saveAttributes);
|
||||
if (R_FAILED(fsError))
|
||||
{
|
||||
logger::log("Error deleting save data: 0x%X.", fsError);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ static void unzipReadThreadFunction(unzFile source, int64_t fileSize, std::share
|
|||
void fs::copyDirectoryToZip(const fslib::Path &source, zipFile destination, sys::ProgressTask *task)
|
||||
{
|
||||
fslib::Directory sourceDir(source);
|
||||
if (!sourceDir.isOpen())
|
||||
if (!sourceDir)
|
||||
{
|
||||
logger::log("Error opening source directory: %s", fslib::getErrorString());
|
||||
return;
|
||||
|
|
@ -88,7 +88,7 @@ void fs::copyDirectoryToZip(const fslib::Path &source, zipFile destination, sys:
|
|||
// Open source file.
|
||||
fslib::Path fullSource = source / sourceDir[i];
|
||||
fslib::File sourceFile(fullSource, FsOpenMode_Read);
|
||||
if (!sourceFile.isOpen())
|
||||
if (!sourceFile)
|
||||
{
|
||||
logger::log("Error zipping file: %s", fslib::getErrorString());
|
||||
continue;
|
||||
|
|
@ -198,8 +198,8 @@ void fs::copyZipToDirectory(unzFile source,
|
|||
// Get file information.
|
||||
unz_file_info64 currentFileInfo;
|
||||
char filename[FS_MAX_PATH] = {0};
|
||||
if (unzGetCurrentFileInfo64(source, ¤tFileInfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0) != UNZ_OK ||
|
||||
unzOpenCurrentFile(source) != UNZ_OK)
|
||||
if (unzOpenCurrentFile(source) != UNZ_OK ||
|
||||
unzGetCurrentFileInfo64(source, ¤tFileInfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0) != UNZ_OK)
|
||||
{
|
||||
logger::log("Error opening and getting information for file in zip.");
|
||||
continue;
|
||||
|
|
@ -217,7 +217,7 @@ void fs::copyZipToDirectory(unzFile source,
|
|||
}
|
||||
|
||||
fslib::File destinationFile(fullDestination, FsOpenMode_Create | FsOpenMode_Write, currentFileInfo.uncompressed_size);
|
||||
if (!destinationFile.isOpen())
|
||||
if (!destinationFile)
|
||||
{
|
||||
logger::log("Error creating file from zip: %s", fslib::getErrorString());
|
||||
continue;
|
||||
|
|
@ -293,3 +293,21 @@ void fs::copyZipToDirectory(unzFile source,
|
|||
}
|
||||
} while (unzGoToNextFile(source) != UNZ_END_OF_LIST_OF_FILE);
|
||||
}
|
||||
|
||||
bool fs::zipHasContents(const fslib::Path &zipPath)
|
||||
{
|
||||
unzFile testZip = unzOpen(zipPath.cString());
|
||||
if (!testZip)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int zipError = unzGoToFirstFile(testZip);
|
||||
if (zipError != UNZ_OK)
|
||||
{
|
||||
unzClose(testZip);
|
||||
return false;
|
||||
}
|
||||
unzClose(testZip);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,5 @@ int main(void)
|
|||
jksv.update();
|
||||
jksv.render();
|
||||
}
|
||||
config::save();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include "strings.hpp"
|
||||
#include "JSON.hpp"
|
||||
#include "fslib.hpp"
|
||||
#include "stringUtil.hpp"
|
||||
#include "stringutil.hpp"
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include "stringUtil.hpp"
|
||||
#include "stringutil.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdarg>
|
||||
|
|
@ -134,6 +134,15 @@ void ui::Menu::addOption(std::string_view newOption)
|
|||
m_options.push_back(newOption.data());
|
||||
}
|
||||
|
||||
void ui::Menu::editOption(int index, std::string_view newOption)
|
||||
{
|
||||
if (index < 0 || index >= static_cast<int>(m_options.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_options[index] = newOption.data();
|
||||
}
|
||||
|
||||
int ui::Menu::getSelected(void) const
|
||||
{
|
||||
return m_selected;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ void ui::PopMessageManager::update(void)
|
|||
{
|
||||
currentMessage.m_y += (currentMessage.m_targetY - currentMessage.m_y) / animationScaling;
|
||||
}
|
||||
currentY -= 52;
|
||||
currentY -= 56;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ void ui::PopMessageManager::render(void)
|
|||
for (auto &popMessage : manager.m_messages)
|
||||
{
|
||||
// Render a dialog box around it.
|
||||
ui::renderDialogBox(NULL, 20, popMessage.m_y - 4, popMessage.m_width, 48);
|
||||
ui::renderDialogBox(NULL, 20, popMessage.m_y - 6, popMessage.m_width, 52);
|
||||
// Render the actual text.
|
||||
sdl::text::render(NULL, 36, popMessage.m_y, 32, sdl::text::NO_TEXT_WRAP, colors::WHITE, popMessage.m_message.c_str());
|
||||
}
|
||||
|
|
@ -100,5 +100,6 @@ void ui::PopMessageManager::pushMessage(int displayTicks, const char *format, ..
|
|||
return;
|
||||
}
|
||||
// Push it to the queue.
|
||||
std::scoped_lock<std::mutex> messageLock(manager.m_messageMutex);
|
||||
manager.m_messageQueue.push_back(std::make_pair(displayTicks, vaBuffer));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
#include "config.hpp"
|
||||
#include <cmath>
|
||||
|
||||
ui::SlideOutPanel::SlideOutPanel(int width, Side side) : m_x(side == Side::Left ? -width : 1280), m_width(width), m_side(side)
|
||||
ui::SlideOutPanel::SlideOutPanel(int width, Side side)
|
||||
: m_x(side == Side::Left ? -width : 1280), m_width(width), m_targetX(side == Side::Left ? 0 : 1280 - m_width), m_side(side)
|
||||
{
|
||||
static int slidePanelTargetID = 0;
|
||||
std::string panelTargetName = "PanelTarget_" + std::to_string(slidePanelTargetID++);
|
||||
|
|
@ -14,28 +15,28 @@ void ui::SlideOutPanel::update(bool hasFocus)
|
|||
{
|
||||
double scaling = config::getAnimationScaling();
|
||||
|
||||
if (!m_isOpen && m_side == Side::Left && m_x < 0)
|
||||
// The first two conditions are just a workaround because my math keeps leaving two pixels.
|
||||
if (!m_isOpen && m_side == Side::Left && m_x >= -4)
|
||||
{
|
||||
m_x = 0;
|
||||
m_isOpen = true;
|
||||
}
|
||||
else if (!m_isOpen && m_side == Side::Right && m_x - m_targetX <= 4)
|
||||
{
|
||||
m_x = 1280 - m_width;
|
||||
m_isOpen = true;
|
||||
}
|
||||
else if (!m_isOpen && m_side == Side::Left && m_x != m_targetX)
|
||||
{
|
||||
m_x -= std::ceil(m_x / scaling);
|
||||
}
|
||||
else if (!m_isOpen && m_side == Side::Right && m_x > 1280 - m_width)
|
||||
else if (!m_isOpen && m_side == Side::Right && m_x != m_targetX)
|
||||
{
|
||||
m_x += std::ceil((1280.0f - (static_cast<double>(m_width)) - m_x) / scaling);
|
||||
}
|
||||
else if (m_closePanel && m_side == Side::Left && m_x > -(m_width))
|
||||
{
|
||||
m_x -= std::ceil((m_width - m_x) / scaling);
|
||||
}
|
||||
else if (m_closePanel && m_side == Side::Right && m_x < 1280)
|
||||
{
|
||||
m_x += std::ceil((1280.0f - m_x) / scaling);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_isOpen = true;
|
||||
}
|
||||
|
||||
if (hasFocus && m_isOpen)
|
||||
// I'm going to leave it to the individual elements whether they update if the state is active.
|
||||
if (m_isOpen)
|
||||
{
|
||||
for (auto ¤tElement : m_elements)
|
||||
{
|
||||
|
|
@ -50,6 +51,7 @@ void ui::SlideOutPanel::render(SDL_Texture *Target, bool hasFocus)
|
|||
{
|
||||
currentElement->render(m_renderTarget->get(), hasFocus);
|
||||
}
|
||||
|
||||
m_renderTarget->render(NULL, m_x, 0);
|
||||
}
|
||||
|
||||
|
|
@ -85,6 +87,11 @@ void ui::SlideOutPanel::pushNewElement(std::shared_ptr<ui::Element> newElement)
|
|||
m_elements.push_back(newElement);
|
||||
}
|
||||
|
||||
void ui::SlideOutPanel::clearElements(void)
|
||||
{
|
||||
m_elements.clear();
|
||||
}
|
||||
|
||||
SDL_Texture *ui::SlideOutPanel::get(void)
|
||||
{
|
||||
return m_renderTarget->get();
|
||||
|
|
|
|||
71
source/ui/TextScroll.cpp
Normal file
71
source/ui/TextScroll.cpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#include "ui/TextScroll.hpp"
|
||||
#include "sdl.hpp"
|
||||
|
||||
ui::TextScroll::TextScroll(std::string_view text, int fontSize, int availableWidth, int y, sdl::Color color)
|
||||
: m_text(text.data()), m_y(y), m_fontSize(fontSize), m_textColor(color), m_scrollTimer(3000)
|
||||
{
|
||||
// Grab text width first.
|
||||
m_textWidth = sdl::text::getWidth(m_fontSize, m_text.c_str());
|
||||
|
||||
// Check if text needs scrolling for width provided.
|
||||
if (m_textWidth > availableWidth)
|
||||
{
|
||||
m_x = 8;
|
||||
m_textScrolling = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just center it.
|
||||
m_x = (availableWidth / 2) - (m_textWidth / 2);
|
||||
}
|
||||
}
|
||||
|
||||
ui::TextScroll &ui::TextScroll::operator=(const ui::TextScroll &textScroll)
|
||||
{
|
||||
m_text = textScroll.m_text;
|
||||
m_x = textScroll.m_x;
|
||||
m_y = textScroll.m_y;
|
||||
m_fontSize = textScroll.m_fontSize;
|
||||
m_textColor = textScroll.m_textColor;
|
||||
m_textWidth = textScroll.m_textWidth;
|
||||
m_textScrolling = textScroll.m_textScrolling;
|
||||
m_textScrollTriggered = textScroll.m_textScrollTriggered;
|
||||
m_scrollTimer = textScroll.m_scrollTimer;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ui::TextScroll::update(bool hasFocus)
|
||||
{
|
||||
// I don't think needs to care about having focus.
|
||||
if (m_textScrolling && m_scrollTimer.isTriggered())
|
||||
{
|
||||
m_x -= 2;
|
||||
m_textScrollTriggered = true;
|
||||
}
|
||||
else if (m_textScrollTriggered && m_x > -(m_textWidth + 16))
|
||||
{
|
||||
m_x -= 2;
|
||||
}
|
||||
else if (m_textScrollTriggered && m_x <= -(m_textWidth + 16))
|
||||
{
|
||||
// This will snap the text back to where it was, but the user won't even notice it. It just looks like it's scrolling.
|
||||
m_x = 8;
|
||||
m_textScrollTriggered = false;
|
||||
m_scrollTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
void ui::TextScroll::render(SDL_Texture *target, bool hasFocus)
|
||||
{
|
||||
// If we don't need to scroll the text, just render it as-is centered according to the width passed earlier.
|
||||
if (!m_textScrolling)
|
||||
{
|
||||
sdl::text::render(target, m_x, m_y, m_fontSize, sdl::text::NO_TEXT_WRAP, m_textColor, m_text.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're going to render text twice so it looks like it's scrolling and doesn't end. Ever.
|
||||
sdl::text::render(target, m_x, m_y, m_fontSize, sdl::text::NO_TEXT_WRAP, m_textColor, m_text.c_str());
|
||||
sdl::text::render(target, m_x + m_textWidth + 24, m_y, m_fontSize, sdl::text::NO_TEXT_WRAP, m_textColor, m_text.c_str());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user