aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/user/Architectures.md2
-rw-r--r--src/common/fs/fs_types.h4
-rw-r--r--src/core/file_sys/savedata_factory.cpp5
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp92
-rw-r--r--src/core/hle/service/acc/profile_manager.h5
-rw-r--r--src/qt_common/qt_content_util.cpp38
-rw-r--r--src/qt_common/qt_content_util.h3
-rw-r--r--src/qt_common/qt_game_util.cpp15
-rw-r--r--src/qt_common/qt_game_util.h3
-rw-r--r--src/yuzu/main.cpp5
-rw-r--r--src/yuzu/migration_worker.cpp1
11 files changed, 156 insertions, 17 deletions
diff --git a/docs/user/Architectures.md b/docs/user/Architectures.md
index 6d60716684..240feb666d 100644
--- a/docs/user/Architectures.md
+++ b/docs/user/Architectures.md
@@ -63,6 +63,8 @@ While all modern Linux distributions are supported (Fedora >40, Ubuntu >24.04, D
Intel and Nvidia GPU support is limited. AMD (RADV) drivers receive first-class testing and are known to provide the most stable Eden experience possible.
+Wayland is not recommended. Testing has shown significantly worse performance on most Wayland compositors compared to X11, alongside mysterious bugs and compatibility errors. For now, set `QT_QPA_PLATFORM=xcb` when running Eden, or pass `-platform xcb` to the launch arguments.
+
## Windows
Windows 10 and 11 are supported. Support for Windows 8.x is unknown, and Windows 7 support is unlikely to ever be added.
diff --git a/src/common/fs/fs_types.h b/src/common/fs/fs_types.h
index 900f85d24e..7b7359fa6f 100644
--- a/src/common/fs/fs_types.h
+++ b/src/common/fs/fs_types.h
@@ -1,8 +1,12 @@
+// 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
#pragma once
+#include <filesystem>
#include <functional>
#include "common/common_funcs.h"
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index edf51e74de..dda8d526d3 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <memory>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -129,10 +128,6 @@ 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/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index a4394046fa..4a892f7c65 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -4,13 +4,17 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
#include <cstring>
+#include <filesystem>
+#include <iostream>
#include <random>
#include <fmt/ranges.h>
#include "common/fs/file.h"
#include "common/fs/fs.h"
+#include "common/fs/fs_types.h"
#include "common/fs/path_util.h"
#include <ranges>
#include "common/settings.h"
@@ -90,6 +94,11 @@ bool ProfileManager::RemoveProfileAtIndex(std::size_t index) {
return true;
}
+void ProfileManager::RemoveAllProfiles()
+{
+ profiles = {};
+}
+
/// Helper function to register a user to the system
Result ProfileManager::AddUser(const ProfileInfo& user) {
if (!AddToProfiles(user)) {
@@ -259,8 +268,9 @@ void ProfileManager::CloseUser(UUID uuid) {
/// Gets all valid user ids on the system
UserIDArray ProfileManager::GetAllUsers() const {
UserIDArray output{};
- std::ranges::transform(profiles, output.begin(),
- [](const ProfileInfo& p) { return p.user_uuid; });
+ std::ranges::transform(profiles, output.begin(), [](const ProfileInfo& p) {
+ return p.user_uuid;
+ });
return output;
}
@@ -387,18 +397,19 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
void ProfileManager::ParseUserSaveFile() {
const auto save_path(FS::GetEdenPath(FS::EdenPath::NANDDir) / ACC_SAVE_AVATORS_BASE_PATH /
"profiles.dat");
+
const FS::IOFile save(save_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile);
if (!save.IsOpen()) {
LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
- "user 'eden' with random UUID.");
+ "user 'Eden' with random UUID.");
return;
}
ProfileDataRaw data;
if (!save.ReadObject(data)) {
LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user "
- "'eden' with random UUID.");
+ "'Eden' with random UUID.");
return;
}
@@ -471,6 +482,79 @@ void ProfileManager::WriteUserSaveFile() {
is_save_needed = false;
}
+void ProfileManager::ResetUserSaveFile()
+{
+ RemoveAllProfiles();
+ ParseUserSaveFile();
+}
+
+std::vector<std::string> ProfileManager::FindOrphanedProfiles()
+{
+ std::vector<std::string> good_uuids;
+
+ 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]);
+ }();
+
+ good_uuids.emplace_back(uuid_string);
+ }
+
+ // TODO: fetch save_id programmatically
+ const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir)
+ / "user/save/0000000000000000";
+
+ std::vector<std::string> orphaned_profiles;
+
+ Common::FS::IterateDirEntries(
+ path,
+ [&good_uuids, &orphaned_profiles](const std::filesystem::directory_entry& entry) -> bool {
+ const std::string uuid = entry.path().stem().string();
+
+ // first off, we should always clear empty profiles
+ // 99% of the time these are useless. If not, they are recreated anyways...
+ namespace fs = std::filesystem;
+
+ const auto is_empty = [&entry]() -> bool {
+ try {
+ for (const auto& file : fs::recursive_directory_iterator(entry.path())) {
+ if (file.is_regular_file()) {
+ return true;
+ }
+ }
+ } catch (const fs::filesystem_error& e) {
+ // if we get an error--no worries, just pretend it's not empty
+ return false;
+ }
+ return false;
+ }();
+
+ if (!is_empty) {
+ fs::remove_all(entry);
+ return true;
+ }
+
+ // if profiles.dat contains the UUID--all good
+ // if not--it's an orphaned profile and should be resolved by the user
+ if (std::find(good_uuids.begin(), good_uuids.end(), uuid) == good_uuids.end()) {
+ orphaned_profiles.emplace_back(uuid);
+ }
+ return true;
+ },
+ Common::FS::DirEntryFilter::Directory);
+
+ return orphaned_profiles;
+}
+
void ProfileManager::SetUserPosition(u64 position, Common::UUID uuid) {
auto idxOpt = GetUserIndex(uuid);
if (!idxOpt)
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index d64e42715c..b164ed011a 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -103,10 +103,15 @@ public:
void WriteUserSaveFile();
+ void ResetUserSaveFile();
+
+ std::vector<std::string> FindOrphanedProfiles();
+
private:
void ParseUserSaveFile();
std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
bool RemoveProfileAtIndex(std::size_t index);
+ void RemoveAllProfiles();
bool is_save_needed{};
std::array<ProfileInfo, MAX_USERS> profiles{};
diff --git a/src/qt_common/qt_content_util.cpp b/src/qt_common/qt_content_util.cpp
index e4625aa423..2f659cf1b2 100644
--- a/src/qt_common/qt_content_util.cpp
+++ b/src/qt_common/qt_content_util.cpp
@@ -1,8 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
+#include "qt_common/qt_game_util.h"
#include "qt_content_util.h"
#include "common/fs/fs.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "frontend_common/content_manager.h"
#include "frontend_common/firmware_manager.h"
#include "qt_common/qt_common.h"
@@ -310,4 +312,40 @@ void VerifyInstalledContents() {
}
}
+void FixProfiles()
+{
+ // Reset user save files after config is initialized and migration is done.
+ // Doing it at init time causes profiles to read from the wrong place entirely if NAND dir is not default
+ // TODO: better solution
+ system->GetProfileManager().ResetUserSaveFile();
+ std::vector<std::string> orphaned = system->GetProfileManager().FindOrphanedProfiles();
+
+ // no orphaned dirs--all good :)
+ if (orphaned.empty())
+ return;
+
+ // otherwise, let the user know
+ QString qorphaned;
+
+ // max. of 8 orphaned profiles is fair, I think
+ // 33 = 32 (UUID) + 1 (\n)
+ qorphaned.reserve(8 * 33);
+
+ for (const std::string& s : orphaned) {
+ qorphaned += "\n" + QString::fromStdString(s);
+ }
+
+ QtCommon::Frontend::Critical(
+ tr("Orphaned Profiles Detected!"),
+ tr("UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!\n"
+ "Eden has detected the following save directories with no attached profile:\n"
+ "%1\n\n"
+ "Click \"OK\" to open your save folder and fix up your profiles.\n"
+ "Hint: copy the contents of the largest or last-modified folder elsewhere, "
+ "delete all orphaned profiles, and move your copied contents to the good profile.")
+ .arg(qorphaned));
+
+ QtCommon::Game::OpenSaveFolder();
+}
+
} // namespace QtCommon::Content
diff --git a/src/qt_common/qt_content_util.h b/src/qt_common/qt_content_util.h
index b572c1c4a3..b95e78c0a0 100644
--- a/src/qt_common/qt_content_util.h
+++ b/src/qt_common/qt_content_util.h
@@ -45,5 +45,8 @@ void InstallKeys();
// Content //
void VerifyGameContents(const std::string &game_path);
void VerifyInstalledContents();
+
+// Profiles //
+void FixProfiles();
}
#endif // QT_CONTENT_UTIL_H
diff --git a/src/qt_common/qt_game_util.cpp b/src/qt_common/qt_game_util.cpp
index 5d0b4d8ae7..ac922ea967 100644
--- a/src/qt_common/qt_game_util.cpp
+++ b/src/qt_common/qt_game_util.cpp
@@ -178,6 +178,12 @@ void OpenNANDFolder()
OpenEdenFolder(Common::FS::EdenPath::NANDDir);
}
+void OpenSaveFolder()
+{
+ const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "user/save/0000000000000000";
+ QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path.string())));
+}
+
void OpenSDMCFolder()
{
OpenEdenFolder(Common::FS::EdenPath::SDMCDir);
@@ -379,21 +385,21 @@ void RemoveCacheStorage(u64 program_id)
}
// Metadata //
-void ResetMetadata()
+void ResetMetadata(bool show_message)
{
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."));
+ if (show_message) 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,
+ if (show_message) QtCommon::Frontend::Information(rootObject,
title,
tr("The operation completed successfully."));
UISettings::values.is_game_list_reload_pending.exchange(true);
} else {
- QtCommon::Frontend::Warning(
+ if (show_message) QtCommon::Frontend::Warning(
rootObject,
title,
tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
@@ -573,5 +579,4 @@ void CreateHomeMenuShortcut(ShortcutTarget target) {
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
index 0a21208659..5c6bb24910 100644
--- a/src/qt_common/qt_game_util.h
+++ b/src/qt_common/qt_game_util.h
@@ -52,6 +52,7 @@ bool MakeShortcutIcoPath(const u64 program_id,
void OpenEdenFolder(const Common::FS::EdenPath &path);
void OpenRootDataFolder();
void OpenNANDFolder();
+void OpenSaveFolder();
void OpenSDMCFolder();
void OpenModFolder();
void OpenLogFolder();
@@ -67,7 +68,7 @@ void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
void RemoveCacheStorage(u64 program_id);
// Metadata //
-void ResetMetadata();
+void ResetMetadata(bool show_message = true);
// Shortcuts //
void CreateShortcut(const std::string& game_path,
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 901a39cc9f..44ed29f141 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -545,6 +545,9 @@ GMainWindow::GMainWindow(bool has_broken_vulkan)
// Gen keys if necessary
OnCheckFirmwareDecryption();
+ // Check for orphaned profiles and reset profile data if necessary
+ QtCommon::Content::FixProfiles();
+
game_list->LoadCompatibilityList();
// force reload on first load to ensure add-ons get updated
game_list->PopulateAsync(UISettings::values.game_dirs);
@@ -3947,7 +3950,7 @@ void GMainWindow::OnToggleStatusBar() {
void GMainWindow::OnGameListRefresh()
{
// Resets metadata cache and reloads
- QtCommon::Game::ResetMetadata();
+ QtCommon::Game::ResetMetadata(false);
game_list->RefreshGameDirectory();
SetFirmwareVersion();
}
diff --git a/src/yuzu/migration_worker.cpp b/src/yuzu/migration_worker.cpp
index 42ec006026..95f205ec0c 100644
--- a/src/yuzu/migration_worker.cpp
+++ b/src/yuzu/migration_worker.cpp
@@ -7,7 +7,6 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <filesystem>
-#include <qdebug.h>
#include "common/fs/path_util.h"