Merge pull request #302 from J-D-K/dev

Add Generic SaveImportState/save importing
This commit is contained in:
JK
2025-09-24 19:21:19 -04:00
committed by GitHub
28 changed files with 425 additions and 55 deletions

View File

@@ -0,0 +1,72 @@
#pragma once
#include "StateManager.hpp"
#include "appstates/BaseState.hpp"
#include "data/data.hpp"
#include "fslib.hpp"
#include "sys/sys.hpp"
#include "ui/ui.hpp"
#include <memory>
class SaveImportState final : public BaseState
{
public:
/// @brief Constructs a new SaveImportState.
SaveImportState(data::User *user);
static inline std::shared_ptr<SaveImportState> create(data::User *user)
{
return std::make_shared<SaveImportState>(user);
}
static inline std::shared_ptr<SaveImportState> create_and_push(data::User *user)
{
auto newState = SaveImportState::create(user);
StateManager::push_state(newState);
return newState;
}
void update() override;
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 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{};
/// @brief Panel shared by all instances.
static inline std::shared_ptr<ui::SlideOutPanel> sm_slidePanel{};
/// @brief Ensures the menu and panel are initialized correctly.
void initialize_static_members();
/// @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

@@ -91,6 +91,9 @@ class UserOptionState final : public BaseState
/// @brief Deletes all save data for the user.
void delete_all_save_data();
/// @brief Creates and pushes the save import state.
void create_push_save_import();
/// @brief Performs operations to reset static members and marks the state for purging.
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
{
@@ -29,9 +23,18 @@ namespace fs
int64_t saveDataSize{};
int64_t journalSize{};
uint64_t commitID{};
uint8_t saveDataSpaceID{};
} __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

@@ -278,7 +278,8 @@
"0: Alle für `%s` dumpen",
"1: Speicherdaten für `%s` erstellen",
"2: Alle Speicherdaten für `%s` erstellen",
"3: Alle Speicherdaten für `%s` löschen"
"3: Alle Speicherdaten für `%s` löschen",
"4: Speicherdaten für `%s` importieren"
],
"WebDavStrings": [
"0: WebDav erfolgreich gestartet!",

View File

@@ -277,7 +277,8 @@
"0: Dump all for `%s`",
"1: Create Save Data for `%s`",
"2: Create All Save Data for `%s`",
"3: Delete All Save Data for `%s`"
"3: Delete All Save Data for `%s`",
"4: Import Save Data for `%s`"
],
"WebDavStrings": [
"0: WebDav successfully started!",

View File

@@ -278,7 +278,8 @@
"0: Dump all for `%s`",
"1: Create Save Data for `%s`",
"2: Create All Save Data for `%s`",
"3: Delete All Save Data for `%s`"
"3: Delete All Save Data for `%s`",
"4: Import Save Data for `%s`"
],
"WebDavStrings": [
"0: WebDav successfully started!",
@@ -289,4 +290,4 @@
"1: No [B]",
"2: OK [A]"
]
}
}

View File

@@ -278,7 +278,8 @@
"0: Volcar todo para `%s`",
"1: Crear datos guardados para `%s`",
"2: Crear todos los datos guardados para `%s`",
"3: Eliminar todos los datos guardados para `%s`"
"3: Eliminar todos los datos guardados para `%s`",
"4: Importar datos guardados de `%s`"
],
"WebDavStrings": [
"0: ¡WebDav iniciado correctamente!",

View File

@@ -278,7 +278,8 @@
"0: Volcar todo para `%s`",
"1: Crear datos guardados para `%s`",
"2: Crear todos los datos guardados para `%s`",
"3: Eliminar todos los datos guardados para `%s`"
"3: Eliminar todos los datos guardados para `%s`",
"4: Importar datos guardados de `%s`"
],
"WebDavStrings": [
"0: ¡WebDav iniciado correctamente!",

View File

@@ -278,7 +278,8 @@
"0: Exporter tout pour `%s`",
"1: Créer des données sauvegardées pour `%s`",
"2: Créer toutes les données sauvegardées pour `%s`",
"3: Supprimer toutes les données sauvegardées pour `%s`"
"3: Supprimer toutes les données sauvegardées pour `%s`",
"4: Importer les données de sauvegarde pour `%s`"
],
"WebDavStrings": [
"0: WebDav démarré avec succès !",

View File

@@ -278,7 +278,8 @@
"0: Exporter tout pour `%s`",
"1: Créer des données sauvegardées pour `%s`",
"2: Créer toutes les données sauvegardées pour `%s`",
"3: Supprimer toutes les données sauvegardées pour `%s`"
"3: Supprimer toutes les données sauvegardées pour `%s`",
"4: Importer les données de sauvegarde pour `%s`"
],
"WebDavStrings": [
"0: WebDav démarré avec succès !",

View File

@@ -278,7 +278,8 @@
"0: Esporta tutto per `%s`",
"1: Crea dati di salvataggio per `%s`",
"2: Crea tutti i dati di salvataggio per `%s`",
"3: Elimina tutti i dati di salvataggio per `%s`"
"3: Elimina tutti i dati di salvataggio per `%s`",
"4: Importa i dati di salvataggio per `%s`"
],
"WebDavStrings": [
"0: WebDav avviato con successo!",

View File

@@ -278,7 +278,8 @@
"0: `%s` の すべてを ダンプ",
"1: `%s` の セーブデータを 作成",
"2: `%s` の すべての セーブデータを 作成",
"3: `%s` の すべての セーブデータを 削除"
"3: `%s` の すべての セーブデータを 削除",
"4: `%s` のセーブデータをインポート"
],
"WebDavStrings": [
"0: WebDav が 正常に 開始されました!",

View File

@@ -278,7 +278,8 @@
"0: `%s` 의 모든 데이터 덤프",
"1: `%s` 의 저장 데이터 생성",
"2: `%s` 의 모든 저장 데이터 생성",
"3: `%s` 의 모든 저장 데이터 삭제"
"3: `%s` 의 모든 저장 데이터 삭제",
"4: `%s` 의 세이브 데이터를 가져오기"
],
"WebDavStrings": [
"0: WebDav 가 성공적으로 시작되었습니다!",
@@ -289,4 +290,4 @@
"1: 아니요 [B]",
"2: 확인 [A]"
]
}
}

View File

@@ -278,7 +278,8 @@
"0: Dump alles voor `%s`",
"1: Maak opslaggegevens aan voor `%s`",
"2: Maak alle opslaggegevens aan voor `%s`",
"3: Verwijder alle opslaggegevens voor `%s`"
"3: Verwijder alle opslaggegevens voor `%s`",
"4: Savegegevens importeren voor `%s`"
],
"WebDavStrings": [
"0: WebDav succesvol gestart!",

View File

@@ -278,7 +278,8 @@
"0: Despejar tudo para `%s`",
"1: Criar dados guardados para `%s`",
"2: Criar todos os dados guardados para `%s`",
"3: Eliminar todos os dados guardados para `%s`"
"3: Eliminar todos os dados guardados para `%s`",
"4: Importar dados de salvamento para `%s`"
],
"WebDavStrings": [
"0: WebDav iniciado com sucesso!",

View File

@@ -278,7 +278,8 @@
"0: Descartar tudo para `%s`",
"1: Criar dados salvos para `%s`",
"2: Criar todos os dados salvos para `%s`",
"3: Deletar todos os dados salvos para `%s`"
"3: Deletar todos os dados salvos para `%s`",
"4: Importar dados de salvamento para `%s`"
],
"WebDavStrings": [
"0: WebDav iniciado com sucesso!",

View File

@@ -278,7 +278,8 @@
"0: Сбросить всё для `%s`",
"1: Создать данные сохранения для `%s`",
"2: Создать все данные сохранения для `%s`",
"3: Удалить все данные сохранения для `%s`"
"3: Удалить все данные сохранения для `%s`",
"4: Импортировать сохранение для `%s`"
],
"WebDavStrings": [
"0: WebDav успешно запущен!",

View File

@@ -278,7 +278,8 @@
"0: 导出 `%s` 的所有存档",
"1: 为 `%s` 创建存档数据",
"2: 为 `%s` 创建所有存档数据",
"3: 删除 `%s` 的所有存档数据"
"3: 删除 `%s` 的所有存档数据",
"4: 导入 `%s` 的存档数据"
],
"WebDavStrings": [
"0: WebDav 启动成功!",

View File

@@ -278,7 +278,8 @@
"0: 匯出 `%s` 的所有存檔",
"1: 為 `%s` 建立存檔資料",
"2: 為 `%s` 建立所有存檔資料",
"3: 刪除 `%s` 的所有存檔資料"
"3: 刪除 `%s` 的所有存檔資料",
"4: 匯入 `%s` 的存檔資料"
],
"WebDavStrings": [
"0: WebDav 啟動成功!",

View File

@@ -0,0 +1,87 @@
#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)
, m_saveDir()
{
SaveImportState::initialize_static_members();
SaveImportState::initialize_directory_menu();
}
void SaveImportState::update()
{
const bool hasFocus = BaseState::has_focus();
sm_slidePanel->update(hasFocus);
if (!sm_slidePanel->is_open()) { return; }
const bool aPressed = input::button_pressed(HidNpadButton_A);
const bool bPressed = input::button_pressed(HidNpadButton_B);
if (aPressed) { SaveImportState::import_backup(); }
else if (bPressed) { sm_slidePanel->close(); }
else if (sm_slidePanel->is_closed()) { SaveImportState::deactivate_state(); }
}
void SaveImportState::render()
{
const bool hasFocus = BaseState::has_focus();
sm_slidePanel->clear_target();
sm_slidePanel->render(sdl::Texture::Null, hasFocus);
}
void SaveImportState::initialize_static_members()
{
static constexpr int SIZE_PANEL_WIDTH = 512;
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);
sm_slidePanel->push_new_element(sm_saveMenu);
}
void SaveImportState::initialize_directory_menu()
{
const fslib::Path saveDirPath{config::get_working_directory() / "saves"};
m_saveDir.open(saveDirPath);
if (!m_saveDir.is_open()) { return; }
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();
sm_slidePanel->reset();
BaseState::deactivate();
}

View File

@@ -5,6 +5,7 @@
#include "appstates/MainMenuState.hpp"
#include "appstates/ProgressState.hpp"
#include "appstates/SaveCreateState.hpp"
#include "appstates/SaveImportState.hpp"
#include "appstates/TaskState.hpp"
#include "config/config.hpp"
#include "data/data.hpp"
@@ -28,7 +29,8 @@ namespace
BACKUP_ALL,
CREATE_SAVE,
CREATE_ALL_SAVE,
DELETE_ALL_SAVE
DELETE_ALL_SAVE,
IMPORT_SAVE_DATA
};
} // namespace
@@ -64,10 +66,11 @@ void UserOptionState::update()
switch (selected)
{
case BACKUP_ALL: UserOptionState::backup_all(); break;
case CREATE_SAVE: UserOptionState::create_save_create(); break;
case CREATE_ALL_SAVE: UserOptionState::create_all_save_data(); break;
case DELETE_ALL_SAVE: UserOptionState::delete_all_save_data(); break;
case BACKUP_ALL: UserOptionState::backup_all(); break;
case CREATE_SAVE: UserOptionState::create_save_create(); break;
case CREATE_ALL_SAVE: UserOptionState::create_all_save_data(); break;
case DELETE_ALL_SAVE: UserOptionState::delete_all_save_data(); break;
case IMPORT_SAVE_DATA: UserOptionState::create_push_save_import(); break;
}
}
else if (bPressed) { sm_menuPanel->close(); }
@@ -159,6 +162,15 @@ void UserOptionState::delete_all_save_data()
ConfirmTask::create_push_fade(queryString, true, tasks::useroptions::delete_all_save_data_for_user, m_dataStruct);
}
void UserOptionState::create_push_save_import()
{
const fslib::Path saveDirPath{config::get_working_directory() / "saves"};
if (!fs::directory_has_contents(saveDirPath)) { return; }
sm_menuPanel->hide();
SaveImportState::create_and_push(m_user);
}
void UserOptionState::deactivate_state()
{
sm_menuPanel->clear_elements();

View File

@@ -5,6 +5,7 @@
#include "fs/save_data_functions.hpp"
#include "fs/save_mount.hpp"
#include "fslib.hpp"
#include "logging/logger.hpp"
namespace
{
@@ -17,20 +18,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,
.ownerID = extraData.owner_id,
.timestamp = extraData.timestamp,
.flags = extraData.flags,
.saveDataSize = extraData.data_size,
.journalSize = extraData.journal_size,
.commitID = extraData.commit_id,
.saveDataSpaceID = saveInfo->save_data_space_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

@@ -33,6 +33,7 @@ void logger::initialize()
void logger::log(const char *format, ...) noexcept
{
static std::mutex logLock{};
static const fslib::Path logPath{PATH_JKSV_LOG};
std::array<char, VA_BUFFER_SIZE> vaBuffer = {0};
std::va_list vaList{};
@@ -46,7 +47,6 @@ void logger::log(const char *format, ...) noexcept
std::strftime(timeDateBuffer.data(), TIME_BUFFER, "[%c] ", &localTime);
std::lock_guard<std::mutex> logGuard(logLock);
const fslib::Path logPath{PATH_JKSV_LOG};
fslib::File logFile(logPath, FsOpenMode_Append);
if (logFile.is_open() && logFile.get_size() >= SIZE_LOG_LIMIT)
{

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

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

@@ -0,0 +1,137 @@
#include "tasks/saveimport.hpp"
#include "appstates/MainMenuState.hpp"
#include "appstates/SaveImportState.hpp"
#include "error.hpp"
#include "fs/fs.hpp"
#include "fslib.hpp"
#include "strings/strings.hpp"
#include "stringutil.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 || metaData.revision < 1) { TASK_FINISH_RETURN(task); }
const bool saveExists = test_for_save(user, metaData);
if (!saveExists)
{
// Gonna borrow this.
const char *statusFormat = strings::get_by_name(strings::names::USEROPTION_STATUS, 0);
const std::string hexID = stringutil::get_formatted_string("%016llX", metaData.applicationID);
std::string status = stringutil::get_formatted_string(statusFormat, hexID.c_str());
task->set_status(status);
fs::create_save_data_for(user, metaData);
user->clear_data_entries();
user->load_user_data();
MainMenuState::refresh_view_states();
}
const FsSaveDataInfo *saveInfo = user->get_save_info_by_id(metaData.applicationID);
if (error::is_null(saveInfo)) { TASK_FINISH_RETURN(task); }
{
fs::ScopedSaveMount saveMount{fs::DEFAULT_SAVE_MOUNT, saveInfo};
if (!saveMount.is_open()) { TASK_FINISH_RETURN(task); }
const bool isDir = fslib::directory_exists(target);
if (isDir) { fs::copy_directory_commit(target, fs::DEFAULT_SAVE_ROOT, metaData.journalSize, task); }
else
{
fs::MiniUnzip unzip{target};
if (!unzip.is_open()) { TASK_FINISH_RETURN(task); }
fs::copy_zip_to_directory(unzip, fs::DEFAULT_SAVE_ROOT, metaData.journalSize, task);
}
}
task->complete();
}
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;
}