Browse Source

[desktop, fs] main_window separation; fix Ryujinx save data link issues (#2929)

Some genius decided to put the entire MainWindow class into main.h and
main.cpp, which is not only horrific practice but also completely
destroys clangd beyond repair. Please, just don't do this.

(this will probably merge conflict to hell and back)

Also, fixes a bunch of issues with Ryujinx save data link:
- Paths with spaces would cause mklink to fail
- Add support for portable directories
- Symlink detection was incorrect sometimes(????)
- Some other stuff I'm forgetting

Furthermore, when selecting "From Eden" and attempting to save in Ryujinx, Ryujinx would destroy the link for... some reason? So to get around this we just copy the Eden data to Ryujinx then treat it like a "From Ryujinx" op

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2929
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
pull/2974/head v0.0.4-rc2
crueter 1 month ago
parent
commit
08f3639c80
No known key found for this signature in database GPG Key ID: 425ACD2D4830EBC6
  1. 2
      CMakeLists.txt
  2. 2
      cpmfile.json
  3. 4
      src/common/CMakeLists.txt
  4. 19
      src/common/fs/ryujinx_compat.cpp
  5. 11
      src/common/fs/ryujinx_compat.h
  6. 39
      src/common/fs/symlink.cpp
  7. 2
      src/common/fs/symlink.h
  8. 57
      src/core/hle/service/acc/profile_manager.cpp
  9. 2
      src/core/hle/service/acc/profile_manager.h
  10. 11
      src/qt_common/abstract/frontend.cpp
  11. 4
      src/qt_common/abstract/frontend.h
  12. 26
      src/qt_common/config/qt_config.cpp
  13. 54
      src/qt_common/config/shared_translation.h
  14. 2
      src/qt_common/config/uisettings.h
  15. 6
      src/qt_common/qt_string_lookup.h
  16. 7
      src/qt_common/util/content.h
  17. 76
      src/qt_common/util/fs.cpp
  18. 9
      src/qt_common/util/fs.h
  19. 8
      src/yuzu/CMakeLists.txt
  20. 13
      src/yuzu/applets/qt_amiibo_settings.cpp
  21. 7
      src/yuzu/applets/qt_amiibo_settings.h
  22. 10
      src/yuzu/applets/qt_controller.cpp
  23. 7
      src/yuzu/applets/qt_controller.h
  24. 13
      src/yuzu/applets/qt_error.cpp
  25. 7
      src/yuzu/applets/qt_error.h
  26. 10
      src/yuzu/applets/qt_profile_select.cpp
  27. 7
      src/yuzu/applets/qt_profile_select.h
  28. 25
      src/yuzu/applets/qt_software_keyboard.cpp
  29. 7
      src/yuzu/applets/qt_software_keyboard.h
  30. 15
      src/yuzu/applets/qt_web_browser.cpp
  31. 7
      src/yuzu/applets/qt_web_browser.h
  32. 12
      src/yuzu/bootmanager.cpp
  33. 7
      src/yuzu/bootmanager.h
  34. 60
      src/yuzu/data_dialog.cpp
  35. 25
      src/yuzu/data_dialog.ui
  36. 4
      src/yuzu/deps_dialog.cpp
  37. 10
      src/yuzu/game_list.cpp
  38. 8
      src/yuzu/game_list.h
  39. 4945
      src/yuzu/main.cpp
  40. 4899
      src/yuzu/main_window.cpp
  41. 13
      src/yuzu/main_window.h
  42. 37
      src/yuzu/migration_worker.cpp
  43. 22
      src/yuzu/migration_worker.h
  44. 2
      src/yuzu/multiplayer/direct_connect.cpp
  45. 2
      src/yuzu/multiplayer/host_room.cpp
  46. 2
      src/yuzu/multiplayer/lobby.cpp
  47. 26
      src/yuzu/ryujinx_dialog.cpp
  48. 61
      src/yuzu/user_data_migration.cpp
  49. 1
      src/yuzu/user_data_migration.h
  50. 50
      src/yuzu/util/util.cpp
  51. 16
      src/yuzu/util/util.h

2
CMakeLists.txt

@ -586,7 +586,7 @@ else()
find_package(zstd 1.5 REQUIRED MODULE)
# wow
find_package(Boost 1.57.0 CONFIG REQUIRED OPTIONAL_COMPONENTS headers context system fiber)
find_package(Boost 1.57.0 CONFIG REQUIRED OPTIONAL_COMPONENTS headers context system fiber filesystem)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR ANDROID)
find_package(gamemode 1.7 MODULE)

2
cpmfile.json

@ -20,7 +20,7 @@
"hash": "4fb7f6fde92762305aad8754d7643cd918dd1f3f67e104e9ab385b18c73178d72a17321354eb203b790b6702f2cf6d725a5d6e2dfbc63b1e35f9eb59fb42ece9",
"git_version": "1.89.0",
"version": "1.57",
"find_args": "CONFIG",
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
"patches": [
"0001-clang-cl.patch",
"0002-use-marmasm.patch",

4
src/common/CMakeLists.txt

@ -252,11 +252,13 @@ if(CXX_CLANG)
endif()
if (BOOST_NO_HEADERS)
target_link_libraries(common PUBLIC Boost::algorithm Boost::icl Boost::pool)
target_link_libraries(common PUBLIC Boost::algorithm Boost::icl Boost::pool Boost::filesystem)
else()
target_link_libraries(common PUBLIC Boost::headers)
endif()
target_link_libraries(common PUBLIC Boost::filesystem)
if (lz4_ADDED)
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
endif()

19
src/common/fs/ryujinx_compat.cpp

@ -14,16 +14,29 @@ namespace fs = std::filesystem;
fs::path GetKvdbPath()
{
return GetLegacyPath(EmuPath::RyujinxDir) / "bis" / "system" / "save" / "8000000000000000" / "0"
return GetKvdbPath(GetLegacyPath(EmuPath::RyujinxDir));
}
fs::path GetKvdbPath(const fs::path& path) {
return path / "bis" / "system" / "save" / "8000000000000000" / "0"
/ "imkvdb.arc";
}
fs::path GetRyuPathFromSavePath(const fs::path& path) {
// This is a horrible hack, but I cba to find something better
return path.parent_path().parent_path().parent_path().parent_path().parent_path();
}
fs::path GetRyuSavePath(const u64 &save_id)
{
return GetRyuSavePath(GetLegacyPath(EmuPath::RyujinxDir), save_id);
}
std::filesystem::path GetRyuSavePath(const std::filesystem::path& path, const u64& save_id) {
std::string hex = fmt::format("{:016x}", save_id);
// TODO: what's the difference between 0 and 1?
return GetLegacyPath(EmuPath::RyujinxDir) / "bis" / "user" / "save" / hex / "0";
// TODO: what's the difference between 0 and 1?
return path / "bis" / "user" / "save" / hex / "0";
}
IMENReadResult ReadKvdb(const fs::path &path, std::vector<IMEN> &imens)

11
src/common/fs/ryujinx_compat.h

@ -7,16 +7,17 @@
#include <filesystem>
#include <vector>
namespace fs = std::filesystem;
namespace Common::FS {
constexpr const char IMEN_MAGIC[4] = {0x49, 0x4d, 0x45, 0x4e};
constexpr const char IMKV_MAGIC[4] = {0x49, 0x4d, 0x4b, 0x56};
constexpr const u8 IMEN_SIZE = 0x8c;
fs::path GetKvdbPath();
fs::path GetRyuSavePath(const u64 &program_id);
std::filesystem::path GetKvdbPath();
std::filesystem::path GetKvdbPath(const std::filesystem::path &path);
std::filesystem::path GetRyuPathFromSavePath(const std::filesystem::path &path);
std::filesystem::path GetRyuSavePath(const u64 &save_id);
std::filesystem::path GetRyuSavePath(const std::filesystem::path &path, const u64 &save_id);
enum class IMENReadResult {
Nonexistent, // ryujinx not found
@ -35,6 +36,6 @@ struct IMEN
static_assert(sizeof(IMEN) == 0x10, "IMEN has incorrect size.");
IMENReadResult ReadKvdb(const fs::path &path, std::vector<IMEN> &imens);
IMENReadResult ReadKvdb(const std::filesystem::path &path, std::vector<IMEN> &imens);
} // namespace Common::FS

39
src/common/fs/symlink.cpp

@ -4,10 +4,12 @@
#include "symlink.h"
#ifdef _WIN32
#include <windows.h>
#include <fmt/format.h>
#include <windows.h>
#endif
#include <boost/filesystem.hpp>
namespace fs = std::filesystem;
// The sole purpose of this file is to treat symlinks like symlinks on POSIX,
@ -15,29 +17,40 @@ namespace fs = std::filesystem;
// This is because, for some inexplicable reason, Microsoft has locked symbolic
// links behind a "security policy", whereas directory junctions--functionally identical
// for directories, by the way--are not. Why? I don't know.
// And no, they do NOT provide a standard API for this (at least to my knowledge).
// CreateSymbolicLink, even when EXPLICITLY TOLD to create a junction, still fails
// because of their security policy.
// I don't know what kind of drugs the Windows developers have been on since NT started.
// Microsoft still has not implemented any of this in their std::filesystem implemenation,
// which ALSO means that it DOES NOT FOLLOW ANY DIRECTORY JUNCTIONS... AT ALL.
// Nor does any of their command line utilities or APIs. So you're quite literally
// on your own.
namespace Common::FS {
bool CreateSymlink(const fs::path &from, const fs::path &to)
bool CreateSymlink(fs::path from, fs::path to)
{
#ifdef _WIN32
const std::string command = fmt::format("mklink /J {} {}", to.string(), from.string());
return system(command.c_str()) == 0;
#else
from.make_preferred();
to.make_preferred();
std::error_code ec;
fs::create_directory_symlink(from, to, ec);
return !ec;
#ifdef _WIN32
if (ec) {
const std::string command = fmt::format("mklink /J \"{}\" \"{}\"",
to.string(),
from.string());
return system(command.c_str()) == 0;
}
#endif
return !ec;
}
bool IsSymlink(const fs::path &path)
{
#ifdef _WIN32
auto attributes = GetFileAttributesW(path.wstring().c_str());
return attributes & FILE_ATTRIBUTE_REPARSE_POINT;
#else
return fs::is_symlink(path);
#endif
return boost::filesystem::is_symlink(boost::filesystem::path{path});
}
} // namespace Common::FS

2
src/common/fs/symlink.h

@ -6,7 +6,7 @@
#include <filesystem>
namespace Common::FS {
bool CreateSymlink(const std::filesystem::path &from, const std::filesystem::path &to);
bool CreateSymlink(std::filesystem::path from, std::filesystem::path to);
bool IsSymlink(const std::filesystem::path &path);
} // namespace Common::FS

57
src/core/hle/service/acc/profile_manager.cpp

@ -7,8 +7,6 @@
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <iostream>
#include <random>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/find.hpp>
@ -18,11 +16,11 @@
#include "common/fs/fs.h"
#include "common/fs/fs_types.h"
#include "common/fs/path_util.h"
#include "common/fs/symlink.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/file_sys/savedata_factory.h"
#include "core/hle/service/acc/profile_manager.h"
#include <ranges>
namespace Service::Account {
@ -492,6 +490,32 @@ void ProfileManager::ResetUserSaveFile()
ParseUserSaveFile();
}
std::vector<UUID> ProfileManager::FindExistingProfileUUIDs()
{
std::vector<UUID> uuids;
for (const ProfileInfo& p : profiles) {
auto uuid = p.user_uuid;
if (!uuid.IsInvalid()) {
uuids.emplace_back(uuid);
}
}
return uuids;
}
std::vector<std::string> ProfileManager::FindExistingProfileStrings()
{
std::vector<UUID> uuids = FindExistingProfileUUIDs();
std::vector<std::string> uuid_strings;
for (const UUID &uuid : uuids) {
auto user_id = uuid.AsU128();
uuid_strings.emplace_back(fmt::format("{:016X}{:016X}", user_id[1], user_id[0]));
}
return uuid_strings;
}
std::vector<std::string> ProfileManager::FindGoodProfiles()
{
namespace fs = std::filesystem;
@ -501,31 +525,17 @@ std::vector<std::string> ProfileManager::FindGoodProfiles()
const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir)
/ "user/save/0000000000000000";
// some exceptions because certain games just LOVE TO CAUSE ISSUES
static constexpr const std::array<const char* const, 2> EXCEPTION_UUIDS
= {"5755CC2A545A87128500000000000000", "00000000000000000000000000000000"};
// some exceptions, e.g. the "system" profile
static constexpr const std::array<const char* const, 1> EXCEPTION_UUIDS
= {"00000000000000000000000000000000"};
for (const char *const uuid : EXCEPTION_UUIDS) {
if (fs::exists(path / uuid))
good_uuids.emplace_back(uuid);
}
for (const ProfileInfo& p : profiles) {
std::string uuid_string = [p]() -> std::string {
auto uuid = p.user_uuid;
// "ignore" invalid uuids
if (uuid.IsInvalid()) {
return "0";
}
auto user_id = uuid.AsU128();
return fmt::format("{:016X}{:016X}", user_id[1], user_id[0]);
}();
if (uuid_string != "0") good_uuids.emplace_back(uuid_string);
}
auto existing = FindExistingProfileStrings();
good_uuids.insert(good_uuids.end(), existing.begin(), existing.end());
return good_uuids;
}
@ -562,7 +572,8 @@ std::vector<std::string> ProfileManager::FindOrphanedProfiles()
override = true;
// if there are any regular files (NOT directories) there, do NOT delete it :p
if (file.is_regular_file())
// Also: check for symlinks
if (file.is_regular_file() || Common::FS::IsSymlink(file.path()))
return false;
}
} catch (const fs::filesystem_error& e) {

2
src/core/hle/service/acc/profile_manager.h

@ -105,6 +105,8 @@ public:
void ResetUserSaveFile();
std::vector<Common::UUID> FindExistingProfileUUIDs();
std::vector<std::string> FindExistingProfileStrings();
std::vector<std::string> FindGoodProfiles();
std::vector<std::string> FindOrphanedProfiles();

11
src/qt_common/abstract/frontend.cpp

@ -28,7 +28,7 @@ const QString GetOpenFileName(const QString &title,
Options options)
{
#ifdef YUZU_QT_WIDGETS
return QFileDialog::getOpenFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options);
return QFileDialog::getOpenFileName(rootObject, title, dir, filter, selectedFilter, options);
#endif
}
@ -39,7 +39,14 @@ const QString GetSaveFileName(const QString &title,
Options options)
{
#ifdef YUZU_QT_WIDGETS
return QFileDialog::getSaveFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options);
return QFileDialog::getSaveFileName(rootObject, title, dir, filter, selectedFilter, options);
#endif
}
const QString GetExistingDirectory(const QString& caption, const QString& dir,
Options options) {
#ifdef YUZU_QT_WIDGETS
return QFileDialog::getExistingDirectory(rootObject, caption, dir, options);
#endif
}

4
src/qt_common/abstract/frontend.h

@ -135,5 +135,9 @@ const QString GetSaveFileName(const QString &title,
QString *selectedFilter = nullptr,
Options options = Options());
const QString GetExistingDirectory(const QString &caption = QString(),
const QString &dir = QString(),
Options options = Option::ShowDirsOnly);
} // namespace QtCommon::Frontend
#endif // FRONTEND_H

26
src/qt_common/config/qt_config.cpp

@ -294,6 +294,17 @@ void QtConfig::ReadUIGamelistValues() {
}
EndArray();
const int linked_size = BeginArray("ryujinx_linked");
for (int i = 0; i < linked_size; ++i) {
SetArrayIndex(i);
QDir ryu_dir = QString::fromStdString(ReadStringSetting("ryujinx_path"));
u64 program_id = ReadUnsignedIntegerSetting("program_id");
UISettings::values.ryujinx_link_paths.insert(program_id, ryu_dir);
}
EndArray();
EndGroup();
}
@ -499,6 +510,21 @@ void QtConfig::SaveUIGamelistValues() {
}
EndArray(); // favorites
BeginArray(std::string("ryujinx_linked"));
int i = 0;
QMapIterator iter(UISettings::values.ryujinx_link_paths);
while (iter.hasNext()) {
iter.next();
SetArrayIndex(i);
WriteIntegerSetting("program_id", iter.key());
WriteStringSetting("ryujinx_path", iter.value().absolutePath().toStdString());
++i;
}
EndArray(); // ryujinx
EndGroup();
}

54
src/qt_common/config/shared_translation.h

@ -28,54 +28,54 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject *parent);
std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent);
static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map = {
{Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))},
{Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))},
{Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))},
{Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "None"))},
{Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FXAA"))},
{Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "SMAA"))},
};
static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map = {
{Settings::ScalingFilter::NearestNeighbor,
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Nearest"))},
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Nearest"))},
{Settings::ScalingFilter::Bilinear,
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bilinear"))},
{Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))},
{Settings::ScalingFilter::ZeroTangent, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Zero-Tangent"))},
{Settings::ScalingFilter::BSpline, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "B-Spline"))},
{Settings::ScalingFilter::Mitchell, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Mitchell"))},
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Bilinear"))},
{Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Bicubic"))},
{Settings::ScalingFilter::ZeroTangent, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Zero-Tangent"))},
{Settings::ScalingFilter::BSpline, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "B-Spline"))},
{Settings::ScalingFilter::Mitchell, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Mitchell"))},
{Settings::ScalingFilter::Spline1,
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Spline-1"))},
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Spline-1"))},
{Settings::ScalingFilter::Gaussian,
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))},
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Gaussian"))},
{Settings::ScalingFilter::Lanczos,
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Lanczos"))},
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Lanczos"))},
{Settings::ScalingFilter::ScaleForce,
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))},
{Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
{Settings::ScalingFilter::Area, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Area"))},
{Settings::ScalingFilter::Mmpx, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "MMPX"))},
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "ScaleForce"))},
{Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR"))},
{Settings::ScalingFilter::Area, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Area"))},
{Settings::ScalingFilter::Mmpx, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "MMPX"))},
};
static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map = {
{Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
{Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
{Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Docked"))},
{Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Handheld"))},
};
static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map = {
{Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
{Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
{Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
{Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Normal"))},
{Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "High"))},
{Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Extreme"))},
};
static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map = {
{Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Vulkan"))},
{Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "OpenGL"))},
{Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
{Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Vulkan"))},
{Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL"))},
{Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Null"))},
};
static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map = {
{Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
{Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
{Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
{Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "GLSL"))},
{Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "GLASM"))},
{Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "SPIRV"))},
};
} // namespace ConfigurationShared

2
src/qt_common/config/uisettings.h

@ -14,6 +14,7 @@
#include <QString>
#include <QStringList>
#include <QVector>
#include <qdir.h>
#include "common/common_types.h"
#include "common/settings.h"
#include "common/settings_enums.h"
@ -201,6 +202,7 @@ struct Values {
Setting<bool> cache_game_list{linkage, true, "cache_game_list", Category::UiGameList};
Setting<bool> favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList};
QVector<u64> favorited_ids;
QMap<u64, QDir> ryujinx_link_paths;
// Compatibility List
Setting<bool> show_compat{linkage, true, "show_compat", Category::UiGameList};

6
src/qt_common/qt_string_lookup.h

@ -8,6 +8,12 @@
#include "frozen/map.h"
#include "frozen/string.h"
/// Small helper to look up enums.
/// res = the result code
/// base = the base matching value in the StringKey table
#define LOOKUP_ENUM(res, base) StringLookup::Lookup( \
static_cast<StringLookup::StringKey>((int) res + (int) StringLookup::base))
namespace QtCommon::StringLookup {
Q_NAMESPACE

7
src/qt_common/util/content.h

@ -25,8 +25,7 @@ enum class FirmwareInstallResult {
inline const QString GetFirmwareInstallResultString(FirmwareInstallResult result)
{
return QtCommon::StringLookup::Lookup(static_cast<StringLookup::StringKey>(
(int) result + (int) QtCommon::StringLookup::FwInstallSuccess));
return LOOKUP_ENUM(result, FwInstallSuccess);
}
/**
@ -36,9 +35,7 @@ inline const QString GetFirmwareInstallResultString(FirmwareInstallResult result
*/
inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult result)
{
// this can probably be made into a common function of sorts
return QtCommon::StringLookup::Lookup(static_cast<StringLookup::StringKey>(
(int) result + (int) QtCommon::StringLookup::KeyInstallSuccess));
return LOOKUP_ENUM(result, KeyInstallSuccess);
}
void InstallFirmware(const QString &location, bool recursive);

76
src/qt_common/util/fs.cpp

@ -1,11 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <filesystem>
#include "fs.h"
#include "common/fs/ryujinx_compat.h"
#include "common/fs/symlink.h"
#include "frontend_common/data_manager.h"
#include "fs.h"
#include "qt_common/abstract/frontend.h"
#include "qt_common/qt_string_lookup.h"
@ -56,6 +56,9 @@ bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir)
orig = eden_dir;
}
linked.make_preferred();
orig.make_preferred();
// first cleanup the symlink/junction,
try {
// NB: do NOT use remove_all, as Windows treats this as a remove_all to the target,
@ -84,47 +87,64 @@ bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir)
return true;
}
u64 GetRyujinxSaveID(const u64 &program_id)
const fs::path GetRyujinxSavePath(const fs::path &path_hint, const u64 &program_id)
{
auto path = Common::FS::GetKvdbPath();
auto ryu_path = path_hint;
auto kvdb_path = Common::FS::GetKvdbPath(ryu_path);
if (!fs::exists(kvdb_path)) {
using namespace QtCommon::Frontend;
auto res = Warning(
tr("Could not find Ryujinx installation"),
tr("Could not find a valid Ryujinx installation. This may typically occur if you are "
"using Ryujinx in portable mode.\n\nWould you like to manually select a portable "
"folder to use?"), StandardButton::Yes | StandardButton::No);
if (res == StandardButton::Yes) {
auto selected_path = GetExistingDirectory(tr("Ryujinx Portable Location"), QDir::homePath()).toStdString();
if (selected_path.empty())
return fs::path{};
ryu_path = selected_path;
// In case the user selects the actual ryujinx installation dir INSTEAD OF
// the portable dir
if (fs::exists(ryu_path / "portable")) {
ryu_path = ryu_path / "portable";
}
kvdb_path = Common::FS::GetKvdbPath(ryu_path);
if (!fs::exists(kvdb_path)) {
QtCommon::Frontend::Critical(
tr("Not a valid Ryujinx directory"),
tr("The specified directory does not contain valid Ryujinx data."));
return fs::path{};
}
} else {
return fs::path{};
}
}
std::vector<Common::FS::IMEN> imens;
Common::FS::IMENReadResult res = Common::FS::ReadKvdb(path, imens);
Common::FS::IMENReadResult res = Common::FS::ReadKvdb(kvdb_path, imens);
if (res == Common::FS::IMENReadResult::Success) {
// TODO: this can probably be done with std::find_if but I'm lazy
for (const Common::FS::IMEN &imen : imens) {
if (imen.title_id == program_id)
return imen.save_id;
return Common::FS::GetRyuSavePath(ryu_path, imen.save_id);
}
QtCommon::Frontend::Critical(
tr("Could not find Ryujinx save data"),
StringLookup::Lookup(StringLookup::RyujinxNoSaveId).arg(program_id, 0, 16));
} else {
// TODO: make this long thing a function or something
QString caption = StringLookup::Lookup(
static_cast<StringLookup::StringKey>((int) res + (int) StringLookup::KvdbNonexistent));
QString caption = LOOKUP_ENUM(res, KvdbNonexistent);
QtCommon::Frontend::Critical(tr("Could not find Ryujinx save data"), caption);
}
return -1;
}
std::optional<std::pair<fs::path, fs::path> > GetEmuPaths(
const u64 program_id, const u64 save_id, const std::string &user_id)
{
fs::path ryu_dir = Common::FS::GetRyuSavePath(save_id);
if (user_id.empty())
return std::nullopt;
std::string hex_program = fmt::format("{:016X}", program_id);
fs::path eden_dir
= FrontendCommon::DataManager::GetDataDir(FrontendCommon::DataManager::DataDir::Saves,
user_id)
/ hex_program;
return std::make_pair(eden_dir, ryu_dir);
return fs::path{};
}
} // namespace QtCommon::FS

9
src/qt_common/util/fs.h

@ -10,13 +10,10 @@
namespace QtCommon::FS {
void LinkRyujinx(std::filesystem::path &from, std::filesystem::path &to);
u64 GetRyujinxSaveID(const u64 &program_id);
/// @brief {eden, ryu}
std::optional<std::pair<std::filesystem::path, std::filesystem::path>> GetEmuPaths(
const u64 program_id, const u64 save_id, const std::string &user_id);
const std::filesystem::path GetRyujinxSavePath(const std::filesystem::path &path_hint, const u64 &program_id);
/// returns FALSE if the dirs are NOT linked
bool CheckUnlink(const std::filesystem::path &eden_dir, const std::filesystem::path &ryu_dir);
bool CheckUnlink(const std::filesystem::path& eden_dir,
const std::filesystem::path& ryu_dir);
} // namespace QtCommon::FS

8
src/yuzu/CMakeLists.txt

@ -12,6 +12,8 @@ if (YUZU_USE_BUNDLED_QT AND PLATFORM_LINUX)
set(CMAKE_BUILD_RPATH "${CMAKE_BINARY_DIR}/bin/lib/")
endif()
find_package(Qt6 REQUIRED COMPONENTS Widgets)
add_executable(yuzu
Info.plist
about_dialog.cpp
@ -169,9 +171,9 @@ add_executable(yuzu
loading_screen.cpp
loading_screen.h
loading_screen.ui
main.cpp
main.h
main.ui
multiplayer/chat_room.cpp
multiplayer/chat_room.h
multiplayer/chat_room.ui
@ -235,6 +237,7 @@ add_executable(yuzu
data_dialog.h data_dialog.cpp data_dialog.ui
data_widget.ui
ryujinx_dialog.h ryujinx_dialog.cpp ryujinx_dialog.ui
main_window.h main_window.cpp
)
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
@ -441,6 +444,7 @@ endif()
if (YUZU_ROOM)
target_link_libraries(yuzu PRIVATE yuzu-room)
target_link_libraries(yuzu PRIVATE Qt6::Widgets)
endif()
create_target_directory_groups(yuzu)

13
src/yuzu/applets/qt_amiibo_settings.cpp

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -18,7 +21,7 @@
#include "web_service/web_backend.h"
#endif
#include "yuzu/applets/qt_amiibo_settings.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
QtAmiiboSettingsDialog::QtAmiiboSettingsDialog(QWidget* parent,
Core::Frontend::CabinetParameters parameters_,
@ -244,12 +247,12 @@ void QtAmiiboSettingsDialog::SetSettingsDescription() {
}
}
QtAmiiboSettings::QtAmiiboSettings(GMainWindow& parent) {
QtAmiiboSettings::QtAmiiboSettings(MainWindow& parent) {
connect(this, &QtAmiiboSettings::MainWindowShowAmiiboSettings, &parent,
&GMainWindow::AmiiboSettingsShowDialog, Qt::QueuedConnection);
&MainWindow::AmiiboSettingsShowDialog, Qt::QueuedConnection);
connect(this, &QtAmiiboSettings::MainWindowRequestExit, &parent,
&GMainWindow::AmiiboSettingsRequestExit, Qt::QueuedConnection);
connect(&parent, &GMainWindow::AmiiboSettingsFinished, this,
&MainWindow::AmiiboSettingsRequestExit, Qt::QueuedConnection);
connect(&parent, &MainWindow::AmiiboSettingsFinished, this,
&QtAmiiboSettings::MainWindowFinished, Qt::QueuedConnection);
}

7
src/yuzu/applets/qt_amiibo_settings.h

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -8,7 +11,7 @@
#include <QDialog>
#include "core/frontend/applets/cabinet.h"
class GMainWindow;
class MainWindow;
class QCheckBox;
class QComboBox;
class QDialogButtonBox;
@ -65,7 +68,7 @@ class QtAmiiboSettings final : public QObject, public Core::Frontend::CabinetApp
Q_OBJECT
public:
explicit QtAmiiboSettings(GMainWindow& parent);
explicit QtAmiiboSettings(MainWindow& parent);
~QtAmiiboSettings() override;
void Close() const override;

10
src/yuzu/applets/qt_controller.cpp

@ -25,7 +25,7 @@
#include "yuzu/configuration/configure_motion_touch.h"
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
#include "yuzu/util/controller_navigation.h"
namespace {
@ -753,12 +753,12 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
}
}
QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
QtControllerSelector::QtControllerSelector(MainWindow& parent) {
connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
&GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
&MainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
connect(this, &QtControllerSelector::MainWindowRequestExit, &parent,
&GMainWindow::ControllerSelectorRequestExit, Qt::QueuedConnection);
connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
&MainWindow::ControllerSelectorRequestExit, Qt::QueuedConnection);
connect(&parent, &MainWindow::ControllerSelectorReconfigureFinished, this,
&QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
}

7
src/yuzu/applets/qt_controller.h

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -8,7 +11,7 @@
#include <QDialog>
#include "core/frontend/applets/controller.h"
class GMainWindow;
class MainWindow;
class QCheckBox;
class QComboBox;
class QDialogButtonBox;
@ -163,7 +166,7 @@ class QtControllerSelector final : public QObject, public Core::Frontend::Contro
Q_OBJECT
public:
explicit QtControllerSelector(GMainWindow& parent);
explicit QtControllerSelector(MainWindow& parent);
~QtControllerSelector() override;
void Close() const override;

13
src/yuzu/applets/qt_error.cpp

@ -1,16 +1,19 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDateTime>
#include "yuzu/applets/qt_error.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) {
QtErrorDisplay::QtErrorDisplay(MainWindow& parent) {
connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent,
&GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection);
&MainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection);
connect(this, &QtErrorDisplay::MainWindowRequestExit, &parent,
&GMainWindow::ErrorDisplayRequestExit, Qt::QueuedConnection);
connect(&parent, &GMainWindow::ErrorDisplayFinished, this,
&MainWindow::ErrorDisplayRequestExit, Qt::QueuedConnection);
connect(&parent, &MainWindow::ErrorDisplayFinished, this,
&QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection);
}

7
src/yuzu/applets/qt_error.h

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -7,13 +10,13 @@
#include "core/frontend/applets/error.h"
class GMainWindow;
class MainWindow;
class QtErrorDisplay final : public QObject, public Core::Frontend::ErrorApplet {
Q_OBJECT
public:
explicit QtErrorDisplay(GMainWindow& parent);
explicit QtErrorDisplay(MainWindow& parent);
~QtErrorDisplay() override;
void Close() const override;

10
src/yuzu/applets/qt_profile_select.cpp

@ -20,7 +20,7 @@
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "yuzu/applets/qt_profile_select.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
#include "yuzu/util/controller_navigation.h"
namespace {
@ -230,12 +230,12 @@ void QtProfileSelectionDialog::SetDialogPurpose(
}
}
QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
QtProfileSelector::QtProfileSelector(MainWindow& parent) {
connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent,
&GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection);
&MainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection);
connect(this, &QtProfileSelector::MainWindowRequestExit, &parent,
&GMainWindow::ProfileSelectorRequestExit, Qt::QueuedConnection);
connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this,
&MainWindow::ProfileSelectorRequestExit, Qt::QueuedConnection);
connect(&parent, &MainWindow::ProfileSelectorFinishedSelection, this,
&QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection);
}

7
src/yuzu/applets/qt_profile_select.h

@ -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
@ -9,7 +12,7 @@
#include "core/frontend/applets/profile_select.h"
class ControllerNavigation;
class GMainWindow;
class MainWindow;
class QDialogButtonBox;
class QGraphicsScene;
class QLabel;
@ -69,7 +72,7 @@ class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSe
Q_OBJECT
public:
explicit QtProfileSelector(GMainWindow& parent);
explicit QtProfileSelector(MainWindow& parent);
~QtProfileSelector() override;
void Close() const override;

25
src/yuzu/applets/qt_software_keyboard.cpp

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -15,7 +18,7 @@
#include "hid_core/hid_types.h"
#include "ui_qt_software_keyboard.h"
#include "yuzu/applets/qt_software_keyboard.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
#include "yuzu/util/overlay_dialog.h"
namespace {
@ -1541,24 +1544,24 @@ void QtSoftwareKeyboardDialog::InputThread() {
}
}
QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {
QtSoftwareKeyboard::QtSoftwareKeyboard(MainWindow& main_window) {
connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window,
&GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection);
&MainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection);
connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window,
&GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection);
&MainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection);
connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window,
&GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection);
&MainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection);
connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window,
&GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection);
&MainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection);
connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window,
&GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection);
&MainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection);
connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window,
&GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection);
&MainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection);
connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window,
&GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this,
&MainWindow::SoftwareKeyboardExit, Qt::QueuedConnection);
connect(&main_window, &MainWindow::SoftwareKeyboardSubmitNormalText, this,
&QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this,
connect(&main_window, &MainWindow::SoftwareKeyboardSubmitInlineText, this,
&QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection);
}

7
src/yuzu/applets/qt_software_keyboard.h

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -27,7 +30,7 @@ namespace Ui {
class QtSoftwareKeyboardDialog;
}
class GMainWindow;
class MainWindow;
class QtSoftwareKeyboardDialog final : public QDialog {
Q_OBJECT
@ -230,7 +233,7 @@ class QtSoftwareKeyboard final : public QObject, public Core::Frontend::Software
Q_OBJECT
public:
explicit QtSoftwareKeyboard(GMainWindow& parent);
explicit QtSoftwareKeyboard(MainWindow& parent);
~QtSoftwareKeyboard() override;
void Close() const override {

15
src/yuzu/applets/qt_web_browser.cpp

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -18,7 +21,7 @@
#endif
#include "yuzu/applets/qt_web_browser.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
#ifdef YUZU_USE_QT_WEB_ENGINE
@ -391,14 +394,14 @@ void QtNXWebEngineView::FocusFirstLinkElement() {
#endif
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
QtWebBrowser::QtWebBrowser(MainWindow& main_window) {
connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window,
&GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
&MainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
connect(this, &QtWebBrowser::MainWindowRequestExit, &main_window,
&GMainWindow::WebBrowserRequestExit, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
&MainWindow::WebBrowserRequestExit, Qt::QueuedConnection);
connect(&main_window, &MainWindow::WebBrowserExtractOfflineRomFS, this,
&QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserClosed, this,
connect(&main_window, &MainWindow::WebBrowserClosed, this,
&QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
}

7
src/yuzu/applets/qt_web_browser.h

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -14,7 +17,7 @@
#include "core/frontend/applets/web_browser.h"
class GMainWindow;
class MainWindow;
class InputInterpreter;
class UrlRequestInterceptor;
@ -193,7 +196,7 @@ class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserAppl
Q_OBJECT
public:
explicit QtWebBrowser(GMainWindow& parent);
explicit QtWebBrowser(MainWindow& parent);
~QtWebBrowser() override;
void Close() const override;

12
src/yuzu/bootmanager.cpp

@ -57,7 +57,7 @@
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
#include "yuzu/bootmanager.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
#include "qt_common/qt_common.h"
class QObject;
@ -272,7 +272,7 @@ struct NullRenderWidget : public RenderWidget {
explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {}
};
GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
GRenderWindow::GRenderWindow(MainWindow* parent, EmuThread* emu_thread_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
Core::System& system_)
: QWidget(parent),
@ -290,11 +290,11 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
strict_context_required = QGuiApplication::platformName() == QStringLiteral("wayland") ||
QGuiApplication::platformName() == QStringLiteral("wayland-egl");
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram,
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &MainWindow::OnLoadComplete);
connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &MainWindow::OnExecuteProgram,
Qt::QueuedConnection);
connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection);
connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged);
connect(this, &GRenderWindow::ExitSignal, parent, &MainWindow::OnExit, Qt::QueuedConnection);
connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &MainWindow::OnTasStateChanged);
mouse_constrain_timer.setInterval(default_mouse_constrain_timeout);
connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse);

7
src/yuzu/bootmanager.h

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -29,7 +32,7 @@
#include "common/thread.h"
#include "core/frontend/emu_window.h"
class GMainWindow;
class MainWindow;
class QCamera;
class QCameraImageCapture;
class QCloseEvent;
@ -146,7 +149,7 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
Q_OBJECT
public:
explicit GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
explicit GRenderWindow(MainWindow* parent, EmuThread* emu_thread_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
Core::System& system_);
~GRenderWindow() override;

60
src/yuzu/data_dialog.cpp

@ -2,12 +2,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "data_dialog.h"
#include "core/hle/service/acc/profile_manager.h"
#include "frontend_common/data_manager.h"
#include "qt_common/qt_common.h"
#include "qt_common/util/content.h"
#include "qt_common/qt_string_lookup.h"
#include "ui_data_dialog.h"
#include "util/util.h"
#include <QDesktopServices>
#include <QFileDialog>
@ -26,17 +25,18 @@ DataDialog::DataDialog(QWidget *parent)
ui->setupUi(this);
// TODO: Should we make this a single widget that pulls data from a model?
#define WIDGET(name) \
#define WIDGET(label, name) \
ui->page->addWidget(new DataWidget(FrontendCommon::DataManager::DataDir::name, \
QtCommon::StringLookup::name##Tooltip, \
QStringLiteral(#name), \
this));
this)); \
ui->labels->addItem(label);
WIDGET(Shaders)
WIDGET(UserNand)
WIDGET(SysNand)
WIDGET(Mods)
WIDGET(Saves)
WIDGET(tr("Shaders"), Shaders)
WIDGET(tr("UserNAND"), UserNand)
WIDGET(tr("SysNAND"), SysNand)
WIDGET(tr("Mods"), Mods)
WIDGET(tr("Saves"), Saves)
#undef WIDGET
@ -82,7 +82,7 @@ void DataWidget::clear()
{
std::string user_id{};
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
user_id = selectProfile();
user_id = GetProfileIDString();
}
QtCommon::Content::ClearDataDir(m_dir, user_id);
scan();
@ -92,7 +92,7 @@ void DataWidget::open()
{
std::string user_id{};
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
user_id = selectProfile();
user_id = GetProfileIDString();
}
QDesktopServices::openUrl(QUrl::fromLocalFile(
QString::fromStdString(FrontendCommon::DataManager::GetDataDirString(m_dir, user_id))));
@ -102,7 +102,7 @@ void DataWidget::upload()
{
std::string user_id{};
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
user_id = selectProfile();
user_id = GetProfileIDString();
}
QtCommon::Content::ExportDataDir(m_dir, user_id, m_exportName);
}
@ -111,7 +111,7 @@ void DataWidget::download()
{
std::string user_id{};
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
user_id = selectProfile();
user_id = GetProfileIDString();
}
QtCommon::Content::ImportDataDir(m_dir, user_id, std::bind(&DataWidget::scan, this));
}
@ -131,37 +131,3 @@ void DataWidget::scan() {
watcher->setFuture(
QtConcurrent::run([this]() { return FrontendCommon::DataManager::DataDirSize(m_dir); }));
}
std::string DataWidget::selectProfile()
{
const auto select_profile = [this] {
const Core::Frontend::ProfileSelectParameters parameters{
.mode = Service::AM::Frontend::UiMode::UserSelector,
.invalid_uid_list = {},
.display_options = {},
.purpose = Service::AM::Frontend::UserSelectionPurpose::General,
};
QtProfileSelectionDialog dialog(*QtCommon::system, this, parameters);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint
| Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Rejected) {
return -1;
}
return dialog.GetIndex();
};
const auto index = select_profile();
if (index == -1) {
return "";
}
const auto uuid = QtCommon::system->GetProfileManager().GetUser(static_cast<std::size_t>(index));
ASSERT(uuid);
const auto user_id = uuid->AsU128();
return fmt::format("{:016X}{:016X}", user_id[1], user_id[0]);
}

25
src/yuzu/data_dialog.ui

@ -36,31 +36,6 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<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>
<item>
<property name="text">
<string>Saves</string>
</property>
</item>
</widget>
</item>
<item>

4
src/yuzu/deps_dialog.cpp

@ -18,7 +18,7 @@ DepsDialog::DepsDialog(QWidget* parent)
{
ui->setupUi(this);
constexpr size_t rows = Common::dep_hashes.size();
constexpr int rows = (int) Common::dep_hashes.size();
ui->tableDeps->setRowCount(rows);
QStringList labels;
@ -29,7 +29,7 @@ DepsDialog::DepsDialog(QWidget* parent)
ui->tableDeps->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeMode::Fixed);
ui->tableDeps->horizontalHeader()->setMinimumSectionSize(200);
for (size_t i = 0; i < rows; ++i) {
for (int i = 0; i < rows; ++i) {
const std::string name = Common::dep_names.at(i);
const std::string sha = Common::dep_hashes.at(i);
const std::string url = Common::dep_urls.at(i);

10
src/yuzu/game_list.cpp

@ -23,7 +23,7 @@
#include "yuzu/compatibility_list.h"
#include "yuzu/game_list_p.h"
#include "yuzu/game_list_worker.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
#include "yuzu/util/controller_navigation.h"
#include <fmt/ranges.h>
#include <regex>
@ -314,7 +314,7 @@ void GameList::OnFilterCloseClicked() {
GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
GMainWindow* parent)
MainWindow* parent)
: QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
play_time_manager{play_time_manager_}, system{system_} {
watcher = new QFileSystemWatcher(this);
@ -347,7 +347,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
item_model->setSortRole(GameListItemPath::SortRole);
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
connect(main_window, &MainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
@ -943,8 +943,8 @@ void GameList::RemoveFavorite(u64 program_id) {
}
}
GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} {
connect(parent, &GMainWindow::UpdateThemedIcons, this,
GameListPlaceholder::GameListPlaceholder(MainWindow* parent) : QWidget{parent} {
connect(parent, &MainWindow::UpdateThemedIcons, this,
&GameListPlaceholder::onUpdateThemedIcons);
layout = new QVBoxLayout;

8
src/yuzu/game_list.h

@ -33,7 +33,7 @@ class ControllerNavigation;
class GameListWorker;
class GameListSearchField;
class GameListDir;
class GMainWindow;
class MainWindow;
enum class AmLaunchType;
enum class StartGameType;
@ -69,7 +69,7 @@ public:
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
FileSys::ManualContentProvider* provider_,
PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
GMainWindow* parent = nullptr);
MainWindow* parent = nullptr);
~GameList() override;
QString GetLastFilterResultItem() const;
@ -153,7 +153,7 @@ private:
std::shared_ptr<FileSys::VfsFilesystem> vfs;
FileSys::ManualContentProvider* provider;
GameListSearchField* search_field;
GMainWindow* main_window = nullptr;
MainWindow* main_window = nullptr;
QVBoxLayout* layout = nullptr;
QTreeView* tree_view = nullptr;
QStandardItemModel* item_model = nullptr;
@ -171,7 +171,7 @@ private:
class GameListPlaceholder : public QWidget {
Q_OBJECT
public:
explicit GameListPlaceholder(GMainWindow* parent = nullptr);
explicit GameListPlaceholder(MainWindow* parent = nullptr);
~GameListPlaceholder();
signals:

4945
src/yuzu/main.cpp
File diff suppressed because it is too large
View File

4899
src/yuzu/main_window.cpp
File diff suppressed because it is too large
View File

13
src/yuzu/main.h → src/yuzu/main_window.h

@ -29,7 +29,7 @@
#include <QDBusObjectPath>
#include <QVariant>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QtDBus>
#include <QSocketNotifier>
#endif
#ifdef ENABLE_UPDATE_CHECKER
@ -38,7 +38,6 @@
#endif
class QtConfig;
class ClickableLabel;
class EmuThread;
class GameList;
class GImageInfo;
@ -154,7 +153,7 @@ private:
constexpr static int MaxMultiplier = 8;
};
class GMainWindow : public QMainWindow {
class MainWindow : public QMainWindow {
Q_OBJECT
/// Max number of recently loaded items to keep track of
@ -163,8 +162,8 @@ class GMainWindow : public QMainWindow {
public:
void filterBarSetChecked(bool state);
void UpdateUITheme();
explicit GMainWindow(bool has_broken_vulkan);
~GMainWindow() override;
explicit MainWindow(bool has_broken_vulkan);
~MainWindow() override;
bool DropAction(QDropEvent* event);
void AcceptDropEvent(QDropEvent* event);
@ -467,11 +466,9 @@ private:
*/
bool question(QWidget* parent, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons =
QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
std::string GetProfileID();
std::unique_ptr<Ui::MainWindow> ui;
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;

37
src/yuzu/migration_worker.cpp

@ -11,11 +11,11 @@
#include "common/fs/path_util.h"
MigrationWorker::MigrationWorker(const Emulator selected_legacy_emu_,
MigrationWorker::MigrationWorker(const Emulator selected_emu_,
const bool clear_shader_cache_,
const MigrationStrategy strategy_)
: QObject()
, selected_legacy_emu(selected_legacy_emu_)
, selected_emu(selected_emu_)
, clear_shader_cache(clear_shader_cache_)
, strategy(strategy_)
{}
@ -25,15 +25,20 @@ void MigrationWorker::process()
namespace fs = std::filesystem;
constexpr auto copy_options = fs::copy_options::update_existing | fs::copy_options::recursive;
const fs::path legacy_user_dir = selected_legacy_emu.get_user_dir();
const fs::path legacy_config_dir = selected_legacy_emu.get_config_dir();
const fs::path legacy_cache_dir = selected_legacy_emu.get_cache_dir();
const fs::path legacy_user_dir = selected_emu.get_user_dir();
const fs::path legacy_config_dir = selected_emu.get_config_dir();
const fs::path legacy_cache_dir = selected_emu.get_cache_dir();
// TODO(crueter): Make these constexpr since they're defaulted
const fs::path eden_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir);
const fs::path config_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir);
const fs::path cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir);
const fs::path shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
fs::path eden_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir);
fs::path config_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir);
fs::path cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir);
fs::path shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
eden_dir.make_preferred();
config_dir.make_preferred();
cache_dir.make_preferred();
shader_dir.make_preferred();
try {
fs::remove_all(eden_dir);
@ -55,8 +60,8 @@ void MigrationWorker::process()
std::exit(-1);
}
// Windows doesn't need any more links, because cache and config
// are already children of the root directory
// Windows doesn't need any more links, because cache and config
// are already children of the root directory
#ifndef WIN32
if (fs::is_directory(legacy_config_dir)) {
Common::FS::CreateSymlink(legacy_config_dir, config_dir);
@ -69,7 +74,7 @@ void MigrationWorker::process()
success_text.append(tr("\n\nNote that your configuration and data will be shared with %1.\n"
"If this is not desirable, delete the following files:\n%2\n%3\n%4")
.arg(selected_legacy_emu.name(),
.arg(selected_emu.name(),
QString::fromStdString(eden_dir.string()),
QString::fromStdString(config_dir.string()),
QString::fromStdString(cache_dir.string())));
@ -79,8 +84,8 @@ void MigrationWorker::process()
// Rename directories if deletion is requested (achieves the same result)
fs::rename(legacy_user_dir, eden_dir);
// Windows doesn't need any more renames, because cache and config
// are already children of the root directory
// Windows doesn't need any more renames, because cache and config
// are already children of the root directory
#ifndef WIN32
if (fs::is_directory(legacy_config_dir)) {
fs::rename(legacy_config_dir, config_dir);
@ -96,8 +101,8 @@ void MigrationWorker::process()
// Default behavior: copy
fs::copy(legacy_user_dir, eden_dir, copy_options);
// Windows doesn't need any more copies, because cache and config
// are already children of the root directory
// Windows doesn't need any more copies, because cache and config
// are already children of the root directory
#ifndef WIN32
if (fs::is_directory(legacy_config_dir)) {
fs::copy(legacy_config_dir, config_dir, copy_options);

22
src/yuzu/migration_worker.h

@ -7,14 +7,12 @@
#include <QObject>
#include "common/fs/path_util.h"
using namespace Common::FS;
typedef struct Emulator {
const char *m_name;
EmuPath e_user_dir;
EmuPath e_config_dir;
EmuPath e_cache_dir;
Common::FS::EmuPath e_user_dir;
Common::FS::EmuPath e_config_dir;
Common::FS::EmuPath e_cache_dir;
const std::string get_user_dir() const {
return Common::FS::GetLegacyPath(e_user_dir).string();
@ -35,11 +33,13 @@ typedef struct Emulator {
}
} Emulator;
#define STRUCT_EMU(name, enumName) Emulator{name, Common::FS::enumName##Dir, Common::FS::enumName##ConfigDir, Common::FS::enumName##CacheDir}
static constexpr std::array<Emulator, 4> legacy_emus = {
Emulator{QT_TR_NOOP("Citron"), CitronDir, CitronConfigDir, CitronCacheDir},
Emulator{QT_TR_NOOP("Sudachi"), SudachiDir, SudachiConfigDir, SudachiCacheDir},
Emulator{QT_TR_NOOP("Suyu"), SuyuDir, SuyuConfigDir, SuyuCacheDir},
Emulator{QT_TR_NOOP("Yuzu"), YuzuDir, YuzuConfigDir, YuzuCacheDir},
STRUCT_EMU(QT_TR_NOOP("Citron"), Citron),
STRUCT_EMU(QT_TR_NOOP("Sudachi"), Sudachi),
STRUCT_EMU(QT_TR_NOOP("Suyu"), Suyu),
STRUCT_EMU(QT_TR_NOOP("Yuzu"), Yuzu),
};
class MigrationWorker : public QObject
@ -52,7 +52,7 @@ public:
Link,
};
MigrationWorker(const Emulator selected_legacy_emu,
MigrationWorker(const Emulator selected_emu,
const bool clear_shader_cache,
const MigrationStrategy strategy);
@ -64,7 +64,7 @@ signals:
void error(const QString &error_message);
private:
Emulator selected_legacy_emu;
Emulator selected_emu;
bool clear_shader_cache;
MigrationStrategy strategy;
QString success_text = tr("Data was migrated successfully.");

2
src/yuzu/multiplayer/direct_connect.cpp

@ -15,7 +15,7 @@
#include "core/internal_network/network_interface.h"
#include "network/network.h"
#include "ui_direct_connect.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
#include "yuzu/multiplayer/client_room.h"
#include "yuzu/multiplayer/direct_connect.h"
#include "yuzu/multiplayer/message.h"

2
src/yuzu/multiplayer/host_room.cpp

@ -20,7 +20,7 @@
#include "network/announce_multiplayer_session.h"
#include "ui_host_room.h"
#include "yuzu/game_list_p.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
#include "yuzu/multiplayer/host_room.h"
#include "yuzu/multiplayer/message.h"
#include "yuzu/multiplayer/state.h"

2
src/yuzu/multiplayer/lobby.cpp

@ -15,7 +15,7 @@
#include "network/network.h"
#include "ui_lobby.h"
#include "yuzu/game_list_p.h"
#include "yuzu/main.h"
#include "yuzu/main_window.h"
#include "yuzu/multiplayer/client_room.h"
#include "yuzu/multiplayer/lobby.h"
#include "yuzu/multiplayer/lobby_p.h"

26
src/yuzu/ryujinx_dialog.cpp

@ -1,25 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "qt_common/abstract/frontend.h"
#include "ryujinx_dialog.h"
#include "qt_common/util/fs.h"
#include "ui_ryujinx_dialog.h"
#include <filesystem>
namespace fs = std::filesystem;
RyujinxDialog::RyujinxDialog(std::filesystem::path eden_path,
std::filesystem::path ryu_path,
QWidget *parent)
: QDialog(parent)
, ui(new Ui::RyujinxDialog)
, m_eden(eden_path.make_preferred())
, m_ryu(ryu_path.make_preferred())
, ui(new Ui::RyujinxDialog)
, m_eden(eden_path.make_preferred())
, m_ryu(ryu_path.make_preferred())
{
ui->setupUi(this);
connect(ui->eden, &QPushButton::clicked, this, &RyujinxDialog::fromEden);
connect(ui->ryujinx, &QPushButton::clicked, this, &RyujinxDialog::fromRyujinx);
connect(ui->cancel, &QPushButton::clicked, this, &RyujinxDialog::reject);
}
RyujinxDialog::~RyujinxDialog()
@ -30,7 +30,21 @@ RyujinxDialog::~RyujinxDialog()
void RyujinxDialog::fromEden()
{
accept();
QtCommon::FS::LinkRyujinx(m_eden, m_ryu);
// Workaround: Ryujinx deletes and re-creates its directory structure???
// So we just copy Eden's data to Ryujinx and then link the other way
namespace fs = std::filesystem;
try {
fs::remove_all(m_ryu);
fs::create_directories(m_ryu);
fs::copy(m_eden, m_ryu, fs::copy_options::recursive);
} catch (std::exception &e) {
QtCommon::Frontend::Critical(tr("Failed to link save data"),
tr("OS returned error: %1").arg(QString::fromStdString(e.what())));
}
// ?ploo
QtCommon::FS::LinkRyujinx(m_ryu, m_eden);
}
void RyujinxDialog::fromRyujinx()

61
src/yuzu/user_data_migration.cpp

@ -23,8 +23,6 @@
#include <QThread>
#include <filesystem>
namespace fs = std::filesystem;
UserDataMigrator::UserDataMigrator(QMainWindow *main_window)
{
// NOTE: Logging is not initialized yet, do not produce logs here.
@ -32,7 +30,7 @@ UserDataMigrator::UserDataMigrator(QMainWindow *main_window)
// Check migration if config directory does not exist
// TODO: ProfileManager messes with us a bit here, and force-creates the /nand/system/save/8000000000000010/su/avators/profiles.dat
// file. Find a way to reorder operations and have it create after this guy runs.
if (!fs::is_directory(Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir))) {
if (!std::filesystem::is_directory(Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir))) {
ShowMigrationPrompt(main_window);
}
}
@ -40,23 +38,7 @@ UserDataMigrator::UserDataMigrator(QMainWindow *main_window)
void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window)
{
namespace fs = std::filesystem;
// define strings here for easy access
QString prompt_prefix_text = QtCommon::StringLookup::Lookup(
QtCommon::StringLookup::MigrationPromptPrefix);
QString migration_prompt_message = QtCommon::StringLookup::Lookup(
QtCommon::StringLookup::MigrationPrompt);
QString clear_shader_tooltip = QtCommon::StringLookup::Lookup(
QtCommon::StringLookup::MigrationTooltipClearShader);
QString keep_old_data_tooltip = QtCommon::StringLookup::Lookup(
QtCommon::StringLookup::MigrationTooltipKeepOld);
QString clear_old_data_tooltip = QtCommon::StringLookup::Lookup(
QtCommon::StringLookup::MigrationTooltipClearOld);
QString link_old_dir_tooltip = QtCommon::StringLookup::Lookup(
QtCommon::StringLookup::MigrationTooltipLinkOld);
// actual migration code
using namespace QtCommon::StringLookup;
MigrationDialog migration_prompt;
migration_prompt.setWindowTitle(QObject::tr("Migration"));
@ -69,11 +51,11 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window)
#define BUTTON(clazz, name, text, tooltip, checkState) \
clazz *name = new clazz(&migration_prompt); \
name->setText(text); \
name->setToolTip(tooltip); \
name->setToolTip(Lookup(tooltip)); \
name->setChecked(checkState); \
migration_prompt.addBox(name);
BUTTON(QCheckBox, clear_shaders, QObject::tr("Clear Shader Cache"), clear_shader_tooltip, true)
BUTTON(QCheckBox, clear_shaders, QObject::tr("Clear Shader Cache"), MigrationTooltipClearShader, true)
u32 id = 0;
@ -81,9 +63,9 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window)
BUTTON(QRadioButton, name, text, tooltip, checkState) \
group->addButton(name, ++id);
RADIO(keep_old, QObject::tr("Keep Old Data"), keep_old_data_tooltip, true)
RADIO(clear_old, QObject::tr("Clear Old Data"), clear_old_data_tooltip, false)
RADIO(link_old, QObject::tr("Link Old Directory"), link_old_dir_tooltip, false)
RADIO(keep_old, QObject::tr("Keep Old Data"), MigrationTooltipKeepOld, true)
RADIO(clear_old, QObject::tr("Clear Old Data"), MigrationTooltipClearOld, false)
RADIO(link_old, QObject::tr("Link Old Directory"), MigrationTooltipLinkOld, false)
#undef RADIO
#undef BUTTON
@ -101,7 +83,7 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window)
// makes my life easier
qRegisterMetaType<Emulator>();
QString prompt_text = prompt_prefix_text;
QString prompt_text = Lookup(MigrationPromptPrefix);
// natural language processing is a nightmare
for (const Emulator &emu : found) {
@ -114,7 +96,7 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window)
}
prompt_text.append(QObject::tr("\n\n"));
prompt_text = prompt_text % QStringLiteral("\n\n") % migration_prompt_message;
prompt_text = prompt_text % QStringLiteral("\n\n") % Lookup(MigrationPrompt);
migration_prompt.setText(prompt_text);
migration_prompt.addButton(QObject::tr("No"), true);
@ -127,24 +109,12 @@ void UserDataMigrator::ShowMigrationPrompt(QMainWindow *main_window)
return ShowMigrationCancelledMessage(main_window);
}
MigrationWorker::MigrationStrategy strategy;
switch (group->checkedId()) {
default:
[[fallthrough]];
case 1:
strategy = MigrationWorker::MigrationStrategy::Copy;
break;
case 2:
strategy = MigrationWorker::MigrationStrategy::Move;
break;
case 3:
strategy = MigrationWorker::MigrationStrategy::Link;
break;
}
MigrationWorker::MigrationStrategy strategy = static_cast<MigrationWorker::MigrationStrategy>(
group->checkedId());
selected_emu = button->property("emulator").value<Emulator>();
MigrateUserData(main_window,
button->property("emulator").value<Emulator>(),
clear_shaders->isChecked(),
strategy);
}
@ -161,12 +131,9 @@ void UserDataMigrator::ShowMigrationCancelledMessage(QMainWindow *main_window)
}
void UserDataMigrator::MigrateUserData(QMainWindow *main_window,
const Emulator selected_legacy_emu,
const bool clear_shader_cache,
const MigrationWorker::MigrationStrategy strategy)
{
selected_emu = selected_legacy_emu;
// Create a dialog to let the user know it's migrating
QProgressDialog *progress = new QProgressDialog(main_window);
progress->setWindowTitle(QObject::tr("Migrating"));
@ -176,7 +143,7 @@ void UserDataMigrator::MigrateUserData(QMainWindow *main_window,
progress->setWindowModality(Qt::WindowModality::ApplicationModal);
QThread *thread = new QThread(main_window);
MigrationWorker *worker = new MigrationWorker(selected_legacy_emu, clear_shader_cache, strategy);
MigrationWorker *worker = new MigrationWorker(selected_emu, clear_shader_cache, strategy);
worker->moveToThread(thread);
thread->connect(thread, &QThread::started, worker, &MigrationWorker::process);

1
src/yuzu/user_data_migration.h

@ -22,7 +22,6 @@ private:
void ShowMigrationPrompt(QMainWindow* main_window);
void ShowMigrationCancelledMessage(QMainWindow* main_window);
void MigrateUserData(QMainWindow* main_window,
const Emulator selected_legacy_emu,
const bool clear_shader_cache,
const MigrationWorker::MigrationStrategy strategy);
};

50
src/yuzu/util/util.cpp

@ -8,7 +8,11 @@
#include <cmath>
#include <QPainter>
#include "applets/qt_profile_select.h"
#include "common/logging/log.h"
#include "core/frontend/applets/profile_select.h"
#include "core/hle/service/acc/profile_manager.h"
#include "qt_common/qt_common.h"
#include "yuzu/util/util.h"
#ifdef _WIN32
@ -153,3 +157,49 @@ bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image)
return false;
#endif
}
const std::optional<Common::UUID> GetProfileID() {
// if there's only a single profile, the user probably wants to use that... right?
const auto& profiles = QtCommon::system->GetProfileManager().FindExistingProfileUUIDs();
if (profiles.size() == 1) {
return profiles[0];
}
const auto select_profile = [] {
const Core::Frontend::ProfileSelectParameters parameters{
.mode = Service::AM::Frontend::UiMode::UserSelector,
.invalid_uid_list = {},
.display_options = {},
.purpose = Service::AM::Frontend::UserSelectionPurpose::General,
};
QtProfileSelectionDialog dialog(*QtCommon::system, QtCommon::rootObject, parameters);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Rejected) {
return -1;
}
return dialog.GetIndex();
};
const auto index = select_profile();
if (index == -1) {
return std::nullopt;
}
const auto uuid =
QtCommon::system->GetProfileManager().GetUser(static_cast<std::size_t>(index));
ASSERT(uuid);
return uuid;
}
std::string GetProfileIDString() {
const auto uuid = GetProfileID();
if (!uuid)
return "";
auto user_id = uuid->AsU128();
return fmt::format("{:016X}{:016X}", user_id[1], user_id[0]);
}

16
src/yuzu/util/util.h

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -6,6 +9,7 @@
#include <filesystem>
#include <QFont>
#include <QString>
#include "common/uuid.h"
/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
[[nodiscard]] QFont GetMonospaceFont();
@ -27,3 +31,15 @@
* @return bool If the operation succeeded
*/
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
/**
* Prompt the user for a profile ID. If there is only one valid profile, returns that profile.
* @return The selected profile, or an std::nullopt if none were selected
*/
const std::optional<Common::UUID> GetProfileID();
/**
* Prompt the user for a profile ID. If there is only one valid profile, returns that profile.
* @return A string representation of the selected profile, or an empty string if none were seleeced
*/
std::string GetProfileIDString();
Loading…
Cancel
Save