Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DolphinQt: Add function to import/export userdir from/to a zip file. #10711

Closed
Closed
1 change: 1 addition & 0 deletions Source/Core/Common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ add_library(common
MemArena.h
MemoryUtil.cpp
MemoryUtil.h
MinizipUtil.cpp
MinizipUtil.h
MsgHandler.cpp
MsgHandler.h
Expand Down
13 changes: 13 additions & 0 deletions Source/Core/Common/Config/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,19 @@ void Save()
OnConfigChanged();
}

void Reload()
{
{
WriteLock lock(s_layers_rw_lock);

for (auto& layer : s_layers)
layer.second->DeleteAllKeys();
for (auto& layer : s_layers)
layer.second->Load();
}
OnConfigChanged();
}

void Init()
{
// These layers contain temporary values
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Common/Config/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ u64 GetConfigVersion();
// Explicit load and save of layers
void Load();
void Save();
void Reload();

void Init();
void Shutdown();
Expand Down
74 changes: 74 additions & 0 deletions Source/Core/Common/MinizipUtil.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "Common/MinizipUtil.h"

#include <ctime> // required by minizip includes
#include <string>

#include <mz_strm.h>
#include <mz_strm_os.h>
#include <mz_zip.h>
#include <mz_zip_rw.h>

#include "Common/ScopeGuard.h"

namespace Common
{
bool PackDirectoryToZip(const std::string& directory_path, const std::string& zip_path)
{
void* stream = nullptr;
mz_stream_os_create(&stream);
if (!stream)
return false;

Common::ScopeGuard delete_stream_guard([&stream] { mz_stream_os_delete(&stream); });

if (mz_stream_os_open(stream, zip_path.c_str(), MZ_OPEN_MODE_CREATE) != MZ_OK)
return false;

Common::ScopeGuard close_stream_guard([&stream] { mz_stream_os_close(stream); });

void* writer = nullptr;
mz_zip_writer_create(&writer);
if (!writer)
return false;

Common::ScopeGuard delete_writer_guard([&writer] { mz_zip_writer_delete(&writer); });

mz_zip_writer_set_compress_method(writer, Z_DEFLATED);
mz_zip_writer_set_compress_level(writer, MZ_COMPRESS_LEVEL_NORMAL);

if (mz_zip_writer_open(writer, stream, 0) != MZ_OK)
return false;

Common::ScopeGuard close_writer_guard([&writer] { mz_zip_writer_close(writer); });

if (mz_zip_writer_add_path(writer, directory_path.c_str(), nullptr, 0, 1) != MZ_OK)
return false;

return true;
}

bool UnpackZipToDirectory(const std::string& zip_path, const std::string& directory_path)
{
void* reader = nullptr;
mz_zip_reader_create(&reader);
if (!reader)
return false;

Common::ScopeGuard delete_reader_guard([&reader] { mz_zip_reader_delete(&reader); });

mz_zip_reader_set_encoding(reader, MZ_ENCODING_UTF8);

if (mz_zip_reader_open_file(reader, zip_path.c_str()) != MZ_OK)
return false;

Common::ScopeGuard close_reader_guard([&reader] { mz_zip_reader_close(reader); });

if (mz_zip_reader_save_all(reader, directory_path.c_str()) != MZ_OK)
return false;

return true;
}
} // namespace Common
9 changes: 9 additions & 0 deletions Source/Core/Common/MinizipUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include <algorithm>
#include <string>

#include <unzip.h>

Expand Down Expand Up @@ -38,4 +39,12 @@ bool ReadFileFromZip(unzFile file, ContiguousContainer* destination)

return true;
}

// Packs the directory at directory_path into a new zip file at zip_path.
// If zip_path exists it will be overwritten.
bool PackDirectoryToZip(const std::string& directory_path, const std::string& zip_path);

// Unpacks the zip file at zip_path into the directory at directory_path.
// Existing files may be overwritten if the zip file contains a file with the same relative path.
bool UnpackZipToDirectory(const std::string& zip_path, const std::string& directory_path);
} // namespace Common
8 changes: 8 additions & 0 deletions Source/Core/Common/StringUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -681,4 +681,12 @@ void ToUpper(std::string* str)
{
std::transform(str->begin(), str->end(), str->begin(), [](char c) { return Common::ToUpper(c); });
}

bool CaseInsensitiveEquals(std::string_view a, std::string_view b)
{
if (a.size() != b.size())
return false;
return std::equal(a.begin(), a.end(), b.begin(),
[](char ca, char cb) { return Common::ToLower(ca) == Common::ToLower(cb); });
}
} // namespace Common
1 change: 1 addition & 0 deletions Source/Core/Common/StringUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,5 @@ inline char ToUpper(char ch)
}
void ToLower(std::string* str);
void ToUpper(std::string* str);
bool CaseInsensitiveEquals(std::string_view a, std::string_view b);
} // namespace Common
14 changes: 3 additions & 11 deletions Source/Core/DiscIO/RiivolutionPatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,22 +319,14 @@ static void ApplyPatchToFile(const Patch& patch, const File& file_patch,
file_patch.m_fileoffset, file_patch.m_length, file_patch.m_resize);
}

static bool CaseInsensitiveEquals(std::string_view a, std::string_view b)
{
if (a.size() != b.size())
return false;
return std::equal(a.begin(), a.end(), b.begin(),
[](char ca, char cb) { return Common::ToLower(ca) == Common::ToLower(cb); });
}

static FSTBuilderNode* FindFileNodeInFST(std::string_view path, std::vector<FSTBuilderNode>* fst,
bool create_if_not_exists)
{
const size_t path_separator = path.find('/');
const bool is_file = path_separator == std::string_view::npos;
const std::string_view name = is_file ? path : path.substr(0, path_separator);
const auto it = std::find_if(fst->begin(), fst->end(), [&](const FSTBuilderNode& node) {
return CaseInsensitiveEquals(node.m_filename, name);
return Common::CaseInsensitiveEquals(node.m_filename, name);
});

if (it == fst->end())
Expand Down Expand Up @@ -376,7 +368,7 @@ static DiscIO::FSTBuilderNode* FindFilenameNodeInFST(std::string_view filename,
if (result)
return result;
}
else if (CaseInsensitiveEquals(node.m_filename, filename))
else if (Common::CaseInsensitiveEquals(node.m_filename, filename))
{
return &node;
}
Expand All @@ -397,7 +389,7 @@ static void ApplyFilePatchToFST(const Patch& patch, const File& file,
if (node)
ApplyPatchToFile(patch, file, node);
}
else if (CaseInsensitiveEquals(file.m_disc, "main.dol"))
else if (Common::CaseInsensitiveEquals(file.m_disc, "main.dol"))
{
// Special case: If the filename is "main.dol", we want to patch the main executable.
ApplyPatchToFile(patch, file, dol_node);
Expand Down
3 changes: 3 additions & 0 deletions Source/Core/DolphinLib.props
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@
<ClInclude Include="UICommon\DiscordPresence.h" />
<ClInclude Include="UICommon\GameFile.h" />
<ClInclude Include="UICommon\GameFileCache.h" />
<ClInclude Include="UICommon\ImportExportUserdir.h" />
<ClInclude Include="UICommon\NetPlayIndex.h" />
<ClInclude Include="UICommon\ResourcePack\Manager.h" />
<ClInclude Include="UICommon\ResourcePack\Manifest.h" />
Expand Down Expand Up @@ -733,6 +734,7 @@
<ClCompile Include="Common\Matrix.cpp" />
<ClCompile Include="Common\MemArenaWin.cpp" />
<ClCompile Include="Common\MemoryUtil.cpp" />
<ClCompile Include="Common\MinizipUtil.cpp" />
<ClCompile Include="Common\MsgHandler.cpp" />
<ClCompile Include="Common\NandPaths.cpp" />
<ClCompile Include="Common\Network.cpp" />
Expand Down Expand Up @@ -1100,6 +1102,7 @@
<ClCompile Include="UICommon\DiscordPresence.cpp" />
<ClCompile Include="UICommon\GameFile.cpp" />
<ClCompile Include="UICommon\GameFileCache.cpp" />
<ClCompile Include="UICommon\ImportExportUserdir.cpp" />
<ClCompile Include="UICommon\NetPlayIndex.cpp" />
<ClCompile Include="UICommon\ResourcePack\Manager.cpp" />
<ClCompile Include="UICommon\ResourcePack\Manifest.cpp" />
Expand Down
125 changes: 123 additions & 2 deletions Source/Core/DolphinQt/MenuBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

#include "UICommon/AutoUpdate.h"
#include "UICommon/GameFile.h"
#include "UICommon/ImportExportUserdir.h"

QPointer<MenuBar> MenuBar::s_menu_bar;

Expand Down Expand Up @@ -281,8 +282,17 @@ void MenuBar::AddToolsMenu()

tools_menu->addSeparator();

tools_menu->addAction(tr("Import Wii Save..."), this, &MenuBar::ImportWiiSave);
tools_menu->addAction(tr("Export All Wii Saves"), this, &MenuBar::ExportWiiSaves);
m_import_wii_save =
tools_menu->addAction(tr("Import Wii Save..."), this, &MenuBar::ImportWiiSave);
m_export_wii_saves =
tools_menu->addAction(tr("Export All Wii Saves"), this, &MenuBar::ExportWiiSaves);

tools_menu->addSeparator();

m_import_userdir =
tools_menu->addAction(tr("Import User Directory..."), this, &MenuBar::ImportUserDirBackup);
m_export_userdir =
tools_menu->addAction(tr("Export User Directory..."), this, &MenuBar::ExportUserDirBackup);

QMenu* menu = new QMenu(tr("Connect Wii Remotes"), tools_menu);

Expand Down Expand Up @@ -1009,6 +1019,11 @@ void MenuBar::UpdateToolsMenu(bool emulation_started)
File::Exists(SConfig::GetInstance().GetBootROMPath(EUR_DIR)));
m_import_backup->setEnabled(!emulation_started);
m_check_nand->setEnabled(!emulation_started);
m_wad_install_action->setEnabled(!emulation_started);
m_import_wii_save->setEnabled(!emulation_started);
m_export_wii_saves->setEnabled(!emulation_started);
m_import_userdir->setEnabled(!emulation_started);
m_export_userdir->setEnabled(!emulation_started);

if (!emulation_started)
{
Expand Down Expand Up @@ -1041,6 +1056,112 @@ void MenuBar::UpdateToolsMenu(bool emulation_started)
}
}

void MenuBar::ImportUserDirBackup()
{
auto warning_result = ModalMessageBox::question(
this, tr("Warning"),
tr("Importing a User Directory will delete all data currently stored within the User "
"Directory. This includes save files, controller configuration, and other user-configured "
"data. You may want to export your current User Directory before importing a new "
"one.\n\nAre you sure you want to continue?"));
if (warning_result != QMessageBox::Yes)
return;

const QString path = DolphinFileDialog::getOpenFileName(
this, tr("Select the User Directory backup to import"), QDir::currentPath(),
QStringLiteral("%1 (*.zip);;%2 (*)").arg(tr("ZIP archive")).arg(tr("All Files")));
if (path.isEmpty())
return;

ParallelProgressDialog progress(tr("Importing User Directory from %1...").arg(path), QString(), 0,
0, this);
progress.GetRaw()->setWindowTitle(tr("Importing"));
progress.GetRaw()->setWindowModality(Qt::WindowModal);

auto future = std::async(std::launch::async, [&]() -> UICommon::ImportUserDirResult {
auto result = UICommon::ImportUserDir(path.toStdString());
progress.Reset();
return result;
});

progress.GetRaw()->exec();

auto result = future.get();
switch (result)
{
case UICommon::ImportUserDirResult::Success:
ModalMessageBox::information(this, tr("Success"), tr("Successfully imported User Directory."));
break;
case UICommon::ImportUserDirResult::ArchiveFileError:
ModalMessageBox::critical(this, tr("Failure"),
tr("Importing User Directory failed: The selected archive could not "
"be opened or is corrupt."));
break;
case UICommon::ImportUserDirResult::ArchiveDoesNotContainUserdir:
ModalMessageBox::critical(this, tr("Failure"),
tr("Importing User Directory failed: The selected archive does not "
"appear to contain a User Directory backup."));
break;
case UICommon::ImportUserDirResult::OldUserdirDeleteError:
ModalMessageBox::critical(
this, tr("Failure"),
tr("Importing User Directory failed: Unable to delete the existing User Directory. The "
"directory may now be in an inconsistent state. It is recommended to close Dolphin and "
"manually delete the directory before continuing."));
break;
case UICommon::ImportUserDirResult::ExtractError:
ModalMessageBox::critical(
this, tr("Failure"),
tr("Importing User Directory failed: Failed to extract archive. The archive may be corrupt "
"or there may not be enough disk space available. The directory may now be in an "
"inconsistent state. It is recommended to close Dolphin and manually delete the "
"directory before continuing."));
break;
default:
ModalMessageBox::critical(this, tr("Failure"), tr("Importing User Directory failed."));
break;
}
}

void MenuBar::ExportUserDirBackup()
{
const QString path = DolphinFileDialog::getSaveFileName(
this, tr("Select storage location for User Directory backup"), QDir::currentPath(),
QStringLiteral("%1 (*.zip);;%2 (*)").arg(tr("ZIP archive")).arg(tr("All Files")));
if (path.isEmpty())
return;

ParallelProgressDialog progress(tr("Exporting User Directory to %1...").arg(path), tr("Cancel"),
0, 0, this);
progress.GetRaw()->setWindowTitle(tr("Exporting"));
progress.GetRaw()->setWindowModality(Qt::WindowModal);

Common::Flag cancel_request(false);
auto future = std::async(std::launch::async, [&]() -> bool {
progress.connect(&progress, &ParallelProgressDialog::Canceled, this,
[&cancel_request]() { cancel_request.Set(); });
bool result = UICommon::ExportUserDir(path.toStdString(), &cancel_request);
progress.Reset();
return result;
});

progress.GetRaw()->exec();

bool success = future.get();

if (cancel_request.IsSet())
return;

if (success)
{
ModalMessageBox::information(this, tr("Success"), tr("Successfully exported User Directory."));
}
else
{
ModalMessageBox::critical(this, tr("Failure"), tr("Exporting User Directory failed."));
}
}

void MenuBar::InstallWAD()
{
QString wad_file = DolphinFileDialog::getOpenFileName(
Expand Down
7 changes: 7 additions & 0 deletions Source/Core/DolphinQt/MenuBar.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ class MenuBar final : public QMenuBar
void AddJITMenu();
void AddSymbolsMenu();

void ImportUserDirBackup();
void ExportUserDirBackup();

void UpdateStateSlotMenu();

void InstallWAD();
Expand Down Expand Up @@ -206,6 +209,10 @@ class MenuBar final : public QMenuBar
QAction* m_import_backup;
QAction* m_check_nand;
QAction* m_extract_certificates;
QAction* m_import_wii_save;
QAction* m_export_wii_saves;
QAction* m_import_userdir;
QAction* m_export_userdir;
std::array<QAction*, 5> m_wii_remotes;

// Emulation
Expand Down
2 changes: 2 additions & 0 deletions Source/Core/UICommon/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ add_library(uicommon
GameFile.h
GameFileCache.cpp
GameFileCache.h
ImportExportUserdir.cpp
ImportExportUserdir.h
NetPlayIndex.cpp
NetPlayIndex.h
ResourcePack/Manager.cpp
Expand Down
Loading