diff --git a/include/fs/SaveMetaData.hpp b/include/fs/SaveMetaData.hpp index d228d1e..5e71974 100644 --- a/include/fs/SaveMetaData.hpp +++ b/include/fs/SaveMetaData.hpp @@ -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 diff --git a/include/strings.hpp b/include/strings.hpp index 459f521..e5ea2f7 100644 --- a/include/strings.hpp +++ b/include/strings.hpp @@ -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"; diff --git a/romfs/Text/ENUS.json b/romfs/Text/ENUS.json index 8ad9ddf..f58fb49 100644 --- a/romfs/Text/ENUS.json +++ b/romfs/Text/ENUS.json @@ -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`!", diff --git a/source/appstates/BackupMenuState.cpp b/source/appstates/BackupMenuState.cpp index ac7b2e1..f60ab5a 100644 --- a/source/appstates/BackupMenuState.cpp +++ b/source/appstates/BackupMenuState.cpp @@ -185,6 +185,8 @@ void BackupMenuState::update(void) } std::shared_ptr 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 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_ptrm_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_ptrfinished(); 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, diff --git a/source/appstates/TitleOptionState.cpp b/source/appstates/TitleOptionState.cpp index 55ca2d5..4d40907 100644 --- a/source/appstates/TitleOptionState.cpp +++ b/source/appstates/TitleOptionState.cpp @@ -435,7 +435,7 @@ static void extend_save_data(sys::Task *task, std::shared_ptr 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; diff --git a/source/data/data.cpp b/source/data/data.cpp index 927c6a2..4140249 100644 --- a/source/data/data.cpp +++ b/source/data/data.cpp @@ -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... diff --git a/source/fs/SaveMetaData.cpp b/source/fs/SaveMetaData.cpp index 6981255..d020c67 100644 --- a/source/fs/SaveMetaData.cpp +++ b/source/fs/SaveMetaData.cpp @@ -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; } diff --git a/source/fs/io.cpp b/source/fs/io.cpp index 1d041e7..9b3c4bd 100644 --- a/source/fs/io.cpp +++ b/source/fs/io.cpp @@ -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(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(); } diff --git a/source/fs/save_data_functions.cpp b/source/fs/save_data_functions.cpp index babab7c..52c8247 100644 --- a/source/fs/save_data_functions.cpp +++ b/source/fs/save_data_functions.cpp @@ -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()), diff --git a/source/ui/TextScroll.cpp b/source/ui/TextScroll.cpp index 319b66a..b965664 100644 --- a/source/ui/TextScroll.cpp +++ b/source/ui/TextScroll.cpp @@ -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; }