mirror of
https://github.com/J-D-K/JKSV.git
synced 2026-04-25 07:57:04 -05:00
Basic, core stuff working AFAIK.
This commit is contained in:
parent
ecbeff1856
commit
39747a0031
|
|
@ -1 +1 @@
|
|||
Subproject commit ab3074859bf0b0d9cdc075395a70d23cd544ed86
|
||||
Subproject commit fdb613fce0a104699a172f59f15c1b45c0b9fa77
|
||||
|
|
@ -15,8 +15,9 @@ namespace data
|
|||
TitleInfo(uint64_t applicationID);
|
||||
|
||||
/// @brief Initializes a TitleInfo instance using external (cached) NsApplicationControlData
|
||||
/// @param applicationID Application ID of the title loaded from cache.
|
||||
/// @param controlData Reference to the control data to init from.
|
||||
TitleInfo(NsApplicationControlData &controlData);
|
||||
TitleInfo(uint64_t applicationID, NsApplicationControlData &controlData);
|
||||
|
||||
/// @brief Returns the application ID of the title.
|
||||
/// @return Title's application ID.
|
||||
|
|
@ -88,7 +89,7 @@ namespace data
|
|||
uint64_t m_applicationID = 0;
|
||||
|
||||
/// @brief This contains the NACP and the icon.
|
||||
NsApplicationControlData m_data = {0};
|
||||
NsApplicationControlData m_data;
|
||||
|
||||
/// @brief Saves whether or not the title has control data.
|
||||
bool m_hasData = false;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ namespace fs
|
|||
/// @brief This is the magic value written to the beginning.
|
||||
constexpr uint64_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.
|
||||
typedef struct __attribute__((packed))
|
||||
{
|
||||
|
|
@ -32,4 +35,10 @@ namespace fs
|
|||
/// @brief Total size of the files in the backup. For ZIP, this is uncompressed.
|
||||
uint64_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.
|
||||
/// @param titleInfo TitleInfo instance to use to create the meta.
|
||||
/// @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);
|
||||
} // namespace fs
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ namespace fs
|
|||
static constexpr std::string_view DEFAULT_SAVE_MOUNT = "save";
|
||||
|
||||
/// @brief Same as above, but as a root directory.
|
||||
static constexpr std::string_view DEFAULT_SAVE_PATH = "save:/";
|
||||
static constexpr std::string_view DEFAULT_SAVE_ROOT = "save:/";
|
||||
} // namespace fs
|
||||
|
|
|
|||
|
|
@ -26,12 +26,24 @@ namespace fs
|
|||
std::string_view commitDevice,
|
||||
sys::ProgressTask *Task = nullptr);
|
||||
|
||||
/// @brief Writes data to infoOut to help clean up somes stuff.
|
||||
/// @return Filled, complete zip_fileinfo struct.
|
||||
zip_fileinfo create_zip_fileinfo(void);
|
||||
/// @brief Gets a filled zip_fileinfo struct.
|
||||
/// @param info Reference to the info struct to fill.
|
||||
void create_zip_fileinfo(zip_fileinfo &info);
|
||||
|
||||
/// @brief Returns whether or not zip has files inside.
|
||||
/// @param zipPath Path to zip to check.
|
||||
/// @return True if at least one file is found. False if none.
|
||||
bool zip_has_contents(const fslib::Path &zipPath);
|
||||
|
||||
/// @brief Attempts to locate a file in a zip file. If it's found, the current file is set to it.
|
||||
/// @param zip Zip file to search.
|
||||
/// @param name
|
||||
/// @return True if the file is found. False if it's not.
|
||||
bool locate_file_in_zip(unzFile zip, std::string_view name);
|
||||
|
||||
/// @brief Gets the total uncompressed size of the files inside the zip file.
|
||||
/// @param zip Zip file to get the total size of.
|
||||
/// @return Total size of the zip file passed.
|
||||
/// @note unzFile passed is set to the first file afterwards.
|
||||
uint64_t get_zip_total_size(unzFile zip);
|
||||
} // namespace fs
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "ui/PopMessageManager.hpp"
|
||||
#include <switch.h>
|
||||
|
||||
// Normally I try to avoid C macros in C++, but this cleans stuff up nicely.
|
||||
#define ABORT_ON_FAILURE(x) \
|
||||
if (!x) \
|
||||
{ \
|
||||
|
|
@ -19,8 +20,13 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
/// @brief Build month.
|
||||
constexpr uint8_t BUILD_MON = 5;
|
||||
|
||||
/// @brief Build day.
|
||||
constexpr uint8_t BUILD_DAY = 31;
|
||||
|
||||
/// @brief Year.
|
||||
constexpr uint16_t BUILD_YEAR = 2025;
|
||||
} // namespace
|
||||
|
||||
|
|
@ -44,7 +50,7 @@ JKSV::JKSV(void)
|
|||
// This doesn't rely on stdio or anything.
|
||||
logger::initialize();
|
||||
|
||||
// Need to init RomFS here for now until I update FsLib to take care of this.
|
||||
// Need to init RomFS here for now until I update FsLib to take care of this. Never mind. That isn't going to happen.
|
||||
ABORT_ON_FAILURE(initialize_service(romfsInit, "RomFS"));
|
||||
|
||||
// Let FsLib take care of calls to SDMC instead of fs_dev
|
||||
|
|
@ -55,7 +61,7 @@ JKSV::JKSV(void)
|
|||
ABORT_ON_FAILURE(sdl::text::initialize());
|
||||
|
||||
// Services.
|
||||
// Using administrator so JKSV can still run in Applet mode.
|
||||
// Using administrator so JKSV can still run in Applet mode, barely.
|
||||
ABORT_ON_FAILURE(initialize_service(accountInitialize, "Account", AccountServiceType_Administrator));
|
||||
ABORT_ON_FAILURE(initialize_service(nsInitialize, "NS"));
|
||||
ABORT_ON_FAILURE(initialize_service(pdmqryInitialize, "PDMQry"));
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@
|
|||
#include "ui/TextScroll.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace
|
||||
{
|
||||
/// @brief This is the length allotted for naming backups.
|
||||
constexpr size_t SIZE_BACKUP_NAME_LENGTH = 0x80;
|
||||
} // namespace
|
||||
|
||||
// This struct is used to pass data to Restore, Delete, and upload.
|
||||
struct TargetStruct
|
||||
{
|
||||
|
|
@ -72,7 +78,7 @@ BackupMenuState::BackupMenuState(data::User *user, data::TitleInfo *titleInfo, F
|
|||
sm_slidePanel->push_new_element(std::make_shared<ui::TextScroll>(panelString, 22, sm_panelWidth, 8, colors::WHITE));
|
||||
|
||||
|
||||
fslib::Directory saveCheck(fs::DEFAULT_SAVE_PATH);
|
||||
fslib::Directory saveCheck(fs::DEFAULT_SAVE_ROOT);
|
||||
m_saveHasData = saveCheck.get_count() > 0;
|
||||
|
||||
BackupMenuState::refresh();
|
||||
|
|
@ -88,11 +94,11 @@ void BackupMenuState::update(void)
|
|||
if (input::button_pressed(HidNpadButton_A) && sm_backupMenu->get_selected() == 0 && m_saveHasData)
|
||||
{
|
||||
// get name for backup.
|
||||
char backupName[0x81] = {0};
|
||||
char backupName[SIZE_BACKUP_NAME_LENGTH + 1] = {0};
|
||||
|
||||
// Set backup to default.
|
||||
std::snprintf(backupName,
|
||||
0x80,
|
||||
SIZE_BACKUP_NAME_LENGTH,
|
||||
"%s - %s",
|
||||
m_user->get_path_safe_nickname(),
|
||||
stringutil::get_date_string().c_str());
|
||||
|
|
@ -102,15 +108,15 @@ void BackupMenuState::update(void)
|
|||
backupName,
|
||||
strings::get_by_name(strings::names::KEYBOARD_STRINGS, 0),
|
||||
backupName,
|
||||
0x80))
|
||||
SIZE_BACKUP_NAME_LENGTH))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// To do: This isn't a good way to check for this... Check to make sure zip has zip extension.
|
||||
if (config::get_by_key(config::keys::EXPORT_TO_ZIP) && std::strstr(backupName, ".zip") == NULL)
|
||||
{
|
||||
// To do: I should check this.
|
||||
std::strcat(backupName, ".zip");
|
||||
// I guess this is a safer way to accomplish this?
|
||||
std::snprintf(backupName, SIZE_BACKUP_NAME_LENGTH, "%s.zip", backupName);
|
||||
}
|
||||
else if (!config::get_by_key(config::keys::EXPORT_TO_ZIP) && !std::strstr(backupName, ".zip") &&
|
||||
!fslib::directory_exists(m_directoryPath / backupName) &&
|
||||
|
|
@ -127,6 +133,7 @@ void BackupMenuState::update(void)
|
|||
}
|
||||
else if (input::button_pressed(HidNpadButton_A) && sm_backupMenu->get_selected() == 0 && !m_saveHasData)
|
||||
{
|
||||
// This just makes the little no save data found pop up.
|
||||
ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
strings::get_by_name(strings::names::POP_MESSAGES_BACKUP_MENU, 0));
|
||||
}
|
||||
|
|
@ -290,18 +297,16 @@ static void create_new_backup(sys::ProgressTask *task,
|
|||
{
|
||||
// SaveMeta
|
||||
FsSaveDataInfo *saveInfo = user->get_save_info_by_id(titleInfo->get_application_id());
|
||||
if (!saveInfo)
|
||||
{
|
||||
logger::log("Error retrieving save data information for %016lX.", titleInfo->get_application_id());
|
||||
task->finished();
|
||||
return;
|
||||
}
|
||||
|
||||
// I got tired of typing out the cast.
|
||||
fs::SaveMetaData saveMeta = {.m_magic = fs::SAVE_META_MAGIC,
|
||||
.m_applicationID = titleInfo->get_application_id(),
|
||||
.m_saveType = saveInfo->save_data_type,
|
||||
.m_saveRank = saveInfo->save_data_rank,
|
||||
.m_saveSpaceID = saveInfo->save_data_space_id,
|
||||
.m_saveDataSize = titleInfo->get_save_data_size(saveInfo->save_data_type),
|
||||
.m_saveDataSizeMax = titleInfo->get_save_data_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_PATH)};
|
||||
fs::SaveMetaData saveMeta;
|
||||
fs::create_save_meta_data(titleInfo, saveInfo, saveMeta);
|
||||
|
||||
// This extension search is lazy and needs to be revised.
|
||||
if (config::get_by_key(config::keys::EXPORT_TO_ZIP) || std::strstr(targetPath.c_string(), "zip"))
|
||||
|
|
@ -315,11 +320,12 @@ static void create_new_backup(sys::ProgressTask *task,
|
|||
}
|
||||
|
||||
// Data for save meta.
|
||||
zip_fileinfo saveMetaInfo = fs::create_zip_fileinfo();
|
||||
zip_fileinfo saveMetaInfo;
|
||||
fs::create_zip_fileinfo(saveMetaInfo);
|
||||
|
||||
// Write meta to zip.
|
||||
int zipError = zipOpenNewFileInZip64(newBackup,
|
||||
".jksv_save_meta.bin",
|
||||
fs::NAME_SAVE_META.data(),
|
||||
&saveMetaInfo,
|
||||
NULL,
|
||||
0,
|
||||
|
|
@ -335,20 +341,20 @@ static void create_new_backup(sys::ProgressTask *task,
|
|||
zipCloseFileInZip(newBackup);
|
||||
}
|
||||
|
||||
fs::copy_directory_to_zip(fs::DEFAULT_SAVE_PATH, newBackup, task);
|
||||
fs::copy_directory_to_zip(fs::DEFAULT_SAVE_ROOT, newBackup, task);
|
||||
zipClose(newBackup, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
fslib::Path saveMetaPath = targetPath / ".jksv_save_meta.bin";
|
||||
fslib::File saveMetaOut(targetPath, FsOpenMode_Create | FsOpenMode_Write, sizeof(fs::SaveMetaData));
|
||||
fslib::Path saveMetaPath = targetPath / fs::NAME_SAVE_META;
|
||||
fslib::File saveMetaOut(saveMetaPath, FsOpenMode_Create | FsOpenMode_Write, sizeof(fs::SaveMetaData));
|
||||
if (saveMetaOut)
|
||||
{
|
||||
saveMetaOut.write(&saveMeta, sizeof(fs::SaveMetaData));
|
||||
}
|
||||
}
|
||||
fs::copy_directory(fs::DEFAULT_SAVE_PATH, targetPath, 0, {}, task);
|
||||
fs::copy_directory(fs::DEFAULT_SAVE_ROOT, targetPath, 0, {}, task);
|
||||
}
|
||||
spawningState->refresh();
|
||||
task->finished();
|
||||
|
|
@ -356,6 +362,9 @@ static void create_new_backup(sys::ProgressTask *task,
|
|||
|
||||
static void overwrite_backup(sys::ProgressTask *task, std::shared_ptr<TargetStruct> dataStruct)
|
||||
{
|
||||
// Might need this later.
|
||||
FsSaveDataInfo *saveInfo = dataStruct->m_user->get_save_info_by_id(dataStruct->m_titleInfo->get_application_id());
|
||||
|
||||
// directory_exists can also be used to check if the target is a directory.
|
||||
if (fslib::directory_exists(dataStruct->m_targetPath) &&
|
||||
!fslib::delete_directory_recursively(dataStruct->m_targetPath))
|
||||
|
|
@ -373,23 +382,60 @@ static void overwrite_backup(sys::ProgressTask *task, std::shared_ptr<TargetStru
|
|||
return;
|
||||
}
|
||||
|
||||
// Gonna need a new save meta.
|
||||
fs::SaveMetaData meta;
|
||||
fs::create_save_meta_data(dataStruct->m_titleInfo, saveInfo, meta);
|
||||
|
||||
if (std::strcmp("zip", dataStruct->m_targetPath.get_extension()))
|
||||
{
|
||||
zipFile backupZip = zipOpen64(dataStruct->m_targetPath.c_string(), APPEND_STATUS_CREATE);
|
||||
fs::copy_directory_to_zip(fs::DEFAULT_SAVE_PATH, backupZip, task);
|
||||
|
||||
// Need the zip info for the meta.
|
||||
zip_fileinfo saveMetaInfo;
|
||||
fs::create_zip_fileinfo(saveMetaInfo);
|
||||
|
||||
int zipError = zipOpenNewFileInZip64(backupZip,
|
||||
fs::NAME_SAVE_META.data(),
|
||||
&saveMetaInfo,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
0,
|
||||
NULL,
|
||||
Z_DEFLATED,
|
||||
config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL),
|
||||
0);
|
||||
if (zipError == ZIP_OK)
|
||||
{
|
||||
zipWriteInFileInZip(backupZip, &meta, sizeof(fs::SaveMetaData));
|
||||
zipCloseFileInZip(backupZip);
|
||||
}
|
||||
|
||||
fs::copy_directory_to_zip(fs::DEFAULT_SAVE_ROOT, backupZip, task);
|
||||
zipClose(backupZip, NULL);
|
||||
} // I hope this check works for making sure this is a folder
|
||||
else if (dataStruct->m_targetPath.get_extension() == nullptr && fslib::create_directory(dataStruct->m_targetPath))
|
||||
{
|
||||
fs::copy_directory(fs::DEFAULT_SAVE_PATH, dataStruct->m_targetPath, 0, {}, task);
|
||||
// Write this quick.
|
||||
{
|
||||
fslib::Path metaPath = dataStruct->m_targetPath / fs::NAME_SAVE_META;
|
||||
fslib::File metaFile(metaPath, FsOpenMode_Create | FsOpenMode_Write, sizeof(fs::SaveMetaData));
|
||||
if (metaFile)
|
||||
{
|
||||
metaFile.write(&meta, sizeof(fs::SaveMetaData));
|
||||
}
|
||||
}
|
||||
|
||||
fs::copy_directory(fs::DEFAULT_SAVE_ROOT, dataStruct->m_targetPath, 0, {}, task);
|
||||
}
|
||||
task->finished();
|
||||
}
|
||||
|
||||
static void restore_backup(sys::ProgressTask *task, std::shared_ptr<TargetStruct> dataStruct)
|
||||
{
|
||||
// Wipe the save root first.
|
||||
if (!fslib::delete_directory_recursively(fs::DEFAULT_SAVE_PATH))
|
||||
// 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))
|
||||
{
|
||||
logger::log("Error restoring save: %s", fslib::get_error_string());
|
||||
ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
|
|
@ -401,7 +447,7 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptr<TargetStruct
|
|||
if (fslib::directory_exists(dataStruct->m_targetPath))
|
||||
{
|
||||
fs::copy_directory(dataStruct->m_targetPath,
|
||||
fs::DEFAULT_SAVE_PATH,
|
||||
fs::DEFAULT_SAVE_ROOT,
|
||||
dataStruct->m_journalSize,
|
||||
fs::DEFAULT_SAVE_MOUNT,
|
||||
task);
|
||||
|
|
@ -409,6 +455,7 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptr<TargetStruct
|
|||
else if (std::strstr(dataStruct->m_targetPath.c_string(), ".zip") != NULL)
|
||||
{
|
||||
unzFile targetZip = unzOpen64(dataStruct->m_targetPath.c_string());
|
||||
logger::log("targetZip");
|
||||
if (!targetZip)
|
||||
{
|
||||
ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS,
|
||||
|
|
@ -418,7 +465,7 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptr<TargetStruct
|
|||
return;
|
||||
}
|
||||
fs::copy_zip_to_directory(targetZip,
|
||||
fs::DEFAULT_SAVE_PATH,
|
||||
fs::DEFAULT_SAVE_ROOT,
|
||||
dataStruct->m_journalSize,
|
||||
fs::DEFAULT_SAVE_MOUNT,
|
||||
task);
|
||||
|
|
@ -427,7 +474,7 @@ static void restore_backup(sys::ProgressTask *task, std::shared_ptr<TargetStruct
|
|||
else
|
||||
{
|
||||
fs::copy_file(dataStruct->m_targetPath,
|
||||
fs::DEFAULT_SAVE_PATH,
|
||||
fs::DEFAULT_SAVE_ROOT,
|
||||
dataStruct->m_journalSize,
|
||||
fs::DEFAULT_SAVE_MOUNT,
|
||||
task);
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ static void reset_save_data(sys::Task *task, std::shared_ptr<TargetStruct> dataS
|
|||
}
|
||||
|
||||
// Wipe the root.
|
||||
if (!fslib::delete_directory_recursively(fs::DEFAULT_SAVE_PATH))
|
||||
if (!fslib::delete_directory_recursively(fs::DEFAULT_SAVE_ROOT))
|
||||
{
|
||||
fslib::close_file_system(fs::DEFAULT_SAVE_MOUNT);
|
||||
logger::log(ERROR_RESETTING_SAVE, fslib::get_error_string());
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptr<UserStr
|
|||
|
||||
// Check to make sure the save actually has data to avoid blanks.
|
||||
{
|
||||
fslib::Directory saveCheck(fs::DEFAULT_SAVE_PATH);
|
||||
fslib::Directory saveCheck(fs::DEFAULT_SAVE_ROOT);
|
||||
if (saveMounted && saveCheck.get_count() <= 0)
|
||||
{
|
||||
// Gonna borrow these messages. No point in repeating them.
|
||||
|
|
@ -210,7 +210,7 @@ static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptr<UserStr
|
|||
logger::log("Error creating zip: %s", fslib::get_error_string());
|
||||
continue;
|
||||
}
|
||||
fs::copy_directory_to_zip(fs::DEFAULT_SAVE_PATH, targetZip, task);
|
||||
fs::copy_directory_to_zip(fs::DEFAULT_SAVE_ROOT, targetZip, task);
|
||||
zipClose(targetZip, NULL);
|
||||
}
|
||||
else if (currentTitle && saveMounted)
|
||||
|
|
@ -224,7 +224,7 @@ static void backup_all_for_user(sys::ProgressTask *task, std::shared_ptr<UserStr
|
|||
logger::log("Error creating backup directory: %s", fslib::get_error_string());
|
||||
continue;
|
||||
}
|
||||
fs::copy_directory(fs::DEFAULT_SAVE_PATH, targetPath, 0, {}, task);
|
||||
fs::copy_directory(fs::DEFAULT_SAVE_ROOT, targetPath, 0, {}, task);
|
||||
}
|
||||
|
||||
if (saveMounted)
|
||||
|
|
|
|||
|
|
@ -55,9 +55,10 @@ data::TitleInfo::TitleInfo(uint64_t applicationID) : m_applicationID(application
|
|||
}
|
||||
|
||||
// To do: Make this safer...
|
||||
data::TitleInfo::TitleInfo(NsApplicationControlData &controlData)
|
||||
data::TitleInfo::TitleInfo(uint64_t applicationID, NsApplicationControlData &controlData)
|
||||
: m_applicationID(applicationID)
|
||||
{
|
||||
// Start by memcpying the thing.
|
||||
// Start by making a copy of this.
|
||||
std::memcpy(&m_data, &controlData, sizeof(NsApplicationControlData));
|
||||
|
||||
// Grab the language entry for the texture name.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace
|
|||
// This is easer to read imo
|
||||
using UserIDPair = std::pair<AccountUid, data::User>;
|
||||
|
||||
/// @brief This is the struct used for caching title info for 20.0+.
|
||||
/// @brief Struct used for reading the cache from file.
|
||||
typedef struct
|
||||
{
|
||||
uint64_t m_applicationID;
|
||||
|
|
@ -160,12 +160,6 @@ void data::get_title_info_by_type(FsSaveDataType saveType, std::vector<data::Tit
|
|||
|
||||
static bool load_create_user_accounts(void)
|
||||
{
|
||||
// These are the IDs used for system type account users.
|
||||
static constexpr AccountUid deviceID = {FsSaveDataType_Device};
|
||||
static constexpr AccountUid bcatID = {FsSaveDataType_Bcat};
|
||||
static constexpr AccountUid cacheID = {FsSaveDataType_Cache};
|
||||
static constexpr AccountUid systemID = {FsSaveDataType_System};
|
||||
|
||||
// For saving total accounts found.
|
||||
int total = 0;
|
||||
// The Switch can only have up to eight user accounts.
|
||||
|
|
@ -187,19 +181,23 @@ static bool load_create_user_accounts(void)
|
|||
|
||||
// Create and push the system type users. Path safe names are hard coded to English for these.
|
||||
// Clang-format makes these hard to read.
|
||||
s_userVector.push_back(std::make_pair(deviceID,
|
||||
data::User(deviceID,
|
||||
s_userVector.push_back(std::make_pair(ID_DEVICE_USER,
|
||||
data::User(ID_DEVICE_USER,
|
||||
strings::get_by_name(strings::names::SAVE_DATA_TYPES, 3),
|
||||
"Device",
|
||||
FsSaveDataType_Device)));
|
||||
s_userVector.push_back(std::make_pair(
|
||||
bcatID,
|
||||
data::User(bcatID, strings::get_by_name(strings::names::SAVE_DATA_TYPES, 2), "BCAT", FsSaveDataType_Bcat)));
|
||||
s_userVector.push_back(std::make_pair(
|
||||
cacheID,
|
||||
data::User(cacheID, strings::get_by_name(strings::names::SAVE_DATA_TYPES, 5), "Cache", FsSaveDataType_Cache)));
|
||||
s_userVector.push_back(std::make_pair(systemID,
|
||||
data::User(systemID,
|
||||
s_userVector.push_back(std::make_pair(ID_BCAT_USER,
|
||||
data::User(ID_BCAT_USER,
|
||||
strings::get_by_name(strings::names::SAVE_DATA_TYPES, 2),
|
||||
"BCAT",
|
||||
FsSaveDataType_Bcat)));
|
||||
s_userVector.push_back(std::make_pair(ID_CACHE_USER,
|
||||
data::User(ID_CACHE_USER,
|
||||
strings::get_by_name(strings::names::SAVE_DATA_TYPES, 5),
|
||||
"Cache",
|
||||
FsSaveDataType_Cache)));
|
||||
s_userVector.push_back(std::make_pair(ID_SYSTEM_USER,
|
||||
data::User(ID_SYSTEM_USER,
|
||||
strings::get_by_name(strings::names::SAVE_DATA_TYPES, 0),
|
||||
"System",
|
||||
FsSaveDataType_System)));
|
||||
|
|
@ -220,7 +218,8 @@ static void load_application_records(void)
|
|||
// Loop and read the records into the map.
|
||||
while (R_SUCCEEDED(nsListApplicationRecord(&record, 1, offset++, &count)) && count > 0)
|
||||
{
|
||||
s_titleInfoMap.emplace(std::make_pair(record.application_id, data::TitleInfo(record.application_id)));
|
||||
// s_titleInfoMap.emplace(record.application_id, data::TitleInfo(record.application_id));
|
||||
s_titleInfoMap.emplace(record.application_id, record.application_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -320,7 +319,7 @@ static void load_save_data_info(void)
|
|||
// Search the map just to be sure it was loaded previously. This can happen.
|
||||
if (s_titleInfoMap.find(applicationID) == s_titleInfoMap.end())
|
||||
{
|
||||
s_titleInfoMap.emplace(std::make_pair(applicationID, data::TitleInfo(applicationID)));
|
||||
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.
|
||||
|
|
@ -391,27 +390,25 @@ static bool read_cache_file(void)
|
|||
// Read it
|
||||
cache.read(&titleCount, sizeof(unsigned int));
|
||||
|
||||
// This should be the size of the cached control data.
|
||||
ssize_t cacheSize = titleCount * sizeof(CacheEntry);
|
||||
|
||||
// Allocate array to read into.
|
||||
std::unique_ptr<CacheEntry[]> dataBuffer = std::make_unique<CacheEntry[]>(titleCount);
|
||||
|
||||
// Read off the entire file in one shot.
|
||||
ssize_t readSize = cache.read(dataBuffer.get(), cacheSize);
|
||||
if (readSize != cacheSize)
|
||||
// Allocate buffer for reading the rest.
|
||||
std::unique_ptr<CacheEntry[]> entryBuffer = std::make_unique<CacheEntry[]>(titleCount);
|
||||
if (!entryBuffer)
|
||||
{
|
||||
logger::log("Cache size/read error!");
|
||||
logger::log("Error allocating memory to read cache!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cache.read(entryBuffer.get(), sizeof(CacheEntry) * titleCount) != sizeof(CacheEntry) * titleCount)
|
||||
{
|
||||
logger::log("Error reading cache file! Size mismatch!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop through the cache entries and emplace them to the map.
|
||||
for (unsigned int i = 0; i < titleCount; i++)
|
||||
{
|
||||
// Grab a pointer to make this easier to read.
|
||||
CacheEntry *currentEntry = &dataBuffer[i];
|
||||
|
||||
s_titleInfoMap.emplace(std::make_pair(currentEntry->m_applicationID, data::TitleInfo(currentEntry->m_data)));
|
||||
s_titleInfoMap.emplace(entryBuffer[i].m_applicationID,
|
||||
std::move(data::TitleInfo(entryBuffer[i].m_applicationID, entryBuffer[i].m_data)));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
17
source/fs/SaveMetaData.cpp
Normal file
17
source/fs/SaveMetaData.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#include "fs/SaveMetaData.hpp"
|
||||
#include "fs/directory_functions.hpp"
|
||||
#include "fs/save_mount.hpp"
|
||||
|
||||
void fs::create_save_meta_data(data::TitleInfo *titleInfo, const FsSaveDataInfo *saveInfo, fs::SaveMetaData &meta)
|
||||
{
|
||||
meta = {.m_magic = fs::SAVE_META_MAGIC,
|
||||
.m_applicationID = titleInfo->get_application_id(),
|
||||
.m_saveType = saveInfo->save_data_type,
|
||||
.m_saveRank = saveInfo->save_data_rank,
|
||||
.m_saveSpaceID = saveInfo->save_data_space_id,
|
||||
.m_saveDataSize = titleInfo->get_save_data_size(saveInfo->save_data_type),
|
||||
.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)};
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include "fs/zip.hpp"
|
||||
#include "config.hpp"
|
||||
#include "fs/SaveMetaData.hpp"
|
||||
#include "logger.hpp"
|
||||
#include "strings.hpp"
|
||||
#include <condition_variable>
|
||||
|
|
@ -11,42 +12,54 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
// Size used for Zipping files.
|
||||
constexpr size_t ZIP_BUFFER_SIZE = 0x100000;
|
||||
// Size used for unzipping.
|
||||
constexpr size_t UNZIP_BUFFER_SIZE = 0x600000;
|
||||
/// @brief Buffer size used for writing files to ZIP.
|
||||
constexpr size_t SIZE_ZIP_BUFFER = 0x100000;
|
||||
|
||||
/// @brief Buffer size used for decompressing files from ZIP.
|
||||
constexpr size_t SIZE_UNZIP_BUFFER = 0x600000;
|
||||
} // namespace
|
||||
|
||||
// Shared struct for Zip/File IO
|
||||
typedef struct
|
||||
{
|
||||
// Mutex and condition for buffer.
|
||||
/// @brief Mutex for blocking the shared buffer.
|
||||
std::mutex m_bufferLock;
|
||||
|
||||
/// @brief Conditional for locking and unlocking.
|
||||
std::condition_variable m_bufferCondition;
|
||||
|
||||
/// @brief Bool that lets threads communicate when they are using the buffer.
|
||||
bool m_bufferIsFull = false;
|
||||
// Number of bytes read from file.
|
||||
|
||||
/// @brief Number of bytes read from the file.
|
||||
ssize_t m_readCount = 0;
|
||||
// Shared/reading buffer.
|
||||
|
||||
/// @brief Shared/reading buffer.
|
||||
std::unique_ptr<unsigned char[]> m_sharedBuffer;
|
||||
} ZipIOStruct;
|
||||
|
||||
// Function for reading files for Zipping.
|
||||
static void zipReadThreadFunction(fslib::File &source, std::shared_ptr<ZipIOStruct> sharedData)
|
||||
{
|
||||
// Don't call this every loop. Not sure if compiler optimizes that out or not now.
|
||||
int64_t fileSize = source.get_size();
|
||||
|
||||
// Loop until the file is completely read.
|
||||
for (int64_t readCount = 0; readCount < fileSize;)
|
||||
{
|
||||
// Read into shared buffer.
|
||||
sharedData->m_readCount = source.read(sharedData->m_sharedBuffer.get(), ZIP_BUFFER_SIZE);
|
||||
sharedData->m_readCount = source.read(sharedData->m_sharedBuffer.get(), SIZE_ZIP_BUFFER);
|
||||
|
||||
// Update read count
|
||||
readCount += sharedData->m_readCount;
|
||||
|
||||
// Signal other thread buffer is ready to go.
|
||||
sharedData->m_bufferIsFull = true;
|
||||
sharedData->m_bufferCondition.notify_one();
|
||||
|
||||
// Wait for other thread to release lock on buffer so this thread can read again.
|
||||
std::unique_lock<std::mutex> m_bufferLock(sharedData->m_bufferLock);
|
||||
sharedData->m_bufferCondition.wait(m_bufferLock,
|
||||
[&sharedData]() { return sharedData->m_bufferIsFull == false; });
|
||||
std::unique_lock<std::mutex> bufferLock(sharedData->m_bufferLock);
|
||||
sharedData->m_bufferCondition.wait(bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull == false; });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -56,16 +69,15 @@ static void unzipReadThreadFunction(unzFile source, int64_t fileSize, std::share
|
|||
for (int64_t readCount = 0; readCount < fileSize;)
|
||||
{
|
||||
// Read from zip file.
|
||||
sharedData->m_readCount = unzReadCurrentFile(source, sharedData->m_sharedBuffer.get(), UNZIP_BUFFER_SIZE);
|
||||
sharedData->m_readCount = unzReadCurrentFile(source, sharedData->m_sharedBuffer.get(), SIZE_UNZIP_BUFFER);
|
||||
|
||||
readCount += sharedData->m_readCount;
|
||||
|
||||
sharedData->m_bufferIsFull = true;
|
||||
sharedData->m_bufferCondition.notify_one();
|
||||
|
||||
std::unique_lock<std::mutex> m_bufferLock(sharedData->m_bufferLock);
|
||||
sharedData->m_bufferCondition.wait(m_bufferLock,
|
||||
[&sharedData]() { return sharedData->m_bufferIsFull == false; });
|
||||
std::unique_lock<std::mutex> bufferLock(sharedData->m_bufferLock);
|
||||
sharedData->m_bufferCondition.wait(bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull == false; });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +90,9 @@ void fs::copy_directory_to_zip(const fslib::Path &source, zipFile destination, s
|
|||
return;
|
||||
}
|
||||
|
||||
// Grab this here instead of calling the config function for every file.
|
||||
int compressionLevel = config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL);
|
||||
|
||||
for (int64_t i = 0; i < sourceDir.get_count(); i++)
|
||||
{
|
||||
if (sourceDir.is_directory(i))
|
||||
|
|
@ -97,7 +112,8 @@ void fs::copy_directory_to_zip(const fslib::Path &source, zipFile destination, s
|
|||
}
|
||||
|
||||
// Zip info
|
||||
zip_fileinfo fileInfo = fs::create_zip_fileinfo();
|
||||
zip_fileinfo fileInfo;
|
||||
fs::create_zip_fileinfo(fileInfo);
|
||||
|
||||
// Create new file in zip
|
||||
const char *zipNameBegin = std::strchr(fullSource.get_path(), '/') + 1;
|
||||
|
|
@ -110,8 +126,8 @@ void fs::copy_directory_to_zip(const fslib::Path &source, zipFile destination, s
|
|||
0,
|
||||
NULL,
|
||||
Z_DEFLATED,
|
||||
config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL),
|
||||
1);
|
||||
compressionLevel,
|
||||
0);
|
||||
if (zipError != ZIP_OK)
|
||||
{
|
||||
logger::log("Error creating file in zip: %i.", zipError);
|
||||
|
|
@ -120,10 +136,10 @@ void fs::copy_directory_to_zip(const fslib::Path &source, zipFile destination, s
|
|||
|
||||
// Shared data for thread.
|
||||
std::shared_ptr<ZipIOStruct> sharedData(new ZipIOStruct);
|
||||
sharedData->m_sharedBuffer = std::make_unique<unsigned char[]>(ZIP_BUFFER_SIZE);
|
||||
sharedData->m_sharedBuffer = std::make_unique<unsigned char[]>(SIZE_ZIP_BUFFER);
|
||||
|
||||
// Local buffer for writing.
|
||||
std::unique_ptr<unsigned char[]> localBuffer(new unsigned char[ZIP_BUFFER_SIZE]);
|
||||
std::unique_ptr<unsigned char[]> localBuffer(new unsigned char[SIZE_ZIP_BUFFER]);
|
||||
|
||||
// Update task if passed.
|
||||
if (task)
|
||||
|
|
@ -178,10 +194,11 @@ void fs::copy_zip_to_directory(unzFile source,
|
|||
std::string_view commitDevice,
|
||||
sys::ProgressTask *task)
|
||||
{
|
||||
// With the new save meta, this might never fail... Need to figure this out some time.
|
||||
int zipError = unzGoToFirstFile(source);
|
||||
if (zipError != UNZ_OK)
|
||||
{
|
||||
logger::log("Error opening empty ZIP file: %i.", zipError);
|
||||
logger::log("Error unzipping file: Zip is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -190,17 +207,25 @@ void fs::copy_zip_to_directory(unzFile source,
|
|||
// Get file information.
|
||||
unz_file_info64 currentFileInfo;
|
||||
char filename[FS_MAX_PATH] = {0};
|
||||
if (unzOpenCurrentFile(source) != UNZ_OK ||
|
||||
unzGetCurrentFileInfo64(source, ¤tFileInfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0) != UNZ_OK)
|
||||
|
||||
if (unzGetCurrentFileInfo64(source, ¤tFileInfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0) != UNZ_OK ||
|
||||
unzOpenCurrentFile(source) != UNZ_OK)
|
||||
{
|
||||
logger::log("Error getting information for or opening file for reading in zip!");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save meta file filter.
|
||||
if (filename == fs::NAME_SAVE_META)
|
||||
{
|
||||
logger::log("Error opening and getting information for file in zip.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create full path to item, make sure directories are created if needed.
|
||||
fslib::Path fullDestination = destination / filename;
|
||||
|
||||
fslib::Path directories = fullDestination.sub_path(fullDestination.find_last_of('/') - 1);
|
||||
fslib::Path directories = fullDestination.sub_path(fullDestination.find_last_of('/'));
|
||||
|
||||
// To do: Make FsLib handle this correctly. First condition is a workaround for now...
|
||||
if (directories.is_valid() && !fslib::create_directories_recursively(directories))
|
||||
{
|
||||
|
|
@ -219,14 +244,14 @@ void fs::copy_zip_to_directory(unzFile source,
|
|||
|
||||
// Shared data for both threads
|
||||
std::shared_ptr<ZipIOStruct> sharedData(new ZipIOStruct);
|
||||
sharedData->m_sharedBuffer = std::make_unique<unsigned char[]>(UNZIP_BUFFER_SIZE);
|
||||
sharedData->m_sharedBuffer = std::make_unique<unsigned char[]>(SIZE_UNZIP_BUFFER);
|
||||
|
||||
// Local buffer
|
||||
std::unique_ptr<unsigned char[]> localBuffer(new unsigned char[SIZE_UNZIP_BUFFER]);
|
||||
|
||||
// Spawn read thread.
|
||||
std::thread readThread(unzipReadThreadFunction, source, currentFileInfo.uncompressed_size, sharedData);
|
||||
|
||||
// Local buffer
|
||||
std::unique_ptr<unsigned char[]> localBuffer(new unsigned char[UNZIP_BUFFER_SIZE]);
|
||||
|
||||
// Set status
|
||||
if (task)
|
||||
{
|
||||
|
|
@ -239,9 +264,8 @@ void fs::copy_zip_to_directory(unzFile source,
|
|||
{
|
||||
{
|
||||
// Wait for buffer.
|
||||
std::unique_lock<std::mutex> m_bufferLock(sharedData->m_bufferLock);
|
||||
sharedData->m_bufferCondition.wait(m_bufferLock,
|
||||
[&sharedData]() { return sharedData->m_bufferIsFull; });
|
||||
std::unique_lock<std::mutex> bufferLock(sharedData->m_bufferLock);
|
||||
sharedData->m_bufferCondition.wait(bufferLock, [&sharedData]() { return sharedData->m_bufferIsFull; });
|
||||
|
||||
// Save read count for later
|
||||
readCount = sharedData->m_readCount;
|
||||
|
|
@ -259,28 +283,37 @@ void fs::copy_zip_to_directory(unzFile source,
|
|||
{
|
||||
// Close.
|
||||
destinationFile.close();
|
||||
|
||||
// Commit
|
||||
if (!fslib::commit_data_to_file_system(commitDevice))
|
||||
{
|
||||
logger::log("Error committing data to save: %s", fslib::get_error_string());
|
||||
}
|
||||
|
||||
// Reopen, seek to previous position.
|
||||
destinationFile.open(fullDestination, FsOpenMode_Write);
|
||||
destinationFile.seek(writeCount, destinationFile.BEGINNING);
|
||||
|
||||
// Reset journal
|
||||
journalCount = 0;
|
||||
}
|
||||
// Write data.
|
||||
destinationFile.write(localBuffer.get(), readCount);
|
||||
|
||||
// Update write and journal count
|
||||
writeCount += readCount;
|
||||
journalCount += journalCount;
|
||||
journalCount += readCount;
|
||||
|
||||
// Update status
|
||||
if (task)
|
||||
{
|
||||
task->update_current(writeCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Join the read thread.
|
||||
readThread.join();
|
||||
|
||||
// Close file and commit again just for good measure.
|
||||
destinationFile.close();
|
||||
if (!fslib::commit_data_to_file_system(commitDevice))
|
||||
|
|
@ -290,7 +323,7 @@ void fs::copy_zip_to_directory(unzFile source,
|
|||
} while (unzGoToNextFile(source) != UNZ_END_OF_LIST_OF_FILE);
|
||||
}
|
||||
|
||||
zip_fileinfo fs::create_zip_fileinfo(void)
|
||||
void fs::create_zip_fileinfo(zip_fileinfo &info)
|
||||
{
|
||||
// Grab the current time.
|
||||
std::time_t currentTime = std::time(NULL);
|
||||
|
|
@ -299,16 +332,15 @@ zip_fileinfo fs::create_zip_fileinfo(void)
|
|||
std::tm *localTime = std::localtime(¤tTime);
|
||||
|
||||
// Create struct to return.
|
||||
zip_fileinfo fileInfo = {.tmz_date = {.tm_sec = localTime->tm_sec,
|
||||
.tm_min = localTime->tm_min,
|
||||
.tm_hour = localTime->tm_hour,
|
||||
.tm_mday = localTime->tm_mday,
|
||||
.tm_mon = localTime->tm_mon,
|
||||
.tm_year = localTime->tm_year + 1900},
|
||||
.dosDate = 0,
|
||||
.internal_fa = 0,
|
||||
.external_fa = 0};
|
||||
return fileInfo;
|
||||
info = {.tmz_date = {.tm_sec = localTime->tm_sec,
|
||||
.tm_min = localTime->tm_min,
|
||||
.tm_hour = localTime->tm_hour,
|
||||
.tm_mday = localTime->tm_mday,
|
||||
.tm_mon = localTime->tm_mon,
|
||||
.tm_year = localTime->tm_year + 1900},
|
||||
.dosDate = 0,
|
||||
.internal_fa = 0,
|
||||
.external_fa = 0};
|
||||
}
|
||||
|
||||
bool fs::zip_has_contents(const fslib::Path &zipPath)
|
||||
|
|
@ -328,3 +360,72 @@ bool fs::zip_has_contents(const fslib::Path &zipPath)
|
|||
unzClose(testZip);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool fs::locate_file_in_zip(unzFile zip, std::string_view name)
|
||||
{
|
||||
// Go to the first file, first.
|
||||
int zipError = unzGoToFirstFile(zip);
|
||||
if (zipError != UNZ_OK)
|
||||
{
|
||||
logger::log("Error locating file: Zip is empty!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// This should be a large enough buffer.
|
||||
char filename[FS_MAX_PATH] = {0};
|
||||
// File info.
|
||||
unz_file_info64 fileinfo = {0};
|
||||
|
||||
// Loop through files. If minizip has a better way of doing this, I couldn't find it.
|
||||
do
|
||||
{
|
||||
// Grab this stuff.
|
||||
zipError = unzGetCurrentFileInfo64(zip, &fileinfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0);
|
||||
if (zipError != UNZ_OK)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (filename == name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} while (unzGoToNextFile(zip) != UNZ_END_OF_LIST_OF_FILE);
|
||||
|
||||
// Guess it wasn't found?
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t fs::get_zip_total_size(unzFile zip)
|
||||
{
|
||||
// First, first.
|
||||
int zipError = unzGoToFirstFile(zip);
|
||||
if (zipError != UNZ_OK)
|
||||
{
|
||||
logger::log("Error getting total zip file size: %i.", zipError);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Size.
|
||||
uint64_t zipSize = 0;
|
||||
|
||||
// File's name and info buffers.
|
||||
char filename[FS_MAX_PATH] = {0};
|
||||
unz_file_info64 fileinfo = {0};
|
||||
|
||||
do
|
||||
{
|
||||
zipError = unzGetCurrentFileInfo64(zip, &fileinfo, filename, 0, NULL, 0, NULL, 0);
|
||||
if (zipError != UNZ_OK)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add
|
||||
zipSize += fileinfo.uncompressed_size;
|
||||
} while (unzGoToNextFile(zip) != UNZ_END_OF_LIST_OF_FILE);
|
||||
|
||||
// Reset. Maybe this should be error checked, but I don't see the point here?
|
||||
unzGoToFirstFile(zip);
|
||||
|
||||
return zipSize;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user