Browse Source

[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 <crueter@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2894
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
pull/2898/head
crueter 2 months ago
parent
commit
e93159b047
No known key found for this signature in database GPG Key ID: 425ACD2D4830EBC6
  1. 31
      docs/user/Orphaned.md
  2. 13
      docs/user/README.md
  3. 62
      src/core/hle/service/acc/profile_manager.cpp
  4. 2
      src/core/hle/service/fatal/fatal.cpp
  5. 3
      src/qt_common/discord/discord_impl.cpp
  6. 27
      src/qt_common/util/content.cpp

31
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.

13
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)**

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

@ -10,16 +10,19 @@
#include <iostream>
#include <random>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/find.hpp>
#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"
#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 {
@ -39,6 +42,7 @@ struct ProfileDataRaw {
INSERT_PADDING_BYTES(0x10);
std::array<UserRaw, MAX_USERS> 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<std::string> ProfileManager::FindGoodProfiles()
{
namespace fs = std::filesystem;
std::vector<std::string> 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<const char* const, 2> 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<std::string> 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<std::string> ProfileManager::FindOrphanedProfiles()
{
std::vector<std::string> 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<std::string> 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;

2
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"

3
src/qt_common/discord/discord_impl.cpp

@ -13,7 +13,6 @@
#include <discord_rpc.h>
#include <fmt/format.h>
#include <qdebug.h>
#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() {

27
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 (<br>)
qorphaned.reserve(8 * 36);
for (const std::string& s : orphaned) {
qorphaned = qorphaned % QStringLiteral("\n") % QString::fromStdString(s);
qorphaned = qorphaned % QStringLiteral("<br>") % 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 (<br>)
qgood.reserve(8 * 36);
for (const std::string& s : good) {
qgood = qgood % QStringLiteral("\n") % QString::fromStdString(s);
qgood = qgood % QStringLiteral("<br>") % 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!<br>"
"Eden has detected the following save directories with no attached profile:<br>"
"%1<br><br>"
"The following profiles are valid:<br>"
"%2<br><br>"
"Click \"OK\" to open your save folder and fix up your profiles.<br>"
"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.<br><br>"
"Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br>")
.arg(qorphaned, qgood));
QtCommon::Game::OpenSaveFolder();

Loading…
Cancel
Save