mirror of
https://github.com/J-D-K/JKSV.git
synced 2026-08-28 13:34:05 -05:00
Merge pull request #302 from J-D-K/dev
Add Generic SaveImportState/save importing
This commit is contained in:
72
include/appstates/SaveImportState.hpp
Normal file
72
include/appstates/SaveImportState.hpp
Normal 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();
|
||||
};
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
8
include/tasks/saveimport.hpp
Normal file
8
include/tasks/saveimport.hpp
Normal 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);
|
||||
}
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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]"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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 !",
|
||||
|
||||
@@ -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 !",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -278,7 +278,8 @@
|
||||
"0: `%s` の すべてを ダンプ",
|
||||
"1: `%s` の セーブデータを 作成",
|
||||
"2: `%s` の すべての セーブデータを 作成",
|
||||
"3: `%s` の すべての セーブデータを 削除"
|
||||
"3: `%s` の すべての セーブデータを 削除",
|
||||
"4: `%s` のセーブデータをインポート"
|
||||
],
|
||||
"WebDavStrings": [
|
||||
"0: WebDav が 正常に 開始されました!",
|
||||
|
||||
@@ -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]"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -278,7 +278,8 @@
|
||||
"0: Сбросить всё для `%s`",
|
||||
"1: Создать данные сохранения для `%s`",
|
||||
"2: Создать все данные сохранения для `%s`",
|
||||
"3: Удалить все данные сохранения для `%s`"
|
||||
"3: Удалить все данные сохранения для `%s`",
|
||||
"4: Импортировать сохранение для `%s`"
|
||||
],
|
||||
"WebDavStrings": [
|
||||
"0: WebDav успешно запущен!",
|
||||
|
||||
@@ -278,7 +278,8 @@
|
||||
"0: 导出 `%s` 的所有存档",
|
||||
"1: 为 `%s` 创建存档数据",
|
||||
"2: 为 `%s` 创建所有存档数据",
|
||||
"3: 删除 `%s` 的所有存档数据"
|
||||
"3: 删除 `%s` 的所有存档数据",
|
||||
"4: 导入 `%s` 的存档数据"
|
||||
],
|
||||
"WebDavStrings": [
|
||||
"0: WebDav 启动成功!",
|
||||
|
||||
@@ -278,7 +278,8 @@
|
||||
"0: 匯出 `%s` 的所有存檔",
|
||||
"1: 為 `%s` 建立存檔資料",
|
||||
"2: 為 `%s` 建立所有存檔資料",
|
||||
"3: 刪除 `%s` 的所有存檔資料"
|
||||
"3: 刪除 `%s` 的所有存檔資料",
|
||||
"4: 匯入 `%s` 的存檔資料"
|
||||
],
|
||||
"WebDavStrings": [
|
||||
"0: WebDav 啟動成功!",
|
||||
|
||||
87
source/appstates/SaveImportState.cpp
Normal file
87
source/appstates/SaveImportState.cpp
Normal 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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
137
source/tasks/saveimport.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user