Progress is progress.

This commit is contained in:
J-D-K 2024-12-24 16:54:31 -05:00
parent 3fff5de46d
commit 2395a8f1ff
23 changed files with 677 additions and 40 deletions

View File

@ -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.

View 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;
};

View 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;
};

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.",

View File

@ -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"
]
}

View 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))
{

View File

@ -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)

View File

@ -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());

View File

@ -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());
}

View File

@ -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))

View 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());
}

View File

@ -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;
});

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View 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

View File

@ -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);
}