diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 07b14a8ab9..b610792271 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -490,6 +490,32 @@ void ProfileManager::ResetUserSaveFile() ParseUserSaveFile(); } +std::vector ProfileManager::FindExistingProfileUUIDs() +{ + std::vector uuids; + for (const ProfileInfo& p : profiles) { + auto uuid = p.user_uuid; + if (!uuid.IsInvalid()) { + uuids.emplace_back(uuid); + } + } + + return uuids; +} + +std::vector ProfileManager::FindExistingProfileStrings() +{ + std::vector uuids = FindExistingProfileUUIDs(); + std::vector 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 ProfileManager::FindGoodProfiles() { namespace fs = std::filesystem; @@ -499,31 +525,17 @@ std::vector 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 EXCEPTION_UUIDS - = {"5755CC2A545A87128500000000000000", "00000000000000000000000000000000"}; + // some exceptions, e.g. the "system" profile + static constexpr const std::array 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; } diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index 9c91fcde41..4948118b92 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -105,6 +105,8 @@ public: void ResetUserSaveFile(); + std::vector FindExistingProfileUUIDs(); + std::vector FindExistingProfileStrings(); std::vector FindGoodProfiles(); std::vector FindOrphanedProfiles(); diff --git a/src/qt_common/util/fs.cpp b/src/qt_common/util/fs.cpp index c1465bea88..dd105849aa 100644 --- a/src/qt_common/util/fs.cpp +++ b/src/qt_common/util/fs.cpp @@ -140,7 +140,6 @@ const fs::path GetRyujinxSavePath(const fs::path &path_hint, const u64 &program_ 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 = LOOKUP_ENUM(res, KvdbNonexistent); QtCommon::Frontend::Critical(tr("Could not find Ryujinx save data"), caption); } diff --git a/src/qt_common/util/fs.h b/src/qt_common/util/fs.h index 277eab3535..41669e8019 100644 --- a/src/qt_common/util/fs.h +++ b/src/qt_common/util/fs.h @@ -3,6 +3,7 @@ #include "common/common_types.h" #include +#include #pragma once diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 354d18a884..cee3498ea8 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "common/fs/ryujinx_compat.h" -#include "common/fs/symlink.h" #include "main_window.h" #include "network/network.h" #include "qt_common/discord/discord.h" @@ -2371,33 +2370,8 @@ void MainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, if (has_user_save) { // User save data - 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 user_id = - QtCommon::system->GetProfileManager().GetUser(static_cast(index)); - ASSERT(user_id); + const auto user_id = GetProfileID(); + assert(user_id); const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( {}, vfs_nand_dir, FileSys::SaveDataSpaceId::User, FileSys::SaveDataType::Account, @@ -2824,8 +2798,14 @@ void MainWindow::OnGameListOpenPerGameProperties(const std::string& file) { OpenPerGameConfiguration(title_id, file); } -std::string MainWindow::GetProfileID() +const std::optional MainWindow::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 = [this] { const Core::Frontend::ProfileSelectParameters parameters{ .mode = Service::AM::Frontend::UiMode::UserSelector, @@ -2847,13 +2827,21 @@ std::string MainWindow::GetProfileID() const auto index = select_profile(); if (index == -1) { - return ""; + return std::nullopt; } const auto uuid = QtCommon::system->GetProfileManager().GetUser(static_cast(index)); ASSERT(uuid); - const auto user_id = uuid->AsU128(); + return uuid; +} + +std::string MainWindow::GetProfileIDString() +{ + const auto uuid = GetProfileID(); + if (!uuid) return ""; + + auto user_id = uuid->AsU128(); return fmt::format("{:016X}{:016X}", user_id[1], user_id[0]); } @@ -2862,44 +2850,29 @@ void MainWindow::OnLinkToRyujinx(const u64& program_id) { namespace fs = std::filesystem; - const std::string user_id = GetProfileID(); - const std::string hex_program = fmt::format("{:016X}", program_id); + fs::path ryu_dir; - const fs::path eden_dir = FrontendCommon::DataManager::GetDataDir( - FrontendCommon::DataManager::DataDir::Saves, user_id) / - hex_program; + // find an existing Ryujinx linked path in config.ini; if it exists, use it as a "hint" + // If it's not defined in config.ini, use default + const fs::path existing_path = + UISettings::values.ryujinx_link_paths + .value(program_id, QDir(Common::FS::GetLegacyPath(Common::FS::RyujinxDir))) + .filesystemAbsolutePath(); - fs::path ryu_dir; + // this function also prompts the user to manually specify a portable location + ryu_dir = QtCommon::FS::GetRyujinxSavePath(existing_path, program_id); - // filesystem error: read_symlink: Function not implemented - // Theoretically, the check immediately after this should account for it; - // keyword THEORETICALLY -#ifndef __MINGW32__ - // If the Eden directory is a symlink we can just read that and use it as our Ryu dir - if (Common::FS::IsSymlink(eden_dir)) { - ryu_dir = fs::read_symlink(eden_dir); - - // Fallback: if the Eden save dir is symlinked to a nonexistent location, - // just delete and recreate it to remove the symlink. - if (!fs::exists(ryu_dir)) { - fs::remove(eden_dir); - fs::create_directories(eden_dir); - ryu_dir = fs::path{}; - } - } -#endif + if (ryu_dir.empty()) return; - // Otherwise, prompt the user - if (ryu_dir.empty()) { - const fs::path existing_path = - UISettings::values.ryujinx_link_paths - .value(program_id, QDir(Common::FS::GetLegacyPath(Common::FS::RyujinxDir))) - .filesystemAbsolutePath(); + const std::string user_id = GetProfileIDString(); + if (user_id.empty()) return; - ryu_dir = QtCommon::FS::GetRyujinxSavePath(existing_path, program_id); + const std::string hex_program = fmt::format("{:016X}", program_id); + + const fs::path eden_dir = FrontendCommon::DataManager::GetDataDir( + FrontendCommon::DataManager::DataDir::Saves, user_id) / + hex_program; - if (ryu_dir.empty()) return; - } // CheckUnlink basically just checks to see if one or both are linked, and prompts the user to // unlink if this is the case. diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h index fcf3dc052e..a5e23aaa80 100644 --- a/src/yuzu/main_window.h +++ b/src/yuzu/main_window.h @@ -469,7 +469,8 @@ private: QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); - std::string GetProfileID(); + const std::optional GetProfileID(); + std::string GetProfileIDString(); std::unique_ptr ui; diff --git a/src/yuzu/ryujinx_dialog.cpp b/src/yuzu/ryujinx_dialog.cpp index db10c06d93..940b348b68 100644 --- a/src/yuzu/ryujinx_dialog.cpp +++ b/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 -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(e.what())); + } + + // ?ploo + QtCommon::FS::LinkRyujinx(m_ryu, m_eden); } void RyujinxDialog::fromRyujinx()