mirror of
https://github.com/J-D-K/JKSV.git
synced 2026-04-26 01:59:55 -05:00
Progress is progress.
This commit is contained in:
parent
3fff5de46d
commit
2395a8f1ff
|
|
@ -11,11 +11,12 @@
|
|||
class BackupMenuState : public AppState
|
||||
{
|
||||
public:
|
||||
BackupMenuState(Data::User *User, Data::TitleInfo *TitleInfo);
|
||||
BackupMenuState(Data::User *User, Data::TitleInfo *TitleInfo, FsSaveDataType SaveType);
|
||||
~BackupMenuState() {};
|
||||
|
||||
void Update(void);
|
||||
void Render(void);
|
||||
|
||||
// Refreshes/Updates menu and listing.
|
||||
void RefreshListing(void);
|
||||
|
||||
|
|
@ -24,6 +25,8 @@ class BackupMenuState : public AppState
|
|||
Data::User *m_User;
|
||||
// Pointer to title info.
|
||||
Data::TitleInfo *m_TitleInfo;
|
||||
// Save data type.
|
||||
FsSaveDataType m_SaveType;
|
||||
// Backup folder path
|
||||
FsLib::Path m_DirectoryPath;
|
||||
// Directory listing of that folder.
|
||||
|
|
|
|||
68
Include/AppStates/ConfirmState.hpp
Normal file
68
Include/AppStates/ConfirmState.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
#include "AppStates/AppState.hpp"
|
||||
#include "AppStates/ProgressState.hpp"
|
||||
#include "AppStates/TaskState.hpp"
|
||||
#include "Colors.hpp"
|
||||
#include "Input.hpp"
|
||||
#include "JKSV.hpp"
|
||||
#include "SDL.hpp"
|
||||
#include "System/Task.hpp"
|
||||
#include "UI/RenderFunctions.hpp"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <switch.h>
|
||||
#include <tuple>
|
||||
|
||||
// To do: I didn't want to accomplish this with structs, but templating it was holding me up too much.
|
||||
struct ConfirmStruct
|
||||
{
|
||||
};
|
||||
|
||||
template <typename TaskType, typename StateType>
|
||||
class ConfirmState : public AppState
|
||||
{
|
||||
public:
|
||||
// All functions using confirmation must follow this signature. The struct must be cast to the intended type for now.
|
||||
using TaskFunction = void (*)(TaskType *, std::shared_ptr<ConfirmStruct>);
|
||||
// Constructor
|
||||
ConfirmState(std::string_view QueryString, bool HoldRequired, TaskFunction Function, std::shared_ptr<ConfirmStruct> DataStruct)
|
||||
: m_QueryString(QueryString.data()), m_Hold(HoldRequired), m_Function(Function), m_DataStruct(DataStruct) {};
|
||||
|
||||
~ConfirmState() {};
|
||||
|
||||
void Update(void)
|
||||
{
|
||||
if (Input::ButtonPressed(HidNpadButton_A))
|
||||
{
|
||||
AppState::Deactivate();
|
||||
JKSV::PushState(std::make_shared<StateType>(m_Function, m_DataStruct));
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_B))
|
||||
{
|
||||
AppState::Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
void Render(void)
|
||||
{
|
||||
// Dim background
|
||||
SDL::RenderRectFill(NULL, 0, 0, 1280, 720, Colors::BackgroundDim);
|
||||
// Render dialog
|
||||
UI::RenderDialogBox(NULL, 280, 262, 720, 256);
|
||||
// Text
|
||||
SDL::Text::Render(NULL, 312, 288, 18, 656, Colors::White, m_QueryString.c_str());
|
||||
// Fake buttons. Maybe real later.
|
||||
SDL::RenderLine(NULL, 280, 454, 999, 454, Colors::White);
|
||||
SDL::RenderLine(NULL, 640, 454, 640, 517, Colors::White);
|
||||
}
|
||||
|
||||
private:
|
||||
// Query string
|
||||
std::string m_QueryString;
|
||||
// Whether or not holding is required to confirm.
|
||||
bool m_Hold;
|
||||
// Function
|
||||
TaskFunction m_Function;
|
||||
// Shared ptr to data to send to confirmation function.
|
||||
std::shared_ptr<ConfirmStruct> m_DataStruct;
|
||||
};
|
||||
24
Include/AppStates/UserOptionState.hpp
Normal file
24
Include/AppStates/UserOptionState.hpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
#include "AppStates/AppState.hpp"
|
||||
#include "Data/User.hpp"
|
||||
#include "UI/Menu.hpp"
|
||||
#include "UI/SlideOutPanel.hpp"
|
||||
#include <memory>
|
||||
|
||||
class UserOptionState : public AppState
|
||||
{
|
||||
public:
|
||||
UserOptionState(Data::User *User);
|
||||
~UserOptionState() {};
|
||||
|
||||
void Update(void);
|
||||
void Render(void);
|
||||
|
||||
private:
|
||||
// Target user
|
||||
Data::User *m_User;
|
||||
// Menu
|
||||
UI::Menu m_UserOptionMenu;
|
||||
// Static panel shared by all instances.
|
||||
static inline std::unique_ptr<UI::SlideOutPanel> m_MenuPanel = nullptr;
|
||||
};
|
||||
|
|
@ -17,6 +17,14 @@ namespace Data
|
|||
const char *GetPathSafeTitle(void);
|
||||
// Returns publisher
|
||||
const char *GetPublisher(void);
|
||||
// Returns save data size for save type. 0 on default.
|
||||
uint64_t GetSaveDataSize(FsSaveDataType SaveType) const;
|
||||
// Returns save data size max for save type. 0 on default.
|
||||
uint64_t GetSaveDataSizeMax(FsSaveDataType SaveType) const;
|
||||
// Returns journal size for given save type. 0 on default.
|
||||
uint64_t GetJournalSize(FsSaveDataType SaveType) const;
|
||||
// Returns the max journal size for save data type. 0 on default.
|
||||
uint64_t GetJournalSizeMax(FsSaveDataType SaveType) const;
|
||||
|
||||
// Returns icon
|
||||
SDL::SharedTexture GetIcon(void) const;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,16 @@
|
|||
#include "System/ProgressTask.hpp"
|
||||
#include <minizip/unzip.h>
|
||||
#include <minizip/zip.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace FS
|
||||
{
|
||||
// Copies directory recursively into Destination.
|
||||
void CopyDirectoryToZip(const FsLib::Path &Source, zipFile Destination, System::ProgressTask *Task = nullptr);
|
||||
// Recursively copies Source to destination.
|
||||
void CopyZipToDirectory(unzFile Source, const FsLib::Path &Destination, uint64_t JournalSize, System::ProgressTask *Task = nullptr);
|
||||
// Recursively copies Source to destination. This is only used for unzipping saves.
|
||||
void CopyZipToDirectory(unzFile Source,
|
||||
const FsLib::Path &Destination,
|
||||
uint64_t JournalSize,
|
||||
std::string_view CommitDevice,
|
||||
System::ProgressTask *Task = nullptr);
|
||||
} // namespace FS
|
||||
|
|
|
|||
|
|
@ -3,10 +3,18 @@
|
|||
|
||||
namespace StringUtil
|
||||
{
|
||||
enum class DateFormat
|
||||
{
|
||||
YearMonthDay,
|
||||
YearDayMonth
|
||||
};
|
||||
|
||||
// Gets a string formatted with va args.
|
||||
std::string GetFormattedString(const char *Format, ...);
|
||||
// Replaces a sequence of characters in a string with another.
|
||||
void ReplaceInString(std::string &Target, std::string_view Find, std::string_view Replace);
|
||||
// Tries to make string path safe. Returns false if it's not possible.
|
||||
bool SanitizeStringForPath(const char *StringIn, char *StringOut, size_t StringOutSize);
|
||||
// Gets date string. Asc is default if nothing passed.
|
||||
std::string GetDateString(StringUtil::DateFormat Format = StringUtil::DateFormat::YearMonthDay);
|
||||
} // namespace StringUtil
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ namespace Strings
|
|||
static constexpr std::string_view OnOff = "OnOff";
|
||||
static constexpr std::string_view BackupMenu = "BackupMenu";
|
||||
static constexpr std::string_view CopyingFiles = "CopyingFiles";
|
||||
static constexpr std::string_view BackupMenuConfirmations = "BackupMenuConfirmations";
|
||||
static constexpr std::string_view DeletingFiles = "DeletingFiles";
|
||||
static constexpr std::string_view KeyboardStrings = "KeyboardStrings";
|
||||
static constexpr std::string_view UserOptions = "UserOptions";
|
||||
} // namespace Names
|
||||
} // namespace Strings
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 67e903fe3b30ae8240e22f3f2d775eb8270dab7d
|
||||
Subproject commit c3a41490252faa9ca1d8f270fcdc338596d53ef6
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
"CopyingFiles": [
|
||||
"Copying #%s#, guv...",
|
||||
"Compressing #%s# to a lovely ZIP...",
|
||||
"Decompressing #%s# from that same ZIP..."
|
||||
"Decompressing #%s# from that ZIP..."
|
||||
],
|
||||
"KeyboardStrings": [
|
||||
"Enter a jolly new backup name.",
|
||||
|
|
|
|||
|
|
@ -61,6 +61,13 @@
|
|||
"Compressing #%s# to ZIP...",
|
||||
"Decompressing #%s# from ZIP..."
|
||||
],
|
||||
"BackupMenuConfirmations": [
|
||||
"Are you sure you really want to restore #%s#?",
|
||||
"Are you sure you really want to delete #%s#?"
|
||||
],
|
||||
"DeletingFiles": [
|
||||
"Deleting #%s#..."
|
||||
],
|
||||
"KeyboardStrings": [
|
||||
"Enter a new backup name.",
|
||||
"Enter cache index.",
|
||||
|
|
@ -71,5 +78,22 @@
|
|||
"Enter a name for the new folder.",
|
||||
"Enter a new output folder name for %s.",
|
||||
"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#"
|
||||
],
|
||||
"TitleOptions": [
|
||||
"Information",
|
||||
"Blacklist Title",
|
||||
"Change Output folder",
|
||||
"Open in File Mode",
|
||||
"Delete all save backups",
|
||||
"Reset save data.",
|
||||
"Delete save data from system",
|
||||
"Extend save data container",
|
||||
"Export SVI file"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "AppStates/BackupMenuState.hpp"
|
||||
#include "AppStates/ConfirmState.hpp"
|
||||
#include "AppStates/ProgressState.hpp"
|
||||
#include "Colors.hpp"
|
||||
#include "Config.hpp"
|
||||
|
|
@ -9,13 +10,24 @@
|
|||
#include "Input.hpp"
|
||||
#include "JKSV.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "SDL.hpp"
|
||||
#include "StringUtil.hpp"
|
||||
#include "Strings.hpp"
|
||||
#include "System/ProgressTask.hpp"
|
||||
#include "System/Task.hpp"
|
||||
#include <cstring>
|
||||
|
||||
// This struct is used to pass data to Restore, Delete, and upload.
|
||||
struct TargetStruct : ConfirmStruct
|
||||
{
|
||||
FsLib::Path TargetPath;
|
||||
uint64_t JournalSize = 0;
|
||||
BackupMenuState *CreatingState = nullptr;
|
||||
};
|
||||
|
||||
// This is the function to create new backups.
|
||||
static void CreateNewBackup(System::ProgressTask *Task, FsLib::Path DestinationPath)
|
||||
static void CreateNewBackup(System::ProgressTask *Task, FsLib::Path DestinationPath, BackupMenuState *CreatingState)
|
||||
{
|
||||
// This extension search is lazy and needs to be revised.
|
||||
if (Config::GetByKey(Config::Keys::ExportToZip) || std::strstr(DestinationPath.CString(), ".zip") != NULL)
|
||||
|
|
@ -28,11 +40,70 @@ static void CreateNewBackup(System::ProgressTask *Task, FsLib::Path DestinationP
|
|||
{
|
||||
FS::CopyDirectory(FS::DEFAULT_SAVE_PATH, DestinationPath, 0, {}, Task);
|
||||
}
|
||||
CreatingState->RefreshListing();
|
||||
Task->Finished();
|
||||
}
|
||||
|
||||
BackupMenuState::BackupMenuState(Data::User *User, Data::TitleInfo *TitleInfo)
|
||||
: m_User(User), m_TitleInfo(TitleInfo), m_DirectoryPath(Config::GetWorkingDirectory() / m_TitleInfo->GetPathSafeTitle()),
|
||||
static void RestoreBackup(System::ProgressTask *Task, std::shared_ptr<ConfirmStruct> DataStruct)
|
||||
{
|
||||
// Wipe the save root first.
|
||||
if (!FsLib::DeleteDirectoryRecursively(FS::DEFAULT_SAVE_PATH))
|
||||
{
|
||||
Logger::Log("Error restoring save. Unable to reset save data: %s", FsLib::GetErrorString());
|
||||
Task->Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cast struct to what we really need.
|
||||
std::shared_ptr<TargetStruct> Data = std::static_pointer_cast<TargetStruct>(DataStruct);
|
||||
|
||||
if (FsLib::DirectoryExists(Data->TargetPath))
|
||||
{
|
||||
FS::CopyDirectory(Data->TargetPath, FS::DEFAULT_SAVE_PATH, Data->JournalSize, FS::DEFAULT_SAVE_MOUNT, Task);
|
||||
}
|
||||
else if (std::strstr(Data->TargetPath.CString(), ".zip") != NULL)
|
||||
{
|
||||
unzFile TargetZip = unzOpen64(Data->TargetPath.CString());
|
||||
if (!TargetZip)
|
||||
{
|
||||
Logger::Log("Error opening zip for reading.");
|
||||
Task->Finished();
|
||||
return;
|
||||
}
|
||||
FS::CopyZipToDirectory(TargetZip, FS::DEFAULT_SAVE_PATH, Data->JournalSize, FS::DEFAULT_SAVE_MOUNT, Task);
|
||||
unzClose(TargetZip);
|
||||
}
|
||||
else
|
||||
{
|
||||
FS::CopyFile(Data->TargetPath, FS::DEFAULT_SAVE_PATH, Data->JournalSize, FS::DEFAULT_SAVE_MOUNT, Task);
|
||||
}
|
||||
Task->Finished();
|
||||
}
|
||||
|
||||
static void DeleteBackup(System::Task *Task, std::shared_ptr<ConfirmStruct> DataStruct)
|
||||
{
|
||||
std::shared_ptr<TargetStruct> Data = std::static_pointer_cast<TargetStruct>(DataStruct);
|
||||
|
||||
if (Task)
|
||||
{
|
||||
Task->SetStatus(Strings::GetByName(Strings::Names::DeletingFiles, 0), Data->TargetPath.CString());
|
||||
}
|
||||
|
||||
if (FsLib::DirectoryExists(Data->TargetPath) && !FsLib::DeleteDirectoryRecursively(Data->TargetPath))
|
||||
{
|
||||
Logger::Log("Error deleting folder backup: %s", FsLib::GetErrorString());
|
||||
}
|
||||
else if (!FsLib::DeleteFile(Data->TargetPath))
|
||||
{
|
||||
Logger::Log("Error deleting backup: %s", FsLib::GetErrorString());
|
||||
}
|
||||
Data->CreatingState->RefreshListing();
|
||||
Task->Finished();
|
||||
}
|
||||
|
||||
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_TitleTimer(3000)
|
||||
{
|
||||
if (!m_Initialized)
|
||||
|
|
@ -63,8 +134,13 @@ void BackupMenuState::Update(void)
|
|||
if (Input::ButtonPressed(HidNpadButton_A) && m_BackupMenu->GetSelected() == 0)
|
||||
{
|
||||
// Get name for backup.
|
||||
char BackupName[0x64] = {0};
|
||||
if (!Keyboard::GetInput(SwkbdType_QWERTY, "", Strings::GetByName(Strings::Names::KeyboardStrings, 0), BackupName, 0x64))
|
||||
char BackupName[0x81] = {0};
|
||||
|
||||
// Set backup to default.
|
||||
std::snprintf(BackupName, 0x64, "%s - %s", m_User->GetPathSafeNickname(), StringUtil::GetDateString().c_str());
|
||||
|
||||
if (!Input::ButtonHeld(HidNpadButton_ZR) &&
|
||||
!Keyboard::GetInput(SwkbdType_QWERTY, BackupName, Strings::GetByName(Strings::Names::KeyboardStrings, 0), BackupName, 0x80))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -80,7 +156,38 @@ void BackupMenuState::Update(void)
|
|||
return;
|
||||
}
|
||||
// Push the task.
|
||||
JKSV::PushState(std::make_shared<ProgressState>(CreateNewBackup, m_DirectoryPath / BackupName));
|
||||
JKSV::PushState(std::make_shared<ProgressState>(CreateNewBackup, m_DirectoryPath / BackupName, this));
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_Y) && m_BackupMenu->GetSelected() > 0 &&
|
||||
(m_SaveType != FsSaveDataType_System || Config::GetByKey(Config::Keys::AllowSystemSaveWriting)))
|
||||
{
|
||||
int Selected = m_BackupMenu->GetSelected() - 1;
|
||||
|
||||
std::shared_ptr<ConfirmStruct> DataStruct = std::make_shared<TargetStruct>();
|
||||
std::static_pointer_cast<TargetStruct>(DataStruct)->TargetPath = m_DirectoryPath / m_DirectoryListing[Selected];
|
||||
std::static_pointer_cast<TargetStruct>(DataStruct)->JournalSize = m_TitleInfo->GetJournalSize(m_SaveType);
|
||||
|
||||
std::string QueryString =
|
||||
StringUtil::GetFormattedString(Strings::GetByName(Strings::Names::BackupMenuConfirmations, 0), m_DirectoryListing[Selected]);
|
||||
|
||||
JKSV::PushState(std::make_shared<ConfirmState<System::ProgressTask, ProgressState>>(QueryString, false, RestoreBackup, DataStruct));
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_X) && m_BackupMenu->GetSelected() > 0)
|
||||
{
|
||||
// Selected needs to be offset by one to account for New
|
||||
int Selected = m_BackupMenu->GetSelected() - 1;
|
||||
|
||||
// Create struct to pass.
|
||||
std::shared_ptr<ConfirmStruct> DataStruct = std::make_shared<TargetStruct>();
|
||||
std::static_pointer_cast<TargetStruct>(DataStruct)->TargetPath = m_DirectoryPath / m_DirectoryListing[Selected];
|
||||
std::static_pointer_cast<TargetStruct>(DataStruct)->CreatingState = this;
|
||||
|
||||
// Get the string.
|
||||
std::string QueryString =
|
||||
StringUtil::GetFormattedString(Strings::GetByName(Strings::Names::BackupMenuConfirmations, 1), m_DirectoryListing[Selected]);
|
||||
|
||||
// Create/push new state.
|
||||
JKSV::PushState(std::make_shared<ConfirmState<System::Task, TaskState>>(QueryString, false, DeleteBackup, DataStruct));
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_B))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "AppStates/TextTitleSelectState.hpp"
|
||||
#include "AppStates/TitleSelectCommon.hpp"
|
||||
#include "AppStates/TitleSelectState.hpp"
|
||||
#include "AppStates/UserOptionState.hpp"
|
||||
#include "Colors.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "Input.hpp"
|
||||
|
|
@ -62,6 +63,10 @@ void MainMenuState::Update(void)
|
|||
m_States.at(m_MainMenu.GetSelected())->Reactivate();
|
||||
JKSV::PushState(m_States.at(m_MainMenu.GetSelected()));
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_X) && m_MainMenu.GetSelected() < static_cast<int>(m_Users.size()))
|
||||
{
|
||||
JKSV::PushState(std::make_shared<UserOptionState>(m_Users.at(m_MainMenu.GetSelected())));
|
||||
}
|
||||
}
|
||||
|
||||
void MainMenuState::Render(void)
|
||||
|
|
|
|||
|
|
@ -26,17 +26,6 @@ void ProgressState::Render(void)
|
|||
// This will dim the background.
|
||||
SDL::RenderRectFill(NULL, 0, 0, 1280, 720, Colors::BackgroundDim);
|
||||
|
||||
// Debug info
|
||||
SDL::Text::Render(NULL,
|
||||
0,
|
||||
0,
|
||||
16,
|
||||
SDL::Text::NO_TEXT_WRAP,
|
||||
Colors::Green,
|
||||
"Goal: %f\nCurrent: %f",
|
||||
m_Task.GetGoal(),
|
||||
m_Task.GetCurrentProgress());
|
||||
|
||||
// Render the dialog and little loading bar thingy.
|
||||
UI::RenderDialogBox(NULL, 280, 262, 720, 256);
|
||||
SDL::Text::Render(NULL, 312, 288, 18, 648, Colors::White, m_Task.GetStatus().c_str());
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ void TaskState::Render(void)
|
|||
// Grab task string.
|
||||
std::string Status = m_Task.GetStatus();
|
||||
// Center so it looks perty
|
||||
int StatusX = 640 - (SDL::Text::GetWidth(18, Status.c_str()) / 2);
|
||||
int StatusX = 640 - (SDL::Text::GetWidth(24, Status.c_str()) / 2);
|
||||
// Dim the background states.
|
||||
SDL::RenderRectFill(NULL, 0, 0, 1280, 720, Colors::BackgroundDim);
|
||||
// Render the status.
|
||||
SDL::Text::Render(NULL, StatusX, 351, 18, SDL::Text::NO_TEXT_WRAP, Colors::White, Status.c_str());
|
||||
SDL::Text::Render(NULL, StatusX, 351, 24, SDL::Text::NO_TEXT_WRAP, Colors::White, Status.c_str());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,15 +30,15 @@ void TitleSelectState::Update(void)
|
|||
{
|
||||
// Get data needed to mount save.
|
||||
uint64_t ApplicationID = m_User->GetApplicationIDAt(m_TitleView.GetSelected());
|
||||
FsSaveDataInfo *SaveInfo = m_User->GetSaveInfoByID(ApplicationID);
|
||||
Data::TitleInfo *TitleInfo = Data::GetTitleInfoByID(ApplicationID);
|
||||
|
||||
// Path to output to.
|
||||
FsLib::Path TargetPath = Config::GetWorkingDirectory() / TitleInfo->GetPathSafeTitle();
|
||||
|
||||
if ((FsLib::DirectoryExists(TargetPath) || FsLib::CreateDirectory(TargetPath)) &&
|
||||
FS::MountSaveData(*m_User->GetSaveInfoByID(ApplicationID), FS::DEFAULT_SAVE_MOUNT))
|
||||
if ((FsLib::DirectoryExists(TargetPath) || FsLib::CreateDirectory(TargetPath)) && FS::MountSaveData(*SaveInfo, FS::DEFAULT_SAVE_MOUNT))
|
||||
{
|
||||
JKSV::PushState(std::make_shared<BackupMenuState>(m_User, TitleInfo));
|
||||
JKSV::PushState(std::make_shared<BackupMenuState>(m_User, TitleInfo, static_cast<FsSaveDataType>(SaveInfo->save_data_type)));
|
||||
}
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_B))
|
||||
|
|
|
|||
44
Source/AppStates/UserOptionState.cpp
Normal file
44
Source/AppStates/UserOptionState.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#include "AppStates/UserOptionState.hpp"
|
||||
#include "Input.hpp"
|
||||
#include "StringUtil.hpp"
|
||||
#include "Strings.hpp"
|
||||
#include "System/ProgressTask.hpp"
|
||||
|
||||
UserOptionState::UserOptionState(Data::User *User) : m_User(User), m_UserOptionMenu(8, 8, 460, 22, 720)
|
||||
{
|
||||
// Check if panel needs to be created. It's shared by all instances.
|
||||
if (!m_MenuPanel)
|
||||
{
|
||||
m_MenuPanel = std::make_unique<UI::SlideOutPanel>(480, UI::SlideOutPanel::Side::Right);
|
||||
}
|
||||
|
||||
const char *CurrentString = nullptr;
|
||||
int CurrentStringIndex = 0;
|
||||
while ((CurrentString = Strings::GetByName(Strings::Names::UserOptions, CurrentStringIndex++)) != nullptr)
|
||||
{
|
||||
m_UserOptionMenu.AddOption(StringUtil::GetFormattedString(CurrentString, m_User->GetNickname()));
|
||||
}
|
||||
}
|
||||
|
||||
void UserOptionState::Update(void)
|
||||
{
|
||||
m_MenuPanel->Update(AppState::HasFocus());
|
||||
if (Input::ButtonPressed(HidNpadButton_B))
|
||||
{
|
||||
m_MenuPanel->Close();
|
||||
}
|
||||
else if (m_MenuPanel->IsClosed())
|
||||
{
|
||||
AppState::Deactivate();
|
||||
m_MenuPanel->Reset();
|
||||
}
|
||||
|
||||
m_UserOptionMenu.Update(AppState::HasFocus());
|
||||
}
|
||||
|
||||
void UserOptionState::Render(void)
|
||||
{
|
||||
m_MenuPanel->ClearTarget();
|
||||
m_UserOptionMenu.Render(m_MenuPanel->Get(), AppState::HasFocus());
|
||||
m_MenuPanel->Render(NULL, AppState::HasFocus());
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include "Data/Data.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "Data/AccountUID.hpp"
|
||||
#include "FS/SaveMount.hpp"
|
||||
#include "FsLib.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "Strings.hpp"
|
||||
|
|
@ -111,7 +112,14 @@ bool Data::Initialize(void)
|
|||
break;
|
||||
}
|
||||
|
||||
// Test if save is even mountable.
|
||||
if (Config::GetByKey(Config::Keys::OnlyListMountable) && !FS::MountSaveData(SaveInfo, FS::DEFAULT_SAVE_MOUNT))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FsLib::CloseFileSystem(FS::DEFAULT_SAVE_MOUNT);
|
||||
|
||||
// Find the user with info ID
|
||||
auto FindUser = std::find_if(s_UserVector.begin(), s_UserVector.end(), [&SaveInfo](UserIDPair &IDPair) {
|
||||
return IDPair.first == SaveInfo.uid;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -74,6 +74,188 @@ const char *Data::TitleInfo::GetPublisher(void)
|
|||
return Entry->author;
|
||||
}
|
||||
|
||||
uint64_t Data::TitleInfo::GetSaveDataSize(FsSaveDataType SaveType) const
|
||||
{
|
||||
switch (SaveType)
|
||||
{
|
||||
case FsSaveDataType_Account:
|
||||
{
|
||||
return m_NACP.user_account_save_data_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Bcat:
|
||||
{
|
||||
return m_NACP.bcat_delivery_cache_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device:
|
||||
{
|
||||
return m_NACP.device_save_data_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Temporary:
|
||||
{
|
||||
return m_NACP.temporary_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache:
|
||||
{
|
||||
return m_NACP.cache_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t Data::TitleInfo::GetSaveDataSizeMax(FsSaveDataType SaveType) const
|
||||
{
|
||||
switch (SaveType)
|
||||
{
|
||||
case FsSaveDataType_Account:
|
||||
{
|
||||
return m_NACP.user_account_save_data_size_max > m_NACP.user_account_save_data_size ? m_NACP.user_account_save_data_size_max
|
||||
: m_NACP.user_account_save_data_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Bcat:
|
||||
{
|
||||
return m_NACP.bcat_delivery_cache_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device:
|
||||
{
|
||||
return m_NACP.device_save_data_size_max > m_NACP.device_save_data_size ? m_NACP.device_save_data_size_max
|
||||
: m_NACP.device_save_data_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Temporary:
|
||||
{
|
||||
return m_NACP.temporary_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache:
|
||||
{
|
||||
return m_NACP.cache_storage_data_and_journal_size_max > m_NACP.cache_storage_size ? m_NACP.cache_storage_data_and_journal_size_max
|
||||
: m_NACP.cache_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t Data::TitleInfo::GetJournalSize(FsSaveDataType SaveType) const
|
||||
{
|
||||
switch (SaveType)
|
||||
{
|
||||
case FsSaveDataType_Account:
|
||||
{
|
||||
return m_NACP.user_account_save_data_journal_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Bcat:
|
||||
{
|
||||
// I'm just assuming this is right...
|
||||
return m_NACP.bcat_delivery_cache_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device:
|
||||
{
|
||||
return m_NACP.device_save_data_journal_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Temporary:
|
||||
{
|
||||
// Again, just assuming.
|
||||
return m_NACP.temporary_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache:
|
||||
{
|
||||
return m_NACP.cache_storage_journal_size;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t Data::TitleInfo::GetJournalSizeMax(FsSaveDataType SaveType) const
|
||||
{
|
||||
switch (SaveType)
|
||||
{
|
||||
case FsSaveDataType_Account:
|
||||
{
|
||||
return m_NACP.user_account_save_data_journal_size_max > m_NACP.user_account_save_data_journal_size
|
||||
? m_NACP.user_account_save_data_journal_size_max
|
||||
: m_NACP.user_account_save_data_journal_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Bcat:
|
||||
{
|
||||
return m_NACP.bcat_delivery_cache_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device:
|
||||
{
|
||||
return m_NACP.device_save_data_journal_size_max > m_NACP.device_save_data_journal_size ? m_NACP.device_save_data_journal_size_max
|
||||
: m_NACP.device_save_data_journal_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Temporary:
|
||||
{
|
||||
return m_NACP.temporary_storage_size;
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache:
|
||||
{
|
||||
return m_NACP.cache_storage_data_and_journal_size_max > m_NACP.cache_storage_journal_size
|
||||
? m_NACP.cache_storage_data_and_journal_size_max
|
||||
: m_NACP.cache_storage_journal_size;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL::SharedTexture Data::TitleInfo::GetIcon(void) const
|
||||
{
|
||||
return m_Icon;
|
||||
|
|
|
|||
|
|
@ -78,9 +78,6 @@ void FS::CopyFile(const FsLib::Path &Source,
|
|||
// This thread has a local buffer so the read thread can continue while this one writes.
|
||||
std::unique_ptr<unsigned char[]> LocalBuffer(new unsigned char[FILE_BUFFER_SIZE]);
|
||||
|
||||
// This stores the number of bytes the other thread reads locally.
|
||||
size_t ReadCount = 0;
|
||||
|
||||
// Get file size for loop and set goal.
|
||||
int64_t FileSize = SourceFile.GetSize();
|
||||
if (Task)
|
||||
|
|
@ -88,7 +85,7 @@ void FS::CopyFile(const FsLib::Path &Source,
|
|||
Task->Reset(static_cast<double>(FileSize));
|
||||
}
|
||||
|
||||
for (int64_t WriteCount = 0, JournalCount = 0; WriteCount < FileSize;)
|
||||
for (int64_t WriteCount = 0, ReadCount = 0, JournalCount = 0; WriteCount < FileSize;)
|
||||
{
|
||||
{
|
||||
// Wait for lock/signal.
|
||||
|
|
@ -107,7 +104,7 @@ void FS::CopyFile(const FsLib::Path &Source,
|
|||
}
|
||||
|
||||
// Journaling size check. Breathing room is given.
|
||||
if (JournalSize != 0 && (JournalCount + ReadCount) >= JournalSize - 0x100000)
|
||||
if (JournalSize != 0 && (JournalCount + ReadCount) >= static_cast<int64_t>(JournalSize) - 0x100000)
|
||||
{
|
||||
// Reset journal count.
|
||||
JournalCount = 0;
|
||||
|
|
|
|||
|
|
@ -8,13 +8,21 @@ bool FS::MountSaveData(const FsSaveDataInfo &SaveInfo, std::string_view DeviceNa
|
|||
case FsSaveDataType_System:
|
||||
{
|
||||
// This should work because JKSV's internal ID for the system user is 0.
|
||||
return FsLib::OpenSystemSaveFileSystem(DeviceName, SaveInfo.system_save_data_id, SaveInfo.uid);
|
||||
return FsLib::OpenSystemSaveFileSystem(DeviceName,
|
||||
SaveInfo.system_save_data_id,
|
||||
static_cast<FsSaveDataSpaceId>(SaveInfo.save_data_space_id),
|
||||
static_cast<FsSaveDataRank>(SaveInfo.save_data_rank),
|
||||
SaveInfo.uid);
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Account:
|
||||
{
|
||||
return FsLib::OpenAccountSaveFileSystem(DeviceName, SaveInfo.application_id, SaveInfo.uid);
|
||||
return FsLib::OpenAccountSaveFileSystem(DeviceName,
|
||||
SaveInfo.application_id,
|
||||
SaveInfo.uid,
|
||||
static_cast<FsSaveDataSpaceId>(SaveInfo.save_data_space_id),
|
||||
static_cast<FsSaveDataRank>(SaveInfo.save_data_rank));
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -38,7 +46,11 @@ bool FS::MountSaveData(const FsSaveDataInfo &SaveInfo, std::string_view DeviceNa
|
|||
|
||||
case FsSaveDataType_Cache:
|
||||
{
|
||||
return FsLib::OpenCacheSaveFileSystem(DeviceName, SaveInfo.application_id, SaveInfo.save_data_index);
|
||||
return FsLib::OpenCacheSaveFileSystem(DeviceName,
|
||||
SaveInfo.application_id,
|
||||
SaveInfo.save_data_index,
|
||||
static_cast<FsSaveDataSpaceId>(SaveInfo.save_data_space_id),
|
||||
static_cast<FsSaveDataRank>(SaveInfo.save_data_rank));
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ typedef struct
|
|||
} ZipIOStruct;
|
||||
|
||||
// Function for reading files for Zipping.
|
||||
void ZipReadThreadFunction(FsLib::File &Source, std::shared_ptr<ZipIOStruct> SharedData)
|
||||
static void ZipReadThreadFunction(FsLib::File &Source, std::shared_ptr<ZipIOStruct> SharedData)
|
||||
{
|
||||
int64_t FileSize = Source.GetSize();
|
||||
for (int64_t ReadCount = 0; ReadCount < FileSize;)
|
||||
|
|
@ -50,11 +50,20 @@ void ZipReadThreadFunction(FsLib::File &Source, std::shared_ptr<ZipIOStruct> Sha
|
|||
}
|
||||
|
||||
// Function for reading data from Zip to buffer.
|
||||
void UnzipReadThreadFunction(unzFile Source, int64_t FileSize, std::shared_ptr<ZipIOStruct> SharedData)
|
||||
static void UnzipReadThreadFunction(unzFile Source, int64_t FileSize, std::shared_ptr<ZipIOStruct> SharedData)
|
||||
{
|
||||
for (int64_t ReadCount = 0; ReadCount < FileSize;)
|
||||
{
|
||||
ReadCount = unzReadCurrentFile(Source, SharedData->SharedBuffer.get(), UNZIP_BUFFER_SIZE);
|
||||
// Read from zip file.
|
||||
SharedData->ReadCount = unzReadCurrentFile(Source, SharedData->SharedBuffer.get(), UNZIP_BUFFER_SIZE);
|
||||
|
||||
ReadCount += SharedData->ReadCount;
|
||||
|
||||
SharedData->BufferIsFull = true;
|
||||
SharedData->BufferCondition.notify_one();
|
||||
|
||||
std::unique_lock<std::mutex> BufferLock(SharedData->BufferLock);
|
||||
SharedData->BufferCondition.wait(BufferLock, [&SharedData]() { return SharedData->BufferIsFull == false; });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +94,7 @@ void FS::CopyDirectoryToZip(const FsLib::Path &Source, zipFile Destination, Syst
|
|||
continue;
|
||||
}
|
||||
|
||||
// Date for file.
|
||||
// Date for file(s)
|
||||
std::time_t Timer;
|
||||
std::time(&Timer);
|
||||
std::tm *LocalTime = std::localtime(&Timer);
|
||||
|
|
@ -170,3 +179,117 @@ void FS::CopyDirectoryToZip(const FsLib::Path &Source, zipFile Destination, Syst
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FS::CopyZipToDirectory(unzFile Source,
|
||||
const FsLib::Path &Destination,
|
||||
uint64_t JournalSize,
|
||||
std::string_view CommitDevice,
|
||||
System::ProgressTask *Task)
|
||||
{
|
||||
int ZipError = unzGoToFirstFile(Source);
|
||||
if (ZipError != UNZ_OK)
|
||||
{
|
||||
Logger::Log("Error opening empty ZIP file: %i.", ZipError);
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
// Get file information.
|
||||
unz_file_info64 CurrentFileInfo;
|
||||
char FileName[FS_MAX_PATH] = {0};
|
||||
if (unzGetCurrentFileInfo64(Source, &CurrentFileInfo, FileName, FS_MAX_PATH, NULL, 0, NULL, 0) != UNZ_OK ||
|
||||
unzOpenCurrentFile(Source) != UNZ_OK)
|
||||
{
|
||||
Logger::Log("Error opening and getting information for file in zip.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create full path to item, make sure directories are created if needed.
|
||||
FsLib::Path FullDestination = Destination / FileName;
|
||||
|
||||
FsLib::Path Directories = FullDestination.SubPath(FullDestination.FindLastOf('/') - 1);
|
||||
// To do: Make FsLib handle this correctly. First condition is a workaround for now...
|
||||
if (Directories.IsValid() && !FsLib::CreateDirectoriesRecursively(Directories))
|
||||
{
|
||||
Logger::Log("Error creating zip file path \"%s\": %s", Directories.CString(), FsLib::GetErrorString());
|
||||
continue;
|
||||
}
|
||||
|
||||
FsLib::File DestinationFile(FullDestination, FsOpenMode_Create | FsOpenMode_Write, CurrentFileInfo.uncompressed_size);
|
||||
if (!DestinationFile.IsOpen())
|
||||
{
|
||||
Logger::Log("Error creating file from zip: %s", FsLib::GetErrorString());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Shared data for both threads
|
||||
std::shared_ptr<ZipIOStruct> SharedData(new ZipIOStruct);
|
||||
SharedData->SharedBuffer = std::make_unique<unsigned char[]>(UNZIP_BUFFER_SIZE);
|
||||
|
||||
// Spawn read thread.
|
||||
std::thread ReadThread(UnzipReadThreadFunction, Source, CurrentFileInfo.uncompressed_size, SharedData);
|
||||
|
||||
// Local buffer
|
||||
std::unique_ptr<unsigned char[]> LocalBuffer(new unsigned char[UNZIP_BUFFER_SIZE]);
|
||||
|
||||
// Set status
|
||||
if (Task)
|
||||
{
|
||||
Task->SetStatus(Strings::GetByName(Strings::Names::CopyingFiles, 3), FileName);
|
||||
Task->Reset(static_cast<double>(CurrentFileInfo.uncompressed_size));
|
||||
}
|
||||
|
||||
for (int64_t WriteCount = 0, ReadCount = 0, JournalCount = 0; WriteCount < static_cast<int64_t>(CurrentFileInfo.uncompressed_size);)
|
||||
{
|
||||
{
|
||||
// Wait for buffer.
|
||||
std::unique_lock<std::mutex> BufferLock(SharedData->BufferLock);
|
||||
SharedData->BufferCondition.wait(BufferLock, [&SharedData]() { return SharedData->BufferIsFull; });
|
||||
|
||||
// Save read count for later
|
||||
ReadCount = SharedData->ReadCount;
|
||||
|
||||
// Copy shared to local
|
||||
std::memcpy(LocalBuffer.get(), SharedData->SharedBuffer.get(), ReadCount);
|
||||
|
||||
// Signal this thread is done.
|
||||
SharedData->BufferIsFull = false;
|
||||
SharedData->BufferCondition.notify_one();
|
||||
}
|
||||
|
||||
// Journaling check
|
||||
if (JournalCount + ReadCount >= static_cast<int64_t>(JournalSize))
|
||||
{
|
||||
// Close.
|
||||
DestinationFile.Close();
|
||||
// Commit
|
||||
if (!FsLib::CommitDataToFileSystem(CommitDevice))
|
||||
{
|
||||
Logger::Log("Error committing data to save: %s", FsLib::GetErrorString());
|
||||
}
|
||||
// Reopen, seek to previous position.
|
||||
DestinationFile.Open(FullDestination, FsOpenMode_Write);
|
||||
DestinationFile.Seek(WriteCount, DestinationFile.Beginning);
|
||||
// Reset journal
|
||||
JournalCount = 0;
|
||||
}
|
||||
// Write data.
|
||||
DestinationFile.Write(LocalBuffer.get(), ReadCount);
|
||||
// Update write and journal count
|
||||
WriteCount += ReadCount;
|
||||
JournalCount += JournalCount;
|
||||
// Update status
|
||||
if (Task)
|
||||
{
|
||||
Task->UpdateCurrent(WriteCount);
|
||||
}
|
||||
}
|
||||
// Close file and commit again just for good measure.
|
||||
DestinationFile.Close();
|
||||
if (!FsLib::CommitDataToFileSystem(CommitDevice))
|
||||
{
|
||||
Logger::Log("Error performing final file commit: %s", FsLib::GetErrorString());
|
||||
}
|
||||
} while (unzGoToNextFile(Source) != UNZ_END_OF_LIST_OF_FILE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
namespace
|
||||
{
|
||||
constexpr uint8_t BUILD_MON = 12;
|
||||
constexpr uint8_t BUILD_DAY = 22;
|
||||
constexpr uint8_t BUILD_DAY = 24;
|
||||
constexpr uint16_t BUILD_YEAR = 2024;
|
||||
} // namespace
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <array>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <switch.h>
|
||||
|
||||
namespace
|
||||
|
|
@ -79,3 +80,29 @@ bool StringUtil::SanitizeStringForPath(const char *StringIn, char *StringOut, si
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string StringUtil::GetDateString(StringUtil::DateFormat Format)
|
||||
{
|
||||
char StringBuffer[0x80];
|
||||
|
||||
std::time_t Timer;
|
||||
std::time(&Timer);
|
||||
std::tm *LocalTime = std::localtime(&Timer);
|
||||
|
||||
switch (Format)
|
||||
{
|
||||
case StringUtil::DateFormat::YearMonthDay:
|
||||
{
|
||||
std::strftime(StringBuffer, 0x80, "%Y-%m-%d_%H-%M-%S", LocalTime);
|
||||
}
|
||||
break;
|
||||
|
||||
case StringUtil::DateFormat::YearDayMonth:
|
||||
{
|
||||
std::strftime(StringBuffer, 0x80, "%Y-%d-%m_%H-%M-%S", LocalTime);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return std::string(StringBuffer);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user