diff --git a/.ci/license-header.sh b/.ci/license-header.sh
index fecffaa7d3..3d4929d1c1 100755
--- a/.ci/license-header.sh
+++ b/.ci/license-header.sh
@@ -5,10 +5,13 @@ HEADER_HASH="$(cat "$PWD/.ci/license/header-hash.txt")"
echo "Getting branch changes"
-BRANCH=`git rev-parse --abbrev-ref HEAD`
-COMMITS=`git log ${BRANCH} --not master --pretty=format:"%h"`
-RANGE="${COMMITS[${#COMMITS[@]}-1]}^..${COMMITS[0]}"
-FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r`
+# BRANCH=`git rev-parse --abbrev-ref HEAD`
+# COMMITS=`git log ${BRANCH} --not master --pretty=format:"%h"`
+# RANGE="${COMMITS[${#COMMITS[@]}-1]}^..${COMMITS[0]}"
+# FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r`
+
+BASE=`git merge-base master HEAD`
+FILES=`git diff --name-only $BASE`
#FILES=$(git diff --name-only master)
diff --git a/docs/CPM.md b/docs/CPM.md
index 2afcdaf164..bce224da40 100644
--- a/docs/CPM.md
+++ b/docs/CPM.md
@@ -245,6 +245,6 @@ include(CPMUtil)
Currently, `cpm-fetch.sh` defines the following directories for cpmfiles (max depth of 2, so subdirs are caught as well):
-`externals src/yuzu src/dynarmic .`
+`externals src/qt_common src/dynarmic .`
Whenever you add a new cpmfile, update the script accordingly
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index eb66e55964..184b049d06 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -234,6 +234,8 @@ if (YUZU_ROOM_STANDALONE)
endif()
if (ENABLE_QT)
+ add_definitions(-DYUZU_QT_WIDGETS)
+ add_subdirectory(qt_common)
add_subdirectory(yuzu)
endif()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index 98f342c274..b383cc147f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -232,8 +232,6 @@ class GameAdapter(private val activity: AppCompatActivity) :
binding.root.findNavController().navigate(action)
}
- val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
if (NativeLibrary.gameRequiresFirmware(game.programId) && !NativeLibrary.isFirmwareAvailable()) {
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.loader_requires_firmware)
@@ -248,23 +246,6 @@ class GameAdapter(private val activity: AppCompatActivity) :
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
- } else if (BooleanSetting.DISABLE_NCA_VERIFICATION.getBoolean(false) && !preferences.getBoolean(
- Settings.PREF_HIDE_NCA_POPUP, false)) {
- MaterialAlertDialogBuilder(activity)
- .setTitle(R.string.nca_verification_disabled)
- .setMessage(activity.getString(R.string.nca_verification_disabled_description))
- .setPositiveButton(android.R.string.ok) { _, _ ->
- launch()
- }
- .setNeutralButton(R.string.dont_show_again) { _, _ ->
- preferences.edit {
- putBoolean(Settings.PREF_HIDE_NCA_POPUP, true)
- }
-
- launch()
- }
- .setNegativeButton(android.R.string.cancel) { _, _ -> }
- .show()
} else {
launch()
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index 6d4bfd97ac..3c5b9003de 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -35,7 +35,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_SAMPLE_SHADING("sample_shading"),
PICTURE_IN_PICTURE("picture_in_picture"),
USE_CUSTOM_RTC("custom_rtc_enabled"),
- DISABLE_NCA_VERIFICATION("disable_nca_verification"),
BLACK_BACKGROUNDS("black_backgrounds"),
JOYSTICK_REL_CENTER("joystick_rel_center"),
DPAD_SLIDE("dpad_slide"),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 883d8efaef..1f2ba81a73 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -297,13 +297,6 @@ abstract class SettingsItem(
descriptionId = R.string.use_custom_rtc_description
)
)
- put(
- SwitchSetting(
- BooleanSetting.DISABLE_NCA_VERIFICATION,
- titleId = R.string.disable_nca_verification,
- descriptionId = R.string.disable_nca_verification_description
- )
- )
put(
StringInputSetting(
StringSetting.WEB_TOKEN,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 630bcb0d74..14d62ceec3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -210,7 +210,6 @@ class SettingsFragmentPresenter(
add(IntSetting.LANGUAGE_INDEX.key)
add(BooleanSetting.USE_CUSTOM_RTC.key)
add(LongSetting.CUSTOM_RTC.key)
- add(BooleanSetting.DISABLE_NCA_VERIFICATION.key)
add(HeaderSetting(R.string.network))
add(StringSetting.WEB_TOKEN.key)
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 2c7923d5a3..6e428a1f7f 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -784,9 +784,6 @@
Game Requires Firmware
dump and install firmware, or press "OK" to launch anyways.]]>
- NCA Verification Disabled
- This is required to run new games and updates, but may cause instability or crashes if NCA files are corrupt, modified, or tampered with. If unsure, re-enable verification in Advanced Settings -> System, and use firmware versions of 19.0.1 or below.
-
Searching for game...
Game not found for Title ID: %1$s
diff --git a/src/common/settings.h b/src/common/settings.h
index 9d448a2b38..fafd765804 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -626,10 +626,6 @@ struct Values {
true, true, &rng_seed_enabled};
Setting device_name{
linkage, "Eden", "device_name", Category::System, Specialization::Default, true, true};
- SwitchableSetting disable_nca_verification{linkage, true, "disable_nca_verification",
- Category::System, Specialization::Default};
- Setting hide_nca_verification_popup{
- linkage, false, "hide_nca_verification_popup", Category::System, Specialization::Default};
Setting current_user{linkage, 0, "current_user", Category::System};
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
index 615a624f4f..71ba458cef 100644
--- a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
+++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp
@@ -238,9 +238,7 @@ void BucketTree::Initialize(size_t node_size, s64 end_offset) {
ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
ASSERT(Common::IsPowerOfTwo(node_size));
- if (!Settings::values.disable_nca_verification.GetValue()) {
- ASSERT(end_offset > 0);
- }
+ ASSERT(end_offset > 0);
ASSERT(!this->IsInitialized());
m_node_size = node_size;
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
index 4cfa5c58f8..37fb71e9e3 100644
--- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
+++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -1296,91 +1296,65 @@ Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl(
ASSERT(base_storage != nullptr);
ASSERT(layer_info_offset >= 0);
- if (!Settings::values.disable_nca_verification.GetValue()) {
- // Define storage types.
- using VerificationStorage = HierarchicalIntegrityVerificationStorage;
- using StorageInfo = VerificationStorage::HierarchicalStorageInformation;
+ // Define storage types.
+ using VerificationStorage = HierarchicalIntegrityVerificationStorage;
+ using StorageInfo = VerificationStorage::HierarchicalStorageInformation;
- // Validate the meta info.
- HierarchicalIntegrityVerificationInformation level_hash_info;
- std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info),
- sizeof(level_hash_info));
+ // Validate the meta info.
+ HierarchicalIntegrityVerificationInformation level_hash_info;
+ std::memcpy(std::addressof(level_hash_info),
+ std::addressof(meta_info.level_hash_info),
+ sizeof(level_hash_info));
- R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers,
- ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
- R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount,
- ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
+ R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers,
+ ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
+ R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount,
+ ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
- // Get the base storage size.
- s64 base_storage_size = base_storage->GetSize();
+ // Get the base storage size.
+ s64 base_storage_size = base_storage->GetSize();
- // Create storage info.
- StorageInfo storage_info;
- for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) {
- const auto& layer_info = level_hash_info.info[i];
- R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size,
- ResultNcaBaseStorageOutOfRangeD);
-
- storage_info[i + 1] = std::make_shared(
- base_storage, layer_info.size, layer_info_offset + layer_info.offset);
- }
-
- // Set the last layer info.
- const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2];
- const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get();
- R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size,
+ // Create storage info.
+ StorageInfo storage_info;
+ for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) {
+ const auto& layer_info = level_hash_info.info[i];
+ R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size,
ResultNcaBaseStorageOutOfRangeD);
- if (layer_info_offset > 0) {
- R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset,
- ResultRomNcaInvalidIntegrityLayerInfoOffset);
- }
- storage_info[level_hash_info.max_layers - 1] = std::make_shared(
- std::move(base_storage), layer_info.size, last_layer_info_offset);
- // Make the integrity romfs storage.
- auto integrity_storage = std::make_shared();
- R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
-
- // Initialize the integrity storage.
- R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info,
- max_data_cache_entries, max_hash_cache_entries,
- buffer_level));
-
- // Set the output.
- *out = std::move(integrity_storage);
- R_SUCCEED();
- } else {
- // Read IVFC layout
- HierarchicalIntegrityVerificationInformation lhi{};
- std::memcpy(std::addressof(lhi), std::addressof(meta_info.level_hash_info), sizeof(lhi));
-
- R_UNLESS(IntegrityMinLayerCount <= lhi.max_layers,
- ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
- R_UNLESS(lhi.max_layers <= IntegrityMaxLayerCount,
- ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount);
-
- const auto& data_li = lhi.info[lhi.max_layers - 2];
-
- const s64 base_size = base_storage->GetSize();
-
- // Compute the data layer window
- const s64 data_off = (layer_info_offset > 0) ? 0LL : data_li.offset.Get();
- R_UNLESS(data_off + data_li.size <= base_size, ResultNcaBaseStorageOutOfRangeD);
- if (layer_info_offset > 0) {
- R_UNLESS(data_off + data_li.size <= layer_info_offset,
- ResultRomNcaInvalidIntegrityLayerInfoOffset);
- }
-
- // TODO: Passthrough (temporary compatibility: integrity disabled)
- auto data_view = std::make_shared(base_storage, data_li.size, data_off);
- R_UNLESS(data_view != nullptr, ResultAllocationMemoryFailedAllocateShared);
-
- auto passthrough = std::make_shared(std::move(data_view));
- R_UNLESS(passthrough != nullptr, ResultAllocationMemoryFailedAllocateShared);
-
- *out = std::move(passthrough);
- R_SUCCEED();
+ storage_info[i + 1] = std::make_shared(base_storage,
+ layer_info.size,
+ layer_info_offset + layer_info.offset);
}
+
+ // Set the last layer info.
+ const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2];
+ const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get();
+ R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size,
+ ResultNcaBaseStorageOutOfRangeD);
+ if (layer_info_offset > 0) {
+ R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset,
+ ResultRomNcaInvalidIntegrityLayerInfoOffset);
+ }
+ storage_info[level_hash_info.max_layers - 1]
+ = std::make_shared(std::move(base_storage),
+ layer_info.size,
+ last_layer_info_offset);
+
+ // Make the integrity romfs storage.
+ auto integrity_storage = std::make_shared();
+ R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
+
+ // Initialize the integrity storage.
+ R_TRY(integrity_storage->Initialize(level_hash_info,
+ meta_info.master_hash,
+ storage_info,
+ max_data_cache_entries,
+ max_hash_cache_entries,
+ buffer_level));
+
+ // Set the output.
+ *out = std::move(integrity_storage);
+ R_SUCCEED();
}
Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out,
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 106922e04f..edf51e74de 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -126,6 +129,10 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
std::string out = GetSaveDataSpaceIdPath(space);
+ LOG_INFO(Common_Filesystem, "Save ID: {:016X}", save_id);
+ LOG_INFO(Common_Filesystem, "User ID[1]: {:016X}", user_id[1]);
+ LOG_INFO(Common_Filesystem, "User ID[0]: {:016X}", user_id[0]);
+
switch (type) {
case SaveDataType::System:
return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
diff --git a/src/frontend_common/firmware_manager.h b/src/frontend_common/firmware_manager.h
index 20f3b41478..23fce59eb3 100644
--- a/src/frontend_common/firmware_manager.h
+++ b/src/frontend_common/firmware_manager.h
@@ -7,6 +7,7 @@
#include "common/common_types.h"
#include "core/core.h"
#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/content_archive.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/filesystem/filesystem.h"
#include
@@ -143,6 +144,8 @@ inline std::pair GetFirmwareVersion
return {firmware_data, result};
}
+
+// TODO(crueter): GET AS STRING
}
-#endif // FIRMWARE_MANAGER_H
+#endif
diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt
new file mode 100644
index 0000000000..9d292da401
--- /dev/null
+++ b/src/qt_common/CMakeLists.txt
@@ -0,0 +1,47 @@
+# 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
+
+find_package(Qt6 REQUIRED COMPONENTS Core)
+find_package(Qt6 REQUIRED COMPONENTS Core)
+
+add_library(qt_common STATIC
+ qt_common.h
+ qt_common.cpp
+
+ uisettings.cpp
+ uisettings.h
+
+ qt_config.cpp
+ qt_config.h
+
+ shared_translation.cpp
+ shared_translation.h
+ qt_path_util.h qt_path_util.cpp
+ qt_game_util.h qt_game_util.cpp
+ qt_frontend_util.h qt_frontend_util.cpp
+ qt_meta.h qt_meta.cpp
+ qt_content_util.h qt_content_util.cpp
+ qt_rom_util.h qt_rom_util.cpp
+ qt_applet_util.h qt_applet_util.cpp
+ qt_progress_dialog.h qt_progress_dialog.cpp
+
+)
+
+create_target_directory_groups(qt_common)
+
+# TODO(crueter)
+if (ENABLE_QT)
+ target_link_libraries(qt_common PRIVATE Qt6::Widgets)
+endif()
+
+add_subdirectory(externals)
+
+target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip frozen::frozen)
+target_link_libraries(qt_common PRIVATE Qt6::Core)
+
+if (NOT WIN32)
+ target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
+endif()
diff --git a/src/yuzu/externals/CMakeLists.txt b/src/qt_common/externals/CMakeLists.txt
similarity index 86%
rename from src/yuzu/externals/CMakeLists.txt
rename to src/qt_common/externals/CMakeLists.txt
index 50594a741f..189a52c0a6 100644
--- a/src/yuzu/externals/CMakeLists.txt
+++ b/src/qt_common/externals/CMakeLists.txt
@@ -14,3 +14,7 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON)
# QuaZip
AddJsonPackage(quazip)
+
+# frozen
+# TODO(crueter): Qt String Lookup
+AddJsonPackage(frozen)
diff --git a/src/yuzu/externals/cpmfile.json b/src/qt_common/externals/cpmfile.json
similarity index 55%
rename from src/yuzu/externals/cpmfile.json
rename to src/qt_common/externals/cpmfile.json
index e3590d0f7f..0b464b95b2 100644
--- a/src/yuzu/externals/cpmfile.json
+++ b/src/qt_common/externals/cpmfile.json
@@ -8,5 +8,12 @@
"options": [
"QUAZIP_INSTALL OFF"
]
+ },
+ "frozen": {
+ "package": "frozen",
+ "repo": "serge-sans-paille/frozen",
+ "sha": "61dce5ae18",
+ "hash": "1ae3d073e659c1f24b2cdd76379c90d6af9e06bc707d285a4fafce05f7a4c9e592ff208c94a9ae0f0d07620b3c6cec191f126b03d70ad4dfa496a86ed5658a6d",
+ "bundled": true
}
}
diff --git a/src/qt_common/qt_applet_util.cpp b/src/qt_common/qt_applet_util.cpp
new file mode 100644
index 0000000000..1b0189392e
--- /dev/null
+++ b/src/qt_common/qt_applet_util.cpp
@@ -0,0 +1,4 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_applet_util.h"
diff --git a/src/qt_common/qt_applet_util.h b/src/qt_common/qt_applet_util.h
new file mode 100644
index 0000000000..2b48d16698
--- /dev/null
+++ b/src/qt_common/qt_applet_util.h
@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_APPLET_UTIL_H
+#define QT_APPLET_UTIL_H
+
+// TODO
+namespace QtCommon::Applets {
+
+}
+#endif // QT_APPLET_UTIL_H
diff --git a/src/yuzu/qt_common.cpp b/src/qt_common/qt_common.cpp
similarity index 55%
rename from src/yuzu/qt_common.cpp
rename to src/qt_common/qt_common.cpp
index 413402165c..6be241c740 100644
--- a/src/yuzu/qt_common.cpp
+++ b/src/qt_common/qt_common.cpp
@@ -1,12 +1,19 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_common.h"
+#include "common/fs/fs.h"
#include
#include
-#include
#include "common/logging/log.h"
#include "core/frontend/emu_window.h"
-#include "yuzu/qt_common.h"
+
+#include
+
+#include
+
+#include
#if !defined(WIN32) && !defined(__APPLE__)
#include
@@ -15,7 +22,19 @@
#endif
namespace QtCommon {
-Core::Frontend::WindowSystemType GetWindowSystemType() {
+
+#ifdef YUZU_QT_WIDGETS
+QWidget* rootObject = nullptr;
+#else
+QObject* rootObject = nullptr;
+#endif
+
+std::unique_ptr system = nullptr;
+std::shared_ptr vfs = nullptr;
+std::unique_ptr provider = nullptr;
+
+Core::Frontend::WindowSystemType GetWindowSystemType()
+{
// Determine WSI type based on Qt platform.
QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("windows"))
@@ -35,7 +54,8 @@ Core::Frontend::WindowSystemType GetWindowSystemType() {
return Core::Frontend::WindowSystemType::Windows;
} // namespace Core::Frontend::WindowSystemType
-Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
+Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
+{
Core::Frontend::EmuWindow::WindowSystemInfo wsi;
wsi.type = GetWindowSystemType();
@@ -43,8 +63,8 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
// Our Win32 Qt external doesn't have the private API.
wsi.render_surface = reinterpret_cast(window->winId());
#elif defined(__APPLE__)
- wsi.render_surface = reinterpret_cast(objc_msgSend)(
- reinterpret_cast(window->winId()), sel_registerName("layer"));
+ wsi.render_surface = reinterpret_cast(
+ objc_msgSend)(reinterpret_cast(window->winId()), sel_registerName("layer"));
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);
@@ -57,4 +77,46 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
return wsi;
}
+
+const QString tr(const char* str)
+{
+ return QGuiApplication::tr(str);
+}
+
+const QString tr(const std::string& str)
+{
+ return QGuiApplication::tr(str.c_str());
+}
+
+#ifdef YUZU_QT_WIDGETS
+void Init(QWidget* root)
+#else
+void Init(QObject* root)
+#endif
+{
+ system = std::make_unique();
+ rootObject = root;
+ vfs = std::make_unique();
+ provider = std::make_unique();
+}
+
+std::filesystem::path GetEdenCommand() {
+ std::filesystem::path command;
+
+ QString appimage = QString::fromLocal8Bit(getenv("APPIMAGE"));
+ if (!appimage.isEmpty()) {
+ command = std::filesystem::path{appimage.toStdString()};
+ } else {
+ const QStringList args = QGuiApplication::arguments();
+ command = args[0].toStdString();
+ }
+
+ // If relative path, make it an absolute path
+ if (command.c_str()[0] == '.') {
+ command = Common::FS::GetCurrentDir() / command;
+ }
+
+ return command;
+}
+
} // namespace QtCommon
diff --git a/src/qt_common/qt_common.h b/src/qt_common/qt_common.h
new file mode 100644
index 0000000000..a2700427ab
--- /dev/null
+++ b/src/qt_common/qt_common.h
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_COMMON_H
+#define QT_COMMON_H
+
+#include
+#include "core/core.h"
+#include "core/file_sys/registered_cache.h"
+#include
+#include
+
+#include
+
+namespace QtCommon {
+
+#ifdef YUZU_QT_WIDGETS
+extern QWidget *rootObject;
+#else
+extern QObject *rootObject;
+#endif
+
+extern std::unique_ptr system;
+extern std::shared_ptr vfs;
+extern std::unique_ptr provider;
+
+typedef std::function QtProgressCallback;
+
+Core::Frontend::WindowSystemType GetWindowSystemType();
+
+Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow *window);
+
+#ifdef YUZU_QT_WIDGETS
+void Init(QWidget *root);
+#else
+void Init(QObject *root);
+#endif
+
+const QString tr(const char *str);
+const QString tr(const std::string &str);
+
+std::filesystem::path GetEdenCommand();
+} // namespace QtCommon
+#endif
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/qt_common/qt_config.cpp
similarity index 99%
rename from src/yuzu/configuration/qt_config.cpp
rename to src/qt_common/qt_config.cpp
index ae5b330e23..f787873cf6 100644
--- a/src/yuzu/configuration/qt_config.cpp
+++ b/src/qt_common/qt_config.cpp
@@ -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
diff --git a/src/yuzu/configuration/qt_config.h b/src/qt_common/qt_config.h
similarity index 94%
rename from src/yuzu/configuration/qt_config.h
rename to src/qt_common/qt_config.h
index dc2dceb4d7..a8c80dd273 100644
--- a/src/yuzu/configuration/qt_config.h
+++ b/src/qt_common/qt_config.h
@@ -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
diff --git a/src/qt_common/qt_content_util.cpp b/src/qt_common/qt_content_util.cpp
new file mode 100644
index 0000000000..e4625aa423
--- /dev/null
+++ b/src/qt_common/qt_content_util.cpp
@@ -0,0 +1,313 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_content_util.h"
+#include "common/fs/fs.h"
+#include "frontend_common/content_manager.h"
+#include "frontend_common/firmware_manager.h"
+#include "qt_common/qt_common.h"
+#include "qt_common/qt_progress_dialog.h"
+#include "qt_frontend_util.h"
+
+#include
+
+namespace QtCommon::Content {
+
+bool CheckGameFirmware(u64 program_id, QObject* parent)
+{
+ if (FirmwareManager::GameRequiresFirmware(program_id)
+ && !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 "
+ "dump and install firmware, or press \"OK\" to launch anyways.",
+ QMessageBox::Ok | QMessageBox::Cancel,
+ parent);
+
+ return result == QMessageBox::Ok;
+ }
+
+ return true;
+}
+
+void InstallFirmware(const QString& location, bool recursive)
+{
+ QtCommon::Frontend::QtProgressDialog progress(tr("Installing Firmware..."),
+ tr("Cancel"),
+ 0,
+ 100,
+ rootObject);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+ progress.show();
+
+ // Declare progress callback.
+ auto callback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(static_cast((processed_size * 100) / total_size));
+ return progress.wasCanceled();
+ };
+
+ static constexpr const char* failedTitle = "Firmware Install Failed";
+ static constexpr const char* successTitle = "Firmware Install Succeeded";
+ QMessageBox::Icon icon;
+ FirmwareInstallResult result;
+
+ const auto ShowMessage = [&]() {
+ QtCommon::Frontend::ShowMessage(icon,
+ failedTitle,
+ GetFirmwareInstallResultString(result));
+ };
+
+ LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString());
+
+ // Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in
+ // there.)
+ std::filesystem::path firmware_source_path = location.toStdString();
+ if (!Common::FS::IsDir(firmware_source_path)) {
+ return;
+ }
+
+ std::vector out;
+ const Common::FS::DirEntryCallable dir_callback =
+ [&out](const std::filesystem::directory_entry& entry) {
+ if (entry.path().has_extension() && entry.path().extension() == ".nca") {
+ out.emplace_back(entry.path());
+ }
+
+ return true;
+ };
+
+ callback(100, 10);
+
+ if (recursive) {
+ Common::FS::IterateDirEntriesRecursively(firmware_source_path,
+ dir_callback,
+ Common::FS::DirEntryFilter::File);
+ } else {
+ Common::FS::IterateDirEntries(firmware_source_path,
+ dir_callback,
+ Common::FS::DirEntryFilter::File);
+ }
+
+ if (out.size() <= 0) {
+ result = FirmwareInstallResult::NoNCAs;
+ icon = QMessageBox::Warning;
+ ShowMessage();
+ return;
+ }
+
+ // Locate and erase the content of nand/system/Content/registered/*.nca, if any.
+ auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory();
+ if (sysnand_content_vdir->IsWritable()
+ && !sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
+ result = FirmwareInstallResult::FailedDelete;
+ icon = QMessageBox::Critical;
+ ShowMessage();
+ return;
+ }
+
+ LOG_INFO(Frontend,
+ "Cleaned nand/system/Content/registered folder in preparation for new firmware.");
+
+ callback(100, 20);
+
+ auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered");
+
+ bool success = true;
+ int i = 0;
+ for (const auto& firmware_src_path : out) {
+ 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());
+
+ if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
+ LOG_ERROR(Frontend,
+ "Failed to copy firmware file {} to {} in registered folder!",
+ firmware_src_path.generic_string(),
+ firmware_src_path.filename().string());
+ success = false;
+ }
+
+ if (callback(100, 20 + static_cast(((i) / static_cast(out.size())) * 70.0))) {
+ result = FirmwareInstallResult::FailedCorrupted;
+ icon = QMessageBox::Warning;
+ ShowMessage();
+ return;
+ }
+ }
+
+ if (!success) {
+ result = FirmwareInstallResult::FailedCopy;
+ icon = QMessageBox::Critical;
+ ShowMessage();
+ return;
+ }
+
+ // Re-scan VFS for the newly placed firmware files.
+ system->GetFileSystemController().CreateFactories(*vfs);
+
+ auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(90 + static_cast((processed_size * 10) / total_size));
+ return progress.wasCanceled();
+ };
+
+ auto results = ContentManager::VerifyInstalledContents(*QtCommon::system,
+ *QtCommon::provider,
+ VerifyFirmwareCallback,
+ true);
+
+ if (results.size() > 0) {
+ 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));
+ return;
+ }
+
+ progress.close();
+
+ const auto pair = FirmwareManager::GetFirmwareVersion(*system);
+ const auto firmware_data = pair.first;
+ 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)));
+}
+
+QString UnzipFirmwareToTmp(const QString& location)
+{
+ namespace fs = std::filesystem;
+ fs::path tmp{fs::temp_directory_path()};
+
+ if (!fs::create_directories(tmp / "eden" / "firmware")) {
+ return "";
+ }
+
+ tmp /= "eden";
+ tmp /= "firmware";
+
+ QString qCacheDir = QString::fromStdString(tmp.string());
+
+ QFile zip(location);
+
+ QStringList result = JlCompress::extractDir(&zip, qCacheDir);
+ if (result.isEmpty()) {
+ return "";
+ }
+
+ return qCacheDir;
+}
+
+// Content //
+void VerifyGameContents(const std::string& game_path)
+{
+ QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."),
+ tr("Cancel"),
+ 0,
+ 100,
+ rootObject);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+
+ const auto callback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(static_cast((processed_size * 100) / total_size));
+ return progress.wasCanceled();
+ };
+
+ const auto result = ContentManager::VerifyGameContents(*system, game_path, callback);
+
+ switch (result) {
+ case ContentManager::GameVerificationResult::Success:
+ QtCommon::Frontend::Information(rootObject,
+ tr("Integrity verification succeeded!"),
+ tr("The operation completed successfully."));
+ break;
+ case ContentManager::GameVerificationResult::Failed:
+ QtCommon::Frontend::Critical(rootObject,
+ tr("Integrity verification failed!"),
+ tr("File contents may be corrupt or missing."));
+ break;
+ case ContentManager::GameVerificationResult::NotImplemented:
+ QtCommon::Frontend::Warning(
+ rootObject,
+ tr("Integrity verification couldn't be performed"),
+ tr("Firmware installation cancelled, firmware may be in a bad state or corrupted. "
+ "File contents could not be checked for validity."));
+ }
+}
+
+void InstallKeys()
+{
+ const QString key_source_location
+ = QtCommon::Frontend::GetOpenFileName(tr("Select Dumped Keys Location"),
+ {},
+ QStringLiteral("Decryption Keys (*.keys)"),
+ {},
+ QtCommon::Frontend::Option::ReadOnly);
+
+ if (key_source_location.isEmpty()) {
+ return;
+ }
+
+ FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location
+ .toStdString(),
+ "keys");
+
+ system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
+
+ switch (result) {
+ case FirmwareManager::KeyInstallResult::Success:
+ QtCommon::Frontend::Information(tr("Decryption Keys install succeeded"),
+ tr("Decryption Keys were successfully installed"));
+ break;
+ default:
+ QtCommon::Frontend::Critical(tr("Decryption Keys install failed"),
+ tr(FirmwareManager::GetKeyInstallResultString(result)));
+ break;
+ }
+}
+
+void VerifyInstalledContents() {
+ // Initialize a progress dialog.
+ QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, QtCommon::rootObject);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.setMinimumDuration(100);
+ progress.setAutoClose(false);
+ progress.setAutoReset(false);
+
+ // Declare progress callback.
+ auto QtProgressCallback = [&](size_t total_size, size_t processed_size) {
+ progress.setValue(static_cast((processed_size * 100) / total_size));
+ return progress.wasCanceled();
+ };
+
+ const std::vector 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")));
+ QtCommon::Frontend::Critical(
+ tr("Integrity verification failed!"),
+ tr("Verification failed for the following files:\n\n%1").arg(failed_names));
+ }
+}
+
+} // namespace QtCommon::Content
diff --git a/src/qt_common/qt_content_util.h b/src/qt_common/qt_content_util.h
new file mode 100644
index 0000000000..b572c1c4a3
--- /dev/null
+++ b/src/qt_common/qt_content_util.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_CONTENT_UTIL_H
+#define QT_CONTENT_UTIL_H
+
+#include
+#include "common/common_types.h"
+
+namespace QtCommon::Content {
+
+//
+bool CheckGameFirmware(u64 program_id, QObject *parent);
+
+static constexpr std::array FIRMWARE_RESULTS
+ = {"Successfully installed firmware version %1",
+ "",
+ "Unable to locate potential firmware NCA files",
+ "Failed to delete one or more firmware files.",
+ "One or more firmware files failed to copy into NAND.",
+ "Firmware installation cancelled, firmware may be in a bad state or corrupted."
+ "Restart Eden or re-install firmware."};
+
+enum class FirmwareInstallResult {
+ Success,
+ NoOp,
+ NoNCAs,
+ FailedDelete,
+ FailedCopy,
+ FailedCorrupted,
+};
+
+inline constexpr const char *GetFirmwareInstallResultString(FirmwareInstallResult result)
+{
+ return FIRMWARE_RESULTS.at(static_cast(result));
+}
+
+void InstallFirmware(const QString &location, bool recursive);
+
+QString UnzipFirmwareToTmp(const QString &location);
+
+// Keys //
+void InstallKeys();
+
+// Content //
+void VerifyGameContents(const std::string &game_path);
+void VerifyInstalledContents();
+}
+#endif // QT_CONTENT_UTIL_H
diff --git a/src/qt_common/qt_frontend_util.cpp b/src/qt_common/qt_frontend_util.cpp
new file mode 100644
index 0000000000..d519669ad5
--- /dev/null
+++ b/src/qt_common/qt_frontend_util.cpp
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_frontend_util.h"
+#include "qt_common/qt_common.h"
+
+#ifdef YUZU_QT_WIDGETS
+#include
+#endif
+
+namespace QtCommon::Frontend {
+
+StandardButton ShowMessage(
+ Icon icon, const QString &title, const QString &text, StandardButtons buttons, QObject *parent)
+{
+#ifdef YUZU_QT_WIDGETS
+ QMessageBox *box = new QMessageBox(icon, title, text, buttons, (QWidget *) parent);
+ return static_cast(box->exec());
+#endif
+ // TODO(crueter): If Qt Widgets is disabled...
+ // need a way to reference icon/buttons too
+}
+
+const QString GetOpenFileName(const QString &title,
+ const QString &dir,
+ const QString &filter,
+ QString *selectedFilter,
+ Options options)
+{
+#ifdef YUZU_QT_WIDGETS
+ return QFileDialog::getOpenFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options);
+#endif
+}
+
+} // namespace QtCommon::Frontend
diff --git a/src/qt_common/qt_frontend_util.h b/src/qt_common/qt_frontend_util.h
new file mode 100644
index 0000000000..f86b9e1357
--- /dev/null
+++ b/src/qt_common/qt_frontend_util.h
@@ -0,0 +1,148 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_FRONTEND_UTIL_H
+#define QT_FRONTEND_UTIL_H
+
+#include
+#include "qt_common/qt_common.h"
+
+#ifdef YUZU_QT_WIDGETS
+#include
+#include
+#include
+#endif
+
+/**
+ * manages common functionality e.g. message boxes and such for Qt/QML
+ */
+namespace QtCommon::Frontend {
+
+Q_NAMESPACE
+
+#ifdef YUZU_QT_WIDGETS
+using Options = QFileDialog::Options;
+using Option = QFileDialog::Option;
+
+using StandardButton = QMessageBox::StandardButton;
+using StandardButtons = QMessageBox::StandardButtons;
+
+using Icon = QMessageBox::Icon;
+#else
+enum Option {
+ ShowDirsOnly = 0x00000001,
+ DontResolveSymlinks = 0x00000002,
+ DontConfirmOverwrite = 0x00000004,
+ DontUseNativeDialog = 0x00000008,
+ ReadOnly = 0x00000010,
+ HideNameFilterDetails = 0x00000020,
+ DontUseCustomDirectoryIcons = 0x00000040
+};
+Q_ENUM_NS(Option)
+Q_DECLARE_FLAGS(Options, Option)
+Q_FLAG_NS(Options)
+
+enum StandardButton {
+ // keep this in sync with QDialogButtonBox::StandardButton and QPlatformDialogHelper::StandardButton
+ NoButton = 0x00000000,
+ Ok = 0x00000400,
+ Save = 0x00000800,
+ SaveAll = 0x00001000,
+ Open = 0x00002000,
+ Yes = 0x00004000,
+ YesToAll = 0x00008000,
+ No = 0x00010000,
+ NoToAll = 0x00020000,
+ Abort = 0x00040000,
+ Retry = 0x00080000,
+ Ignore = 0x00100000,
+ Close = 0x00200000,
+ Cancel = 0x00400000,
+ Discard = 0x00800000,
+ Help = 0x01000000,
+ Apply = 0x02000000,
+ Reset = 0x04000000,
+ RestoreDefaults = 0x08000000,
+
+ FirstButton = Ok, // internal
+ LastButton = RestoreDefaults, // internal
+
+ YesAll = YesToAll, // obsolete
+ NoAll = NoToAll, // obsolete
+
+ Default = 0x00000100, // obsolete
+ Escape = 0x00000200, // obsolete
+ FlagMask = 0x00000300, // obsolete
+ ButtonMask = ~FlagMask // obsolete
+};
+Q_ENUM_NS(StandardButton)
+
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+typedef StandardButton Button;
+#endif
+Q_DECLARE_FLAGS(StandardButtons, StandardButton)
+Q_FLAG_NS(StandardButtons)
+
+enum Icon {
+ // keep this in sync with QMessageDialogOptions::StandardIcon
+ NoIcon = 0,
+ Information = 1,
+ Warning = 2,
+ Critical = 3,
+ Question = 4
+};
+Q_ENUM_NS(Icon)
+
+#endif
+
+// 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);
+
+#define UTIL_OVERRIDES(level) \
+ inline StandardButton level(QObject *parent, \
+ const QString &title, \
+ const QString &text, \
+ StandardButtons buttons = StandardButton::Ok) \
+ { \
+ 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 \
+ = StandardButton::Ok) \
+ { \
+ return ShowMessage(Icon::level, title, text, buttons, rootObject); \
+ }
+
+UTIL_OVERRIDES(Information)
+UTIL_OVERRIDES(Warning)
+UTIL_OVERRIDES(Critical)
+UTIL_OVERRIDES(Question)
+
+const QString GetOpenFileName(const QString &title,
+ const QString &dir,
+ const QString &filter,
+ QString *selectedFilter = nullptr,
+ Options options = Options());
+
+} // namespace QtCommon::Frontend
+#endif // QT_FRONTEND_UTIL_H
diff --git a/src/qt_common/qt_game_util.cpp b/src/qt_common/qt_game_util.cpp
new file mode 100644
index 0000000000..5d0b4d8ae7
--- /dev/null
+++ b/src/qt_common/qt_game_util.cpp
@@ -0,0 +1,577 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_game_util.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "core/file_sys/savedata_factory.h"
+#include "core/hle/service/am/am_types.h"
+#include "frontend_common/content_manager.h"
+#include "qt_common.h"
+#include "qt_common/uisettings.h"
+#include "qt_frontend_util.h"
+#include "yuzu/util/util.h"
+
+#include
+#include
+#include
+
+#ifdef _WIN32
+#include "common/scope_exit.h"
+#include "common/string_util.h"
+#include
+#include
+#else
+#include "fmt/ostream.h"
+#include
+#endif
+
+namespace QtCommon::Game {
+
+bool CreateShortcutLink(const std::filesystem::path& shortcut_path,
+ const std::string& comment,
+ const std::filesystem::path& icon_path,
+ const std::filesystem::path& command,
+ const std::string& arguments,
+ const std::string& categories,
+ const std::string& keywords,
+ const std::string& name)
+try {
+#ifdef _WIN32 // Windows
+ HRESULT hr = CoInitialize(nullptr);
+ if (FAILED(hr)) {
+ LOG_ERROR(Frontend, "CoInitialize failed");
+ return false;
+ }
+ SCOPE_EXIT
+ {
+ CoUninitialize();
+ };
+ IShellLinkW* ps1 = nullptr;
+ IPersistFile* persist_file = nullptr;
+ SCOPE_EXIT
+ {
+ if (persist_file != nullptr) {
+ persist_file->Release();
+ }
+ if (ps1 != nullptr) {
+ ps1->Release();
+ }
+ };
+ HRESULT hres = CoCreateInstance(CLSID_ShellLink,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW,
+ reinterpret_cast(&ps1));
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to create IShellLinkW instance");
+ return false;
+ }
+ hres = ps1->SetPath(command.c_str());
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set path");
+ return false;
+ }
+ if (!arguments.empty()) {
+ hres = ps1->SetArguments(Common::UTF8ToUTF16W(arguments).data());
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set arguments");
+ return false;
+ }
+ }
+ if (!comment.empty()) {
+ hres = ps1->SetDescription(Common::UTF8ToUTF16W(comment).data());
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set description");
+ return false;
+ }
+ }
+ if (std::filesystem::is_regular_file(icon_path)) {
+ hres = ps1->SetIconLocation(icon_path.c_str(), 0);
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to set icon location");
+ return false;
+ }
+ }
+ hres = ps1->QueryInterface(IID_IPersistFile, reinterpret_cast(&persist_file));
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to get IPersistFile interface");
+ return false;
+ }
+ hres = persist_file->Save(std::filesystem::path{shortcut_path / (name + ".lnk")}.c_str(), TRUE);
+ if (FAILED(hres)) {
+ LOG_ERROR(Frontend, "Failed to save shortcut");
+ return false;
+ }
+ return true;
+#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Any desktop NIX
+ std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
+ std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
+ if (!shortcut_stream.is_open()) {
+ LOG_ERROR(Frontend, "Failed to create shortcut");
+ return false;
+ }
+ // TODO: Migrate fmt::print to std::print in futures STD C++ 23.
+ fmt::print(shortcut_stream, "[Desktop Entry]\n");
+ fmt::print(shortcut_stream, "Type=Application\n");
+ fmt::print(shortcut_stream, "Version=1.0\n");
+ fmt::print(shortcut_stream, "Name={}\n", name);
+ if (!comment.empty()) {
+ fmt::print(shortcut_stream, "Comment={}\n", comment);
+ }
+ if (std::filesystem::is_regular_file(icon_path)) {
+ fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
+ }
+ fmt::print(shortcut_stream, "TryExec={}\n", command.string());
+ fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
+ if (!categories.empty()) {
+ fmt::print(shortcut_stream, "Categories={}\n", categories);
+ }
+ if (!keywords.empty()) {
+ fmt::print(shortcut_stream, "Keywords={}\n", keywords);
+ }
+ return true;
+#else // Unsupported platform
+ return false;
+#endif
+} catch (const std::exception& e) {
+ LOG_ERROR(Frontend, "Failed to create shortcut: {}", e.what());
+ return false;
+}
+
+bool MakeShortcutIcoPath(const u64 program_id,
+ const std::string_view game_file_name,
+ std::filesystem::path& out_icon_path)
+{
+ // Get path to Yuzu icons directory & icon extension
+ std::string ico_extension = "png";
+#if defined(_WIN32)
+ out_icon_path = Common::FS::GetEdenPath(Common::FS::EdenPath::IconsDir);
+ ico_extension = "ico";
+#elif defined(__linux__) || defined(__FreeBSD__)
+ out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256";
+#endif
+ // Create icons directory if it doesn't exist
+ if (!Common::FS::CreateDirs(out_icon_path)) {
+ out_icon_path.clear();
+ return false;
+ }
+
+ // Create icon file path
+ out_icon_path /= (program_id == 0 ? fmt::format("eden-{}.{}", game_file_name, ico_extension)
+ : fmt::format("eden-{:016X}.{}", program_id, ico_extension));
+ return true;
+}
+
+void OpenEdenFolder(const Common::FS::EdenPath& path)
+{
+ QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(Common::FS::GetEdenPathString(path))));
+}
+
+void OpenRootDataFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::EdenDir);
+}
+
+void OpenNANDFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::NANDDir);
+}
+
+void OpenSDMCFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::SDMCDir);
+}
+
+void OpenModFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::LoadDir);
+}
+
+void OpenLogFolder()
+{
+ OpenEdenFolder(Common::FS::EdenPath::LogDir);
+}
+
+static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type)
+{
+ switch (type) {
+ case QtCommon::Game::InstalledEntryType::Game:
+ return tr("Error Removing Contents");
+ case QtCommon::Game::InstalledEntryType::Update:
+ return tr("Error Removing Update");
+ case QtCommon::Game::InstalledEntryType::AddOnContent:
+ return tr("Error Removing DLC");
+ default:
+ return QStringLiteral("Error Removing ");
+ }
+}
+
+// Game Content //
+void RemoveBaseContent(u64 program_id, InstalledEntryType type)
+{
+ const auto res = ContentManager::RemoveBaseContent(system->GetFileSystemController(),
+ program_id);
+ if (res) {
+ QtCommon::Frontend::Information(rootObject,
+ "Successfully Removed",
+ "Successfully removed the installed base game.");
+ } else {
+ QtCommon::Frontend::Warning(
+ rootObject,
+ GetGameListErrorRemoving(type),
+ tr("The base game is not installed in the NAND and cannot be removed."));
+ }
+}
+
+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.");
+ } else {
+ QtCommon::Frontend::Warning(rootObject,
+ GetGameListErrorRemoving(type),
+ tr("There is no update installed for this title."));
+ }
+}
+
+void RemoveAddOnContent(u64 program_id, InstalledEntryType type)
+{
+ const size_t count = ContentManager::RemoveAllDLC(*system, program_id);
+ if (count == 0) {
+ QtCommon::Frontend::Warning(rootObject,
+ GetGameListErrorRemoving(type),
+ tr("There are no DLCs installed for this title."));
+ return;
+ }
+
+ QtCommon::Frontend::Information(rootObject,
+ tr("Successfully Removed"),
+ tr("Successfully removed %1 installed DLC.").arg(count));
+}
+
+// Global Content //
+
+void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target)
+{
+ const auto target_file_name = [target] {
+ switch (target) {
+ case GameListRemoveTarget::GlShaderCache:
+ return "opengl.bin";
+ case GameListRemoveTarget::VkShaderCache:
+ return "vulkan.bin";
+ default:
+ return "";
+ }
+ }();
+ 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);
+ const auto target_file = shader_cache_folder_path / target_file_name;
+
+ if (!Common::FS::Exists(target_file)) {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Transferable Shader Cache"),
+ tr("A shader cache for this title does not exist."));
+ return;
+ }
+ if (Common::FS::RemoveFile(target_file)) {
+ QtCommon::Frontend::Information(rootObject,
+ tr("Successfully Removed"),
+ tr("Successfully removed the transferable shader cache."));
+ } else {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Transferable Shader Cache"),
+ tr("Failed to remove the transferable shader cache."));
+ }
+}
+
+void RemoveVulkanDriverPipelineCache(u64 program_id)
+{
+ static constexpr std::string_view target_file_name = "vulkan_pipelines.bin";
+
+ 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);
+ const auto target_file = shader_cache_folder_path / target_file_name;
+
+ if (!Common::FS::Exists(target_file)) {
+ return;
+ }
+ if (!Common::FS::RemoveFile(target_file)) {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Vulkan Driver Pipeline Cache"),
+ tr("Failed to remove the driver pipeline cache."));
+ }
+}
+
+void RemoveAllTransferableShaderCaches(u64 program_id)
+{
+ const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
+ const auto program_shader_cache_dir = shader_cache_dir / fmt::format("{:016x}", program_id);
+
+ if (!Common::FS::Exists(program_shader_cache_dir)) {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Transferable Shader Caches"),
+ tr("A shader cache for this title does not exist."));
+ return;
+ }
+ if (Common::FS::RemoveDirRecursively(program_shader_cache_dir)) {
+ QtCommon::Frontend::Information(rootObject,
+ tr("Successfully Removed"),
+ tr("Successfully removed the transferable shader caches."));
+ } else {
+ QtCommon::Frontend::Warning(
+ rootObject,
+ tr("Error Removing Transferable Shader Caches"),
+ tr("Failed to remove the transferable shader cache directory."));
+ }
+}
+
+void RemoveCustomConfiguration(u64 program_id, const std::string& game_path)
+{
+ const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path));
+ const auto config_file_name = program_id == 0
+ ? Common::FS::PathToUTF8String(file_path.filename())
+ .append(".ini")
+ : fmt::format("{:016X}.ini", program_id);
+ const auto custom_config_file_path = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir)
+ / "custom" / config_file_name;
+
+ if (!Common::FS::Exists(custom_config_file_path)) {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Custom Configuration"),
+ tr("A custom configuration for this title does not exist."));
+ return;
+ }
+
+ if (Common::FS::RemoveFile(custom_config_file_path)) {
+ QtCommon::Frontend::Information(rootObject,
+ tr("Successfully Removed"),
+ tr("Successfully removed the custom game configuration."));
+ } else {
+ QtCommon::Frontend::Warning(rootObject,
+ tr("Error Removing Custom Configuration"),
+ tr("Failed to remove the custom game configuration."));
+ }
+}
+
+void RemoveCacheStorage(u64 program_id)
+{
+ const auto nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir);
+ auto vfs_nand_dir = vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir),
+ FileSys::OpenMode::Read);
+
+ const auto cache_storage_path
+ = FileSys::SaveDataFactory::GetFullPath({},
+ vfs_nand_dir,
+ FileSys::SaveDataSpaceId::User,
+ FileSys::SaveDataType::Cache,
+ 0 /* program_id */,
+ {},
+ 0);
+
+ const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path);
+
+ // Not an error if it wasn't cleared.
+ Common::FS::RemoveDirRecursively(path);
+}
+
+// Metadata //
+void ResetMetadata()
+{
+ const QString title = tr("Reset Metadata Cache");
+
+ if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir)
+ / "game_list/")) {
+ QtCommon::Frontend::Warning(rootObject, title, tr("The metadata cache is already empty."));
+ } else if (Common::FS::RemoveDirRecursively(
+ Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list")) {
+ QtCommon::Frontend::Information(rootObject,
+ title,
+ tr("The operation completed successfully."));
+ UISettings::values.is_game_list_reload_pending.exchange(true);
+ } else {
+ QtCommon::Frontend::Warning(
+ rootObject,
+ title,
+ tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
+ }
+}
+
+// Uhhh //
+
+// Messages in pre-defined message boxes for less code spaghetti
+inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title)
+{
+ int result = 0;
+ QMessageBox::StandardButtons buttons;
+ switch (imsg) {
+ case ShortcutMessages::Fullscreen:
+ buttons = QMessageBox::Yes | QMessageBox::No;
+ result
+ = QtCommon::Frontend::Information(tr("Create Shortcut"),
+ tr("Do you want to launch the game in fullscreen?"),
+ buttons);
+ return result == QMessageBox::Yes;
+ case ShortcutMessages::Success:
+ QtCommon::Frontend::Information(tr("Shortcut Created"),
+ tr("Successfully created a shortcut to %1").arg(game_title));
+ return false;
+ case ShortcutMessages::Volatile:
+ buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel;
+ result = QtCommon::Frontend::Warning(
+ tr("Shortcut may be Volatile!"),
+ tr("This will create a shortcut to the current AppImage. This may "
+ "not work well if you update. Continue?"),
+ buttons);
+ return result == QMessageBox::Ok;
+ default:
+ buttons = QMessageBox::Ok;
+ QtCommon::Frontend::Critical(tr("Failed to Create Shortcut"),
+ tr("Failed to create a shortcut to %1").arg(game_title),
+ buttons);
+ return false;
+ }
+}
+
+void CreateShortcut(const std::string& game_path,
+ const u64 program_id,
+ const std::string& game_title_,
+ const ShortcutTarget &target,
+ std::string arguments_,
+ const bool needs_title)
+{
+ // Get path to Eden executable
+ std::filesystem::path command = GetEdenCommand();
+
+ // Shortcut path
+ std::filesystem::path shortcut_path = GetShortcutPath(target);
+
+ if (!std::filesystem::exists(shortcut_path)) {
+ CreateShortcutMessagesGUI(ShortcutMessages::Failed,
+ QString::fromStdString(shortcut_path.generic_string()));
+ LOG_ERROR(Frontend, "Invalid shortcut target {}", shortcut_path.generic_string());
+ return;
+ }
+
+ const FileSys::PatchManager pm{program_id, QtCommon::system->GetFileSystemController(),
+ QtCommon::system->GetContentProvider()};
+ const auto control = pm.GetControlMetadata();
+ const auto loader =
+ Loader::GetLoader(*QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read));
+
+ std::string game_title{game_title_};
+
+ // Delete illegal characters from title
+ if (needs_title) {
+ game_title = fmt::format("{:016X}", program_id);
+ if (control.first != nullptr) {
+ game_title = control.first->GetApplicationName();
+ } else {
+ loader->ReadTitle(game_title);
+ }
+ }
+
+ const std::string illegal_chars = "<>:\"/\\|?*.";
+ for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) {
+ if (illegal_chars.find(*it) != std::string::npos) {
+ game_title.erase(it.base() - 1);
+ }
+ }
+
+ const QString qgame_title = QString::fromStdString(game_title);
+
+ // Get icon from game file
+ std::vector icon_image_file{};
+ if (control.second != nullptr) {
+ icon_image_file = control.second->ReadAllBytes();
+ } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) {
+ LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
+ }
+
+ QImage icon_data =
+ QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size()));
+ std::filesystem::path out_icon_path;
+ if (QtCommon::Game::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
+ if (!SaveIconToFile(out_icon_path, icon_data)) {
+ LOG_ERROR(Frontend, "Could not write icon to file");
+ }
+ } else {
+ QtCommon::Frontend::Critical(
+ tr("Create Icon"),
+ tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
+ .arg(QString::fromStdString(out_icon_path.string())));
+ }
+
+#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
+ // Special case for AppImages
+ // Warn once if we are making a shortcut to a volatile AppImage
+ if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) {
+ if (!CreateShortcutMessagesGUI(ShortcutMessages::Volatile, qgame_title)) {
+ return;
+ }
+ UISettings::values.shortcut_already_warned = true;
+ }
+#endif
+
+ // Create shortcut
+ std::string arguments{arguments_};
+ if (CreateShortcutMessagesGUI(ShortcutMessages::Fullscreen, qgame_title)) {
+ arguments = "-f " + arguments;
+ }
+ const std::string comment = fmt::format("Start {:s} with the Eden Emulator", game_title);
+ const std::string categories = "Game;Emulator;Qt;";
+ const std::string keywords = "Switch;Nintendo;";
+
+ if (QtCommon::Game::CreateShortcutLink(shortcut_path, comment, out_icon_path, command,
+ arguments, categories, keywords, game_title)) {
+ CreateShortcutMessagesGUI(ShortcutMessages::Success,
+ qgame_title);
+ return;
+ }
+ CreateShortcutMessagesGUI(ShortcutMessages::Failed,
+ qgame_title);
+}
+
+constexpr std::string GetShortcutPath(ShortcutTarget target) {
+ {
+ std::string shortcut_path{};
+ if (target == ShortcutTarget::Desktop) {
+ shortcut_path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)
+ .toStdString();
+ } else if (target == ShortcutTarget::Applications) {
+ shortcut_path = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)
+ .toStdString();
+ }
+
+ return shortcut_path;
+ }
+}
+
+void CreateHomeMenuShortcut(ShortcutTarget target) {
+ constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch);
+ auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QtCommon::Frontend::Warning(tr("No firmware available"),
+ tr("Please install firmware to use the home menu."));
+ return;
+ }
+
+ auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+ if (!qlaunch_nca) {
+ QtCommon::Frontend::Warning(tr("Home Menu Applet"),
+ tr("Home Menu is not available. Please reinstall firmware."));
+ return;
+ }
+
+ auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
+ const auto game_path = qlaunch_applet_nca->GetFullPath();
+
+ // TODO(crueter): Make this use the Eden icon
+ CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false);
+}
+
+
+} // namespace QtCommon::Game
diff --git a/src/qt_common/qt_game_util.h b/src/qt_common/qt_game_util.h
new file mode 100644
index 0000000000..0a21208659
--- /dev/null
+++ b/src/qt_common/qt_game_util.h
@@ -0,0 +1,85 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_GAME_UTIL_H
+#define QT_GAME_UTIL_H
+
+#include
+#include
+#include "common/fs/path_util.h"
+
+namespace QtCommon::Game {
+
+enum class InstalledEntryType {
+ Game,
+ Update,
+ AddOnContent,
+};
+
+enum class GameListRemoveTarget {
+ GlShaderCache,
+ VkShaderCache,
+ AllShaderCache,
+ CustomConfiguration,
+ CacheStorage,
+};
+
+enum class ShortcutTarget {
+ Desktop,
+ Applications,
+};
+
+enum class ShortcutMessages{
+ Fullscreen = 0,
+ Success = 1,
+ Volatile = 2,
+ Failed = 3
+};
+
+bool CreateShortcutLink(const std::filesystem::path& shortcut_path,
+ const std::string& comment,
+ const std::filesystem::path& icon_path,
+ const std::filesystem::path& command,
+ const std::string& arguments,
+ const std::string& categories,
+ const std::string& keywords,
+ const std::string& name);
+
+bool MakeShortcutIcoPath(const u64 program_id,
+ const std::string_view game_file_name,
+ std::filesystem::path& out_icon_path);
+
+void OpenEdenFolder(const Common::FS::EdenPath &path);
+void OpenRootDataFolder();
+void OpenNANDFolder();
+void OpenSDMCFolder();
+void OpenModFolder();
+void OpenLogFolder();
+
+void RemoveBaseContent(u64 program_id, InstalledEntryType type);
+void RemoveUpdateContent(u64 program_id, InstalledEntryType type);
+void RemoveAddOnContent(u64 program_id, InstalledEntryType type);
+
+void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target);
+void RemoveVulkanDriverPipelineCache(u64 program_id);
+void RemoveAllTransferableShaderCaches(u64 program_id);
+void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
+void RemoveCacheStorage(u64 program_id);
+
+// Metadata //
+void ResetMetadata();
+
+// Shortcuts //
+void CreateShortcut(const std::string& game_path,
+ const u64 program_id,
+ const std::string& game_title_,
+ const ShortcutTarget& target,
+ std::string arguments_,
+ const bool needs_title);
+
+constexpr std::string GetShortcutPath(ShortcutTarget target);
+void CreateHomeMenuShortcut(ShortcutTarget target);
+
+}
+
+#endif // QT_GAME_UTIL_H
diff --git a/src/qt_common/qt_meta.cpp b/src/qt_common/qt_meta.cpp
new file mode 100644
index 0000000000..67ae659771
--- /dev/null
+++ b/src/qt_common/qt_meta.cpp
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_meta.h"
+#include "common/common_types.h"
+#include "core/core.h"
+#include "core/frontend/applets/cabinet.h"
+#include "core/frontend/applets/controller.h"
+#include "core/frontend/applets/profile_select.h"
+#include "core/frontend/applets/software_keyboard.h"
+#include "core/hle/service/am/frontend/applet_web_browser_types.h"
+
+namespace QtCommon::Meta {
+
+void RegisterMetaTypes()
+{
+ // Register integral and floating point types
+ qRegisterMetaType("u8");
+ qRegisterMetaType("u16");
+ qRegisterMetaType("u32");
+ qRegisterMetaType("u64");
+ qRegisterMetaType("u128");
+ qRegisterMetaType("s8");
+ qRegisterMetaType("s16");
+ qRegisterMetaType("s32");
+ qRegisterMetaType("s64");
+ qRegisterMetaType("f32");
+ qRegisterMetaType("f64");
+
+ // Register string types
+ qRegisterMetaType("std::string");
+ qRegisterMetaType("std::wstring");
+ qRegisterMetaType("std::u8string");
+ qRegisterMetaType("std::u16string");
+ qRegisterMetaType("std::u32string");
+ qRegisterMetaType("std::string_view");
+ qRegisterMetaType("std::wstring_view");
+ qRegisterMetaType("std::u8string_view");
+ qRegisterMetaType("std::u16string_view");
+ qRegisterMetaType("std::u32string_view");
+
+ // Register applet types
+
+ // Cabinet Applet
+ qRegisterMetaType("Core::Frontend::CabinetParameters");
+ qRegisterMetaType>(
+ "std::shared_ptr");
+
+ // Controller Applet
+ qRegisterMetaType("Core::Frontend::ControllerParameters");
+
+ // Profile Select Applet
+ qRegisterMetaType(
+ "Core::Frontend::ProfileSelectParameters");
+
+ // Software Keyboard Applet
+ qRegisterMetaType(
+ "Core::Frontend::KeyboardInitializeParameters");
+ qRegisterMetaType(
+ "Core::Frontend::InlineAppearParameters");
+ qRegisterMetaType("Core::Frontend::InlineTextParameters");
+ qRegisterMetaType("Service::AM::Frontend::SwkbdResult");
+ qRegisterMetaType(
+ "Service::AM::Frontend::SwkbdTextCheckResult");
+ qRegisterMetaType(
+ "Service::AM::Frontend::SwkbdReplyType");
+
+ // Web Browser Applet
+ qRegisterMetaType("Service::AM::Frontend::WebExitReason");
+
+ // Register loader types
+ qRegisterMetaType("Core::SystemResultStatus");
+}
+
+}
diff --git a/src/qt_common/qt_meta.h b/src/qt_common/qt_meta.h
new file mode 100644
index 0000000000..c0a37db983
--- /dev/null
+++ b/src/qt_common/qt_meta.h
@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_META_H
+#define QT_META_H
+
+#include
+
+namespace QtCommon::Meta {
+
+//
+void RegisterMetaTypes();
+
+}
+#endif // QT_META_H
diff --git a/src/qt_common/qt_path_util.cpp b/src/qt_common/qt_path_util.cpp
new file mode 100644
index 0000000000..761e6e8405
--- /dev/null
+++ b/src/qt_common/qt_path_util.cpp
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_path_util.h"
+#include
+#include
+#include
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "qt_common/qt_frontend_util.h"
+#include
+
+namespace QtCommon::Path {
+
+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);
+ }
+
+ const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)};
+ const auto qt_shader_cache_path = QString::fromStdString(shader_path_string);
+ return QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path));
+}
+
+}
diff --git a/src/qt_common/qt_path_util.h b/src/qt_common/qt_path_util.h
new file mode 100644
index 0000000000..855b06caa9
--- /dev/null
+++ b/src/qt_common/qt_path_util.h
@@ -0,0 +1,12 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_PATH_UTIL_H
+#define QT_PATH_UTIL_H
+
+#include "common/common_types.h"
+#include
+
+namespace QtCommon::Path { bool OpenShaderCache(u64 program_id, QObject *parent); }
+
+#endif // QT_PATH_UTIL_H
diff --git a/src/qt_common/qt_progress_dialog.cpp b/src/qt_common/qt_progress_dialog.cpp
new file mode 100644
index 0000000000..b4bf74c8bd
--- /dev/null
+++ b/src/qt_common/qt_progress_dialog.cpp
@@ -0,0 +1,4 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_progress_dialog.h"
diff --git a/src/qt_common/qt_progress_dialog.h b/src/qt_common/qt_progress_dialog.h
new file mode 100644
index 0000000000..17f6817ffa
--- /dev/null
+++ b/src/qt_common/qt_progress_dialog.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_PROGRESS_DIALOG_H
+#define QT_PROGRESS_DIALOG_H
+
+#include
+
+#ifdef YUZU_QT_WIDGETS
+#include
+#endif
+
+namespace QtCommon::Frontend {
+#ifdef YUZU_QT_WIDGETS
+
+using QtProgressDialog = QProgressDialog;
+
+// TODO(crueter): QML impl
+#else
+class QtProgressDialog
+{
+public:
+ QtProgressDialog(const QString &labelText,
+ const QString &cancelButtonText,
+ int minimum,
+ int maximum,
+ QObject *parent = nullptr,
+ Qt::WindowFlags f = Qt::WindowFlags());
+
+ bool wasCanceled() const;
+ void setWindowModality(Qt::WindowModality modality);
+ void setMinimumDuration(int durationMs);
+ void setAutoClose(bool autoClose);
+ void setAutoReset(bool autoReset);
+
+public slots:
+ void setLabelText(QString &text);
+ void setRange(int min, int max);
+ void setValue(int progress);
+ bool close();
+
+ void show();
+};
+#endif // YUZU_QT_WIDGETS
+
+}
+#endif // QT_PROGRESS_DIALOG_H
diff --git a/src/qt_common/qt_rom_util.cpp b/src/qt_common/qt_rom_util.cpp
new file mode 100644
index 0000000000..08ccb05a97
--- /dev/null
+++ b/src/qt_common/qt_rom_util.cpp
@@ -0,0 +1,78 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "qt_rom_util.h"
+
+#include
+
+namespace QtCommon::ROM {
+
+bool RomFSRawCopy(size_t total_size,
+ size_t& read_size,
+ QtProgressCallback callback,
+ const FileSys::VirtualDir& src,
+ const FileSys::VirtualDir& dest,
+ bool full)
+{
+ // TODO(crueter)
+ // if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ // return false;
+ // if (dialog.wasCanceled())
+ // return false;
+
+ // std::vector buffer(CopyBufferSize);
+ // auto last_timestamp = std::chrono::steady_clock::now();
+
+ // const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file,
+ // const FileSys::VirtualFile& dest_file) {
+ // if (src_file == nullptr || dest_file == nullptr) {
+ // return false;
+ // }
+ // if (!dest_file->Resize(src_file->GetSize())) {
+ // return false;
+ // }
+
+ // for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) {
+ // if (dialog.wasCanceled()) {
+ // dest_file->Resize(0);
+ // return false;
+ // }
+
+ // using namespace std::literals::chrono_literals;
+ // const auto new_timestamp = std::chrono::steady_clock::now();
+
+ // if ((new_timestamp - last_timestamp) > 33ms) {
+ // last_timestamp = new_timestamp;
+ // dialog.setValue(
+ // static_cast(std::min(read_size, total_size) * 100 / total_size));
+ // QCoreApplication::processEvents();
+ // }
+
+ // const auto read = src_file->Read(buffer.data(), buffer.size(), i);
+ // dest_file->Write(buffer.data(), read, i);
+
+ // read_size += read;
+ // }
+
+ // return true;
+ // };
+
+ // if (full) {
+ // for (const auto& file : src->GetFiles()) {
+ // const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
+ // if (!QtRawCopy(file, out))
+ // return false;
+ // }
+ // }
+
+ // for (const auto& dir : src->GetSubdirectories()) {
+ // const auto out = dest->CreateSubdirectory(dir->GetName());
+ // if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full))
+ // return false;
+ // }
+
+ // return true;
+ return true;
+}
+
+}
diff --git a/src/qt_common/qt_rom_util.h b/src/qt_common/qt_rom_util.h
new file mode 100644
index 0000000000..f76b09753d
--- /dev/null
+++ b/src/qt_common/qt_rom_util.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef QT_ROM_UTIL_H
+#define QT_ROM_UTIL_H
+
+#include "qt_common/qt_common.h"
+#include
+
+namespace QtCommon::ROM {
+
+bool RomFSRawCopy(size_t total_size,
+ size_t& read_size,
+ QtProgressCallback callback,
+ const FileSys::VirtualDir& src,
+ const FileSys::VirtualDir& dest,
+ bool full);
+
+}
+#endif // QT_ROM_UTIL_H
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/qt_common/shared_translation.cpp
similarity index 91%
rename from src/yuzu/configuration/shared_translation.cpp
rename to src/qt_common/shared_translation.cpp
index 1137145659..8f31e07154 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/qt_common/shared_translation.cpp
@@ -7,23 +7,21 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include "yuzu/configuration/shared_translation.h"
+#include "shared_translation.h"
#include
-#include
#include "common/settings.h"
#include "common/settings_enums.h"
#include "common/settings_setting.h"
#include "common/time_zone.h"
-#include "yuzu/uisettings.h"
+#include "qt_common/uisettings.h"
#include