SaveImportState progress.

This commit is contained in:
J-D-K 2025-09-24 15:32:57 -04:00
parent a78dd3456a
commit 9d542b4314
9 changed files with 222 additions and 34 deletions

View File

@ -3,6 +3,7 @@
#include "appstates/BaseState.hpp"
#include "data/data.hpp"
#include "fslib.hpp"
#include "sys/sys.hpp"
#include "ui/ui.hpp"
#include <memory>
@ -29,13 +30,28 @@ class SaveImportState final : public BaseState
void render() override;
// clang-format off
// Struct used to pass data to the task.
struct DataStruct : sys::Task::DataStruct
{
data::User *user{};
fslib::Path path{};
};
// clang-format on
/// @brief Makes things easier to read and type.
using TaskData = std::shared_ptr<SaveImportState::DataStruct>;
private:
/// @brief Pointer to the target user.
data::User *m_user{};
/// @brief
/// @brief Directory listing of the saves directory.
fslib::Directory m_saveDir{};
/// @brief This is used to pass data to the task function.
static inline SaveImportState::TaskData sm_taskData{};
/// @brief Menu shared by all instances.
static inline std::shared_ptr<ui::Menu> sm_saveMenu{};
@ -48,6 +64,9 @@ class SaveImportState final : public BaseState
/// @brief Opens and
void initialize_directory_menu();
/// @brief Imports a backup selected from the menu.
void import_backup();
/// @brief Cleans up and deactivates the state.
void deactivate_state();
};

View File

@ -6,12 +6,6 @@
namespace fs
{
/// @brief This is the magic value written to the beginning.
constexpr uint32_t SAVE_META_MAGIC = 0x56534B4A;
/// @brief This is the filename used for the save data meta info.
static constexpr std::string_view NAME_SAVE_META = ".nx_save_meta.bin";
// clang-format off
struct SaveMetaData
{
@ -23,6 +17,7 @@ namespace fs
uint8_t saveDataType{};
uint8_t saveDataRank{};
uint16_t saveDataIndex{};
uint8_t saveDataSpaceID{};
uint64_t ownerID{};
uint64_t timestamp{};
uint32_t flags{};
@ -32,6 +27,14 @@ namespace fs
} __attribute__((packed));
// clang-format on
/// @brief This is the magic value written to the beginning.
constexpr uint32_t SAVE_META_MAGIC = 0x56534B4A;
/// @brief This is the filename used for the save data meta info.
inline constexpr std::string_view NAME_SAVE_META = ".nx_save_meta.bin";
inline constexpr ssize_t SIZE_SAVE_META = sizeof(fs::SaveMetaData);
/// @brief Didn't feel like a whole new file just for this. Fills an fs::SaveMetaData struct.
bool fill_save_meta_data(const FsSaveDataInfo *saveInfo, SaveMetaData &meta) noexcept;

View File

@ -1,5 +1,6 @@
#pragma once
#include "data/data.hpp"
#include "fs/SaveMetaData.hpp"
#include <switch.h>
@ -11,6 +12,9 @@ namespace fs
/// @return True on success. False on failure.
bool create_save_data_for(data::User *targetUser, data::TitleInfo *titleInfo) noexcept;
/// @brief Creates save data for the user passed using the meta data passed.
bool create_save_data_for(data::User *targetUser, const fs::SaveMetaData &saveMeta) noexcept;
/// @brief Deletes the save data of the FsSaveDataInfo passed.
/// @param saveInfo Save data to delete.
/// @return True on success. False on failure.

View File

@ -0,0 +1,8 @@
#pragma once
#include "sys/Task.hpp"
namespace tasks::saveimport
{
/// @brief Imports a random save backup according to the meta data.
void import_save_backup(sys::threadpool::JobData taskData);
}

View File

@ -1,7 +1,12 @@
#include "appstates/SaveImportState.hpp"
#include "appstates/BackupMenuState.hpp"
#include "appstates/ConfirmState.hpp"
#include "config/config.hpp"
#include "input.hpp"
#include "strings/strings.hpp"
#include "stringutil.hpp"
#include "tasks/saveimport.hpp"
SaveImportState::SaveImportState(data::User *user)
: m_user(user)
@ -21,7 +26,7 @@ void SaveImportState::update()
const bool aPressed = input::button_pressed(HidNpadButton_A);
const bool bPressed = input::button_pressed(HidNpadButton_B);
if (aPressed) {}
if (aPressed) { SaveImportState::import_backup(); }
else if (bPressed) { sm_slidePanel->close(); }
else if (sm_slidePanel->is_closed()) { SaveImportState::deactivate_state(); }
}
@ -38,7 +43,9 @@ void SaveImportState::initialize_static_members()
{
static constexpr int SIZE_PANEL_WIDTH = 512;
if (sm_saveMenu && sm_slidePanel) { return; }
if (sm_taskData && sm_saveMenu && sm_slidePanel) { return; }
sm_taskData = std::make_shared<SaveImportState::DataStruct>();
sm_saveMenu = ui::Menu::create(8, 8, 492, 22, 720);
sm_slidePanel = ui::SlideOutPanel::create(SIZE_PANEL_WIDTH, ui::SlideOutPanel::Side::Right);
@ -56,6 +63,22 @@ void SaveImportState::initialize_directory_menu()
for (const fslib::DirectoryEntry &entry : m_saveDir) { sm_saveMenu->add_option(entry.get_filename()); }
}
void SaveImportState::import_backup()
{
const int selected = sm_saveMenu->get_selected();
const bool holdRequired = config::get_by_key(config::keys::HOLD_FOR_RESTORATION);
const fslib::DirectoryEntry &target = m_saveDir[selected];
const char *targetName = target.get_filename();
sm_taskData->user = m_user;
sm_taskData->path = config::get_working_directory() / "saves" / targetName;
const char *confirmFormat = strings::get_by_name(strings::names::BACKUPMENU_CONFS, 1);
std::string query = stringutil::get_formatted_string(confirmFormat, targetName);
ConfirmProgress::create_and_push(query, holdRequired, tasks::saveimport::import_save_backup, sm_taskData);
}
void SaveImportState::deactivate_state()
{
sm_saveMenu->reset();

View File

@ -17,20 +17,21 @@ bool fs::fill_save_meta_data(const FsSaveDataInfo *saveInfo, fs::SaveMetaData &m
const bool extraRead = fs::read_save_extra_data(saveInfo, extraData);
if (!extraRead) { return false; }
meta = {.magic = fs::SAVE_META_MAGIC,
.revision = 0x00,
.applicationID = extraData.attr.application_id,
.accountID = extraData.attr.uid,
.systemSaveID = extraData.attr.system_save_data_id,
.saveDataType = extraData.attr.save_data_type,
.saveDataRank = extraData.attr.save_data_rank,
.saveDataIndex = extraData.attr.save_data_index,
.ownerID = extraData.owner_id,
.timestamp = extraData.timestamp,
.flags = extraData.flags,
.saveDataSize = extraData.data_size,
.journalSize = extraData.journal_size,
.commitID = extraData.commit_id};
meta = {.magic = fs::SAVE_META_MAGIC,
.revision = 0x01,
.applicationID = extraData.attr.application_id,
.accountID = extraData.attr.uid,
.systemSaveID = extraData.attr.system_save_data_id,
.saveDataType = extraData.attr.save_data_type,
.saveDataRank = extraData.attr.save_data_rank,
.saveDataIndex = extraData.attr.save_data_index,
.saveDataSpaceID = saveInfo->save_data_space_id,
.ownerID = extraData.owner_id,
.timestamp = extraData.timestamp,
.flags = extraData.flags,
.saveDataSize = extraData.data_size,
.journalSize = extraData.journal_size,
.commitID = extraData.commit_id};
return true;
}

View File

@ -3,9 +3,13 @@
#include "error.hpp"
#include "logging/logger.hpp"
namespace
{
inline constexpr FsSaveDataMetaInfo SAVE_CREATE_META = {.size = 0x40060, .type = FsSaveDataMetaType_Thumbnail};
}
bool fs::create_save_data_for(data::User *targetUser, data::TitleInfo *titleInfo) noexcept
{
static constexpr FsSaveDataMetaInfo saveMeta = {.size = 0x40060, .type = FsSaveDataMetaType_Thumbnail};
const uint8_t saveType = targetUser->get_account_save_type();
const uint64_t applicationID = titleInfo->get_application_id();
@ -29,7 +33,26 @@ bool fs::create_save_data_for(data::User *targetUser, data::TitleInfo *titleInfo
.save_data_space_id = FsSaveDataSpaceId_User};
// I want this recorded.
return error::libnx(fsCreateSaveDataFileSystem(&saveAttributes, &saveCreation, &saveMeta)) == false;
return error::libnx(fsCreateSaveDataFileSystem(&saveAttributes, &saveCreation, &SAVE_CREATE_META)) == false;
}
bool fs::create_save_data_for(data::User *targetUser, const fs::SaveMetaData &saveMeta) noexcept
{
const FsSaveDataAttribute saveAttributes = {.application_id = saveMeta.applicationID,
.uid = targetUser->get_account_id(),
.system_save_data_id = saveMeta.systemSaveID,
.save_data_type = saveMeta.saveDataType,
.save_data_rank = saveMeta.saveDataRank,
.save_data_index = saveMeta.saveDataIndex};
const FsSaveDataCreationInfo saveCreation = {.save_data_size = saveMeta.saveDataSize,
.journal_size = saveMeta.journalSize,
.available_size = 0x4000,
.owner_id = saveMeta.ownerID,
.flags = 0,
.save_data_space_id = saveMeta.saveDataSpaceID};
return error::libnx(fsCreateSaveDataFileSystem(&saveAttributes, &saveCreation, &SAVE_CREATE_META)) == false;
}
bool fs::delete_save_data(const FsSaveDataInfo *saveInfo) noexcept
@ -72,4 +95,4 @@ bool fs::read_save_extra_data(const FsSaveDataInfo *saveInfo, FsSaveDataExtraDat
fsReadSaveDataFileSystemExtraDataBySaveDataSpaceId(&extraOut, EXTRA_SIZE, spaceID, saveInfo->save_data_id));
return !readError;
}
}

View File

@ -15,7 +15,6 @@ namespace
{
constexpr const char *STRING_ZIP_EXT = ".zip";
constexpr const char *PATH_JKSV_TEMP = "sdmc:/jksvTemp.zip"; // This is named this so if something fails, people know.
constexpr size_t SIZE_SAVE_META = sizeof(fs::SaveMetaData);
}
// Definitions at bottom.
@ -267,8 +266,12 @@ void tasks::backup::restore_backup_local(sys::threadpool::JobData taskData)
fs::copy_file_commit(target, fs::DEFAULT_SAVE_ROOT, journalSize, task);
}
spawningState->save_data_written();
spawningState->refresh();
if (spawningState)
{
spawningState->save_data_written();
spawningState->refresh();
}
task->complete();
}
@ -575,7 +578,7 @@ static bool read_and_process_meta(const fslib::Path &targetDir, BackupMenuState:
}
fs::SaveMetaData metaData{};
const bool metaRead = metaFile.read(&metaData, SIZE_SAVE_META) == SIZE_SAVE_META;
const bool metaRead = metaFile.read(&metaData, fs::SIZE_SAVE_META) <= fs::SIZE_SAVE_META;
const bool processed = metaRead && fs::process_save_meta_data(saveInfo, metaData);
if (!metaRead || !processed)
{
@ -618,7 +621,7 @@ static bool read_and_process_meta(fs::MiniUnzip &unzip, BackupMenuState::TaskDat
return false;
}
const bool metaRead = metaFound && unzip.read(&saveMeta, SIZE_SAVE_META) == SIZE_SAVE_META;
const bool metaRead = metaFound && unzip.read(&saveMeta, fs::SIZE_SAVE_META) <= fs::SIZE_SAVE_META;
const bool metaProcessed = metaRead && fs::process_save_meta_data(saveInfo, saveMeta);
if (!metaRead || !metaProcessed)
{
@ -638,14 +641,14 @@ static void write_meta_file(const fslib::Path &target, const FsSaveDataInfo *sav
fs::SaveMetaData saveMeta{};
const fslib::Path metaPath{target / fs::NAME_SAVE_META};
const bool hasMeta = fs::fill_save_meta_data(saveInfo, saveMeta);
fslib::File metaFile{metaPath, FsOpenMode_Create | FsOpenMode_Write, SIZE_SAVE_META};
fslib::File metaFile{metaPath, FsOpenMode_Create | FsOpenMode_Write, fs::SIZE_SAVE_META};
if (!metaFile.is_open() || !hasMeta)
{
ui::PopMessageManager::push_message(popTicks, popErrorWritingMeta);
return;
}
const bool metaWritten = metaFile.write(&saveMeta, SIZE_SAVE_META) == SIZE_SAVE_META;
const bool metaWritten = metaFile.write(&saveMeta, fs::SIZE_SAVE_META) <= fs::SIZE_SAVE_META;
if (!metaWritten) { ui::PopMessageManager::push_message(popTicks, popErrorWritingMeta); }
}
@ -656,7 +659,7 @@ static void write_meta_zip(fs::MiniZip &zip, const FsSaveDataInfo *saveInfo)
fs::SaveMetaData saveMeta{};
const bool hasMeta = fs::fill_save_meta_data(saveInfo, saveMeta);
const bool openMeta = hasMeta && zip.open_new_file(fs::NAME_SAVE_META);
const bool writeMeta = openMeta && zip.write(&saveMeta, SIZE_SAVE_META);
const bool writeMeta = openMeta && zip.write(&saveMeta, fs::SIZE_SAVE_META);
const bool closeMeta = openMeta && zip.close_current_file();
if (hasMeta && (!openMeta || !writeMeta || !closeMeta))
{

104
source/tasks/saveimport.cpp Normal file
View File

@ -0,0 +1,104 @@
#include "tasks/saveimport.hpp"
#include "appstates/SaveImportState.hpp"
#include "error.hpp"
#include "fs/fs.hpp"
#include "fslib.hpp"
#include "strings/strings.hpp"
#include "tasks/backup.hpp"
#include "ui/PopMessageManager.hpp"
// Defined at bottom.
static bool read_save_meta(const fslib::Path &path, fs::SaveMetaData &metaOut);
static bool test_for_save(data::User *user, const fs::SaveMetaData &saveMeta);
void tasks::saveimport::import_save_backup(sys::threadpool::JobData taskData)
{
auto castData = std::static_pointer_cast<SaveImportState::DataStruct>(taskData);
sys::ProgressTask *task = static_cast<sys::ProgressTask *>(castData->task);
if (error::is_null(task)) { return; }
data::User *user = castData->user;
const fslib::Path &target = castData->path;
fs::SaveMetaData metaData{};
const bool metaRead = read_save_meta(target, metaData);
if (!metaRead) { TASK_FINISH_RETURN(task); }
const bool saveExists = test_for_save(user, metaData);
if (!saveExists) { fs::create_save_data_for(user, metaData); }
}
static bool read_save_meta(const fslib::Path &path, fs::SaveMetaData &metaOut)
{
const bool isDir = fslib::directory_exists(path);
if (isDir)
{
const fslib::Path metaPath{path / fs::NAME_SAVE_META};
const bool exists = fslib::file_exists(metaPath);
if (!exists) { return false; }
fslib::File metaFile{metaPath, FsOpenMode_Read};
const bool metaRead = metaFile.is_open() && metaFile.get_size() <= fs::SIZE_SAVE_META &&
metaFile.read(&metaOut, fs::SIZE_SAVE_META) <= fs::SIZE_SAVE_META;
const bool magicMatch = metaRead && metaOut.magic == fs::SAVE_META_MAGIC;
if (!metaRead || !magicMatch) { return false; }
}
else
{
fs::MiniUnzip zip{path};
if (!zip.is_open()) { return false; }
const bool located = zip.locate_file(fs::NAME_SAVE_META);
const bool sizeMatch = located && zip.get_uncompressed_size() <= fs::SIZE_SAVE_META;
const bool metaRead = sizeMatch && zip.read(&metaOut, fs::SIZE_SAVE_META) <= fs::SIZE_SAVE_META;
const bool magicMatch = metaRead && metaOut.magic == fs::SAVE_META_MAGIC;
if (!metaRead || !magicMatch) { return false; }
}
return true;
}
static bool test_for_save(data::User *user, const fs::SaveMetaData &saveMeta)
{
const FsSaveDataType saveType = user->get_account_save_type();
bool mounted{};
switch (saveType)
{
case FsSaveDataType_Account:
{
mounted =
fslib::open_account_save_file_system(fs::DEFAULT_SAVE_MOUNT, saveMeta.applicationID, user->get_account_id());
}
break;
case FsSaveDataType_Bcat:
{
mounted = fslib::open_bcat_save_file_system(fs::DEFAULT_SAVE_MOUNT, saveMeta.applicationID);
}
break;
case FsSaveDataType_Device:
{
mounted = fslib::open_device_save_file_system(fs::DEFAULT_SAVE_MOUNT, saveMeta.applicationID);
}
break;
case FsSaveDataType_Cache:
{
mounted = fslib::open_cache_save_file_system(fs::DEFAULT_SAVE_MOUNT,
saveMeta.applicationID,
saveMeta.saveDataIndex,
static_cast<FsSaveDataSpaceId>(saveMeta.saveDataIndex));
}
break;
default: return false;
}
if (mounted) { fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT); }
return mounted;
}