Compare commits

..

4 commits

Author SHA1 Message Date
Ribbit
c404d3e0bd [vk] Unify RAII in Vulkan
Some checks failed
eden-license / license-header (pull_request) Failing after 22s
2025-10-07 02:44:14 +02:00
badd913bee
[desktop] fix save data location, orphaned profiles finder (#2678)
Previously, if the user had their NAND in a nonstandard location,
profiles.dat would be read from the standard Eden path and thus return
effectively garbage data. What this would result in is:

- The Qt profile manager would be completely nonfunctional
- "Open Save Data Location" would put you into the completely wrong
  place
- Games would read from incorrect locations for their saves

To solve this, I made it so that profiles.dat is re-read *after*
QtConfig initializes. It's not the perfect solution, but it works.

Additionally, this adds an orphaned profiles finder:
- walks through the save folders in nand/user/save/000.../
- for each subdirectory, checks to see if profiles.dat contains a
  corresponding UUID
- If not, the profile is "orphaned". It may contain legit save data, so
  let the user decide how to handle it (famous last words)
- Empty profiles are just removed. If they really matter, they're
  instantly recreated anyways.

The orphaned profiles check runs right *after* the decryption keys
check, but before the game list ever gets populated

Signed-off-by: crueter <crueter@eden-emu.dev>

Reviewed-on: #2678
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2025-10-07 01:32:09 +02:00
6a4fa11ac3
[externals] only build spirv-tools as static (#2680)
Upstream forcefully builds the shared lib and also builds shared and
static separately for... some reason. I made a PR that fixes that, so
for now let's use it here

should speed up build on Android and Windows

Signed-off-by: crueter <crueter@eden-emu.dev>

Reviewed-on: #2680
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2025-10-07 01:31:36 +02:00
5d4cfe195b
[vk] Fix Vulkan Upload & Present Barriers for Spec Compliance (#2681)
The barrier before the CPU-upload copy was using VK_PIPELINE_STAGE_HOST_BIT. Validation rules only allow HOST as the source stage if you’re also specifying host-side access flags; inside a command buffer the GPU isn’t executing “host work,” so pairing that stage with the usual image layout transition is technically invalid.

Switching the source stage to VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT keeps the ordering guarantee we need and satisfies the spec, while the actual host visibility is still handled correctly by the preceding vmaFlushAllocation.

Co-authored-by: Ribbit <ribbit@placeholder.com>
Reviewed-on: #2681
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Co-authored-by: Ribbit <ribbit@eden-emu.dev>
Co-committed-by: Ribbit <ribbit@eden-emu.dev>
2025-10-06 17:39:32 +02:00
16 changed files with 165 additions and 26 deletions

View file

@ -13,8 +13,8 @@ fi
cd src/android
chmod +x ./gradlew
./gradlew assembleRelease
./gradlew bundleRelease
./gradlew assembleMainlineRelease
./gradlew bundleMainlineRelease
if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then
rm "${ANDROID_KEYSTORE_FILE}"

View file

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

View file

@ -117,9 +117,9 @@
},
"spirv-tools": {
"package": "SPIRV-Tools",
"repo": "KhronosGroup/SPIRV-Tools",
"tag": "v%VERSION%",
"hash": "b17940433ced72e004c5eeffd7dd411b6afcc6a52ee31de6427d88edceb8172369be8ec8bf5b65708a78bf41fdae264d554aa7750b2209831679ab36bc867567",
"repo": "crueter/SPIRV-Tools",
"sha": "2fa2d44485",
"hash": "45b198be1d09974ccb2438e8bfa5683f23a0421b058297c28eacfd77e454ec2cf87e77850eddd202efff34b004d8d6b4d12e9615e59bd72be904c196f5eb2169",
"git_version": "2025.4",
"options": [
"SPIRV_SKIP_EXECUTABLES ON"

View file

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

View file

@ -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]);

View file

@ -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)

View file

@ -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{};

View file

@ -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

View file

@ -45,5 +45,8 @@ void InstallKeys();
// Content //
void VerifyGameContents(const std::string &game_path);
void VerifyInstalledContents();
// Profiles //
void FixProfiles();
}
#endif // QT_CONTENT_UTIL_H

View file

@ -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

View file

@ -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,

View file

@ -332,7 +332,7 @@ void Layer::UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t i
write_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
write_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
read_barrier);
cmdbuf.CopyBufferToImage(*buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,

View file

@ -412,7 +412,7 @@ void PresentManager::CopyToSwapchainImpl(Frame* frame) {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
.dstAccessMask = 0,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@ -460,7 +460,7 @@ void PresentManager::CopyToSwapchainImpl(Frame* frame) {
MakeImageCopy(frame->width, frame->height, extent.width, extent.height));
}
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, {},
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, {},
{}, {}, post_barriers);
cmdbuf.End();

View file

@ -1068,7 +1068,7 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, READ_BARRIER, {}, middle_out_barrier);
cmdbuf.CopyBufferToImage(copy_buffer, dst_image, VK_IMAGE_LAYOUT_GENERAL, vk_out_copies);
cmdbuf.CopyBufferToImage(copy_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk_out_copies);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, {}, {}, post_barriers);
});

View file

@ -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();
}

View file

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