Implement processing save data meta files.

This commit is contained in:
J-D-K
2025-06-12 20:46:55 -04:00
parent 8dd16c12bc
commit 71b4c1fbfa
10 changed files with 142 additions and 17 deletions

View File

@@ -6,12 +6,13 @@
namespace fs
{
/// @brief This is the magic value written to the beginning.
constexpr uint64_t SAVE_META_MAGIC = 0x56534B4A;
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 = ".jksv_save_meta.bin";
/// @brief This struct is for storing the data necessary to restore saves to a different console.
/// @note Some of this data isn't really needed. Just rather be safe than sorry.
typedef struct __attribute__((packed))
{
/// @brief Magic.
@@ -32,8 +33,8 @@ namespace fs
int64_t m_journalSize;
/// @brief Maximum journaling size.
int64_t m_journalSizeMax;
/// @brief Total size of the files in the backup. For ZIP, this is uncompressed.
uint64_t m_totalSaveSize;
/// @brief Total size of the container upon backup.
int64_t m_totalSaveSize;
} SaveMetaData;
/// @brief Didn't feel like a whole new file just for this. Fills an fs::SaveMetaData struct using the passed TitleInfo pointer.
@@ -41,4 +42,10 @@ namespace fs
/// @param info Reference to FsSaveDataInfo struct to use to fill out the meta struct.
/// @param meta Struct to fill.
void create_save_meta_data(data::TitleInfo *titleInfo, const FsSaveDataInfo *saveInfo, SaveMetaData &meta);
/// @brief Processes the save meta data and applies it to the passed saveInfo pointer.
/// @param saveInfo FsSaveDataInfo to apply the meta to.
/// @param meta Save meta data to apply.
bool process_save_meta_data(const FsSaveDataInfo *saveInfo, SaveMetaData &meta);
} // namespace fs

View File

@@ -23,6 +23,7 @@ namespace strings
static constexpr std::string_view BACKUP_MENU = "BackupMenu";
static constexpr std::string_view COPYING_FILES = "CopyingFiles";
static constexpr std::string_view BACKUPMENU_CONFIRMATIONS = "BackupMenuConfirmations";
static constexpr std::string_view BACKUPMENU_STATUS = "BackupMenuStatus";
static constexpr std::string_view BACKUPMENU_POPS = "BackupMenuPops";
static constexpr std::string_view DELETING_FILES = "DeletingFiles";
static constexpr std::string_view KEYBOARD_STRINGS = "KeyboardStrings";

View File

@@ -95,6 +95,9 @@
"Are you sure you really want to restore #%s#?",
"Are you sure you really want to delete #%s#?"
],
"BackupMenuStatus": [
"Processing save data meta file..."
],
"BackupMenuPops": [
"System save data cannot be restored to the system!"
],
@@ -142,7 +145,7 @@
"Deleting all backups for #%s#.",
"Resetting save data for #%s#.",
"Deleting #%s#'s save data for #%s#...",
"Extending #%s#'s save data for #%s#..."
"Extending `%s`'s save data for #%s#..."
],
"TitleOptionPops": [
"All backups deleted for `%s`!",

View File

@@ -185,6 +185,8 @@ void BackupMenuState::update(void)
}
std::shared_ptr<TargetStruct> dataStruct(new TargetStruct);
dataStruct->m_user = m_user;
dataStruct->m_titleInfo = m_titleInfo;
dataStruct->m_targetPath = m_directoryPath / m_directoryListing[selected];
dataStruct->m_journalSize = m_titleInfo->get_journal_size(m_saveType);
dataStruct->m_spawningState = this;
@@ -234,7 +236,6 @@ void BackupMenuState::update(void)
// Update title scrolling.
m_titleScroll.update(hasFocus);
// Update panel.
sm_slidePanel->update(hasFocus);
// This state bypasses the Slideout panel's normal behavior because it kind of has to.
@@ -447,6 +448,15 @@ static void overwrite_backup(sys::ProgressTask *task, std::shared_ptr<TargetStru
static void restore_backup(sys::ProgressTask *task, std::shared_ptr<TargetStruct> dataStruct)
{
// Going to need this later.
FsSaveDataInfo *saveInfo = dataStruct->m_user->get_save_info_by_id(dataStruct->m_titleInfo->get_application_id());
if (!saveInfo)
{
// To do: Log this.
task->finished();
return;
}
// Wipe the save root first. Forgot to commit the changes before. Oops.
if (!fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT) ||
!fslib::commit_data_to_file_system(fs::DEFAULT_SAVE_MOUNT))
@@ -460,6 +470,19 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptr<TargetStruct
if (fslib::directory_exists(dataStruct->m_targetPath))
{
{
// Process the save meta if it's there.
fs::SaveMetaData meta = {0};
fslib::Path saveMetaPath = dataStruct->m_targetPath / fs::NAME_SAVE_META;
fslib::File saveMetaFile(saveMetaPath, FsOpenMode_Read);
if (saveMetaFile && saveMetaFile.read(&meta, sizeof(fs::SaveMetaData)) == sizeof(fs::SaveMetaData))
{
// Set this so at least the user knows something is going on. Extending saves can take a decent chunk of time.
task->set_status(strings::get_by_name(strings::names::BACKUPMENU_STATUS, 0));
fs::process_save_meta_data(saveInfo, meta);
}
}
fs::copy_directory(dataStruct->m_targetPath,
fs::DEFAULT_SAVE_ROOT,
dataStruct->m_journalSize,
@@ -477,6 +500,19 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptr<TargetStruct
task->finished();
return;
}
{
// I'm not sure if this is risky or not. Guess we'll find out...
fs::SaveMetaData meta = {0};
// The locate_file_in_zip should pinpoint the meta file.
if (fs::locate_file_in_zip(targetZip, fs::NAME_SAVE_META) && unzOpenCurrentFile(targetZip) == UNZ_OK &&
unzReadCurrentFile(targetZip, &meta, sizeof(fs::SaveMetaData)) == sizeof(fs::SaveMetaData))
{
task->set_status(strings::get_by_name(strings::names::BACKUPMENU_STATUS, 0));
fs::process_save_meta_data(saveInfo, meta);
}
}
fs::copy_zip_to_directory(targetZip,
fs::DEFAULT_SAVE_ROOT,
dataStruct->m_journalSize,

View File

@@ -435,7 +435,7 @@ static void extend_save_data(sys::Task *task, std::shared_ptr<TargetStruct> data
// Get how much to extend.
char buffer[5] = {0};
// No default. Maybe change this later?
if (!keyboard::get_input(SwkbdType_NumPad, {}, keyboardString, buffer, 4))
if (!keyboard::get_input(SwkbdType_NumPad, {}, keyboardString, buffer, 5))
{
task->finished();
return;

View File

@@ -377,9 +377,6 @@ static void load_save_data_info(void)
s_titleInfoMap.emplace(applicationID, applicationID);
}
// Grab a reference to the Title info so we don't try to load stats for titles that don't really exist.
data::TitleInfo &titleInfo = s_titleInfoMap.at(applicationID);
// I feel weird allcating space for this even if it's not used, but whatever.
PdmPlayStatistics stats = {0};
// This should be an OKish way to filter out system titles...

View File

@@ -1,9 +1,28 @@
#include "fs/SaveMetaData.hpp"
#include "fs/directory_functions.hpp"
#include "fs/save_data_functions.hpp"
#include "fs/save_mount.hpp"
#include "fslib.hpp"
#include "logger.hpp"
namespace
{
/// @brief This is the string template for errors here.
constexpr std::string_view STRING_ERROR_TEMPLATE = "Error processing save meta for %016llX: %s";
} // namespace
void fs::create_save_meta_data(data::TitleInfo *titleInfo, const FsSaveDataInfo *saveInfo, fs::SaveMetaData &meta)
{
// I'm assuming this is opened and good because we're making a backup.
int64_t containerSize = 0;
if (!fslib::get_device_total_space(fs::DEFAULT_SAVE_ROOT, containerSize))
{
// Log and fall back to file size count.
logger::log("Error getting save container's total size. Defaulting to file size count.");
containerSize = fs::get_directory_total_size(fs::DEFAULT_SAVE_ROOT);
}
// Fill out the meta struct.
meta = {.m_magic = fs::SAVE_META_MAGIC,
.m_applicationID = titleInfo->get_application_id(),
.m_saveType = saveInfo->save_data_type,
@@ -13,5 +32,53 @@ void fs::create_save_meta_data(data::TitleInfo *titleInfo, const FsSaveDataInfo
.m_saveDataSizeMax = titleInfo->get_journal_size_max(saveInfo->save_data_type),
.m_journalSize = titleInfo->get_journal_size(saveInfo->save_data_type),
.m_journalSizeMax = titleInfo->get_journal_size_max(saveInfo->save_data_type),
.m_totalSaveSize = fs::get_directory_total_size(fs::DEFAULT_SAVE_ROOT)};
.m_totalSaveSize = containerSize};
}
bool fs::process_save_meta_data(const FsSaveDataInfo *saveInfo, SaveMetaData &meta)
{
if (meta.m_magic != SAVE_META_MAGIC || saveInfo->application_id != meta.m_applicationID)
{
logger::log(STRING_ERROR_TEMPLATE.data(), meta.m_applicationID, "Invalid magic or mismatched application ID.");
return false;
}
// To do: I'm assuming this function will only be called once the save container is already opened here...
int64_t totalSpace = 0;
if (!fslib::get_device_total_space(fs::DEFAULT_SAVE_ROOT, totalSpace))
{
logger::log(STRING_ERROR_TEMPLATE.data(), meta.m_applicationID, "get_device_total_space");
return false;
}
if (totalSpace >= meta.m_totalSaveSize)
{
// Gonna return true here, because there's no need to do anything to the container.
return true;
}
// First we need to temporarily close the save.
if (!fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT))
{
logger::log(STRING_ERROR_TEMPLATE.data(), meta.m_applicationID, "close_file_system");
// We can't go any further at this point. You can't extend the save while it's open.
return false;
}
// This is where we finally extend the container. Using the large of the two journal sizes
if (!fs::extend_save_data(saveInfo, meta.m_totalSaveSize, meta.m_journalSizeMax))
{
logger::log(STRING_ERROR_TEMPLATE.data(), meta.m_applicationID, "Error extending save data container.");
return false;
}
// If this fails, we're super screwed.
if (!fslib::open_save_data_with_save_info(fs::DEFAULT_SAVE_MOUNT, *saveInfo))
{
logger::log(STRING_ERROR_TEMPLATE.data(), meta.m_applicationID, "open_save_data");
return false;
}
// More later if needed.
return true;
}

View File

@@ -111,22 +111,39 @@ void fs::copy_file(const fslib::Path &source,
journalCount = 0;
// Close destination file, commit.
destinationFile.close();
fslib::commit_data_to_file_system(commitDevice);
// Need to try to commit before going over the journaling space limit.
if (!fslib::commit_data_to_file_system(commitDevice))
{
logger::log("First, %s", fslib::get_error_string());
// I guess break the loop here?
break;
}
// Reopen and seek to previous position since we created it with a size earlier.
destinationFile.open(destination, FsOpenMode_Write);
destinationFile.seek(writeCount, destinationFile.BEGINNING);
destinationFile.open(destination, FsOpenMode_Append);
}
// Write to destination
destinationFile.write(localBuffer.get(), readCount);
// Update write and journal count.
writeCount += readCount;
journalCount += readCount;
// Update task if passed.
if (task)
{
task->update_current(static_cast<double>(writeCount));
}
}
// One last commit for good luck.
if (!fslib::commit_data_to_file_system(commitDevice))
{
logger::log("Second, %s", fslib::get_error_string());
}
// Wait for read thread and free it.
readThread.join();
}

View File

@@ -13,6 +13,7 @@ bool fs::create_save_data_for(data::User *targetUser, data::TitleInfo *titleInfo
.save_data_rank = FsSaveDataRank_Primary,
.save_data_index = 0};
// For just creating it, we're using the safe baseline values.
FsSaveDataCreationInfo saveCreation = {
.save_data_size = titleInfo->get_save_data_size(targetUser->get_account_save_type()),
.journal_size = titleInfo->get_journal_size(targetUser->get_account_save_type()),

View File

@@ -40,19 +40,16 @@ void ui::TextScroll::create(std::string_view text,
// Set the X coordinate to 8 and make sure this knows it needs to scroll.
m_x = 8;
m_textScrolling = true;
logger::log("Scrolling needed?");
}
else if (center)
{
// Just center it.
m_x = (availableWidth / 2) - (m_textWidth / 2);
logger::log("Centered.");
}
else
{
// Just set this to 8. To do: Figure out how to make this cleaner.
m_x = 8;
logger::log("Aligned.");
}
}
@@ -61,7 +58,6 @@ void ui::TextScroll::update(bool hasFocus)
// I don't think needs to care about having focus.
if (m_textScrolling && m_scrollTimer.is_triggered())
{
logger::log("Text scroll triggered?");
m_x -= 2;
m_textScrollTriggered = true;
}