Basic, core stuff working AFAIK.

This commit is contained in:
J-D-K 2025-06-07 15:34:28 -04:00
parent ecbeff1856
commit 39747a0031
13 changed files with 312 additions and 121 deletions

@ -1 +1 @@
Subproject commit ab3074859bf0b0d9cdc075395a70d23cd544ed86
Subproject commit fdb613fce0a104699a172f59f15c1b45c0b9fa77

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"));

View File

@ -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);

View File

@ -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());

View File

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

View File

@ -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.

View File

@ -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;

View 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)};
}

View File

@ -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, &currentFileInfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0) != UNZ_OK)
if (unzGetCurrentFileInfo64(source, &currentFileInfo, 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(&currentTime);
// 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;
}