From e93159b0470f79c8f09fc3de1eb3c3e0b8a61992 Mon Sep 17 00:00:00 2001 From: crueter Date: Thu, 30 Oct 2025 11:03:08 +0100 Subject: [PATCH] [qt] clean up some orphaned_profiles bugs; add help (#2894) Some weird edge cases of "phantom" profiles that are actually needed for... reasons I guess Also, fixed some of the logic w.r.t empty checking, plus added a help page Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2894 Reviewed-by: Lizzie Reviewed-by: Maufeat Reviewed-by: MaranBr --- docs/user/Orphaned.md | 31 ++++++++++ docs/user/README.md | 13 ++-- src/core/hle/service/acc/profile_manager.cpp | 62 +++++++++++++++----- src/core/hle/service/fatal/fatal.cpp | 2 +- src/qt_common/discord/discord_impl.cpp | 3 - src/qt_common/util/content.cpp | 27 +++++---- 6 files changed, 100 insertions(+), 38 deletions(-) create mode 100644 docs/user/Orphaned.md diff --git a/docs/user/Orphaned.md b/docs/user/Orphaned.md new file mode 100644 index 0000000000..f9d9d344ad --- /dev/null +++ b/docs/user/Orphaned.md @@ -0,0 +1,31 @@ +# Orphaned Profiles + +A bug present in earlier versions of Eden and Yuzu caused some profiles to be read from the incorrect location if your NAND directory was set to anything other than the default. This bug was fixed in Eden v0.0.4-rc1, but it can be destructive if you're not careful. + +## What are they? + +Orphaned profiles refer to emulated user profiles that may or may not contain valid save data, but are not referenced by the internal profile map. This means the save data is effectively inaccessible, and should be fixed in order to access your save data. + +## How do I fix it? + +There are lots of different cases of varying complexity. + +Remember to ALWAYS back up your saves! + +### Simple Copy + +Sometimes, a simple copying is all you need. For example, if the orphaned profile folder contains game saves, BUT the good profile is completely empty, then you can simply remove the empty folder and rename the orphaned profile to the same name as the good one. + +### Combination + +In more extreme cases, game saves can be strewn all throughout different profiles. In this case, you must look at each profile individually. + +Typically, one folder will clearly have more recent/numerous save data, in which case you can remove all the other profile folders and follow the same procedure as the simple copy. + +If multiple profile folders contain valid data, the recommended approach is to copy the contents of one folder into the other. There are likely to be file conflicts, the resolution of which is up to you. + +An alternate method for dealing with multiple valid profiles is to go into System -> Profiles, and create a new profile. From there, you can copy the contents of each previously-orphaned profile into a new profile. + +### Edge Cases + +There are way too many edge cases to cover here, but in general, make backups! You can never go wrong if you always have a backup of your saves. \ No newline at end of file diff --git a/docs/user/README.md b/docs/user/README.md index 7928a90615..64895f4aa8 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -4,9 +4,10 @@ The "FAQ". This handbook is primarily aimed at the end-user - baking useful knowledge for enhancing their emulation experience. -- **[The Basics](user/Basics.md)** -- **[Audio](user/Audio.md)** -- **[Graphics](user/Graphics.md)** -- **[Platforms and Architectures](user/Architectures.md)** -- **[Testing](user/Testing.md)** -- **[Data, savefiles and storage](user/Storage.md)** +- **[The Basics](Basics.md)** +- **[Audio](Audio.md)** +- **[Graphics](Graphics.md)** +- **[Platforms and Architectures](Architectures.md)** +- **[Testing](Testing.md)** +- **[Data, savefiles and storage](Storage.md)** +- **[Orphaned Profiles](Orphaned.md)** diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 4f58593288..c0cc2986a7 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -10,16 +10,19 @@ #include #include +#include +#include #include #include "common/fs/file.h" #include "common/fs/fs.h" #include "common/fs/fs_types.h" #include "common/fs/path_util.h" -#include #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 namespace Service::Account { @@ -39,6 +42,7 @@ struct ProfileDataRaw { INSERT_PADDING_BYTES(0x10); std::array users{}; }; + static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size."); // TODO(ogniK): Get actual error codes @@ -490,8 +494,22 @@ void ProfileManager::ResetUserSaveFile() std::vector ProfileManager::FindGoodProfiles() { + namespace fs = std::filesystem; + std::vector good_uuids; + 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"}; + + 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; @@ -506,12 +524,9 @@ std::vector ProfileManager::FindGoodProfiles() return fmt::format("{:016X}{:016X}", user_id[1], user_id[0]); }(); - good_uuids.emplace_back(uuid_string); + if (uuid_string != "0") good_uuids.emplace_back(uuid_string); } - // used for acnh, etc - good_uuids.emplace_back("00000000000000000000000000000000"); - return good_uuids; } @@ -519,6 +534,8 @@ std::vector ProfileManager::FindOrphanedProfiles() { std::vector good_uuids = FindGoodProfiles(); + namespace fs = std::filesystem; + // TODO: fetch save_id programmatically const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "user/save/0000000000000000"; @@ -530,32 +547,47 @@ std::vector ProfileManager::FindOrphanedProfiles() [&good_uuids, &orphaned_profiles](const std::filesystem::directory_entry& entry) -> bool { const std::string uuid = entry.path().stem().string(); + bool override = false; + // 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 { + const auto is_empty = [&entry, &override]() -> bool { try { for (const auto& file : fs::recursive_directory_iterator(entry.path())) { - if (file.is_regular_file()) { - return true; - } + // TODO: .yuzu_save_size is a weird file that gets created by certain games + // I have no idea what its purpose is, but TEMPORARY SOLUTION: just mark the profile as valid if + // this file exists (???) e.g. for SSBU + // In short: if .yuzu_save_size is the ONLY file in a profile it's probably fine to keep + if (file.path().filename().string() == FileSys::GetSaveDataSizeFileName()) + override = true; + + // if there are any regular files (NOT directories) there, do NOT delete it :p + if (file.is_regular_file()) + return false; } } catch (const fs::filesystem_error& e) { // if we get an error--no worries, just pretend it's not empty - return false; + return true; } - return false; + + return true; }(); - if (!is_empty) { + if (is_empty) { fs::remove_all(entry); return true; } + // edge-case: some filesystems forcefully change filenames to lowercase + // so we can just ignore any differences + // looking at you microsoft... ;) + std::string upper_uuid = uuid; + boost::to_upper(upper_uuid); + // 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()) { + if (!override + && std::find(good_uuids.begin(), good_uuids.end(), upper_uuid) == good_uuids.end()) { orphaned_profiles.emplace_back(uuid); } return true; diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index 360abf5da9..52199b0756 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -69,7 +69,7 @@ enum class FatalType : u32 { static void GenerateErrorReport(Core::System& system, Result error_code, const FatalInfo& info) { const auto title_id = system.GetApplicationProcessProgramID(); std::string crash_report = fmt::format( - "Yuzu {}-{} crash report\n" + "Eden {}-{} crash report\n" "Title ID: {:016x}\n" "Result: {:#X} ({:04}-{:04d})\n" "Set flags: 0x{:16X}\n" diff --git a/src/qt_common/discord/discord_impl.cpp b/src/qt_common/discord/discord_impl.cpp index f9ce3caa3f..ad6056be65 100644 --- a/src/qt_common/discord/discord_impl.cpp +++ b/src/qt_common/discord/discord_impl.cpp @@ -13,7 +13,6 @@ #include #include -#include #include "common/common_types.h" #include "common/string_util.h" @@ -88,8 +87,6 @@ void DiscordImpl::UpdateGameStatus(bool use_default) { presence.details = "Currently in game"; presence.startTimestamp = start_time; Discord_UpdatePresence(&presence); - - qDebug() << "game status updated"; } void DiscordImpl::Update() { diff --git a/src/qt_common/util/content.cpp b/src/qt_common/util/content.cpp index df4435f567..c190e11410 100644 --- a/src/qt_common/util/content.cpp +++ b/src/qt_common/util/content.cpp @@ -340,33 +340,34 @@ void FixProfiles() QString qorphaned; // max. of 8 orphaned profiles is fair, I think - // 33 = 32 (UUID) + 1 (\n) - qorphaned.reserve(8 * 33); + // 36 = 32 (UUID) + 4 (
) + qorphaned.reserve(8 * 36); for (const std::string& s : orphaned) { - qorphaned = qorphaned % QStringLiteral("\n") % QString::fromStdString(s); + qorphaned = qorphaned % QStringLiteral("
") % QString::fromStdString(s); } QString qgood; // max. of 8 good profiles is fair, I think - // 33 = 32 (UUID) + 1 (\n) - qgood.reserve(8 * 33); + // 36 = 32 (UUID) + 4 (
) + qgood.reserve(8 * 36); for (const std::string& s : good) { - qgood = qgood % QStringLiteral("\n") % QString::fromStdString(s); + qgood = qgood % QStringLiteral("
") % 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" - "The following profiles are valid:\n" - "%2\n\n" - "Click \"OK\" to open your save folder and fix up your profiles.\n" + tr("UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!
" + "Eden has detected the following save directories with no attached profile:
" + "%1

" + "The following profiles are valid:
" + "%2

" + "Click \"OK\" to open your save folder and fix up your profiles.
" "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.") + "delete all orphaned profiles, and move your copied contents to the good profile.

" + "Still confused? See the help page.
") .arg(qorphaned, qgood)); QtCommon::Game::OpenSaveFolder();