diff --git a/dist/qt_themes/colorful/icons/48x48/trash.png b/dist/qt_themes/colorful/icons/48x48/trash.png
new file mode 100644
index 0000000000..e60dce2e32
Binary files /dev/null and b/dist/qt_themes/colorful/icons/48x48/trash.png differ
diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc
index 82cd367be9..fc85922d6b 100644
--- a/dist/qt_themes/colorful/style.qrc
+++ b/dist/qt_themes/colorful/style.qrc
@@ -18,6 +18,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
icons/48x48/bad_folder.png
icons/48x48/chip.png
icons/48x48/folder.png
+ icons/48x48/trash.png
icons/48x48/list-add.png
icons/48x48/no_avatar.png
icons/48x48/sd_card.png
diff --git a/dist/qt_themes/colorful_midnight_blue/style.qrc b/dist/qt_themes/colorful_midnight_blue/style.qrc
index b9821c6722..2aee8ea1e1 100644
--- a/dist/qt_themes/colorful_midnight_blue/style.qrc
+++ b/dist/qt_themes/colorful_midnight_blue/style.qrc
@@ -11,6 +11,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
../colorful/icons/48x48/bad_folder.png
../colorful/icons/48x48/chip.png
../colorful/icons/48x48/folder.png
+ ../colorful/icons/48x48/trash.png
../colorful/icons/48x48/list-add.png
../colorful/icons/48x48/sd_card.png
../colorful/icons/256x256/plus_folder.png
diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc
index 0d8af20fc1..45a91ef2dc 100644
--- a/dist/qt_themes/default/default.qrc
+++ b/dist/qt_themes/default/default.qrc
@@ -14,6 +14,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
icons/48x48/bad_folder.png
icons/48x48/chip.png
icons/48x48/folder.png
+ icons/48x48/trash.png
icons/48x48/list-add.png
icons/48x48/sd_card.png
icons/48x48/star.png
diff --git a/dist/qt_themes/default/icons/48x48/trash.png b/dist/qt_themes/default/icons/48x48/trash.png
new file mode 100644
index 0000000000..547b821de9
Binary files /dev/null and b/dist/qt_themes/default/icons/48x48/trash.png differ
diff --git a/dist/qt_themes/default_dark/style.qrc b/dist/qt_themes/default_dark/style.qrc
index 7de4737c2c..ac6c9fe4e9 100644
--- a/dist/qt_themes/default_dark/style.qrc
+++ b/dist/qt_themes/default_dark/style.qrc
@@ -13,6 +13,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
../colorful/icons/48x48/bad_folder.png
../colorful/icons/48x48/chip.png
../colorful/icons/48x48/folder.png
+ ../colorful/icons/48x48/trash.png
../qdarkstyle/icons/48x48/no_avatar.png
../colorful/icons/48x48/list-add.png
../colorful/icons/48x48/sd_card.png
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/trash.png b/dist/qt_themes/qdarkstyle/icons/48x48/trash.png
new file mode 100644
index 0000000000..e59fe85c03
Binary files /dev/null and b/dist/qt_themes/qdarkstyle/icons/48x48/trash.png differ
diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc
index a89fb26c68..2b5ca31e72 100644
--- a/dist/qt_themes/qdarkstyle/style.qrc
+++ b/dist/qt_themes/qdarkstyle/style.qrc
@@ -9,6 +9,7 @@
icons/48x48/bad_folder.png
icons/48x48/chip.png
icons/48x48/folder.png
+ icons/48x48/trash.png
icons/48x48/no_avatar.png
icons/48x48/list-add.png
icons/48x48/sd_card.png
diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt
index 70e142bb0c..aa44740596 100644
--- a/src/frontend_common/CMakeLists.txt
+++ b/src/frontend_common/CMakeLists.txt
@@ -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)
diff --git a/src/frontend_common/data_manager.cpp b/src/frontend_common/data_manager.cpp
new file mode 100644
index 0000000000..4a63ed9e28
--- /dev/null
+++ b/src/frontend_common/data_manager.cpp
@@ -0,0 +1,75 @@
+// 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
+#include
+
+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:
+ return (nand_dir / "system").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(std::log10(size) / std::log10(1024)),
+ static_cast(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;
+}
+
+}
diff --git a/src/frontend_common/data_manager.h b/src/frontend_common/data_manager.h
new file mode 100644
index 0000000000..f6ae836c14
--- /dev/null
+++ b/src/frontend_common/data_manager.h
@@ -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
+
+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
diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt
index aa931f113e..fe728e0377 100644
--- a/src/qt_common/CMakeLists.txt
+++ b/src/qt_common/CMakeLists.txt
@@ -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
)
@@ -40,6 +38,7 @@ endif()
add_subdirectory(externals)
target_link_libraries(qt_common PRIVATE core Qt6::Core 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)
diff --git a/src/qt_common/externals/CMakeLists.txt b/src/qt_common/externals/CMakeLists.txt
index e7b2e7b3e6..189a52c0a6 100644
--- a/src/qt_common/externals/CMakeLists.txt
+++ b/src/qt_common/externals/CMakeLists.txt
@@ -17,4 +17,4 @@ AddJsonPackage(quazip)
# frozen
# TODO(crueter): Qt String Lookup
-# AddJsonPackage(frozen)
+AddJsonPackage(frozen)
diff --git a/src/qt_common/qt_content_util.cpp b/src/qt_common/qt_content_util.cpp
index 2f659cf1b2..9cbd1caf02 100644
--- a/src/qt_common/qt_content_util.cpp
+++ b/src/qt_common/qt_content_util.cpp
@@ -348,4 +348,29 @@ void FixProfiles()
QtCommon::Game::OpenSaveFolder();
}
+void ClearDataDir(FrontendCommon::DataManager::DataDir dir) {
+ auto result = QtCommon::Frontend::Warning("Really clear data?",
+ "Important data may be lost!",
+ QMessageBox::Yes | QMessageBox::No);
+
+ if (result != QMessageBox::Yes)
+ return;
+
+ result = QtCommon::Frontend::Warning(
+ "Are you REALLY sure?",
+ "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();
+}
+
} // namespace QtCommon::Content
diff --git a/src/qt_common/qt_content_util.h b/src/qt_common/qt_content_util.h
index b95e78c0a0..b2443829ab 100644
--- a/src/qt_common/qt_content_util.h
+++ b/src/qt_common/qt_content_util.h
@@ -6,6 +6,7 @@
#include
#include "common/common_types.h"
+#include "frontend_common/data_manager.h"
namespace QtCommon::Content {
@@ -46,6 +47,8 @@ void InstallKeys();
void VerifyGameContents(const std::string &game_path);
void VerifyInstalledContents();
+void ClearDataDir(FrontendCommon::DataManager::DataDir dir);
+
// Profiles //
void FixProfiles();
}
diff --git a/src/qt_common/qt_string_lookup.h b/src/qt_common/qt_string_lookup.h
new file mode 100644
index 0000000000..de6acac8a1
--- /dev/null
+++ b/src/qt_common/qt_string_lookup.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace QtCommon::StringLookup {
+
+Q_NAMESPACE
+
+// TODO(crueter): QML interface
+enum StringKey {
+ SavesTooltip,
+ ShadersTooltip,
+ UserNandTooltip,
+ SysNandTooltip,
+ ModsTooltip,
+};
+
+static constexpr const frozen::unordered_map 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());
+}
+
+}
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index c03f7a3abf..f4669d0914 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -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")
diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp
new file mode 100644
index 0000000000..87d81e4f43
--- /dev/null
+++ b/src/yuzu/data_dialog.cpp
@@ -0,0 +1,85 @@
+// 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_string_lookup.h"
+#include "ui_data_dialog.h"
+
+#include
+#include
+#include
+
+DataDialog::DataDialog(QWidget *parent)
+ : QDialog(parent)
+ , ui(std::make_unique())
+{
+ 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())
+ , m_dir(data_dir)
+{
+ ui->setupUi(this);
+
+ ui->tooltip->setText(QtCommon::StringLookup::Lookup(tooltip));
+
+ ui->clear->setIcon(QIcon::fromTheme(QStringLiteral("trash")));
+ ui->open->setIcon(QIcon::fromTheme(QStringLiteral("folder")));
+
+ connect(ui->clear, &QPushButton::clicked, this, &DataWidget::clear);
+ connect(ui->open, &QPushButton::clicked, this, &DataWidget::open);
+
+ 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::scan() {
+ ui->size->setText(tr("Calculating..."));
+
+ QFutureWatcher *watcher = new QFutureWatcher(this);
+
+ connect(watcher, &QFutureWatcher::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); }));
+}
diff --git a/src/yuzu/data_dialog.h b/src/yuzu/data_dialog.h
new file mode 100644
index 0000000000..9f367d6049
--- /dev/null
+++ b/src/yuzu/data_dialog.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef DATA_DIALOG_H
+#define DATA_DIALOG_H
+
+#include
+#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;
+};
+
+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 scan();
+
+private:
+ std::unique_ptr ui;
+ FrontendCommon::DataManager::DataDir m_dir;
+};
+
+#endif // DATA_DIALOG_H
diff --git a/src/yuzu/data_dialog.ui b/src/yuzu/data_dialog.ui
new file mode 100644
index 0000000000..06751e2fb1
--- /dev/null
+++ b/src/yuzu/data_dialog.ui
@@ -0,0 +1,148 @@
+
+
+ DataDialog
+
+
+
+ 0
+ 0
+ 480
+ 320
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 320
+
+
+
+ Data Manager
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
-
+
+ Saves
+
+
+ -
+
+ Shaders
+
+
+ -
+
+ UserNAND
+
+
+ -
+
+ SysNAND
+
+
+ -
+
+ Mods
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 275
+ 200
+
+
+
+ -1
+
+
+
+
+
+ -
+
+
+ 10
+
+
-
+
+
+ Deleting ANY data is IRREVERSABLE!
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+ QDialogButtonBox::StandardButton::Ok
+
+
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ DataDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ DataDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/yuzu/data_widget.ui b/src/yuzu/data_widget.ui
new file mode 100644
index 0000000000..ed67078fa1
--- /dev/null
+++ b/src/yuzu/data_widget.ui
@@ -0,0 +1,147 @@
+
+
+ DataWidget
+
+
+
+ 0
+ 0
+ 275
+ 200
+
+
+
+ Form
+
+
+ -
+
+
-
+
+
+ Tooltip
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+ true
+
+
+
+ -
+
+
+
+ 10
+ true
+
+
+
+ Size
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+ true
+
+
+
+
+
+ -
+
+
+ QLayout::SizeConstraint::SetFixedSize
+
+
+ 25
+
+
-
+
+
+
+ 1
+ 1
+
+
+
+
+ 52
+ 42
+
+
+
+ Open with your system file manager
+
+
+ QPushButton {
+ border-style: solid;
+ border-width:1px;
+ border-radius:25px;
+ border-color: transparent;
+ max-width:50px;
+ max-height:40px;
+ min-width:50px;
+ min-height:40px;
+}
+
+
+
+
+
+
+ 24
+ 24
+
+
+
+
+ -
+
+
+
+ 1
+ 1
+
+
+
+
+ 52
+ 42
+
+
+
+ Delete all data in this directory. THIS IS 100% IRREVERSABLE!
+
+
+ QPushButton {
+ border-style: solid;
+ border-width:1px;
+ border-radius:25px;
+ border-color: transparent;
+ max-width:50px;
+ max-height:40px;
+ min-width:50px;
+ min-height:40px;
+}
+
+
+
+
+
+
+ 24
+ 24
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 44ed29f141..b6dded447c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -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,11 @@ void GMainWindow::OnEdenDependencies() {
depsDialog.exec();
}
+void GMainWindow::OnDataDialog() {
+ DataDialog dataDialog(this);
+ dataDialog.exec();
+}
+
void GMainWindow::OnToggleFilterBar() {
game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked());
if (ui->action_Show_Filter_Bar->isChecked()) {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index e3922759b0..a3e99c05fe 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -387,6 +387,7 @@ private slots:
void OnInstallDecryptionKeys();
void OnAbout();
void OnEdenDependencies();
+ void OnDataDialog();
void OnToggleFilterBar();
void OnToggleStatusBar();
void OnGameListRefresh();
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 5f56c9e6d1..12ff4efdf1 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -158,13 +158,23 @@
+