Compare commits

...

4 commits

Author SHA1 Message Date
38f05e7082
[desktop] feat: import/export data
All checks were successful
eden-license / license-header (pull_request) Successful in 29s
Currently not the ideal solution. Can't be cancelled due to JlCompress
currently lacking a method of cancellation, but for now this is a good
prototype.

Signed-off-by: crueter <crueter@eden-emu.dev>
2025-10-09 15:26:27 -04:00
1dec879ec8
data dialog rewrite
All checks were successful
eden-license / license-header (pull_request) Successful in 24s
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-10-08 22:45:33 -04:00
e8149b77ae
headers
All checks were successful
eden-license / license-header (pull_request) Successful in 24s
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-10-08 16:12:11 -04:00
221d9c71ac
[desktop] Initial data manager prototype
Some checks failed
eden-license / license-header (pull_request) Failing after 31s
Right now, all this adds is a small dialog to the Widgets frontend
showing the user how much space is taken up by their saves, shaders,
NAND, and mods. It also gives them the choice to clear these directories
(with 2x confirmation), OR open the directory in their system file
manager.

In the future, a lot more can be done with this concept. Notably, a
common import/export (a la android) could be added, the UI can obviously
be polished, etc. We could also add this to Android, but I don't think
common import/export is needed *for* Android, and should probably be
left in qt_common.

Signed-off-by: crueter <crueter@eden-emu.dev>
2025-10-08 16:07:02 -04:00
48 changed files with 968 additions and 94 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -18,6 +18,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">icons/48x48/download.png</file>
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>

View file

@ -11,6 +11,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">../colorful/icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">../colorful/icons/48x48/download.png</file>
<file alias="48x48/upload.png">../colorful/icons/48x48/upload.png</file>
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
<file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>

View file

@ -14,6 +14,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">icons/48x48/download.png</file>
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
<file alias="48x48/star.png">icons/48x48/star.png</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

View file

@ -13,6 +13,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
<file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">../colorful/icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">../colorful/icons/48x48/download.png</file>
<file alias="48x48/upload.png">../colorful/icons/48x48/upload.png</file>
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

View file

@ -9,6 +9,9 @@
<file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">icons/48x48/chip.png</file>
<file alias="48x48/folder.png">icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">icons/48x48/download.png</file>
<file alias="48x48/upload.png">icons/48x48/upload.png</file>
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>

View file

@ -6,6 +6,9 @@
<file alias="48x48/bad_folder.png">../qdarkstyle/icons/48x48/bad_folder.png</file>
<file alias="48x48/chip.png">../qdarkstyle/icons/48x48/chip.png</file>
<file alias="48x48/folder.png">../qdarkstyle/icons/48x48/folder.png</file>
<file alias="48x48/user-trash.png">../qdarkstyle/icons/48x48/user-trash.png</file>
<file alias="48x48/download.png">../qdarkstyle/icons/48x48/download.png</file>
<file alias="48x48/upload.png">../qdarkstyle/icons/48x48/upload.png</file>
<file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file>
<file alias="48x48/list-add.png">../qdarkstyle/icons/48x48/list-add.png</file>
<file alias="48x48/sd_card.png">../qdarkstyle/icons/48x48/sd_card.png</file>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 KiB

After

Width:  |  Height:  |  Size: 244 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Before After
Before After

View file

@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
@ -7,6 +10,7 @@ add_library(frontend_common STATIC
content_manager.h
firmware_manager.h
firmware_manager.cpp
data_manager.h data_manager.cpp
)
create_target_directory_groups(frontend_common)

View file

@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "data_manager.h"
#include "common/assert.h"
#include "common/fs/path_util.h"
#include <filesystem>
#include <fmt/format.h>
namespace FrontendCommon::DataManager {
namespace fs = std::filesystem;
const std::string GetDataDir(DataDir dir)
{
const fs::path nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir);
switch (dir) {
case DataDir::Saves:
return (nand_dir / "user" / "save" / "0000000000000000").string();
case DataDir::UserNand:
return (nand_dir / "user" / "Contents" / "registered").string();
case DataDir::SysNand:
// NB: do NOT delete save
// that contains profile data and other stuff
return (nand_dir / "system" / "Contents" / "registered").string();
case DataDir::Mods:
return Common::FS::GetEdenPathString(Common::FS::EdenPath::LoadDir);
case DataDir::Shaders:
return Common::FS::GetEdenPathString(Common::FS::EdenPath::ShaderDir);
default:
UNIMPLEMENTED();
}
return "";
}
u64 ClearDir(DataDir dir)
{
fs::path data_dir = GetDataDir(dir);
u64 result = fs::remove_all(data_dir);
// mkpath at the end just so it actually exists
fs::create_directories(data_dir);
return result;
}
const std::string ReadableBytesSize(u64 size)
{
static constexpr std::array units{"B", "KiB", "MiB", "GiB", "TiB", "PiB"};
if (size == 0) {
return "0 B";
}
const int digit_groups = (std::min) (static_cast<int>(std::log10(size) / std::log10(1024)),
static_cast<int>(units.size()));
return fmt::format("{:.1f} {}", size / std::pow(1024, digit_groups), units[digit_groups]);
}
u64 DataDirSize(DataDir dir)
{
fs::path data_dir = GetDataDir(dir);
u64 size = 0;
if (!fs::exists(data_dir))
return 0;
for (const auto& entry : fs::recursive_directory_iterator(data_dir)) {
if (!entry.is_directory()) {
size += entry.file_size();
}
}
return size;
}
}

View file

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef DATA_MANAGER_H
#define DATA_MANAGER_H
#include "common/common_types.h"
#include <string>
namespace FrontendCommon::DataManager {
enum class DataDir { Saves, UserNand, SysNand, Mods, Shaders };
const std::string GetDataDir(DataDir dir);
u64 ClearDir(DataDir dir);
const std::string ReadableBytesSize(u64 size);
u64 DataDirSize(DataDir dir);
}; // namespace FrontendCommon::DataManager
#endif // DATA_MANAGER_H

View file

@ -4,9 +4,6 @@
# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
find_package(Qt6 REQUIRED COMPONENTS Core)
find_package(Qt6 REQUIRED COMPONENTS Core)
add_library(qt_common STATIC
qt_common.h
qt_common.cpp
@ -27,6 +24,7 @@ add_library(qt_common STATIC
qt_rom_util.h qt_rom_util.cpp
qt_applet_util.h qt_applet_util.cpp
qt_progress_dialog.h qt_progress_dialog.cpp
qt_string_lookup.h
)
@ -37,9 +35,29 @@ if (ENABLE_QT)
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
endif()
target_compile_definitions(qt_common PUBLIC
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
QT_USE_QSTRINGBUILDER
# Disable implicit conversions from/to C strings
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
# Disable implicit type narrowing in signal/slot connect() calls.
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable unsafe overloads of QProcess' start() function.
QT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
QT_NO_URL_CAST_FROM_STRING
)
add_subdirectory(externals)
target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip)
target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip)
target_link_libraries(qt_common PUBLIC frozen::frozen)
if (NOT APPLE AND ENABLE_OPENGL)
target_compile_definitions(qt_common PUBLIC HAS_OPENGL)

View file

@ -16,5 +16,4 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
AddJsonPackage(quazip)
# frozen
# TODO(crueter): Qt String Lookup
# AddJsonPackage(frozen)
AddJsonPackage(frozen)

View file

@ -1,17 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "qt_common/qt_game_util.h"
#include "frontend_common/data_manager.h"
#include "qt_content_util.h"
#include "common/fs/fs.h"
#include "core/hle/service/acc/profile_manager.h"
#include "frontend_common/content_manager.h"
#include "frontend_common/firmware_manager.h"
#include "qt_common/qt_common.h"
#include "qt_common/qt_game_util.h"
#include "qt_common/qt_progress_dialog.h"
#include "qt_frontend_util.h"
#include <QFuture>
#include <QtConcurrentRun>
#include <JlCompress.h>
#include <qfuturewatcher.h>
namespace QtCommon::Content {
@ -21,10 +25,10 @@ bool CheckGameFirmware(u64 program_id, QObject* parent)
&& !FirmwareManager::CheckFirmwarePresence(*system)) {
auto result = QtCommon::Frontend::ShowMessage(
QMessageBox::Warning,
"Game Requires Firmware",
"The game you are trying to launch requires firmware to boot or to get past the "
"opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>"
"dump and install firmware</a>, or press \"OK\" to launch anyways.",
tr("Game Requires Firmware"),
tr("The game you are trying to launch requires firmware to boot or to get past the "
"opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>"
"dump and install firmware</a>, or press \"OK\" to launch anyways."),
QMessageBox::Ok | QMessageBox::Cancel,
parent);
@ -60,8 +64,8 @@ void InstallFirmware(const QString& location, bool recursive)
const auto ShowMessage = [&]() {
QtCommon::Frontend::ShowMessage(icon,
failedTitle,
GetFirmwareInstallResultString(result));
tr(failedTitle),
tr(GetFirmwareInstallResultString(result)));
};
LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString());
@ -125,8 +129,8 @@ void InstallFirmware(const QString& location, bool recursive)
i++;
auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(),
FileSys::OpenMode::Read);
auto firmware_dst_vfile = firmware_vdir
->CreateFileRelative(firmware_src_path.filename().string());
auto firmware_dst_vfile = firmware_vdir->CreateFileRelative(
firmware_src_path.filename().string());
if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
LOG_ERROR(Frontend,
@ -168,9 +172,9 @@ void InstallFirmware(const QString& location, bool recursive)
const auto failed_names = QString::fromStdString(
fmt::format("{}", fmt::join(results, "\n")));
progress.close();
QtCommon::Frontend::Critical(tr("Firmware integrity verification failed!"),
tr("Verification failed for the following files:\n\n%1")
.arg(failed_names));
QtCommon::Frontend::Critical(
tr("Firmware integrity verification failed!"),
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
return;
}
@ -181,10 +185,10 @@ void InstallFirmware(const QString& location, bool recursive)
const std::string display_version(firmware_data.display_version.data());
result = FirmwareInstallResult::Success;
QtCommon::Frontend::Information(rootObject,
tr(successTitle),
tr(GetFirmwareInstallResultString(result))
.arg(QString::fromStdString(display_version)));
QtCommon::Frontend::Information(
rootObject,
tr(successTitle),
tr(GetFirmwareInstallResultString(result)).arg(QString::fromStdString(display_version)));
}
QString UnzipFirmwareToTmp(const QString& location)
@ -193,7 +197,7 @@ QString UnzipFirmwareToTmp(const QString& location)
fs::path tmp{fs::temp_directory_path()};
if (!fs::create_directories(tmp / "eden" / "firmware")) {
return "";
return QString();
}
tmp /= "eden";
@ -205,7 +209,7 @@ QString UnzipFirmwareToTmp(const QString& location)
QStringList result = JlCompress::extractDir(&zip, qCacheDir);
if (result.isEmpty()) {
return "";
return QString();
}
return qCacheDir;
@ -264,9 +268,8 @@ void InstallKeys()
return;
}
FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location
.toStdString(),
"keys");
FirmwareManager::KeyInstallResult result
= FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys");
system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
@ -282,9 +285,14 @@ void InstallKeys()
}
}
void VerifyInstalledContents() {
void VerifyInstalledContents()
{
// Initialize a progress dialog.
QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, QtCommon::rootObject);
QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."),
tr("Cancel"),
0,
100,
QtCommon::rootObject);
progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(100);
progress.setAutoClose(false);
@ -296,16 +304,17 @@ void VerifyInstalledContents() {
return progress.wasCanceled();
};
const std::vector<std::string> result =
ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, QtProgressCallback);
const std::vector<std::string> result
= ContentManager::VerifyInstalledContents(*QtCommon::system,
*QtCommon::provider,
QtProgressCallback);
progress.close();
if (result.empty()) {
QtCommon::Frontend::Information(tr("Integrity verification succeeded!"),
tr("The operation completed successfully."));
} else {
const auto failed_names =
QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
const auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
QtCommon::Frontend::Critical(
tr("Integrity verification failed!"),
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
@ -332,7 +341,7 @@ void FixProfiles()
qorphaned.reserve(8 * 33);
for (const std::string& s : orphaned) {
qorphaned += "\n" + QString::fromStdString(s);
qorphaned = qorphaned % QStringLiteral("\n") % QString::fromStdString(s);
}
QtCommon::Frontend::Critical(
@ -348,4 +357,156 @@ void FixProfiles()
QtCommon::Game::OpenSaveFolder();
}
void ClearDataDir(FrontendCommon::DataManager::DataDir dir)
{
auto result = QtCommon::Frontend::Warning(tr("Really clear data?"),
tr("Important data may be lost!"),
QMessageBox::Yes | QMessageBox::No);
if (result != QMessageBox::Yes)
return;
result = QtCommon::Frontend::Warning(
tr("Are you REALLY sure?"),
tr("Once deleted, your data will NOT come back!\n"
"Only do this if you're 100% sure you want to delete this data."),
QMessageBox::Yes | QMessageBox::No);
if (result != QMessageBox::Yes)
return;
QtCommon::Frontend::QtProgressDialog dialog(tr("Clearing..."), QString(), 0, 0);
dialog.show();
FrontendCommon::DataManager::ClearDir(dir);
dialog.close();
}
void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir, std::function<void()> callback)
{
const std::string dir = FrontendCommon::DataManager::GetDataDir(data_dir);
const QString zip_dump_location
= QtCommon::Frontend::GetSaveFileName(tr("Select Export Location"),
QStringLiteral("export.zip"),
tr("Zipped Archives (*.zip)"));
if (zip_dump_location.isEmpty())
return;
QMetaObject::Connection* connection = new QMetaObject::Connection;
*connection = QObject::connect(qApp, &QGuiApplication::aboutToQuit, rootObject, [=]() mutable {
QtCommon::Frontend::Warning(tr("Still Exporting"),
tr("Eden is still exporting some data, and will continue "
"running in the background until it's done."));
});
QtCommon::Frontend::QtProgressDialog* progress = new QtCommon::Frontend::QtProgressDialog(
tr("Compressing, this may take a while..."), tr("Background"), 0, 0, rootObject);
progress->setWindowModality(Qt::WindowModal);
progress->show();
QGuiApplication::processEvents();
QFuture<bool> future = QtConcurrent::run([&]() {
return JlCompress::compressDir(zip_dump_location,
QString::fromStdString(dir),
true,
QDir::Hidden | QDir::Files | QDir::Dirs);
});
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>(rootObject);
QObject::connect(watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
progress->close();
progress->deleteLater();
QObject::disconnect(*connection);
delete connection;
if (watcher->result()) {
QtCommon::Frontend::Information(tr("Exported Successfully"),
tr("Data was exported successfully."));
} else {
QtCommon::Frontend::Critical(
tr("Export Failed"),
tr("Ensure you have write permissions on the targeted directory and try again."));
}
watcher->deleteLater();
callback();
});
watcher->setFuture(future);
}
void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, std::function<void()> callback)
{
const std::string dir = FrontendCommon::DataManager::GetDataDir(data_dir);
using namespace QtCommon::Frontend;
const QString zip_dump_location = GetOpenFileName(tr("Select Import Location"),
{},
tr("Zipped Archives (*.zip)"));
if (zip_dump_location.isEmpty())
return;
StandardButton button = Warning(
tr("Import Warning"),
tr("All previous data in this directory will be deleted. Are you sure you wish to "
"proceed?"),
StandardButton::Yes | StandardButton::No);
if (button != QMessageBox::Yes)
return;
FrontendCommon::DataManager::ClearDir(data_dir);
QMetaObject::Connection* connection = new QMetaObject::Connection;
*connection = QObject::connect(qApp, &QGuiApplication::aboutToQuit, rootObject, [=]() mutable {
Warning(tr("Still Importing"),
tr("Eden is still importing some data, and will continue "
"running in the background until it's done."));
});
QtProgressDialog* progress = new QtProgressDialog(tr("Decompressing, this may take a while..."),
tr("Background"),
0,
0,
rootObject);
progress->setWindowModality(Qt::WindowModal);
progress->show();
QGuiApplication::processEvents();
QFuture<bool> future = QtConcurrent::run([=]() {
return !JlCompress::extractDir(zip_dump_location,
QString::fromStdString(dir)).empty();
});
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>(rootObject);
QObject::connect(watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
progress->close();
progress->deleteLater();
QObject::disconnect(*connection);
delete connection;
if (watcher->result()) {
Information(tr("Imported Successfully"), tr("Data was imported successfully."));
} else {
Critical(
tr("Import Failed"),
tr("Ensure you have read permissions on the targeted directory and try again."));
}
watcher->deleteLater();
callback();
});
watcher->setFuture(future);
}
} // namespace QtCommon::Content

View file

@ -6,6 +6,7 @@
#include <QObject>
#include "common/common_types.h"
#include "frontend_common/data_manager.h"
namespace QtCommon::Content {
@ -46,6 +47,10 @@ void InstallKeys();
void VerifyGameContents(const std::string &game_path);
void VerifyInstalledContents();
void ClearDataDir(FrontendCommon::DataManager::DataDir dir);
void ExportDataDir(FrontendCommon::DataManager::DataDir dir, std::function<void()> callback = {});
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, std::function<void()> callback = {});
// Profiles //
void FixProfiles();
}

View file

@ -32,4 +32,15 @@ const QString GetOpenFileName(const QString &title,
#endif
}
const QString GetSaveFileName(const QString &title,
const QString &dir,
const QString &filter,
QString *selectedFilter,
Options options)
{
#ifdef YUZU_QT_WIDGETS
return QFileDialog::getSaveFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options);
#endif
}
} // namespace QtCommon::Frontend

View file

@ -97,10 +97,10 @@ Q_ENUM_NS(Icon)
// TODO(crueter) widgets-less impl, choices et al.
StandardButton ShowMessage(Icon icon,
const QString &title,
const QString &text,
StandardButtons buttons = StandardButton::NoButton,
QObject *parent = nullptr);
const QString &title,
const QString &text,
StandardButtons buttons = StandardButton::NoButton,
QObject *parent = nullptr);
#define UTIL_OVERRIDES(level) \
inline StandardButton level(QObject *parent, \
@ -110,21 +110,6 @@ StandardButton ShowMessage(Icon icon,
{ \
return ShowMessage(Icon::level, title, text, buttons, parent); \
} \
inline StandardButton level(QObject *parent, \
const char *title, \
const char *text, \
StandardButtons buttons \
= StandardButton::Ok) \
{ \
return ShowMessage(Icon::level, tr(title), tr(text), buttons, parent); \
} \
inline StandardButton level(const char *title, \
const char *text, \
StandardButtons buttons \
= StandardButton::Ok) \
{ \
return ShowMessage(Icon::level, tr(title), tr(text), buttons, rootObject); \
} \
inline StandardButton level(const QString title, \
const QString &text, \
StandardButtons buttons \
@ -144,5 +129,11 @@ const QString GetOpenFileName(const QString &title,
QString *selectedFilter = nullptr,
Options options = Options());
const QString GetSaveFileName(const QString &title,
const QString &dir,
const QString &filter,
QString *selectedFilter = nullptr,
Options options = Options());
} // namespace QtCommon::Frontend
#endif // QT_FRONTEND_UTIL_H

View file

@ -220,8 +220,8 @@ void RemoveBaseContent(u64 program_id, InstalledEntryType type)
program_id);
if (res) {
QtCommon::Frontend::Information(rootObject,
"Successfully Removed",
"Successfully removed the installed base game.");
tr("Successfully Removed"),
tr("Successfully removed the installed base game."));
} else {
QtCommon::Frontend::Warning(
rootObject,
@ -235,8 +235,8 @@ void RemoveUpdateContent(u64 program_id, InstalledEntryType type)
const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id);
if (res) {
QtCommon::Frontend::Information(rootObject,
"Successfully Removed",
"Successfully removed the installed update.");
tr("Successfully Removed"),
tr("Successfully removed the installed update."));
} else {
QtCommon::Frontend::Warning(rootObject,
GetGameListErrorRemoving(type),

View file

@ -17,7 +17,12 @@ bool OpenShaderCache(u64 program_id, QObject *parent)
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)};
if (!Common::FS::CreateDirs(shader_cache_folder_path)) {
QtCommon::Frontend::ShowMessage(QMessageBox::Warning, "Error Opening Shader Cache", "Failed to create or open shader cache for this title, ensure your app data directory has write permissions.", QMessageBox::Ok, parent);
QtCommon::Frontend::ShowMessage(QMessageBox::Warning,
tr("Error Opening Shader Cache"),
tr("Failed to create or open shader cache for this title, "
"ensure your app data directory has write permissions."),
QMessageBox::Ok,
parent);
}
const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)};

View file

@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QString>
#include <frozen/string.h>
#include <frozen/unordered_map.h>
#include <qobjectdefs.h>
#include <qtmetamacros.h>
namespace QtCommon::StringLookup {
Q_NAMESPACE
// TODO(crueter): QML interface
enum StringKey {
SavesTooltip,
ShadersTooltip,
UserNandTooltip,
SysNandTooltip,
ModsTooltip,
};
static constexpr const frozen::unordered_map<StringKey, frozen::string, 5> strings = {
{SavesTooltip, "Contains game save data. DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING!"},
{ShadersTooltip, "Contains Vulkan and OpenGL pipeline caches. Generally safe to remove."},
{UserNandTooltip, "Contains updates and DLC for games."},
{SysNandTooltip, "Contains firmware and applet data."},
{ModsTooltip, "Contains game mods, patches, and cheats."},
};
static inline const QString Lookup(StringKey key)
{
return QString::fromStdString(strings.at(key).data());
}
}

View file

@ -234,6 +234,9 @@ add_executable(yuzu
deps_dialog.cpp
deps_dialog.h
deps_dialog.ui
data_dialog.h data_dialog.cpp data_dialog.ui
data_widget.ui
)
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
@ -390,7 +393,7 @@ endif()
target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common)
target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets)
target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets Qt6::Concurrent)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (NOT WIN32)
@ -405,25 +408,6 @@ if (UNIX AND NOT APPLE)
endif()
endif()
target_compile_definitions(yuzu PRIVATE
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
QT_USE_QSTRINGBUILDER
# Disable implicit conversions from/to C strings
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
# Disable implicit type narrowing in signal/slot connect() calls.
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable unsafe overloads of QProcess' start() function.
QT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
QT_NO_URL_CAST_FROM_STRING
)
if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
target_compile_definitions(yuzu PRIVATE YUZU_ENABLE_COMPATIBILITY_REPORTING)
endif()

View file

@ -1,10 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QButtonGroup>
#include <QMessageBox>
#include <QPushButton>
#include <QtConcurrent/qtconcurrentrun.h>
#include <qtconcurrentrun.h>
#include "common/logging/log.h"
#include "ui_compatdb.h"
#include "yuzu/compatdb.h"

View file

@ -14,7 +14,7 @@
#include <QRegExpValidator>
#endif
#include <QtConcurrent/QtConcurrentRun>
#include <QtConcurrentRun>
#include "common/settings.h"
#include "ui_configure_web.h"
#include "qt_common/uisettings.h"

105
src/yuzu/data_dialog.cpp Normal file
View file

@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "data_dialog.h"
#include "frontend_common/data_manager.h"
#include "qt_common/qt_content_util.h"
#include "qt_common/qt_frontend_util.h"
#include "qt_common/qt_progress_dialog.h"
#include "qt_common/qt_string_lookup.h"
#include "ui_data_dialog.h"
#include <QDesktopServices>
#include <QFileDialog>
#include <QFutureWatcher>
#include <QProgressDialog>
#include <QtConcurrentRun>
DataDialog::DataDialog(QWidget *parent)
: QDialog(parent)
, ui(std::make_unique<Ui::DataDialog>())
{
ui->setupUi(this);
// TODO: Should we make this a single widget that pulls data from a model?
#define WIDGET(name) \
ui->page->addWidget(new DataWidget(FrontendCommon::DataManager::DataDir::name, \
QtCommon::StringLookup::name##Tooltip, \
this));
WIDGET(Saves)
WIDGET(Shaders)
WIDGET(UserNand)
WIDGET(SysNand)
WIDGET(Mods)
#undef WIDGET
connect(ui->labels, &QListWidget::itemSelectionChanged, this, [this]() {
ui->page->setCurrentIndex(ui->labels->currentRow());
});
}
DataDialog::~DataDialog() = default;
DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir,
QtCommon::StringLookup::StringKey tooltip,
QWidget *parent)
: QWidget(parent)
, ui(std::make_unique<Ui::DataWidget>())
, m_dir(data_dir)
{
ui->setupUi(this);
ui->tooltip->setText(QtCommon::StringLookup::Lookup(tooltip));
ui->clear->setIcon(QIcon::fromTheme(QStringLiteral("user-trash")));
ui->open->setIcon(QIcon::fromTheme(QStringLiteral("folder")));
ui->upload->setIcon(QIcon::fromTheme(QStringLiteral("upload")));
ui->download->setIcon(QIcon::fromTheme(QStringLiteral("download")));
connect(ui->clear, &QPushButton::clicked, this, &DataWidget::clear);
connect(ui->open, &QPushButton::clicked, this, &DataWidget::open);
connect(ui->upload, &QPushButton::clicked, this, &DataWidget::upload);
connect(ui->download, &QPushButton::clicked, this, &DataWidget::download);
scan();
}
void DataWidget::clear()
{
QtCommon::Content::ClearDataDir(m_dir);
scan();
}
void DataWidget::open()
{
QDesktopServices::openUrl(QUrl::fromLocalFile(
QString::fromStdString(FrontendCommon::DataManager::GetDataDir(m_dir))));
}
void DataWidget::upload()
{
QtCommon::Content::ExportDataDir(m_dir);
}
void DataWidget::download()
{
QtCommon::Content::ImportDataDir(m_dir, std::bind(&DataWidget::scan, this));
}
void DataWidget::scan() {
ui->size->setText(tr("Calculating..."));
QFutureWatcher<u64> *watcher = new QFutureWatcher<u64>(this);
connect(watcher, &QFutureWatcher<u64>::finished, this, [=, this]() {
u64 size = watcher->result();
ui->size->setText(
QString::fromStdString(FrontendCommon::DataManager::ReadableBytesSize(size)));
watcher->deleteLater();
});
watcher->setFuture(
QtConcurrent::run([this]() { return FrontendCommon::DataManager::DataDirSize(m_dir); }));
}

50
src/yuzu/data_dialog.h Normal file
View file

@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef DATA_DIALOG_H
#define DATA_DIALOG_H
#include <QDialog>
#include "frontend_common/data_manager.h"
#include "qt_common/qt_string_lookup.h"
#include "ui_data_widget.h"
namespace Ui {
class DataDialog;
}
class DataDialog : public QDialog
{
Q_OBJECT
public:
explicit DataDialog(QWidget *parent = nullptr);
~DataDialog();
private:
std::unique_ptr<Ui::DataDialog> ui;
};
class DataWidget : public QWidget
{
Q_OBJECT
public:
explicit DataWidget(FrontendCommon::DataManager::DataDir data_dir,
QtCommon::StringLookup::StringKey tooltip,
QWidget *parent = nullptr);
public slots:
void clear();
void open();
void upload();
void download();
void scan();
private:
std::unique_ptr<Ui::DataWidget> ui;
FrontendCommon::DataManager::DataDir m_dir;
};
#endif // DATA_DIALOG_H

148
src/yuzu/data_dialog.ui Normal file
View file

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DataDialog</class>
<widget class="QDialog" name="DataDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>320</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>320</height>
</size>
</property>
<property name="windowTitle">
<string>Data Manager</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
<item>
<widget class="QListWidget" name="labels">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Saves</string>
</property>
</item>
<item>
<property name="text">
<string>Shaders</string>
</property>
</item>
<item>
<property name="text">
<string>UserNAND</string>
</property>
</item>
<item>
<property name="text">
<string>SysNAND</string>
</property>
</item>
<item>
<property name="text">
<string>Mods</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="page">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>275</width>
<height>200</height>
</size>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Deleting ANY data is IRREVERSABLE!</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DataDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DataDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

205
src/yuzu/data_widget.ui Normal file
View file

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DataWidget</class>
<widget class="QWidget" name="DataWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>275</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="3,2">
<item>
<widget class="QLabel" name="tooltip">
<property name="text">
<string>Tooltip</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="size">
<property name="font">
<font>
<pointsize>10</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string notr="true">Size</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="open">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>52</width>
<height>47</height>
</size>
</property>
<property name="toolTip">
<string>Open with your system file manager</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
max-width: 50px;
max-height: 45px;
min-width: 50px;
min-height: 45px;
}</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>28</width>
<height>28</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clear">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>52</width>
<height>47</height>
</size>
</property>
<property name="toolTip">
<string>Delete all data in this directory. THIS IS 100% IRREVERSABLE!</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
max-width: 50px;
max-height: 45px;
min-width: 50px;
min-height: 45px;
}</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>28</width>
<height>28</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="upload">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>52</width>
<height>47</height>
</size>
</property>
<property name="toolTip">
<string>Export all data in this directory. This may take a while!</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
max-width: 50px;
max-height: 45px;
min-width: 50px;
min-height: 45px;
}</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>28</width>
<height>28</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="download">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>52</width>
<height>47</height>
</size>
</property>
<property name="toolTip">
<string>Import data for this directory. This may take a while, and will delete ALL EXISTING DATA!</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
max-width: 50px;
max-height: 45px;
min-width: 50px;
min-height: 45px;
}</string>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>28</width>
<height>28</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -156,6 +156,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/debugger/console.h"
#include "yuzu/debugger/controller.h"
#include "yuzu/debugger/wait_tree.h"
#include "yuzu/data_dialog.h"
#include "yuzu/deps_dialog.h"
#include "yuzu/discord.h"
#include "yuzu/game_list.h"
@ -1705,6 +1706,7 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Install_Keys, &GMainWindow::OnInstallDecryptionKeys);
connect_menu(ui->action_About, &GMainWindow::OnAbout);
connect_menu(ui->action_Eden_Dependencies, &GMainWindow::OnEdenDependencies);
connect_menu(ui->action_Data_Manager, &GMainWindow::OnDataDialog);
}
void GMainWindow::UpdateMenuState() {
@ -3934,6 +3936,15 @@ void GMainWindow::OnEdenDependencies() {
depsDialog.exec();
}
void GMainWindow::OnDataDialog() {
DataDialog dataDialog(this);
dataDialog.exec();
// refresh stuff in case it was cleared
OnGameListRefresh();
}
void GMainWindow::OnToggleFilterBar() {
game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked());
if (ui->action_Show_Filter_Bar->isChecked()) {
@ -4474,11 +4485,15 @@ void GMainWindow::SetFirmwareVersion() {
if (result.IsError() || !CheckFirmwarePresence()) {
LOG_INFO(Frontend, "Installed firmware: No firmware available");
ui->menu_Applets->setEnabled(false);
ui->menu_Create_Shortcuts->setEnabled(false);
firmware_label->setVisible(false);
return;
}
firmware_label->setVisible(true);
ui->menu_Applets->setEnabled(true);
ui->menu_Create_Shortcuts->setEnabled(true);
const std::string display_version(firmware_data.display_version.data());
const std::string display_title(firmware_data.display_title.data());

View file

@ -387,6 +387,7 @@ private slots:
void OnInstallDecryptionKeys();
void OnAbout();
void OnEdenDependencies();
void OnDataDialog();
void OnToggleFilterBar();
void OnToggleStatusBar();
void OnGameListRefresh();

View file

@ -158,13 +158,23 @@
</property>
<widget class="QMenu" name="menu_cabinet_applet">
<property name="title">
<string>&amp;Amiibo</string>
<string>Am&amp;iibo</string>
</property>
<addaction name="action_Load_Cabinet_Nickname_Owner"/>
<addaction name="action_Load_Cabinet_Eraser"/>
<addaction name="action_Load_Cabinet_Restorer"/>
<addaction name="action_Load_Cabinet_Formatter"/>
</widget>
<widget class="QMenu" name="menu_Applets">
<property name="title">
<string>&amp;Applets</string>
</property>
<addaction name="action_Load_Home_Menu"/>
<addaction name="action_Load_Album"/>
<addaction name="action_Load_Mii_Edit"/>
<addaction name="action_Open_Controller_Menu"/>
<addaction name="action_Open_Setup"/>
</widget>
<widget class="QMenu" name="menuTAS">
<property name="title">
<string>&amp;TAS</string>
@ -184,7 +194,7 @@
</widget>
<widget class="QMenu" name="menuInstall_Firmware">
<property name="title">
<string>Install Firmware</string>
<string>Install &amp;Firmware</string>
</property>
<addaction name="action_Firmware_From_Folder"/>
<addaction name="action_Firmware_From_ZIP"/>
@ -192,13 +202,10 @@
<addaction name="action_Install_Keys"/>
<addaction name="menuInstall_Firmware"/>
<addaction name="action_Verify_installed_contents"/>
<addaction name="action_Data_Manager"/>
<addaction name="separator"/>
<addaction name="menu_cabinet_applet"/>
<addaction name="action_Load_Album"/>
<addaction name="action_Load_Mii_Edit"/>
<addaction name="action_Open_Controller_Menu"/>
<addaction name="action_Load_Home_Menu"/>
<addaction name="action_Open_Setup"/>
<addaction name="menu_Applets"/>
<addaction name="menu_Create_Shortcuts"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
@ -497,12 +504,12 @@
</action>
<action name="action_Install_Keys">
<property name="text">
<string>Install Decryption Keys</string>
<string>Install Decryption &amp;Keys</string>
</property>
</action>
<action name="action_Load_Home_Menu">
<property name="text">
<string>Open Home Menu</string>
<string>Open &amp;Home Menu</string>
</property>
</action>
<action name="action_Discord">
@ -593,6 +600,11 @@
<string>&amp;Eden Dependencies</string>
</property>
</action>
<action name="action_Data_Manager">
<property name="text">
<string>&amp;Data Manager</string>
</property>
</action>
</widget>
<resources>
<include location="yuzu.qrc"/>

View file

@ -16,7 +16,7 @@
#include <QMetaType>
#include <QTime>
#include <QUrl>
#include <QtConcurrent/QtConcurrentRun>
#include <QtConcurrentRun>
#include "common/logging/log.h"
#include "network/announce_multiplayer_session.h"
#include "ui_chat_room.h"

View file

@ -10,7 +10,7 @@
#include <QLocale>
#include <QMetaType>
#include <QTime>
#include <QtConcurrent/QtConcurrentRun>
#include <QtConcurrentRun>
#include "common/logging/log.h"
#include "network/announce_multiplayer_session.h"
#include "ui_client_room.h"

View file

@ -9,7 +9,7 @@
#include <QIntValidator>
#include <QRegularExpressionValidator>
#include <QString>
#include <QtConcurrent/QtConcurrentRun>
#include <QtConcurrentRun>
#include "common/settings.h"
#include "core/core.h"
#include "core/internal_network/network_interface.h"

View file

@ -12,7 +12,7 @@
#include <QMessageBox>
#include <QMetaType>
#include <QTime>
#include <QtConcurrent/QtConcurrentRun>
#include <QtConcurrentRun>
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"

View file

@ -6,7 +6,7 @@
#include <QInputDialog>
#include <QList>
#include <QtConcurrent/QtConcurrentRun>
#include <QtConcurrentRun>
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"