From 71dc7e72c2cf91d379cddc836aadbb9e5b325f51 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 29 Aug 2025 23:48:25 +0000 Subject: [PATCH 01/23] [gamemode] Make available on other platforms Signed-off-by: lizzie --- CMakeLists.txt | 2 +- externals/gamemode/gamemode_client.h | 28 +++++++++++++++- src/common/CMakeLists.txt | 8 ++--- src/common/gamemode.cpp | 50 ++++++++++++++++++++++++++++ src/common/gamemode.h | 17 ++++++++++ src/common/linux/gamemode.cpp | 40 ---------------------- src/common/linux/gamemode.h | 24 ------------- src/yuzu/main.cpp | 18 +++------- src/yuzu_cmd/yuzu.cpp | 16 ++------- 9 files changed, 105 insertions(+), 98 deletions(-) create mode 100644 src/common/gamemode.cpp create mode 100644 src/common/gamemode.h delete mode 100644 src/common/linux/gamemode.cpp delete mode 100644 src/common/linux/gamemode.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fdf8900775..4aafaf2af2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -497,7 +497,7 @@ else() find_package(Catch2 3.0.1 REQUIRED) endif() - if (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR ANDROID) + if (PLATFORM_LINUX OR ANDROID) find_package(gamemode 1.7 MODULE) endif() diff --git a/externals/gamemode/gamemode_client.h b/externals/gamemode/gamemode_client.h index b9f64fe460..bfbf61c0c2 100644 --- a/externals/gamemode/gamemode_client.h +++ b/externals/gamemode/gamemode_client.h @@ -1,6 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* -Copyright (c) 2017-2019, Feral Interactive +Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors All rights reserved. Redistribution and use in source and binary forms, with or without @@ -103,6 +106,7 @@ typedef int (*api_call_pid_return_int)(pid_t); static api_call_return_int REAL_internal_gamemode_request_start = NULL; static api_call_return_int REAL_internal_gamemode_request_end = NULL; static api_call_return_int REAL_internal_gamemode_query_status = NULL; +static api_call_return_int REAL_internal_gamemode_request_restart = NULL; static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; @@ -166,6 +170,10 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void) (void **)&REAL_internal_gamemode_query_status, sizeof(REAL_internal_gamemode_query_status), false }, + { "real_gamemode_request_restart", + (void **)&REAL_internal_gamemode_request_restart, + sizeof(REAL_internal_gamemode_request_restart), + false }, { "real_gamemode_error_string", (void **)&REAL_internal_gamemode_error_string, sizeof(REAL_internal_gamemode_error_string), @@ -319,6 +327,24 @@ __attribute__((always_inline)) static inline int gamemode_query_status(void) return REAL_internal_gamemode_query_status(); } +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_request_restart(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_request_restart == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_request_restart missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_request_restart(); +} + /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) { diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 665143900a..3683052c30 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -69,6 +69,8 @@ add_library( fs/fs_util.h fs/path_util.cpp fs/path_util.h + gamemode.cpp + gamemode.h hash.h heap_tracker.cpp heap_tracker.h @@ -187,11 +189,7 @@ if(ANDROID) android/applets/software_keyboard.h) endif() -if(LINUX AND NOT APPLE) - target_sources(common PRIVATE linux/gamemode.cpp linux/gamemode.h) - - target_link_libraries(common PRIVATE gamemode::headers) -endif() +target_link_libraries(common PRIVATE gamemode::headers) if(ARCHITECTURE_x86_64) target_sources( diff --git a/src/common/gamemode.cpp b/src/common/gamemode.cpp new file mode 100644 index 0000000000..a3f0ba37ab --- /dev/null +++ b/src/common/gamemode.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// While technically available on al *NIX platforms, Linux is only available +// as the primary target of libgamemode.so - so warnings are suppressed +#ifdef __unix__ +#include +#endif +#include "common/gamemode.h" +#include "common/logging/log.h" +#include "common/settings.h" + +namespace Common::FeralGamemode { + +void Start() noexcept { + if (Settings::values.enable_gamemode) { +#ifdef __unix__ + if (gamemode_request_start() < 0) { +#ifdef __linux__ + LOG_WARNING(Frontend, "{}", gamemode_error_string()); +#else + LOG_INFO(Frontend, "{}", gamemode_error_string()); +#endif + } else { + LOG_INFO(Frontend, "Done"); + } +#endif + } +} + +void Stop() noexcept { + if (Settings::values.enable_gamemode) { +#ifdef __unix__ + if (gamemode_request_end() < 0) { +#ifdef __linux__ + LOG_WARNING(Frontend, "{}", gamemode_error_string()); +#else + LOG_INFO(Frontend, "{}", gamemode_error_string()); +#endif + } else { + LOG_INFO(Frontend, "Done"); + } +#endif + } +} + +} // namespace Common::Linux diff --git a/src/common/gamemode.h b/src/common/gamemode.h new file mode 100644 index 0000000000..05b1936bb5 --- /dev/null +++ b/src/common/gamemode.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Common::FeralGamemode { + +/// @brief Start the gamemode client +void Start() noexcept; + +/// @brief Stop the gmemode client +void Stop() noexcept; + +} // namespace Common::FeralGamemode diff --git a/src/common/linux/gamemode.cpp b/src/common/linux/gamemode.cpp deleted file mode 100644 index 8d3e2934a6..0000000000 --- a/src/common/linux/gamemode.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "common/linux/gamemode.h" -#include "common/logging/log.h" -#include "common/settings.h" - -namespace Common::Linux { - -void StartGamemode() { - if (Settings::values.enable_gamemode) { - if (gamemode_request_start() < 0) { - LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Started gamemode"); - } - } -} - -void StopGamemode() { - if (Settings::values.enable_gamemode) { - if (gamemode_request_end() < 0) { - LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Stopped gamemode"); - } - } -} - -void SetGamemodeState(bool state) { - if (state) { - StartGamemode(); - } else { - StopGamemode(); - } -} - -} // namespace Common::Linux diff --git a/src/common/linux/gamemode.h b/src/common/linux/gamemode.h deleted file mode 100644 index b80646ae27..0000000000 --- a/src/common/linux/gamemode.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -namespace Common::Linux { - -/** - * Start the (Feral Interactive) Linux gamemode if it is installed and it is activated - */ -void StartGamemode(); - -/** - * Stop the (Feral Interactive) Linux gamemode if it is installed and it is activated - */ -void StopGamemode(); - -/** - * Start or stop the (Feral Interactive) Linux gamemode if it is installed and it is activated - * @param state The new state the gamemode should have - */ -void SetGamemodeState(bool state); - -} // namespace Common::Linux diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e23e9a6a48..ab646eb622 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -21,9 +21,7 @@ #include #include #endif -#ifdef __linux__ -#include "common/linux/gamemode.h" -#endif +#include "common/gamemode.h" #include @@ -2393,9 +2391,7 @@ void GMainWindow::OnEmulationStopped() { discord_rpc->Update(); -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif + Common::FeralGamemode::Stop(); // The emulation is stopped, so closing the window or not does not matter anymore disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -3578,10 +3574,7 @@ void GMainWindow::OnStartGame() { play_time_manager->Start(); discord_rpc->Update(); - -#ifdef __linux__ - Common::Linux::StartGamemode(); -#endif + Common::FeralGamemode::StartGamemode(); } void GMainWindow::OnRestartGame() { @@ -3602,10 +3595,7 @@ void GMainWindow::OnPauseGame() { play_time_manager->Stop(); UpdateMenuState(); AllowOSSleep(); - -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif + Common::FeralGamemode::Stop(); } void GMainWindow::OnPauseContinueGame() { diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 599582aba9..7400b48e47 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -63,10 +63,7 @@ __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif - -#ifdef __linux__ -#include "common/linux/gamemode.h" -#endif +#include "common/gamemode.h" static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 @@ -435,10 +432,7 @@ int main(int argc, char** argv) { // Just exit right away. exit(0); }); - -#ifdef __linux__ - Common::Linux::StartGamemode(); -#endif + Common::FeralGamemode::StartGamemode(); void(system.Run()); if (system.DebuggerEnabled()) { @@ -450,11 +444,7 @@ int main(int argc, char** argv) { system.DetachDebugger(); void(system.Pause()); system.ShutdownMainProcess(); - -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif - + Common::FeralGamemode::Stop(); detached_tasks.WaitForAllTasks(); return 0; } From 7745832f69bedee8f113e8f019bef86510eaebda Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 00:04:36 +0000 Subject: [PATCH 02/23] [gamemode] make option available on all nixes Signed-off-by: lizzie --- src/yuzu/main.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ab646eb622..083ec18146 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -428,10 +428,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) #ifdef __unix__ SetupSigInterrupts(); #endif - -#ifdef __linux__ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); -#endif UISettings::RestoreWindowState(config); @@ -3890,9 +3887,7 @@ void GMainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); const auto old_language_index = Settings::values.language_index.GetValue(); -#ifdef __linux__ const bool old_gamemode = Settings::values.enable_gamemode.GetValue(); -#endif Settings::SetConfiguringGlobal(true); ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), @@ -3952,11 +3947,9 @@ void GMainWindow::OnConfigure() { if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) { SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); } -#ifdef __linux__ if (Settings::values.enable_gamemode.GetValue() != old_gamemode) { SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); } -#endif if (!multiplayer_state->IsHostingPublicRoom()) { multiplayer_state->UpdateCredentials(); @@ -5616,13 +5609,15 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { discord_rpc->Update(); } -#ifdef __linux__ void GMainWindow::SetGamemodeEnabled(bool state) { if (emulation_running) { - Common::Linux::SetGamemodeState(state); + if (state) { + Common::FeralGamemode::Start(); + } else { + Common::FeralGamemode::Stop(); + } } } -#endif void GMainWindow::changeEvent(QEvent* event) { #ifdef __unix__ @@ -5782,7 +5777,9 @@ int main(int argc, char* argv[]) { // the user folder in the Qt Frontend, we need to cd into that working directory const auto bin_path = Common::FS::GetBundleDirectory() / ".."; chdir(Common::FS::PathToUTF8String(bin_path).c_str()); -#elif defined(__unix__) && !defined(__ANDROID__) +#endif + +#ifdef __unix__ // Set the DISPLAY variable in order to open web browsers // TODO (lat9nq): Find a better solution for AppImages to start external applications if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) { From b4246491a1a180f2f264c078a99ca431fdf5015a Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 08:43:59 +0000 Subject: [PATCH 03/23] [gamemode] extra win/lin/macos fixes Signed-off-by: lizzie --- externals/CMakeLists.txt | 2 +- src/yuzu/main.cpp | 2 +- src/yuzu_cmd/yuzu.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index aba5451b6d..2fba5d3570 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -143,7 +143,7 @@ if (ANDROID AND ARCHITECTURE_arm64) AddJsonPackage(libadrenotools) endif() -if (UNIX AND NOT APPLE AND NOT TARGET gamemode::headers) +if (NOT TARGET gamemode::headers) add_library(gamemode INTERFACE) target_include_directories(gamemode INTERFACE gamemode) add_library(gamemode::headers ALIAS gamemode) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 083ec18146..b9cb3c06b6 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3571,7 +3571,7 @@ void GMainWindow::OnStartGame() { play_time_manager->Start(); discord_rpc->Update(); - Common::FeralGamemode::StartGamemode(); + Common::FeralGamemode::Start(); } void GMainWindow::OnRestartGame() { diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 7400b48e47..62ca70850c 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -432,7 +432,7 @@ int main(int argc, char** argv) { // Just exit right away. exit(0); }); - Common::FeralGamemode::StartGamemode(); + Common::FeralGamemode::Start(); void(system.Run()); if (system.DebuggerEnabled()) { From f54161b6ab55f9d9921f108773d8d62fa0c178c2 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 13:40:57 +0000 Subject: [PATCH 04/23] [gamemode] windows fix Signed-off-by: lizzie --- src/yuzu/main.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 7857788fcf..6be91aeae9 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -327,8 +327,8 @@ private: void SetupSigInterrupts(); static void HandleSigInterrupt(int); void OnSigInterruptNotifierActivated(); - void SetGamemodeEnabled(bool state); #endif + void SetGamemodeEnabled(bool state); Service::AM::FrontendAppletParameters ApplicationAppletParameters(); Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id, From 63b9d2223550936b259463ed330e0b39ccda8124 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 13 Sep 2025 20:48:40 +0000 Subject: [PATCH 05/23] [gamemode] default disable on msvc, move to UI category Signed-off-by: lizzie --- src/common/settings.h | 10 ++++++++-- src/common/settings_common.h | 1 - src/yuzu/configuration/shared_translation.cpp | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/common/settings.h b/src/common/settings.h index 9d448a2b38..779acdeecd 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -645,8 +645,14 @@ struct Values { true, true}; - // Linux - SwitchableSetting enable_gamemode{linkage, true, "enable_gamemode", Category::Linux}; + // Linux/MinGW may support (requires libdl support) + SwitchableSetting enable_gamemode{linkage, +#ifndef _MSC_VER + true, +#else + false, +#endif + "enable_gamemode", Category::UiGeneral}; // Controls InputSetting> players; diff --git a/src/common/settings_common.h b/src/common/settings_common.h index af16ec692b..7902cbf945 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h @@ -44,7 +44,6 @@ enum class Category : u32 { Multiplayer, Services, Paths, - Linux, LibraryApplet, MaxEnum, }; diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index 1137145659..0f84395672 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp @@ -460,7 +460,10 @@ std::unique_ptr InitializeTranslations(QWidget* parent) tr("Whether or not to check for updates upon startup.")); // Linux - INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QString()); + INSERT(UISettings, + enable_gamemode, + tr("Enable Gamemode"), + QString()); // Ui Debugging From f4386423e88ae2c01b4f7afb1ac46deb50d39332 Mon Sep 17 00:00:00 2001 From: crueter Date: Mon, 15 Sep 2025 17:21:18 +0200 Subject: [PATCH 06/23] [qt] refactor: qt_common lib (#94) This is part of a series of PRs made in preparation for the QML rewrite. this PR specifically moves a bunch of utility functions from main.cpp into qt_common, with the biggest benefit being that QML can reuse the exact same code through ctx passthrough. Also, QtCommon::Frontend is an abstraction layer over several previously Widgets-specific stuff like QMessageBox that gets used everywhere. The idea is that once QML is implemented, these functions can have a Quick version implemented for systems that don't work well with Widgets (sun) or for those on Plasma 6+ (reduces memory usage w/o Widgets linkage) although Quick from C++ is actually anal, but whatever. Other than that this should also just kinda reduce the size of main.cpp which is a 6000-line behemoth rn, and clangd straight up gives up with it for me (likely caused by the massive amount of headers, which this DOES reduce). In the future, I probably want to create a common strings lookup table that both Qt and QML can reference--though I'm not sure how much linguist likes that--which should give us a way to keep language consistent (use frozen-map). TODO: Docs for Qt stuff Co-authored-by: MaranBr Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/94 Reviewed-by: MaranBr Reviewed-by: Shinmegumi --- .ci/license-header.sh | 11 +- docs/CPM.md | 2 +- src/CMakeLists.txt | 2 + .../org/yuzu/yuzu_emu/adapters/GameAdapter.kt | 19 - .../features/settings/model/BooleanSetting.kt | 1 - .../features/settings/model/Settings.kt | 1 - .../settings/model/view/SettingsItem.kt | 7 - .../settings/ui/SettingsFragmentPresenter.kt | 1 - .../app/src/main/res/values-ar/strings.xml | 2 - .../app/src/main/res/values-ckb/strings.xml | 2 - .../app/src/main/res/values-cs/strings.xml | 2 - .../app/src/main/res/values-es/strings.xml | 2 - .../app/src/main/res/values-fa/strings.xml | 2 - .../app/src/main/res/values-fr/strings.xml | 2 - .../app/src/main/res/values-he/strings.xml | 2 - .../app/src/main/res/values-hu/strings.xml | 2 - .../app/src/main/res/values-id/strings.xml | 2 - .../app/src/main/res/values-it/strings.xml | 2 - .../app/src/main/res/values-ja/strings.xml | 2 - .../app/src/main/res/values-ko/strings.xml | 2 - .../app/src/main/res/values-nb/strings.xml | 2 - .../app/src/main/res/values-pl/strings.xml | 2 - .../src/main/res/values-pt-rBR/strings.xml | 2 - .../src/main/res/values-pt-rPT/strings.xml | 2 - .../app/src/main/res/values-ru/strings.xml | 2 - .../app/src/main/res/values-sr/strings.xml | 2 - .../app/src/main/res/values-uk/strings.xml | 2 - .../app/src/main/res/values-vi/strings.xml | 2 - .../src/main/res/values-zh-rCN/strings.xml | 2 - .../src/main/res/values-zh-rTW/strings.xml | 2 - .../app/src/main/res/values/strings.xml | 5 - src/common/settings.h | 4 - .../fssystem/fssystem_bucket_tree.cpp | 4 +- .../fssystem_nca_file_system_driver.cpp | 132 +- src/core/file_sys/savedata_factory.cpp | 7 + src/frontend_common/firmware_manager.h | 5 +- src/qt_common/CMakeLists.txt | 47 + .../externals/CMakeLists.txt | 4 + .../externals/cpmfile.json | 7 + src/qt_common/qt_applet_util.cpp | 4 + src/qt_common/qt_applet_util.h | 11 + src/{yuzu => qt_common}/qt_common.cpp | 78 +- src/qt_common/qt_common.h | 44 + .../configuration => qt_common}/qt_config.cpp | 3 + .../configuration => qt_common}/qt_config.h | 3 + src/qt_common/qt_content_util.cpp | 313 ++++ src/qt_common/qt_content_util.h | 49 + src/qt_common/qt_frontend_util.cpp | 35 + src/qt_common/qt_frontend_util.h | 148 ++ src/qt_common/qt_game_util.cpp | 577 ++++++++ src/qt_common/qt_game_util.h | 85 ++ src/qt_common/qt_meta.cpp | 75 + src/qt_common/qt_meta.h | 15 + src/qt_common/qt_path_util.cpp | 28 + src/qt_common/qt_path_util.h | 12 + src/qt_common/qt_progress_dialog.cpp | 4 + src/qt_common/qt_progress_dialog.h | 47 + src/qt_common/qt_rom_util.cpp | 78 + src/qt_common/qt_rom_util.h | 20 + .../shared_translation.cpp | 120 +- .../shared_translation.h | 9 +- src/{yuzu => qt_common}/uisettings.cpp | 2 +- src/{yuzu => qt_common}/uisettings.h | 2 +- src/yuzu/CMakeLists.txt | 17 +- src/yuzu/bootmanager.cpp | 9 +- src/yuzu/configuration/configure_audio.cpp | 7 +- src/yuzu/configuration/configure_cpu.h | 5 +- src/yuzu/configuration/configure_debug.cpp | 2 +- src/yuzu/configuration/configure_dialog.cpp | 5 +- src/yuzu/configuration/configure_dialog.h | 5 +- .../configuration/configure_filesystem.cpp | 24 +- src/yuzu/configuration/configure_general.cpp | 5 +- src/yuzu/configuration/configure_graphics.cpp | 7 +- src/yuzu/configuration/configure_graphics.h | 5 +- .../configure_graphics_advanced.cpp | 5 +- .../configure_graphics_extensions.cpp | 5 +- src/yuzu/configuration/configure_hotkeys.cpp | 5 +- .../configuration/configure_input_per_game.h | 5 +- .../configuration/configure_input_player.cpp | 2 +- src/yuzu/configuration/configure_per_game.cpp | 5 +- src/yuzu/configuration/configure_per_game.h | 7 +- .../configure_per_game_addons.cpp | 5 +- src/yuzu/configuration/configure_ringcon.cpp | 5 +- src/yuzu/configuration/configure_tas.cpp | 5 +- src/yuzu/configuration/configure_ui.cpp | 2 +- src/yuzu/configuration/configure_web.cpp | 2 +- src/yuzu/configuration/input_profiles.h | 5 +- src/yuzu/configuration/shared_widget.cpp | 5 +- src/yuzu/configuration/shared_widget.h | 5 +- src/yuzu/debugger/console.cpp | 5 +- src/yuzu/debugger/wait_tree.cpp | 5 +- src/yuzu/game_list.cpp | 42 +- src/yuzu/game_list.h | 31 +- src/yuzu/game_list_p.h | 5 +- src/yuzu/game_list_worker.cpp | 33 +- src/yuzu/game_list_worker.h | 10 +- src/yuzu/hotkeys.cpp | 5 +- src/yuzu/install_dialog.cpp | 5 +- src/yuzu/main.cpp | 1313 +++-------------- src/yuzu/main.h | 54 +- src/yuzu/multiplayer/direct_connect.cpp | 2 +- src/yuzu/multiplayer/host_room.cpp | 2 +- src/yuzu/multiplayer/lobby.cpp | 2 +- src/yuzu/multiplayer/state.cpp | 5 +- src/yuzu/play_time_manager.cpp | 4 +- src/yuzu/play_time_manager.h | 6 +- src/yuzu/qt_common.h | 15 - src/yuzu/user_data_migration.h | 1 + src/yuzu/vk_device_info.cpp | 5 +- tools/cpm-fetch-all.sh | 2 +- tools/cpm-fetch.sh | 3 +- 111 files changed, 2235 insertions(+), 1544 deletions(-) create mode 100644 src/qt_common/CMakeLists.txt rename src/{yuzu => qt_common}/externals/CMakeLists.txt (86%) rename src/{yuzu => qt_common}/externals/cpmfile.json (55%) create mode 100644 src/qt_common/qt_applet_util.cpp create mode 100644 src/qt_common/qt_applet_util.h rename src/{yuzu => qt_common}/qt_common.cpp (55%) create mode 100644 src/qt_common/qt_common.h rename src/{yuzu/configuration => qt_common}/qt_config.cpp (99%) rename src/{yuzu/configuration => qt_common}/qt_config.h (94%) create mode 100644 src/qt_common/qt_content_util.cpp create mode 100644 src/qt_common/qt_content_util.h create mode 100644 src/qt_common/qt_frontend_util.cpp create mode 100644 src/qt_common/qt_frontend_util.h create mode 100644 src/qt_common/qt_game_util.cpp create mode 100644 src/qt_common/qt_game_util.h create mode 100644 src/qt_common/qt_meta.cpp create mode 100644 src/qt_common/qt_meta.h create mode 100644 src/qt_common/qt_path_util.cpp create mode 100644 src/qt_common/qt_path_util.h create mode 100644 src/qt_common/qt_progress_dialog.cpp create mode 100644 src/qt_common/qt_progress_dialog.h create mode 100644 src/qt_common/qt_rom_util.cpp create mode 100644 src/qt_common/qt_rom_util.h rename src/{yuzu/configuration => qt_common}/shared_translation.cpp (91%) rename src/{yuzu/configuration => qt_common}/shared_translation.h (94%) rename src/{yuzu => qt_common}/uisettings.cpp (99%) rename src/{yuzu => qt_common}/uisettings.h (99%) delete mode 100644 src/yuzu/qt_common.h diff --git a/.ci/license-header.sh b/.ci/license-header.sh index fecffaa7d3..3d4929d1c1 100755 --- a/.ci/license-header.sh +++ b/.ci/license-header.sh @@ -5,10 +5,13 @@ HEADER_HASH="$(cat "$PWD/.ci/license/header-hash.txt")" echo "Getting branch changes" -BRANCH=`git rev-parse --abbrev-ref HEAD` -COMMITS=`git log ${BRANCH} --not master --pretty=format:"%h"` -RANGE="${COMMITS[${#COMMITS[@]}-1]}^..${COMMITS[0]}" -FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r` +# BRANCH=`git rev-parse --abbrev-ref HEAD` +# COMMITS=`git log ${BRANCH} --not master --pretty=format:"%h"` +# RANGE="${COMMITS[${#COMMITS[@]}-1]}^..${COMMITS[0]}" +# FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r` + +BASE=`git merge-base master HEAD` +FILES=`git diff --name-only $BASE` #FILES=$(git diff --name-only master) diff --git a/docs/CPM.md b/docs/CPM.md index 2afcdaf164..bce224da40 100644 --- a/docs/CPM.md +++ b/docs/CPM.md @@ -245,6 +245,6 @@ include(CPMUtil) Currently, `cpm-fetch.sh` defines the following directories for cpmfiles (max depth of 2, so subdirs are caught as well): -`externals src/yuzu src/dynarmic .` +`externals src/qt_common src/dynarmic .` Whenever you add a new cpmfile, update the script accordingly \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eb66e55964..184b049d06 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -234,6 +234,8 @@ if (YUZU_ROOM_STANDALONE) endif() if (ENABLE_QT) + add_definitions(-DYUZU_QT_WIDGETS) + add_subdirectory(qt_common) add_subdirectory(yuzu) endif() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 0a2cb41745..9ea2a9ee17 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -272,8 +272,6 @@ class GameAdapter(private val activity: AppCompatActivity) : binding.root.findNavController().navigate(action) } - val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - if (NativeLibrary.gameRequiresFirmware(game.programId) && !NativeLibrary.isFirmwareAvailable()) { MaterialAlertDialogBuilder(activity) .setTitle(R.string.loader_requires_firmware) @@ -288,23 +286,6 @@ class GameAdapter(private val activity: AppCompatActivity) : } .setNegativeButton(android.R.string.cancel) { _, _ -> } .show() - } else if (BooleanSetting.DISABLE_NCA_VERIFICATION.getBoolean(false) && !preferences.getBoolean( - Settings.PREF_HIDE_NCA_POPUP, false)) { - MaterialAlertDialogBuilder(activity) - .setTitle(R.string.nca_verification_disabled) - .setMessage(activity.getString(R.string.nca_verification_disabled_description)) - .setPositiveButton(android.R.string.ok) { _, _ -> - launch() - } - .setNeutralButton(R.string.dont_show_again) { _, _ -> - preferences.edit { - putBoolean(Settings.PREF_HIDE_NCA_POPUP, true) - } - - launch() - } - .setNegativeButton(android.R.string.cancel) { _, _ -> } - .show() } else { launch() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 6d4bfd97ac..3c5b9003de 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -35,7 +35,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_SAMPLE_SHADING("sample_shading"), PICTURE_IN_PICTURE("picture_in_picture"), USE_CUSTOM_RTC("custom_rtc_enabled"), - DISABLE_NCA_VERIFICATION("disable_nca_verification"), BLACK_BACKGROUNDS("black_backgrounds"), JOYSTICK_REL_CENTER("joystick_rel_center"), DPAD_SLIDE("dpad_slide"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index 2564849ef4..a52f582031 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -37,7 +37,6 @@ object Settings { const val PREF_SHOULD_SHOW_PRE_ALPHA_WARNING = "ShouldShowPreAlphaWarning" const val PREF_SHOULD_SHOW_EDENS_VEIL_DIALOG = "ShouldShowEdensVeilDialog" const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" - const val PREF_HIDE_NCA_POPUP = "HideNCAVerificationPopup" const val SECTION_STATS_OVERLAY = "Stats Overlay" // Deprecated input overlay preference keys diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 883d8efaef..1f2ba81a73 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -297,13 +297,6 @@ abstract class SettingsItem( descriptionId = R.string.use_custom_rtc_description ) ) - put( - SwitchSetting( - BooleanSetting.DISABLE_NCA_VERIFICATION, - titleId = R.string.disable_nca_verification, - descriptionId = R.string.disable_nca_verification_description - ) - ) put( StringInputSetting( StringSetting.WEB_TOKEN, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 630bcb0d74..14d62ceec3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -210,7 +210,6 @@ class SettingsFragmentPresenter( add(IntSetting.LANGUAGE_INDEX.key) add(BooleanSetting.USE_CUSTOM_RTC.key) add(LongSetting.CUSTOM_RTC.key) - add(BooleanSetting.DISABLE_NCA_VERIFICATION.key) add(HeaderSetting(R.string.network)) add(StringSetting.WEB_TOKEN.key) diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml index c705ae08f1..ed3fc76f3b 100644 --- a/src/android/app/src/main/res/values-ar/strings.xml +++ b/src/android/app/src/main/res/values-ar/strings.xml @@ -498,8 +498,6 @@ ساعة مخصصة في الوقت الحقيقي يسمح لك بتعيين ساعة مخصصة في الوقت الفعلي منفصلة عن وقت النظام الحالي لديك تعيين ساعة مخصصة في الوقت الحقيقي - تعطيل التحقق من NCA - يعطل التحقق من سلامة أرشيفات محتوى NCA. قد يحسن هذا من سرعة التحميل لكنه يخاطر بتلف البيانات أو تمرير ملفات غير صالحة دون اكتشاف. ضروري لجعل الألعاب والتحديثات التي تتطلب نظامًا أساسيًا 20+ تعمل. توليد diff --git a/src/android/app/src/main/res/values-ckb/strings.xml b/src/android/app/src/main/res/values-ckb/strings.xml index af0eeeaa45..34b1ae6252 100644 --- a/src/android/app/src/main/res/values-ckb/strings.xml +++ b/src/android/app/src/main/res/values-ckb/strings.xml @@ -482,8 +482,6 @@ RTCی تایبەتمەند ڕێگەت پێدەدات کاتژمێرێکی کاتی ڕاستەقینەی تایبەتمەند دابنێیت کە جیاوازە لە کاتی ئێستای سیستەمەکەت. دانانی RTCی تایبەتمەند - ناچالاککردنی پشکنینی NCA - پشکنینی پێکهاتەی ئارشیڤەکانی ناوەڕۆکی NCA ناچالاک دەکات. ئەمە لەوانەیە خێرایی بارکردن به‌ره‌وپێش ببات، بەڵام مەترسی لەناوچوونی داتا یان ئەوەی فایلە نادروستەکان بەبێ ئەوەی دۆزرایەوە تێپەڕبن زیاتر دەکات. بۆ ئەوەی یاری و نوێکردنەوەکان کار بکەن کە پێویستی بە فریموێری 20+ هەیە زۆر پێویستە. بەرهەم هێنان diff --git a/src/android/app/src/main/res/values-cs/strings.xml b/src/android/app/src/main/res/values-cs/strings.xml index 8d42b8303f..293524271e 100644 --- a/src/android/app/src/main/res/values-cs/strings.xml +++ b/src/android/app/src/main/res/values-cs/strings.xml @@ -458,8 +458,6 @@ Vlastní RTC Vlastní nastavení času Nastavit vlastní RTC - Zakázat ověřování NCA - Zakáže ověřování integrity archivů obsahu NCA. To může zlepšit rychlost načítání, ale hrozí poškození dat nebo neodhalení neplatných souborů. Je nutné, aby fungovaly hry a aktualizace vyžadující firmware 20+. Generovat diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 12b7183cae..8712f455de 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -506,8 +506,6 @@ RTC personalizado Te permite tener un reloj personalizado en tiempo real diferente del tiempo del propio sistema. Configurar RTC personalizado - Desactivar verificación NCA - Desactiva la verificación de integridad de los archivos de contenido NCA. Esto puede mejorar la velocidad de carga, pero arriesga corrupción de datos o que archivos inválidos pasen desapercibidos. Es necesario para que funcionen juegos y actualizaciones que requieren firmware 20+. Generar diff --git a/src/android/app/src/main/res/values-fa/strings.xml b/src/android/app/src/main/res/values-fa/strings.xml index d7ac1b770a..07ff8ff4e0 100644 --- a/src/android/app/src/main/res/values-fa/strings.xml +++ b/src/android/app/src/main/res/values-fa/strings.xml @@ -504,8 +504,6 @@ زمان سفارشی به شما امکان می‌دهد یک ساعت سفارشی جدا از زمان فعلی سیستم خود تنظیم کنید. تنظیم زمان سفارشی - غیرفعال کردن تأیید اعتبار NCA - بررسی صحت آرشیوهای محتوای NCA را غیرفعال می‌کند. این ممکن است سرعت بارگذاری را بهبود بخشد اما خطر خرابی داده یا تشخیص داده نشدن فایل‌های نامعتبر را به همراه دارد. برای کار کردن بازی‌ها و به‌روزرسانی‌هایی که به فرمور ۲۰+ نیاز دارند، ضروری است. تولید diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index 62e67a6fee..2e06ac98e1 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -506,8 +506,6 @@ RTC personnalisé Vous permet de définir une horloge en temps réel personnalisée distincte de l\'heure actuelle de votre système. Définir l\'horloge RTC personnalisée - Désactiver la vérification NCA - Désactive la vérification d\'intégrité des archives de contenu NCA. Cela peut améliorer la vitesse de chargement mais risque une corruption des données ou que des fichiers invalides ne soient pas détectés. Est nécessaire pour faire fonctionner les jeux et mises à jour nécessitant un firmware 20+. Générer diff --git a/src/android/app/src/main/res/values-he/strings.xml b/src/android/app/src/main/res/values-he/strings.xml index ec5b526ce0..c0c835d633 100644 --- a/src/android/app/src/main/res/values-he/strings.xml +++ b/src/android/app/src/main/res/values-he/strings.xml @@ -505,8 +505,6 @@ RTC מותאם אישית מאפשר לך לקבוע שעון זמן אמת נפרד משעון המערכת שלך. קבע RTC מותאם אישית - השבת אימות NCA - משבית את אימות השלמות של ארכיוני התוכן של NCA. זה עשוי לשפר את מהירות הטעינה אך מסתכן בשחיקת נתונים או שמא קבצים לא חוקיים יעברו ללא זיהוי. זה הכרחי כדי לגרום למשחקים ועדכונים הדורשים firmware 20+ לעבוד. יצירה diff --git a/src/android/app/src/main/res/values-hu/strings.xml b/src/android/app/src/main/res/values-hu/strings.xml index b45692f147..46a5ac7cce 100644 --- a/src/android/app/src/main/res/values-hu/strings.xml +++ b/src/android/app/src/main/res/values-hu/strings.xml @@ -501,8 +501,6 @@ Egyéni RTC Megadhatsz egy valós idejű órát, amely eltér a rendszer által használt órától. Egyéni RTC beállítása - NCA ellenőrzés letiltása - Letiltja az NCA tartalomarchívumok integritás-ellenőrzését. Ez javíthatja a betöltési sebességet, de az adatsérülés vagy az érvénytelen fájlok észrevétlen maradásának kockázatával jár. Elengedhetetlen a 20+ firmware-et igénylő játékok és frissítések működtetéséhez. Generálás diff --git a/src/android/app/src/main/res/values-id/strings.xml b/src/android/app/src/main/res/values-id/strings.xml index 1817fc654a..cffb526ad5 100644 --- a/src/android/app/src/main/res/values-id/strings.xml +++ b/src/android/app/src/main/res/values-id/strings.xml @@ -502,8 +502,6 @@ RTC Kustom Memungkinkan Anda untuk mengatur jam waktu nyata kustom yang terpisah dari waktu sistem saat ini Anda. Setel RTC Kustom - Nonaktifkan Verifikasi NCA - Menonaktifkan verifikasi integritas arsip konten NCA. Ini dapat meningkatkan kecepatan pemuatan tetapi berisiko kerusakan data atau file yang tidak valid tidak terdeteksi. Diperlukan untuk membuat game dan pembaruan yang membutuhkan firmware 20+ bekerja. Hasilkan diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index ff3706dd40..cb234cf61e 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml @@ -505,8 +505,6 @@ RTC Personalizzato Ti permette di impostare un orologio in tempo reale personalizzato, completamente separato da quello di sistema. Imposta un orologio in tempo reale personalizzato - Disabilita verifica NCA - Disabilita la verifica dell\'integrità degli archivi di contenuto NCA. Può migliorare la velocità di caricamento ma rischia il danneggiamento dei dati o che file non validi passino inosservati. Necessario per far funzionare giochi e aggiornamenti che richiedono il firmware 20+. Genera diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index a6c1ebf8ab..abedb1e0bc 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml @@ -491,8 +491,6 @@ カスタム RTC 現在のシステム時間とは別に、任意のリアルタイムクロックを設定できます。 カスタムRTCを設定 - NCA検証を無効化 - NCAコンテンツアーカイブの整合性検証を無効にします。読み込み速度が向上する可能性がありますが、データ破損や不正なファイルが検出されないリスクがあります。ファームウェア20以上が必要なゲームや更新を動作させるために必要です。 生成 diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index 01e2c5f4c0..c6d9457744 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml @@ -501,8 +501,6 @@ 사용자 지정 RTC 현재 시스템 시간과 별도로 사용자 지정 RTC를 설정할 수 있습니다. 사용자 지정 RTC 설정 - NCA 검증 비활성화 - NCA 콘텐츠 아카이브의 무결성 검증을 비활성화합니다. 로딩 속도를 향상시킬 수 있지만 데이터 손상이나 유효하지 않은 파일이 미검증될 위험이 있습니다. 펌웨어 20+가 필요한 게임 및 업데이트를 실행하려면 필요합니다. 생성 diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index 90c0dbf05e..3cc4c6d12c 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml @@ -482,8 +482,6 @@ Tilpasset Sannhetstidsklokke Gjør det mulig å stille inn en egendefinert sanntidsklokke separat fra den gjeldende systemtiden. Angi tilpasset RTC - Deaktiver NCA-verifisering - Deaktiverer integritetsverifisering av NCA-innholdsarkiv. Dette kan forbedre lastehastigheten, men medfører risiko for datakorrupsjon eller at ugyldige filer ikke oppdages. Er nødvendig for å få spill og oppdateringer som trenger firmware 20+ til å fungere. Generer diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index 080064edaf..b9858838e8 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -482,8 +482,6 @@ Niestandardowy RTC Ta opcja pozwala na wybranie własnych ustawień czasu używanych w czasie emulacji, innych niż czas systemu Android. Ustaw niestandardowy czas RTC - Wyłącz weryfikację NCA - Wyłącza weryfikację integralności archiwów zawartości NCA. Może to poprawić szybkość ładowania, ale niesie ryzyko uszkodzenia danych lub niezauważenia nieprawidłowych plików. Konieczne, aby działały gry i aktualizacje wymagające firmware\'u 20+. Generuj diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index 3dc1f83e6e..1296fad889 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml @@ -506,8 +506,6 @@ Data e hora personalizadas Permite a você configurar um relógio em tempo real separado do relógio do seu dispositivo. Definir um relógio em tempo real personalizado - Desativar verificação NCA - Desativa a verificação de integridade de arquivos de conteúdo NCA. Pode melhorar a velocidade de carregamento, mas arrisica corromper dados ou que arquivos inválidos passem despercebidos. É necessário para fazer jogos e atualizações que exigem firmware 20+ funcionarem. Gerar diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index feb4950fcb..a166907877 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml @@ -506,8 +506,6 @@ RTC personalizado Permite a você configurar um relógio em tempo real separado do relógio do seu dispositivo. Defina um relógio em tempo real personalizado - Desativar verificação NCA - Desativa a verificação de integridade dos arquivos de conteúdo NCA. Pode melhorar a velocidade de carregamento, mas arrisca a corrupção de dados ou que ficheiros inválidos passem despercebidos. É necessário para que jogos e atualizações que necessitam de firmware 20+ funcionem. Gerar diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index d56c94f10b..dc68c7b817 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -508,8 +508,6 @@ Пользовательский RTC Позволяет установить пользовательские часы реального времени отдельно от текущего системного времени. Установить пользовательский RTC - Отключить проверку NCA - Отключает проверку целостности архивов содержимого NCA. Может улучшить скорость загрузки, но есть риск повреждения данных или того, что недействительные файлы останутся незамеченными. Необходимо для работы игр и обновлений, требующих прошивку 20+. Создать diff --git a/src/android/app/src/main/res/values-sr/strings.xml b/src/android/app/src/main/res/values-sr/strings.xml index 5b444f6187..c547b3f761 100644 --- a/src/android/app/src/main/res/values-sr/strings.xml +++ b/src/android/app/src/main/res/values-sr/strings.xml @@ -457,8 +457,6 @@ Цустом РТЦ Омогућава вам да поставите прилагођени сат у реалном времену одвојено од тренутног времена система. Подесите прилагођени РТЦ - Искључи верификацију НЦА - Искључује верификацију интегритета НЦА архива садржаја. Ово може побољшати брзину учитавања, али ризикује оштећење података или да неважећи фајлови прођу незапажено. Неопходно је да би игре и ажурирања која захтевају firmware 20+ радили. Генериши diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index 4ae61340dc..b48a8a4a58 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -495,8 +495,6 @@ Свій RTC Дозволяє встановити власний час (Real-time clock, або RTC), відмінний від системного. Встановити RTC - Вимкнути перевірку NCA - Вимкає перевірку цілісності архівів вмісту NCA. Може покращити швидкість завантаження, але ризикує пошкодженням даних або тим, що недійсні файли залишаться непоміченими. Необхідно для роботи ігор та оновлень, які вимагають прошивки 20+. Створити diff --git a/src/android/app/src/main/res/values-vi/strings.xml b/src/android/app/src/main/res/values-vi/strings.xml index dd64fbca55..b19d437ceb 100644 --- a/src/android/app/src/main/res/values-vi/strings.xml +++ b/src/android/app/src/main/res/values-vi/strings.xml @@ -482,8 +482,6 @@ RTC tuỳ chỉnh Cho phép bạn thiết lập một đồng hồ thời gian thực tùy chỉnh riêng biệt so với thời gian hệ thống hiện tại. Thiết lập RTC tùy chỉnh - Tắt xác minh NCA - Tắt xác minh tính toàn vẹn của kho lưu trữ nội dung NCA. Có thể cải thiện tốc độ tải nhưng có nguy cơ hỏng dữ liệu hoặc các tệp không hợp lệ không bị phát hiện. Cần thiết để các trò chơi và bản cập nhật yêu cầu firmware 20+ hoạt động. Tạo diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index a12c00063f..95ab14abd0 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -500,8 +500,6 @@ 自定义系统时间 此选项允许您设置与目前系统时间相独立的自定义系统时钟。 设置自定义系统时间 -禁用NCA验证 -禁用NCA内容存档的完整性验证。可能会提高加载速度,但存在数据损坏或无效文件未被检测到的风险。对于需要固件20+的游戏和更新是必需的。 生成 diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index a125553102..8640875f2c 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml @@ -505,8 +505,6 @@ 自訂 RTC 允許您設定與您的目前系統時間相互獨立的自訂即時時鐘。 設定自訂 RTC - 停用NCA驗證 - 停用NCA內容存檔的完整性驗證。可能會提高載入速度,但存在資料損毀或無效檔案未被偵測到的風險。對於需要韌體20+的遊戲和更新是必需的。 生成 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 20c6e85b6c..d99776b440 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -475,8 +475,6 @@ Custom RTC Allows you to set a custom real-time clock separate from your current system time. Set custom RTC - Disable NCA Verification - Disables integrity verification of NCA content archives. This may improve loading speed but risks data corruption or invalid files going undetected. Some games that require firmware versions 20+ may need this as well. Generate @@ -785,9 +783,6 @@ Game Requires Firmware dump and install firmware, or press "OK" to launch anyways.]]> - NCA Verification Disabled - This is required to run new games and updates, but may cause instability or crashes if NCA files are corrupt, modified, or tampered with. If unsure, re-enable verification in Advanced Settings -> System, and use firmware versions of 19.0.1 or below. - Searching for game... Game not found for Title ID: %1$s diff --git a/src/common/settings.h b/src/common/settings.h index 9d448a2b38..fafd765804 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -626,10 +626,6 @@ struct Values { true, true, &rng_seed_enabled}; Setting device_name{ linkage, "Eden", "device_name", Category::System, Specialization::Default, true, true}; - SwitchableSetting disable_nca_verification{linkage, true, "disable_nca_verification", - Category::System, Specialization::Default}; - Setting hide_nca_verification_popup{ - linkage, false, "hide_nca_verification_popup", Category::System, Specialization::Default}; Setting current_user{linkage, 0, "current_user", Category::System}; diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp index 615a624f4f..71ba458cef 100644 --- a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp @@ -238,9 +238,7 @@ void BucketTree::Initialize(size_t node_size, s64 end_offset) { ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); ASSERT(Common::IsPowerOfTwo(node_size)); - if (!Settings::values.disable_nca_verification.GetValue()) { - ASSERT(end_offset > 0); - } + ASSERT(end_offset > 0); ASSERT(!this->IsInitialized()); m_node_size = node_size; diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp index 4cfa5c58f8..37fb71e9e3 100644 --- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -1296,91 +1296,65 @@ Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( ASSERT(base_storage != nullptr); ASSERT(layer_info_offset >= 0); - if (!Settings::values.disable_nca_verification.GetValue()) { - // Define storage types. - using VerificationStorage = HierarchicalIntegrityVerificationStorage; - using StorageInfo = VerificationStorage::HierarchicalStorageInformation; + // Define storage types. + using VerificationStorage = HierarchicalIntegrityVerificationStorage; + using StorageInfo = VerificationStorage::HierarchicalStorageInformation; - // Validate the meta info. - HierarchicalIntegrityVerificationInformation level_hash_info; - std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), - sizeof(level_hash_info)); + // Validate the meta info. + HierarchicalIntegrityVerificationInformation level_hash_info; + std::memcpy(std::addressof(level_hash_info), + std::addressof(meta_info.level_hash_info), + sizeof(level_hash_info)); - R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - // Get the base storage size. - s64 base_storage_size = base_storage->GetSize(); + // Get the base storage size. + s64 base_storage_size = base_storage->GetSize(); - // Create storage info. - StorageInfo storage_info; - for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) { - const auto& layer_info = level_hash_info.info[i]; - R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, - ResultNcaBaseStorageOutOfRangeD); - - storage_info[i + 1] = std::make_shared( - base_storage, layer_info.size, layer_info_offset + layer_info.offset); - } - - // Set the last layer info. - const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; - const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); - R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, + // Create storage info. + StorageInfo storage_info; + for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) { + const auto& layer_info = level_hash_info.info[i]; + R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, ResultNcaBaseStorageOutOfRangeD); - if (layer_info_offset > 0) { - R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, - ResultRomNcaInvalidIntegrityLayerInfoOffset); - } - storage_info[level_hash_info.max_layers - 1] = std::make_shared( - std::move(base_storage), layer_info.size, last_layer_info_offset); - // Make the integrity romfs storage. - auto integrity_storage = std::make_shared(); - R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); - - // Initialize the integrity storage. - R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, - max_data_cache_entries, max_hash_cache_entries, - buffer_level)); - - // Set the output. - *out = std::move(integrity_storage); - R_SUCCEED(); - } else { - // Read IVFC layout - HierarchicalIntegrityVerificationInformation lhi{}; - std::memcpy(std::addressof(lhi), std::addressof(meta_info.level_hash_info), sizeof(lhi)); - - R_UNLESS(IntegrityMinLayerCount <= lhi.max_layers, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - R_UNLESS(lhi.max_layers <= IntegrityMaxLayerCount, - ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); - - const auto& data_li = lhi.info[lhi.max_layers - 2]; - - const s64 base_size = base_storage->GetSize(); - - // Compute the data layer window - const s64 data_off = (layer_info_offset > 0) ? 0LL : data_li.offset.Get(); - R_UNLESS(data_off + data_li.size <= base_size, ResultNcaBaseStorageOutOfRangeD); - if (layer_info_offset > 0) { - R_UNLESS(data_off + data_li.size <= layer_info_offset, - ResultRomNcaInvalidIntegrityLayerInfoOffset); - } - - // TODO: Passthrough (temporary compatibility: integrity disabled) - auto data_view = std::make_shared(base_storage, data_li.size, data_off); - R_UNLESS(data_view != nullptr, ResultAllocationMemoryFailedAllocateShared); - - auto passthrough = std::make_shared(std::move(data_view)); - R_UNLESS(passthrough != nullptr, ResultAllocationMemoryFailedAllocateShared); - - *out = std::move(passthrough); - R_SUCCEED(); + storage_info[i + 1] = std::make_shared(base_storage, + layer_info.size, + layer_info_offset + layer_info.offset); } + + // Set the last layer info. + const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; + const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); + R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, + ResultNcaBaseStorageOutOfRangeD); + if (layer_info_offset > 0) { + R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, + ResultRomNcaInvalidIntegrityLayerInfoOffset); + } + storage_info[level_hash_info.max_layers - 1] + = std::make_shared(std::move(base_storage), + layer_info.size, + last_layer_info_offset); + + // Make the integrity romfs storage. + auto integrity_storage = std::make_shared(); + R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the integrity storage. + R_TRY(integrity_storage->Initialize(level_hash_info, + meta_info.master_hash, + storage_info, + max_data_cache_entries, + max_hash_cache_entries, + buffer_level)); + + // Set the output. + *out = std::move(integrity_storage); + R_SUCCEED(); } Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 106922e04f..edf51e74de 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -126,6 +129,10 @@ 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/frontend_common/firmware_manager.h b/src/frontend_common/firmware_manager.h index 20f3b41478..23fce59eb3 100644 --- a/src/frontend_common/firmware_manager.h +++ b/src/frontend_common/firmware_manager.h @@ -7,6 +7,7 @@ #include "common/common_types.h" #include "core/core.h" #include "core/file_sys/nca_metadata.h" +#include "core/file_sys/content_archive.h" #include "core/file_sys/registered_cache.h" #include "core/hle/service/filesystem/filesystem.h" #include @@ -143,6 +144,8 @@ inline std::pair GetFirmwareVersion return {firmware_data, result}; } + +// TODO(crueter): GET AS STRING } -#endif // FIRMWARE_MANAGER_H +#endif diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt new file mode 100644 index 0000000000..9d292da401 --- /dev/null +++ b/src/qt_common/CMakeLists.txt @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +# SPDX-FileCopyrightText: 2023 yuzu Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +find_package(Qt6 REQUIRED COMPONENTS Core) +find_package(Qt6 REQUIRED COMPONENTS Core) + +add_library(qt_common STATIC + qt_common.h + qt_common.cpp + + uisettings.cpp + uisettings.h + + qt_config.cpp + qt_config.h + + shared_translation.cpp + shared_translation.h + qt_path_util.h qt_path_util.cpp + qt_game_util.h qt_game_util.cpp + qt_frontend_util.h qt_frontend_util.cpp + qt_meta.h qt_meta.cpp + qt_content_util.h qt_content_util.cpp + qt_rom_util.h qt_rom_util.cpp + qt_applet_util.h qt_applet_util.cpp + qt_progress_dialog.h qt_progress_dialog.cpp + +) + +create_target_directory_groups(qt_common) + +# TODO(crueter) +if (ENABLE_QT) + target_link_libraries(qt_common PRIVATE Qt6::Widgets) +endif() + +add_subdirectory(externals) + +target_link_libraries(qt_common PRIVATE core Qt6::Core SimpleIni::SimpleIni QuaZip::QuaZip frozen::frozen) +target_link_libraries(qt_common PRIVATE Qt6::Core) + +if (NOT WIN32) + target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) +endif() diff --git a/src/yuzu/externals/CMakeLists.txt b/src/qt_common/externals/CMakeLists.txt similarity index 86% rename from src/yuzu/externals/CMakeLists.txt rename to src/qt_common/externals/CMakeLists.txt index 50594a741f..189a52c0a6 100644 --- a/src/yuzu/externals/CMakeLists.txt +++ b/src/qt_common/externals/CMakeLists.txt @@ -14,3 +14,7 @@ set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL ON) # QuaZip AddJsonPackage(quazip) + +# frozen +# TODO(crueter): Qt String Lookup +AddJsonPackage(frozen) diff --git a/src/yuzu/externals/cpmfile.json b/src/qt_common/externals/cpmfile.json similarity index 55% rename from src/yuzu/externals/cpmfile.json rename to src/qt_common/externals/cpmfile.json index e3590d0f7f..0b464b95b2 100644 --- a/src/yuzu/externals/cpmfile.json +++ b/src/qt_common/externals/cpmfile.json @@ -8,5 +8,12 @@ "options": [ "QUAZIP_INSTALL OFF" ] + }, + "frozen": { + "package": "frozen", + "repo": "serge-sans-paille/frozen", + "sha": "61dce5ae18", + "hash": "1ae3d073e659c1f24b2cdd76379c90d6af9e06bc707d285a4fafce05f7a4c9e592ff208c94a9ae0f0d07620b3c6cec191f126b03d70ad4dfa496a86ed5658a6d", + "bundled": true } } diff --git a/src/qt_common/qt_applet_util.cpp b/src/qt_common/qt_applet_util.cpp new file mode 100644 index 0000000000..1b0189392e --- /dev/null +++ b/src/qt_common/qt_applet_util.cpp @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_applet_util.h" diff --git a/src/qt_common/qt_applet_util.h b/src/qt_common/qt_applet_util.h new file mode 100644 index 0000000000..2b48d16698 --- /dev/null +++ b/src/qt_common/qt_applet_util.h @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_APPLET_UTIL_H +#define QT_APPLET_UTIL_H + +// TODO +namespace QtCommon::Applets { + +} +#endif // QT_APPLET_UTIL_H diff --git a/src/yuzu/qt_common.cpp b/src/qt_common/qt_common.cpp similarity index 55% rename from src/yuzu/qt_common.cpp rename to src/qt_common/qt_common.cpp index 413402165c..6be241c740 100644 --- a/src/yuzu/qt_common.cpp +++ b/src/qt_common/qt_common.cpp @@ -1,12 +1,19 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_common.h" +#include "common/fs/fs.h" #include #include -#include #include "common/logging/log.h" #include "core/frontend/emu_window.h" -#include "yuzu/qt_common.h" + +#include + +#include + +#include #if !defined(WIN32) && !defined(__APPLE__) #include @@ -15,7 +22,19 @@ #endif namespace QtCommon { -Core::Frontend::WindowSystemType GetWindowSystemType() { + +#ifdef YUZU_QT_WIDGETS +QWidget* rootObject = nullptr; +#else +QObject* rootObject = nullptr; +#endif + +std::unique_ptr system = nullptr; +std::shared_ptr vfs = nullptr; +std::unique_ptr provider = nullptr; + +Core::Frontend::WindowSystemType GetWindowSystemType() +{ // Determine WSI type based on Qt platform. QString platform_name = QGuiApplication::platformName(); if (platform_name == QStringLiteral("windows")) @@ -35,7 +54,8 @@ Core::Frontend::WindowSystemType GetWindowSystemType() { return Core::Frontend::WindowSystemType::Windows; } // namespace Core::Frontend::WindowSystemType -Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) { +Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) +{ Core::Frontend::EmuWindow::WindowSystemInfo wsi; wsi.type = GetWindowSystemType(); @@ -43,8 +63,8 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) // Our Win32 Qt external doesn't have the private API. wsi.render_surface = reinterpret_cast(window->winId()); #elif defined(__APPLE__) - wsi.render_surface = reinterpret_cast(objc_msgSend)( - reinterpret_cast(window->winId()), sel_registerName("layer")); + wsi.render_surface = reinterpret_cast( + objc_msgSend)(reinterpret_cast(window->winId()), sel_registerName("layer")); #else QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); wsi.display_connection = pni->nativeResourceForWindow("display", window); @@ -57,4 +77,46 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) return wsi; } + +const QString tr(const char* str) +{ + return QGuiApplication::tr(str); +} + +const QString tr(const std::string& str) +{ + return QGuiApplication::tr(str.c_str()); +} + +#ifdef YUZU_QT_WIDGETS +void Init(QWidget* root) +#else +void Init(QObject* root) +#endif +{ + system = std::make_unique(); + rootObject = root; + vfs = std::make_unique(); + provider = std::make_unique(); +} + +std::filesystem::path GetEdenCommand() { + std::filesystem::path command; + + QString appimage = QString::fromLocal8Bit(getenv("APPIMAGE")); + if (!appimage.isEmpty()) { + command = std::filesystem::path{appimage.toStdString()}; + } else { + const QStringList args = QGuiApplication::arguments(); + command = args[0].toStdString(); + } + + // If relative path, make it an absolute path + if (command.c_str()[0] == '.') { + command = Common::FS::GetCurrentDir() / command; + } + + return command; +} + } // namespace QtCommon diff --git a/src/qt_common/qt_common.h b/src/qt_common/qt_common.h new file mode 100644 index 0000000000..a2700427ab --- /dev/null +++ b/src/qt_common/qt_common.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_COMMON_H +#define QT_COMMON_H + +#include +#include "core/core.h" +#include "core/file_sys/registered_cache.h" +#include +#include + +#include + +namespace QtCommon { + +#ifdef YUZU_QT_WIDGETS +extern QWidget *rootObject; +#else +extern QObject *rootObject; +#endif + +extern std::unique_ptr system; +extern std::shared_ptr vfs; +extern std::unique_ptr provider; + +typedef std::function QtProgressCallback; + +Core::Frontend::WindowSystemType GetWindowSystemType(); + +Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow *window); + +#ifdef YUZU_QT_WIDGETS +void Init(QWidget *root); +#else +void Init(QObject *root); +#endif + +const QString tr(const char *str); +const QString tr(const std::string &str); + +std::filesystem::path GetEdenCommand(); +} // namespace QtCommon +#endif diff --git a/src/yuzu/configuration/qt_config.cpp b/src/qt_common/qt_config.cpp similarity index 99% rename from src/yuzu/configuration/qt_config.cpp rename to src/qt_common/qt_config.cpp index ae5b330e23..f787873cf6 100644 --- a/src/yuzu/configuration/qt_config.cpp +++ b/src/qt_common/qt_config.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/yuzu/configuration/qt_config.h b/src/qt_common/qt_config.h similarity index 94% rename from src/yuzu/configuration/qt_config.h rename to src/qt_common/qt_config.h index dc2dceb4d7..a8c80dd273 100644 --- a/src/yuzu/configuration/qt_config.h +++ b/src/qt_common/qt_config.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/qt_common/qt_content_util.cpp b/src/qt_common/qt_content_util.cpp new file mode 100644 index 0000000000..e4625aa423 --- /dev/null +++ b/src/qt_common/qt_content_util.cpp @@ -0,0 +1,313 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_content_util.h" +#include "common/fs/fs.h" +#include "frontend_common/content_manager.h" +#include "frontend_common/firmware_manager.h" +#include "qt_common/qt_common.h" +#include "qt_common/qt_progress_dialog.h" +#include "qt_frontend_util.h" + +#include + +namespace QtCommon::Content { + +bool CheckGameFirmware(u64 program_id, QObject* parent) +{ + if (FirmwareManager::GameRequiresFirmware(program_id) + && !FirmwareManager::CheckFirmwarePresence(*system)) { + auto result = QtCommon::Frontend::ShowMessage( + QMessageBox::Warning, + "Game Requires Firmware", + "The game you are trying to launch requires firmware to boot or to get past the " + "opening menu. Please " + "dump and install firmware, or press \"OK\" to launch anyways.", + QMessageBox::Ok | QMessageBox::Cancel, + parent); + + return result == QMessageBox::Ok; + } + + return true; +} + +void InstallFirmware(const QString& location, bool recursive) +{ + QtCommon::Frontend::QtProgressDialog progress(tr("Installing Firmware..."), + tr("Cancel"), + 0, + 100, + rootObject); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + progress.show(); + + // Declare progress callback. + auto callback = [&](size_t total_size, size_t processed_size) { + progress.setValue(static_cast((processed_size * 100) / total_size)); + return progress.wasCanceled(); + }; + + static constexpr const char* failedTitle = "Firmware Install Failed"; + static constexpr const char* successTitle = "Firmware Install Succeeded"; + QMessageBox::Icon icon; + FirmwareInstallResult result; + + const auto ShowMessage = [&]() { + QtCommon::Frontend::ShowMessage(icon, + failedTitle, + GetFirmwareInstallResultString(result)); + }; + + LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString()); + + // Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in + // there.) + std::filesystem::path firmware_source_path = location.toStdString(); + if (!Common::FS::IsDir(firmware_source_path)) { + return; + } + + std::vector out; + const Common::FS::DirEntryCallable dir_callback = + [&out](const std::filesystem::directory_entry& entry) { + if (entry.path().has_extension() && entry.path().extension() == ".nca") { + out.emplace_back(entry.path()); + } + + return true; + }; + + callback(100, 10); + + if (recursive) { + Common::FS::IterateDirEntriesRecursively(firmware_source_path, + dir_callback, + Common::FS::DirEntryFilter::File); + } else { + Common::FS::IterateDirEntries(firmware_source_path, + dir_callback, + Common::FS::DirEntryFilter::File); + } + + if (out.size() <= 0) { + result = FirmwareInstallResult::NoNCAs; + icon = QMessageBox::Warning; + ShowMessage(); + return; + } + + // Locate and erase the content of nand/system/Content/registered/*.nca, if any. + auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory(); + if (sysnand_content_vdir->IsWritable() + && !sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { + result = FirmwareInstallResult::FailedDelete; + icon = QMessageBox::Critical; + ShowMessage(); + return; + } + + LOG_INFO(Frontend, + "Cleaned nand/system/Content/registered folder in preparation for new firmware."); + + callback(100, 20); + + auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered"); + + bool success = true; + int i = 0; + for (const auto& firmware_src_path : out) { + i++; + auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(), + FileSys::OpenMode::Read); + auto firmware_dst_vfile = firmware_vdir + ->CreateFileRelative(firmware_src_path.filename().string()); + + if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) { + LOG_ERROR(Frontend, + "Failed to copy firmware file {} to {} in registered folder!", + firmware_src_path.generic_string(), + firmware_src_path.filename().string()); + success = false; + } + + if (callback(100, 20 + static_cast(((i) / static_cast(out.size())) * 70.0))) { + result = FirmwareInstallResult::FailedCorrupted; + icon = QMessageBox::Warning; + ShowMessage(); + return; + } + } + + if (!success) { + result = FirmwareInstallResult::FailedCopy; + icon = QMessageBox::Critical; + ShowMessage(); + return; + } + + // Re-scan VFS for the newly placed firmware files. + system->GetFileSystemController().CreateFactories(*vfs); + + auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) { + progress.setValue(90 + static_cast((processed_size * 10) / total_size)); + return progress.wasCanceled(); + }; + + auto results = ContentManager::VerifyInstalledContents(*QtCommon::system, + *QtCommon::provider, + VerifyFirmwareCallback, + true); + + if (results.size() > 0) { + const auto failed_names = QString::fromStdString( + fmt::format("{}", fmt::join(results, "\n"))); + progress.close(); + QtCommon::Frontend::Critical(tr("Firmware integrity verification failed!"), + tr("Verification failed for the following files:\n\n%1") + .arg(failed_names)); + return; + } + + progress.close(); + + const auto pair = FirmwareManager::GetFirmwareVersion(*system); + const auto firmware_data = pair.first; + const std::string display_version(firmware_data.display_version.data()); + + result = FirmwareInstallResult::Success; + QtCommon::Frontend::Information(rootObject, + tr(successTitle), + tr(GetFirmwareInstallResultString(result)) + .arg(QString::fromStdString(display_version))); +} + +QString UnzipFirmwareToTmp(const QString& location) +{ + namespace fs = std::filesystem; + fs::path tmp{fs::temp_directory_path()}; + + if (!fs::create_directories(tmp / "eden" / "firmware")) { + return ""; + } + + tmp /= "eden"; + tmp /= "firmware"; + + QString qCacheDir = QString::fromStdString(tmp.string()); + + QFile zip(location); + + QStringList result = JlCompress::extractDir(&zip, qCacheDir); + if (result.isEmpty()) { + return ""; + } + + return qCacheDir; +} + +// Content // +void VerifyGameContents(const std::string& game_path) +{ + QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), + tr("Cancel"), + 0, + 100, + rootObject); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + + const auto callback = [&](size_t total_size, size_t processed_size) { + progress.setValue(static_cast((processed_size * 100) / total_size)); + return progress.wasCanceled(); + }; + + const auto result = ContentManager::VerifyGameContents(*system, game_path, callback); + + switch (result) { + case ContentManager::GameVerificationResult::Success: + QtCommon::Frontend::Information(rootObject, + tr("Integrity verification succeeded!"), + tr("The operation completed successfully.")); + break; + case ContentManager::GameVerificationResult::Failed: + QtCommon::Frontend::Critical(rootObject, + tr("Integrity verification failed!"), + tr("File contents may be corrupt or missing.")); + break; + case ContentManager::GameVerificationResult::NotImplemented: + QtCommon::Frontend::Warning( + rootObject, + tr("Integrity verification couldn't be performed"), + tr("Firmware installation cancelled, firmware may be in a bad state or corrupted. " + "File contents could not be checked for validity.")); + } +} + +void InstallKeys() +{ + const QString key_source_location + = QtCommon::Frontend::GetOpenFileName(tr("Select Dumped Keys Location"), + {}, + QStringLiteral("Decryption Keys (*.keys)"), + {}, + QtCommon::Frontend::Option::ReadOnly); + + if (key_source_location.isEmpty()) { + return; + } + + FirmwareManager::KeyInstallResult result = FirmwareManager::InstallKeys(key_source_location + .toStdString(), + "keys"); + + system->GetFileSystemController().CreateFactories(*QtCommon::vfs); + + switch (result) { + case FirmwareManager::KeyInstallResult::Success: + QtCommon::Frontend::Information(tr("Decryption Keys install succeeded"), + tr("Decryption Keys were successfully installed")); + break; + default: + QtCommon::Frontend::Critical(tr("Decryption Keys install failed"), + tr(FirmwareManager::GetKeyInstallResultString(result))); + break; + } +} + +void VerifyInstalledContents() { + // Initialize a progress dialog. + QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, QtCommon::rootObject); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + + // Declare progress callback. + auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { + progress.setValue(static_cast((processed_size * 100) / total_size)); + return progress.wasCanceled(); + }; + + const std::vector result = + ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider, QtProgressCallback); + progress.close(); + + if (result.empty()) { + QtCommon::Frontend::Information(tr("Integrity verification succeeded!"), + tr("The operation completed successfully.")); + } else { + const auto failed_names = + QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); + QtCommon::Frontend::Critical( + tr("Integrity verification failed!"), + tr("Verification failed for the following files:\n\n%1").arg(failed_names)); + } +} + +} // namespace QtCommon::Content diff --git a/src/qt_common/qt_content_util.h b/src/qt_common/qt_content_util.h new file mode 100644 index 0000000000..b572c1c4a3 --- /dev/null +++ b/src/qt_common/qt_content_util.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_CONTENT_UTIL_H +#define QT_CONTENT_UTIL_H + +#include +#include "common/common_types.h" + +namespace QtCommon::Content { + +// +bool CheckGameFirmware(u64 program_id, QObject *parent); + +static constexpr std::array FIRMWARE_RESULTS + = {"Successfully installed firmware version %1", + "", + "Unable to locate potential firmware NCA files", + "Failed to delete one or more firmware files.", + "One or more firmware files failed to copy into NAND.", + "Firmware installation cancelled, firmware may be in a bad state or corrupted." + "Restart Eden or re-install firmware."}; + +enum class FirmwareInstallResult { + Success, + NoOp, + NoNCAs, + FailedDelete, + FailedCopy, + FailedCorrupted, +}; + +inline constexpr const char *GetFirmwareInstallResultString(FirmwareInstallResult result) +{ + return FIRMWARE_RESULTS.at(static_cast(result)); +} + +void InstallFirmware(const QString &location, bool recursive); + +QString UnzipFirmwareToTmp(const QString &location); + +// Keys // +void InstallKeys(); + +// Content // +void VerifyGameContents(const std::string &game_path); +void VerifyInstalledContents(); +} +#endif // QT_CONTENT_UTIL_H diff --git a/src/qt_common/qt_frontend_util.cpp b/src/qt_common/qt_frontend_util.cpp new file mode 100644 index 0000000000..d519669ad5 --- /dev/null +++ b/src/qt_common/qt_frontend_util.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_frontend_util.h" +#include "qt_common/qt_common.h" + +#ifdef YUZU_QT_WIDGETS +#include +#endif + +namespace QtCommon::Frontend { + +StandardButton ShowMessage( + Icon icon, const QString &title, const QString &text, StandardButtons buttons, QObject *parent) +{ +#ifdef YUZU_QT_WIDGETS + QMessageBox *box = new QMessageBox(icon, title, text, buttons, (QWidget *) parent); + return static_cast(box->exec()); +#endif + // TODO(crueter): If Qt Widgets is disabled... + // need a way to reference icon/buttons too +} + +const QString GetOpenFileName(const QString &title, + const QString &dir, + const QString &filter, + QString *selectedFilter, + Options options) +{ +#ifdef YUZU_QT_WIDGETS + return QFileDialog::getOpenFileName((QWidget *) rootObject, title, dir, filter, selectedFilter, options); +#endif +} + +} // namespace QtCommon::Frontend diff --git a/src/qt_common/qt_frontend_util.h b/src/qt_common/qt_frontend_util.h new file mode 100644 index 0000000000..f86b9e1357 --- /dev/null +++ b/src/qt_common/qt_frontend_util.h @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_FRONTEND_UTIL_H +#define QT_FRONTEND_UTIL_H + +#include +#include "qt_common/qt_common.h" + +#ifdef YUZU_QT_WIDGETS +#include +#include +#include +#endif + +/** + * manages common functionality e.g. message boxes and such for Qt/QML + */ +namespace QtCommon::Frontend { + +Q_NAMESPACE + +#ifdef YUZU_QT_WIDGETS +using Options = QFileDialog::Options; +using Option = QFileDialog::Option; + +using StandardButton = QMessageBox::StandardButton; +using StandardButtons = QMessageBox::StandardButtons; + +using Icon = QMessageBox::Icon; +#else +enum Option { + ShowDirsOnly = 0x00000001, + DontResolveSymlinks = 0x00000002, + DontConfirmOverwrite = 0x00000004, + DontUseNativeDialog = 0x00000008, + ReadOnly = 0x00000010, + HideNameFilterDetails = 0x00000020, + DontUseCustomDirectoryIcons = 0x00000040 +}; +Q_ENUM_NS(Option) +Q_DECLARE_FLAGS(Options, Option) +Q_FLAG_NS(Options) + +enum StandardButton { + // keep this in sync with QDialogButtonBox::StandardButton and QPlatformDialogHelper::StandardButton + NoButton = 0x00000000, + Ok = 0x00000400, + Save = 0x00000800, + SaveAll = 0x00001000, + Open = 0x00002000, + Yes = 0x00004000, + YesToAll = 0x00008000, + No = 0x00010000, + NoToAll = 0x00020000, + Abort = 0x00040000, + Retry = 0x00080000, + Ignore = 0x00100000, + Close = 0x00200000, + Cancel = 0x00400000, + Discard = 0x00800000, + Help = 0x01000000, + Apply = 0x02000000, + Reset = 0x04000000, + RestoreDefaults = 0x08000000, + + FirstButton = Ok, // internal + LastButton = RestoreDefaults, // internal + + YesAll = YesToAll, // obsolete + NoAll = NoToAll, // obsolete + + Default = 0x00000100, // obsolete + Escape = 0x00000200, // obsolete + FlagMask = 0x00000300, // obsolete + ButtonMask = ~FlagMask // obsolete +}; +Q_ENUM_NS(StandardButton) + +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +typedef StandardButton Button; +#endif +Q_DECLARE_FLAGS(StandardButtons, StandardButton) +Q_FLAG_NS(StandardButtons) + +enum Icon { + // keep this in sync with QMessageDialogOptions::StandardIcon + NoIcon = 0, + Information = 1, + Warning = 2, + Critical = 3, + Question = 4 +}; +Q_ENUM_NS(Icon) + +#endif + +// TODO(crueter) widgets-less impl, choices et al. +StandardButton ShowMessage(Icon icon, + const QString &title, + const QString &text, + StandardButtons buttons = StandardButton::NoButton, + QObject *parent = nullptr); + +#define UTIL_OVERRIDES(level) \ + inline StandardButton level(QObject *parent, \ + const QString &title, \ + const QString &text, \ + StandardButtons buttons = StandardButton::Ok) \ + { \ + return ShowMessage(Icon::level, title, text, buttons, parent); \ + } \ + inline StandardButton level(QObject *parent, \ + const char *title, \ + const char *text, \ + StandardButtons buttons \ + = StandardButton::Ok) \ + { \ + return ShowMessage(Icon::level, tr(title), tr(text), buttons, parent); \ + } \ + inline StandardButton level(const char *title, \ + const char *text, \ + StandardButtons buttons \ + = StandardButton::Ok) \ + { \ + return ShowMessage(Icon::level, tr(title), tr(text), buttons, rootObject); \ + } \ + inline StandardButton level(const QString title, \ + const QString &text, \ + StandardButtons buttons \ + = StandardButton::Ok) \ + { \ + return ShowMessage(Icon::level, title, text, buttons, rootObject); \ + } + +UTIL_OVERRIDES(Information) +UTIL_OVERRIDES(Warning) +UTIL_OVERRIDES(Critical) +UTIL_OVERRIDES(Question) + +const QString GetOpenFileName(const QString &title, + const QString &dir, + const QString &filter, + QString *selectedFilter = nullptr, + Options options = Options()); + +} // namespace QtCommon::Frontend +#endif // QT_FRONTEND_UTIL_H diff --git a/src/qt_common/qt_game_util.cpp b/src/qt_common/qt_game_util.cpp new file mode 100644 index 0000000000..5d0b4d8ae7 --- /dev/null +++ b/src/qt_common/qt_game_util.cpp @@ -0,0 +1,577 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_game_util.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "core/file_sys/savedata_factory.h" +#include "core/hle/service/am/am_types.h" +#include "frontend_common/content_manager.h" +#include "qt_common.h" +#include "qt_common/uisettings.h" +#include "qt_frontend_util.h" +#include "yuzu/util/util.h" + +#include +#include +#include + +#ifdef _WIN32 +#include "common/scope_exit.h" +#include "common/string_util.h" +#include +#include +#else +#include "fmt/ostream.h" +#include +#endif + +namespace QtCommon::Game { + +bool CreateShortcutLink(const std::filesystem::path& shortcut_path, + const std::string& comment, + const std::filesystem::path& icon_path, + const std::filesystem::path& command, + const std::string& arguments, + const std::string& categories, + const std::string& keywords, + const std::string& name) +try { +#ifdef _WIN32 // Windows + HRESULT hr = CoInitialize(nullptr); + if (FAILED(hr)) { + LOG_ERROR(Frontend, "CoInitialize failed"); + return false; + } + SCOPE_EXIT + { + CoUninitialize(); + }; + IShellLinkW* ps1 = nullptr; + IPersistFile* persist_file = nullptr; + SCOPE_EXIT + { + if (persist_file != nullptr) { + persist_file->Release(); + } + if (ps1 != nullptr) { + ps1->Release(); + } + }; + HRESULT hres = CoCreateInstance(CLSID_ShellLink, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IShellLinkW, + reinterpret_cast(&ps1)); + if (FAILED(hres)) { + LOG_ERROR(Frontend, "Failed to create IShellLinkW instance"); + return false; + } + hres = ps1->SetPath(command.c_str()); + if (FAILED(hres)) { + LOG_ERROR(Frontend, "Failed to set path"); + return false; + } + if (!arguments.empty()) { + hres = ps1->SetArguments(Common::UTF8ToUTF16W(arguments).data()); + if (FAILED(hres)) { + LOG_ERROR(Frontend, "Failed to set arguments"); + return false; + } + } + if (!comment.empty()) { + hres = ps1->SetDescription(Common::UTF8ToUTF16W(comment).data()); + if (FAILED(hres)) { + LOG_ERROR(Frontend, "Failed to set description"); + return false; + } + } + if (std::filesystem::is_regular_file(icon_path)) { + hres = ps1->SetIconLocation(icon_path.c_str(), 0); + if (FAILED(hres)) { + LOG_ERROR(Frontend, "Failed to set icon location"); + return false; + } + } + hres = ps1->QueryInterface(IID_IPersistFile, reinterpret_cast(&persist_file)); + if (FAILED(hres)) { + LOG_ERROR(Frontend, "Failed to get IPersistFile interface"); + return false; + } + hres = persist_file->Save(std::filesystem::path{shortcut_path / (name + ".lnk")}.c_str(), TRUE); + if (FAILED(hres)) { + LOG_ERROR(Frontend, "Failed to save shortcut"); + return false; + } + return true; +#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Any desktop NIX + std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop"); + std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc); + if (!shortcut_stream.is_open()) { + LOG_ERROR(Frontend, "Failed to create shortcut"); + return false; + } + // TODO: Migrate fmt::print to std::print in futures STD C++ 23. + fmt::print(shortcut_stream, "[Desktop Entry]\n"); + fmt::print(shortcut_stream, "Type=Application\n"); + fmt::print(shortcut_stream, "Version=1.0\n"); + fmt::print(shortcut_stream, "Name={}\n", name); + if (!comment.empty()) { + fmt::print(shortcut_stream, "Comment={}\n", comment); + } + if (std::filesystem::is_regular_file(icon_path)) { + fmt::print(shortcut_stream, "Icon={}\n", icon_path.string()); + } + fmt::print(shortcut_stream, "TryExec={}\n", command.string()); + fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments); + if (!categories.empty()) { + fmt::print(shortcut_stream, "Categories={}\n", categories); + } + if (!keywords.empty()) { + fmt::print(shortcut_stream, "Keywords={}\n", keywords); + } + return true; +#else // Unsupported platform + return false; +#endif +} catch (const std::exception& e) { + LOG_ERROR(Frontend, "Failed to create shortcut: {}", e.what()); + return false; +} + +bool MakeShortcutIcoPath(const u64 program_id, + const std::string_view game_file_name, + std::filesystem::path& out_icon_path) +{ + // Get path to Yuzu icons directory & icon extension + std::string ico_extension = "png"; +#if defined(_WIN32) + out_icon_path = Common::FS::GetEdenPath(Common::FS::EdenPath::IconsDir); + ico_extension = "ico"; +#elif defined(__linux__) || defined(__FreeBSD__) + out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256"; +#endif + // Create icons directory if it doesn't exist + if (!Common::FS::CreateDirs(out_icon_path)) { + out_icon_path.clear(); + return false; + } + + // Create icon file path + out_icon_path /= (program_id == 0 ? fmt::format("eden-{}.{}", game_file_name, ico_extension) + : fmt::format("eden-{:016X}.{}", program_id, ico_extension)); + return true; +} + +void OpenEdenFolder(const Common::FS::EdenPath& path) +{ + QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(Common::FS::GetEdenPathString(path)))); +} + +void OpenRootDataFolder() +{ + OpenEdenFolder(Common::FS::EdenPath::EdenDir); +} + +void OpenNANDFolder() +{ + OpenEdenFolder(Common::FS::EdenPath::NANDDir); +} + +void OpenSDMCFolder() +{ + OpenEdenFolder(Common::FS::EdenPath::SDMCDir); +} + +void OpenModFolder() +{ + OpenEdenFolder(Common::FS::EdenPath::LoadDir); +} + +void OpenLogFolder() +{ + OpenEdenFolder(Common::FS::EdenPath::LogDir); +} + +static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type) +{ + switch (type) { + case QtCommon::Game::InstalledEntryType::Game: + return tr("Error Removing Contents"); + case QtCommon::Game::InstalledEntryType::Update: + return tr("Error Removing Update"); + case QtCommon::Game::InstalledEntryType::AddOnContent: + return tr("Error Removing DLC"); + default: + return QStringLiteral("Error Removing "); + } +} + +// Game Content // +void RemoveBaseContent(u64 program_id, InstalledEntryType type) +{ + const auto res = ContentManager::RemoveBaseContent(system->GetFileSystemController(), + program_id); + if (res) { + QtCommon::Frontend::Information(rootObject, + "Successfully Removed", + "Successfully removed the installed base game."); + } else { + QtCommon::Frontend::Warning( + rootObject, + GetGameListErrorRemoving(type), + tr("The base game is not installed in the NAND and cannot be removed.")); + } +} + +void RemoveUpdateContent(u64 program_id, InstalledEntryType type) +{ + const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id); + if (res) { + QtCommon::Frontend::Information(rootObject, + "Successfully Removed", + "Successfully removed the installed update."); + } else { + QtCommon::Frontend::Warning(rootObject, + GetGameListErrorRemoving(type), + tr("There is no update installed for this title.")); + } +} + +void RemoveAddOnContent(u64 program_id, InstalledEntryType type) +{ + const size_t count = ContentManager::RemoveAllDLC(*system, program_id); + if (count == 0) { + QtCommon::Frontend::Warning(rootObject, + GetGameListErrorRemoving(type), + tr("There are no DLCs installed for this title.")); + return; + } + + QtCommon::Frontend::Information(rootObject, + tr("Successfully Removed"), + tr("Successfully removed %1 installed DLC.").arg(count)); +} + +// Global Content // + +void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) +{ + const auto target_file_name = [target] { + switch (target) { + case GameListRemoveTarget::GlShaderCache: + return "opengl.bin"; + case GameListRemoveTarget::VkShaderCache: + return "vulkan.bin"; + default: + return ""; + } + }(); + const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); + const auto shader_cache_folder_path = shader_cache_dir / fmt::format("{:016x}", program_id); + const auto target_file = shader_cache_folder_path / target_file_name; + + if (!Common::FS::Exists(target_file)) { + QtCommon::Frontend::Warning(rootObject, + tr("Error Removing Transferable Shader Cache"), + tr("A shader cache for this title does not exist.")); + return; + } + if (Common::FS::RemoveFile(target_file)) { + QtCommon::Frontend::Information(rootObject, + tr("Successfully Removed"), + tr("Successfully removed the transferable shader cache.")); + } else { + QtCommon::Frontend::Warning(rootObject, + tr("Error Removing Transferable Shader Cache"), + tr("Failed to remove the transferable shader cache.")); + } +} + +void RemoveVulkanDriverPipelineCache(u64 program_id) +{ + static constexpr std::string_view target_file_name = "vulkan_pipelines.bin"; + + const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); + const auto shader_cache_folder_path = shader_cache_dir / fmt::format("{:016x}", program_id); + const auto target_file = shader_cache_folder_path / target_file_name; + + if (!Common::FS::Exists(target_file)) { + return; + } + if (!Common::FS::RemoveFile(target_file)) { + QtCommon::Frontend::Warning(rootObject, + tr("Error Removing Vulkan Driver Pipeline Cache"), + tr("Failed to remove the driver pipeline cache.")); + } +} + +void RemoveAllTransferableShaderCaches(u64 program_id) +{ + const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); + const auto program_shader_cache_dir = shader_cache_dir / fmt::format("{:016x}", program_id); + + if (!Common::FS::Exists(program_shader_cache_dir)) { + QtCommon::Frontend::Warning(rootObject, + tr("Error Removing Transferable Shader Caches"), + tr("A shader cache for this title does not exist.")); + return; + } + if (Common::FS::RemoveDirRecursively(program_shader_cache_dir)) { + QtCommon::Frontend::Information(rootObject, + tr("Successfully Removed"), + tr("Successfully removed the transferable shader caches.")); + } else { + QtCommon::Frontend::Warning( + rootObject, + tr("Error Removing Transferable Shader Caches"), + tr("Failed to remove the transferable shader cache directory.")); + } +} + +void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) +{ + const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path)); + const auto config_file_name = program_id == 0 + ? Common::FS::PathToUTF8String(file_path.filename()) + .append(".ini") + : fmt::format("{:016X}.ini", program_id); + const auto custom_config_file_path = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir) + / "custom" / config_file_name; + + if (!Common::FS::Exists(custom_config_file_path)) { + QtCommon::Frontend::Warning(rootObject, + tr("Error Removing Custom Configuration"), + tr("A custom configuration for this title does not exist.")); + return; + } + + if (Common::FS::RemoveFile(custom_config_file_path)) { + QtCommon::Frontend::Information(rootObject, + tr("Successfully Removed"), + tr("Successfully removed the custom game configuration.")); + } else { + QtCommon::Frontend::Warning(rootObject, + tr("Error Removing Custom Configuration"), + tr("Failed to remove the custom game configuration.")); + } +} + +void RemoveCacheStorage(u64 program_id) +{ + const auto nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir); + auto vfs_nand_dir = vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), + FileSys::OpenMode::Read); + + const auto cache_storage_path + = FileSys::SaveDataFactory::GetFullPath({}, + vfs_nand_dir, + FileSys::SaveDataSpaceId::User, + FileSys::SaveDataType::Cache, + 0 /* program_id */, + {}, + 0); + + const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path); + + // Not an error if it wasn't cleared. + Common::FS::RemoveDirRecursively(path); +} + +// Metadata // +void ResetMetadata() +{ + 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.")); + } else if (Common::FS::RemoveDirRecursively( + Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list")) { + QtCommon::Frontend::Information(rootObject, + title, + tr("The operation completed successfully.")); + UISettings::values.is_game_list_reload_pending.exchange(true); + } else { + QtCommon::Frontend::Warning( + rootObject, + title, + tr("The metadata cache couldn't be deleted. It might be in use or non-existent.")); + } +} + +// Uhhh // + +// Messages in pre-defined message boxes for less code spaghetti +inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) +{ + int result = 0; + QMessageBox::StandardButtons buttons; + switch (imsg) { + case ShortcutMessages::Fullscreen: + buttons = QMessageBox::Yes | QMessageBox::No; + result + = QtCommon::Frontend::Information(tr("Create Shortcut"), + tr("Do you want to launch the game in fullscreen?"), + buttons); + return result == QMessageBox::Yes; + case ShortcutMessages::Success: + QtCommon::Frontend::Information(tr("Shortcut Created"), + tr("Successfully created a shortcut to %1").arg(game_title)); + return false; + case ShortcutMessages::Volatile: + buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel; + result = QtCommon::Frontend::Warning( + tr("Shortcut may be Volatile!"), + tr("This will create a shortcut to the current AppImage. This may " + "not work well if you update. Continue?"), + buttons); + return result == QMessageBox::Ok; + default: + buttons = QMessageBox::Ok; + QtCommon::Frontend::Critical(tr("Failed to Create Shortcut"), + tr("Failed to create a shortcut to %1").arg(game_title), + buttons); + return false; + } +} + +void CreateShortcut(const std::string& game_path, + const u64 program_id, + const std::string& game_title_, + const ShortcutTarget &target, + std::string arguments_, + const bool needs_title) +{ + // Get path to Eden executable + std::filesystem::path command = GetEdenCommand(); + + // Shortcut path + std::filesystem::path shortcut_path = GetShortcutPath(target); + + if (!std::filesystem::exists(shortcut_path)) { + CreateShortcutMessagesGUI(ShortcutMessages::Failed, + QString::fromStdString(shortcut_path.generic_string())); + LOG_ERROR(Frontend, "Invalid shortcut target {}", shortcut_path.generic_string()); + return; + } + + const FileSys::PatchManager pm{program_id, QtCommon::system->GetFileSystemController(), + QtCommon::system->GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + const auto loader = + Loader::GetLoader(*QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read)); + + std::string game_title{game_title_}; + + // Delete illegal characters from title + if (needs_title) { + game_title = fmt::format("{:016X}", program_id); + if (control.first != nullptr) { + game_title = control.first->GetApplicationName(); + } else { + loader->ReadTitle(game_title); + } + } + + const std::string illegal_chars = "<>:\"/\\|?*."; + for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) { + if (illegal_chars.find(*it) != std::string::npos) { + game_title.erase(it.base() - 1); + } + } + + const QString qgame_title = QString::fromStdString(game_title); + + // Get icon from game file + std::vector icon_image_file{}; + if (control.second != nullptr) { + icon_image_file = control.second->ReadAllBytes(); + } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) { + LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); + } + + QImage icon_data = + QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size())); + std::filesystem::path out_icon_path; + if (QtCommon::Game::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) { + if (!SaveIconToFile(out_icon_path, icon_data)) { + LOG_ERROR(Frontend, "Could not write icon to file"); + } + } else { + QtCommon::Frontend::Critical( + tr("Create Icon"), + tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.") + .arg(QString::fromStdString(out_icon_path.string()))); + } + +#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) + // Special case for AppImages + // Warn once if we are making a shortcut to a volatile AppImage + if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) { + if (!CreateShortcutMessagesGUI(ShortcutMessages::Volatile, qgame_title)) { + return; + } + UISettings::values.shortcut_already_warned = true; + } +#endif + + // Create shortcut + std::string arguments{arguments_}; + if (CreateShortcutMessagesGUI(ShortcutMessages::Fullscreen, qgame_title)) { + arguments = "-f " + arguments; + } + const std::string comment = fmt::format("Start {:s} with the Eden Emulator", game_title); + const std::string categories = "Game;Emulator;Qt;"; + const std::string keywords = "Switch;Nintendo;"; + + if (QtCommon::Game::CreateShortcutLink(shortcut_path, comment, out_icon_path, command, + arguments, categories, keywords, game_title)) { + CreateShortcutMessagesGUI(ShortcutMessages::Success, + qgame_title); + return; + } + CreateShortcutMessagesGUI(ShortcutMessages::Failed, + qgame_title); +} + +constexpr std::string GetShortcutPath(ShortcutTarget target) { + { + std::string shortcut_path{}; + if (target == ShortcutTarget::Desktop) { + shortcut_path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + .toStdString(); + } else if (target == ShortcutTarget::Applications) { + shortcut_path = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + .toStdString(); + } + + return shortcut_path; + } +} + +void CreateHomeMenuShortcut(ShortcutTarget target) { + constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); + auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + QtCommon::Frontend::Warning(tr("No firmware available"), + tr("Please install firmware to use the home menu.")); + return; + } + + auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); + if (!qlaunch_nca) { + QtCommon::Frontend::Warning(tr("Home Menu Applet"), + tr("Home Menu is not available. Please reinstall firmware.")); + return; + } + + auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); + const auto game_path = qlaunch_applet_nca->GetFullPath(); + + // TODO(crueter): Make this use the Eden icon + 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 new file mode 100644 index 0000000000..0a21208659 --- /dev/null +++ b/src/qt_common/qt_game_util.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_GAME_UTIL_H +#define QT_GAME_UTIL_H + +#include +#include +#include "common/fs/path_util.h" + +namespace QtCommon::Game { + +enum class InstalledEntryType { + Game, + Update, + AddOnContent, +}; + +enum class GameListRemoveTarget { + GlShaderCache, + VkShaderCache, + AllShaderCache, + CustomConfiguration, + CacheStorage, +}; + +enum class ShortcutTarget { + Desktop, + Applications, +}; + +enum class ShortcutMessages{ + Fullscreen = 0, + Success = 1, + Volatile = 2, + Failed = 3 +}; + +bool CreateShortcutLink(const std::filesystem::path& shortcut_path, + const std::string& comment, + const std::filesystem::path& icon_path, + const std::filesystem::path& command, + const std::string& arguments, + const std::string& categories, + const std::string& keywords, + const std::string& name); + +bool MakeShortcutIcoPath(const u64 program_id, + const std::string_view game_file_name, + std::filesystem::path& out_icon_path); + +void OpenEdenFolder(const Common::FS::EdenPath &path); +void OpenRootDataFolder(); +void OpenNANDFolder(); +void OpenSDMCFolder(); +void OpenModFolder(); +void OpenLogFolder(); + +void RemoveBaseContent(u64 program_id, InstalledEntryType type); +void RemoveUpdateContent(u64 program_id, InstalledEntryType type); +void RemoveAddOnContent(u64 program_id, InstalledEntryType type); + +void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target); +void RemoveVulkanDriverPipelineCache(u64 program_id); +void RemoveAllTransferableShaderCaches(u64 program_id); +void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); +void RemoveCacheStorage(u64 program_id); + +// Metadata // +void ResetMetadata(); + +// Shortcuts // +void CreateShortcut(const std::string& game_path, + const u64 program_id, + const std::string& game_title_, + const ShortcutTarget& target, + std::string arguments_, + const bool needs_title); + +constexpr std::string GetShortcutPath(ShortcutTarget target); +void CreateHomeMenuShortcut(ShortcutTarget target); + +} + +#endif // QT_GAME_UTIL_H diff --git a/src/qt_common/qt_meta.cpp b/src/qt_common/qt_meta.cpp new file mode 100644 index 0000000000..67ae659771 --- /dev/null +++ b/src/qt_common/qt_meta.cpp @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_meta.h" +#include "common/common_types.h" +#include "core/core.h" +#include "core/frontend/applets/cabinet.h" +#include "core/frontend/applets/controller.h" +#include "core/frontend/applets/profile_select.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/hle/service/am/frontend/applet_web_browser_types.h" + +namespace QtCommon::Meta { + +void RegisterMetaTypes() +{ + // Register integral and floating point types + qRegisterMetaType("u8"); + qRegisterMetaType("u16"); + qRegisterMetaType("u32"); + qRegisterMetaType("u64"); + qRegisterMetaType("u128"); + qRegisterMetaType("s8"); + qRegisterMetaType("s16"); + qRegisterMetaType("s32"); + qRegisterMetaType("s64"); + qRegisterMetaType("f32"); + qRegisterMetaType("f64"); + + // Register string types + qRegisterMetaType("std::string"); + qRegisterMetaType("std::wstring"); + qRegisterMetaType("std::u8string"); + qRegisterMetaType("std::u16string"); + qRegisterMetaType("std::u32string"); + qRegisterMetaType("std::string_view"); + qRegisterMetaType("std::wstring_view"); + qRegisterMetaType("std::u8string_view"); + qRegisterMetaType("std::u16string_view"); + qRegisterMetaType("std::u32string_view"); + + // Register applet types + + // Cabinet Applet + qRegisterMetaType("Core::Frontend::CabinetParameters"); + qRegisterMetaType>( + "std::shared_ptr"); + + // Controller Applet + qRegisterMetaType("Core::Frontend::ControllerParameters"); + + // Profile Select Applet + qRegisterMetaType( + "Core::Frontend::ProfileSelectParameters"); + + // Software Keyboard Applet + qRegisterMetaType( + "Core::Frontend::KeyboardInitializeParameters"); + qRegisterMetaType( + "Core::Frontend::InlineAppearParameters"); + qRegisterMetaType("Core::Frontend::InlineTextParameters"); + qRegisterMetaType("Service::AM::Frontend::SwkbdResult"); + qRegisterMetaType( + "Service::AM::Frontend::SwkbdTextCheckResult"); + qRegisterMetaType( + "Service::AM::Frontend::SwkbdReplyType"); + + // Web Browser Applet + qRegisterMetaType("Service::AM::Frontend::WebExitReason"); + + // Register loader types + qRegisterMetaType("Core::SystemResultStatus"); +} + +} diff --git a/src/qt_common/qt_meta.h b/src/qt_common/qt_meta.h new file mode 100644 index 0000000000..c0a37db983 --- /dev/null +++ b/src/qt_common/qt_meta.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_META_H +#define QT_META_H + +#include + +namespace QtCommon::Meta { + +// +void RegisterMetaTypes(); + +} +#endif // QT_META_H diff --git a/src/qt_common/qt_path_util.cpp b/src/qt_common/qt_path_util.cpp new file mode 100644 index 0000000000..761e6e8405 --- /dev/null +++ b/src/qt_common/qt_path_util.cpp @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_path_util.h" +#include +#include +#include +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "qt_common/qt_frontend_util.h" +#include + +namespace QtCommon::Path { + +bool OpenShaderCache(u64 program_id, QObject *parent) +{ + const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); + const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)}; + if (!Common::FS::CreateDirs(shader_cache_folder_path)) { + QtCommon::Frontend::ShowMessage(QMessageBox::Warning, "Error Opening Shader Cache", "Failed to create or open shader cache for this title, ensure your app data directory has write permissions.", QMessageBox::Ok, parent); + } + + const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)}; + const auto qt_shader_cache_path = QString::fromStdString(shader_path_string); + return QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path)); +} + +} diff --git a/src/qt_common/qt_path_util.h b/src/qt_common/qt_path_util.h new file mode 100644 index 0000000000..855b06caa9 --- /dev/null +++ b/src/qt_common/qt_path_util.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_PATH_UTIL_H +#define QT_PATH_UTIL_H + +#include "common/common_types.h" +#include + +namespace QtCommon::Path { bool OpenShaderCache(u64 program_id, QObject *parent); } + +#endif // QT_PATH_UTIL_H diff --git a/src/qt_common/qt_progress_dialog.cpp b/src/qt_common/qt_progress_dialog.cpp new file mode 100644 index 0000000000..b4bf74c8bd --- /dev/null +++ b/src/qt_common/qt_progress_dialog.cpp @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_progress_dialog.h" diff --git a/src/qt_common/qt_progress_dialog.h b/src/qt_common/qt_progress_dialog.h new file mode 100644 index 0000000000..17f6817ffa --- /dev/null +++ b/src/qt_common/qt_progress_dialog.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_PROGRESS_DIALOG_H +#define QT_PROGRESS_DIALOG_H + +#include + +#ifdef YUZU_QT_WIDGETS +#include +#endif + +namespace QtCommon::Frontend { +#ifdef YUZU_QT_WIDGETS + +using QtProgressDialog = QProgressDialog; + +// TODO(crueter): QML impl +#else +class QtProgressDialog +{ +public: + QtProgressDialog(const QString &labelText, + const QString &cancelButtonText, + int minimum, + int maximum, + QObject *parent = nullptr, + Qt::WindowFlags f = Qt::WindowFlags()); + + bool wasCanceled() const; + void setWindowModality(Qt::WindowModality modality); + void setMinimumDuration(int durationMs); + void setAutoClose(bool autoClose); + void setAutoReset(bool autoReset); + +public slots: + void setLabelText(QString &text); + void setRange(int min, int max); + void setValue(int progress); + bool close(); + + void show(); +}; +#endif // YUZU_QT_WIDGETS + +} +#endif // QT_PROGRESS_DIALOG_H diff --git a/src/qt_common/qt_rom_util.cpp b/src/qt_common/qt_rom_util.cpp new file mode 100644 index 0000000000..08ccb05a97 --- /dev/null +++ b/src/qt_common/qt_rom_util.cpp @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_rom_util.h" + +#include + +namespace QtCommon::ROM { + +bool RomFSRawCopy(size_t total_size, + size_t& read_size, + QtProgressCallback callback, + const FileSys::VirtualDir& src, + const FileSys::VirtualDir& dest, + bool full) +{ + // TODO(crueter) + // if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) + // return false; + // if (dialog.wasCanceled()) + // return false; + + // std::vector buffer(CopyBufferSize); + // auto last_timestamp = std::chrono::steady_clock::now(); + + // const auto QtRawCopy = [&](const FileSys::VirtualFile& src_file, + // const FileSys::VirtualFile& dest_file) { + // if (src_file == nullptr || dest_file == nullptr) { + // return false; + // } + // if (!dest_file->Resize(src_file->GetSize())) { + // return false; + // } + + // for (std::size_t i = 0; i < src_file->GetSize(); i += buffer.size()) { + // if (dialog.wasCanceled()) { + // dest_file->Resize(0); + // return false; + // } + + // using namespace std::literals::chrono_literals; + // const auto new_timestamp = std::chrono::steady_clock::now(); + + // if ((new_timestamp - last_timestamp) > 33ms) { + // last_timestamp = new_timestamp; + // dialog.setValue( + // static_cast(std::min(read_size, total_size) * 100 / total_size)); + // QCoreApplication::processEvents(); + // } + + // const auto read = src_file->Read(buffer.data(), buffer.size(), i); + // dest_file->Write(buffer.data(), read, i); + + // read_size += read; + // } + + // return true; + // }; + + // if (full) { + // for (const auto& file : src->GetFiles()) { + // const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); + // if (!QtRawCopy(file, out)) + // return false; + // } + // } + + // for (const auto& dir : src->GetSubdirectories()) { + // const auto out = dest->CreateSubdirectory(dir->GetName()); + // if (!RomFSRawCopy(total_size, read_size, dialog, dir, out, full)) + // return false; + // } + + // return true; + return true; +} + +} diff --git a/src/qt_common/qt_rom_util.h b/src/qt_common/qt_rom_util.h new file mode 100644 index 0000000000..f76b09753d --- /dev/null +++ b/src/qt_common/qt_rom_util.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef QT_ROM_UTIL_H +#define QT_ROM_UTIL_H + +#include "qt_common/qt_common.h" +#include + +namespace QtCommon::ROM { + +bool RomFSRawCopy(size_t total_size, + size_t& read_size, + QtProgressCallback callback, + const FileSys::VirtualDir& src, + const FileSys::VirtualDir& dest, + bool full); + +} +#endif // QT_ROM_UTIL_H diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/qt_common/shared_translation.cpp similarity index 91% rename from src/yuzu/configuration/shared_translation.cpp rename to src/qt_common/shared_translation.cpp index 1137145659..8f31e07154 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -7,23 +7,21 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "yuzu/configuration/shared_translation.h" +#include "shared_translation.h" #include -#include #include "common/settings.h" #include "common/settings_enums.h" #include "common/settings_setting.h" #include "common/time_zone.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #include #include -#include #include namespace ConfigurationShared { -std::unique_ptr InitializeTranslations(QWidget* parent) +std::unique_ptr InitializeTranslations(QObject* parent) { std::unique_ptr translations = std::make_unique(); const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); }; @@ -409,12 +407,6 @@ std::unique_ptr InitializeTranslations(QWidget* parent) "their resolution, details and supported controllers and depending on this setting.\n" "Setting to Handheld can help improve performance for low end systems.")); INSERT(Settings, current_user, QString(), QString()); - INSERT(Settings, disable_nca_verification, tr("Disable NCA Verification"), - tr("Disables integrity verification of NCA content archives." - "\nThis may improve loading speed but risks data corruption or invalid files going " - "undetected.\n" - "Is necessary to make games and updates work that needs firmware 20+.")); - INSERT(Settings, hide_nca_verification_popup, QString(), QString()); // Controls @@ -473,7 +465,7 @@ std::unique_ptr InitializeTranslations(QWidget* parent) return translations; } -std::unique_ptr ComboboxEnumeration(QWidget* parent) +std::unique_ptr ComboboxEnumeration(QObject* parent) { std::unique_ptr translations = std::make_unique(); const auto& tr = [&](const char* text, const char* context = "") { @@ -650,58 +642,58 @@ std::unique_ptr ComboboxEnumeration(QWidget* parent) translations->insert( {Settings::EnumMetadata::Index(), { - {static_cast(Settings::TimeZone::Auto), - tr("Auto (%1)", "Auto select time zone") - .arg(QString::fromStdString( - Settings::GetTimeZoneString(Settings::TimeZone::Auto)))}, - {static_cast(Settings::TimeZone::Default), - tr("Default (%1)", "Default time zone") - .arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))}, - PAIR(TimeZone, Cet, tr("CET")), - PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")), - PAIR(TimeZone, Cuba, tr("Cuba")), - PAIR(TimeZone, Eet, tr("EET")), - PAIR(TimeZone, Egypt, tr("Egypt")), - PAIR(TimeZone, Eire, tr("Eire")), - PAIR(TimeZone, Est, tr("EST")), - PAIR(TimeZone, Est5Edt, tr("EST5EDT")), - PAIR(TimeZone, Gb, tr("GB")), - PAIR(TimeZone, GbEire, tr("GB-Eire")), - PAIR(TimeZone, Gmt, tr("GMT")), - PAIR(TimeZone, GmtPlusZero, tr("GMT+0")), - PAIR(TimeZone, GmtMinusZero, tr("GMT-0")), - PAIR(TimeZone, GmtZero, tr("GMT0")), - PAIR(TimeZone, Greenwich, tr("Greenwich")), - PAIR(TimeZone, Hongkong, tr("Hongkong")), - PAIR(TimeZone, Hst, tr("HST")), - PAIR(TimeZone, Iceland, tr("Iceland")), - PAIR(TimeZone, Iran, tr("Iran")), - PAIR(TimeZone, Israel, tr("Israel")), - PAIR(TimeZone, Jamaica, tr("Jamaica")), - PAIR(TimeZone, Japan, tr("Japan")), - PAIR(TimeZone, Kwajalein, tr("Kwajalein")), - PAIR(TimeZone, Libya, tr("Libya")), - PAIR(TimeZone, Met, tr("MET")), - PAIR(TimeZone, Mst, tr("MST")), - PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")), - PAIR(TimeZone, Navajo, tr("Navajo")), - PAIR(TimeZone, Nz, tr("NZ")), - PAIR(TimeZone, NzChat, tr("NZ-CHAT")), - PAIR(TimeZone, Poland, tr("Poland")), - PAIR(TimeZone, Portugal, tr("Portugal")), - PAIR(TimeZone, Prc, tr("PRC")), - PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")), - PAIR(TimeZone, Roc, tr("ROC")), - PAIR(TimeZone, Rok, tr("ROK")), - PAIR(TimeZone, Singapore, tr("Singapore")), - PAIR(TimeZone, Turkey, tr("Turkey")), - PAIR(TimeZone, Uct, tr("UCT")), - PAIR(TimeZone, Universal, tr("Universal")), - PAIR(TimeZone, Utc, tr("UTC")), - PAIR(TimeZone, WSu, tr("W-SU")), - PAIR(TimeZone, Wet, tr("WET")), - PAIR(TimeZone, Zulu, tr("Zulu")), - }}); + {static_cast(Settings::TimeZone::Auto), + tr("Auto (%1)", "Auto select time zone") + .arg(QString::fromStdString( + Settings::GetTimeZoneString(Settings::TimeZone::Auto)))}, + {static_cast(Settings::TimeZone::Default), + tr("Default (%1)", "Default time zone") + .arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))}, + PAIR(TimeZone, Cet, tr("CET")), + PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")), + PAIR(TimeZone, Cuba, tr("Cuba")), + PAIR(TimeZone, Eet, tr("EET")), + PAIR(TimeZone, Egypt, tr("Egypt")), + PAIR(TimeZone, Eire, tr("Eire")), + PAIR(TimeZone, Est, tr("EST")), + PAIR(TimeZone, Est5Edt, tr("EST5EDT")), + PAIR(TimeZone, Gb, tr("GB")), + PAIR(TimeZone, GbEire, tr("GB-Eire")), + PAIR(TimeZone, Gmt, tr("GMT")), + PAIR(TimeZone, GmtPlusZero, tr("GMT+0")), + PAIR(TimeZone, GmtMinusZero, tr("GMT-0")), + PAIR(TimeZone, GmtZero, tr("GMT0")), + PAIR(TimeZone, Greenwich, tr("Greenwich")), + PAIR(TimeZone, Hongkong, tr("Hongkong")), + PAIR(TimeZone, Hst, tr("HST")), + PAIR(TimeZone, Iceland, tr("Iceland")), + PAIR(TimeZone, Iran, tr("Iran")), + PAIR(TimeZone, Israel, tr("Israel")), + PAIR(TimeZone, Jamaica, tr("Jamaica")), + PAIR(TimeZone, Japan, tr("Japan")), + PAIR(TimeZone, Kwajalein, tr("Kwajalein")), + PAIR(TimeZone, Libya, tr("Libya")), + PAIR(TimeZone, Met, tr("MET")), + PAIR(TimeZone, Mst, tr("MST")), + PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")), + PAIR(TimeZone, Navajo, tr("Navajo")), + PAIR(TimeZone, Nz, tr("NZ")), + PAIR(TimeZone, NzChat, tr("NZ-CHAT")), + PAIR(TimeZone, Poland, tr("Poland")), + PAIR(TimeZone, Portugal, tr("Portugal")), + PAIR(TimeZone, Prc, tr("PRC")), + PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")), + PAIR(TimeZone, Roc, tr("ROC")), + PAIR(TimeZone, Rok, tr("ROK")), + PAIR(TimeZone, Singapore, tr("Singapore")), + PAIR(TimeZone, Turkey, tr("Turkey")), + PAIR(TimeZone, Uct, tr("UCT")), + PAIR(TimeZone, Universal, tr("Universal")), + PAIR(TimeZone, Utc, tr("UTC")), + PAIR(TimeZone, WSu, tr("W-SU")), + PAIR(TimeZone, Wet, tr("WET")), + PAIR(TimeZone, Zulu, tr("Zulu")), + }}); translations->insert({Settings::EnumMetadata::Index(), { PAIR(AudioMode, Mono, tr("Mono")), diff --git a/src/yuzu/configuration/shared_translation.h b/src/qt_common/shared_translation.h similarity index 94% rename from src/yuzu/configuration/shared_translation.h rename to src/qt_common/shared_translation.h index 574b1e2c78..48a2cb5205 100644 --- a/src/yuzu/configuration/shared_translation.h +++ b/src/qt_common/shared_translation.h @@ -11,23 +11,20 @@ #include #include -#include #include #include #include #include "common/common_types.h" -#include "common/settings.h" - -class QWidget; +#include "common/settings_enums.h" namespace ConfigurationShared { using TranslationMap = std::map>; using ComboboxTranslations = std::vector>; using ComboboxTranslationMap = std::map; -std::unique_ptr InitializeTranslations(QWidget* parent); +std::unique_ptr InitializeTranslations(QObject *parent); -std::unique_ptr ComboboxEnumeration(QWidget* parent); +std::unique_ptr ComboboxEnumeration(QObject* parent); static const std::map anti_aliasing_texts_map = { {Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))}, diff --git a/src/yuzu/uisettings.cpp b/src/qt_common/uisettings.cpp similarity index 99% rename from src/yuzu/uisettings.cpp rename to src/qt_common/uisettings.cpp index 6da5424775..a17f3c0a4a 100644 --- a/src/yuzu/uisettings.cpp +++ b/src/qt_common/uisettings.cpp @@ -7,7 +7,7 @@ #include #include "common/fs/fs.h" #include "common/fs/path_util.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #ifndef CANNOT_EXPLICITLY_INSTANTIATE namespace Settings { diff --git a/src/yuzu/uisettings.h b/src/qt_common/uisettings.h similarity index 99% rename from src/yuzu/uisettings.h rename to src/qt_common/uisettings.h index 3322b31ca3..4981d98dbf 100644 --- a/src/yuzu/uisettings.h +++ b/src/qt_common/uisettings.h @@ -17,7 +17,7 @@ #include "common/common_types.h" #include "common/settings.h" #include "common/settings_enums.h" -#include "configuration/qt_config.h" +#include "qt_common/qt_config.h" using Settings::Category; using Settings::ConfirmStop; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index d663f6c282..8a1d760c50 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -150,12 +150,8 @@ add_executable(yuzu configuration/configure_web.ui configuration/input_profiles.cpp configuration/input_profiles.h - configuration/shared_translation.cpp - configuration/shared_translation.h configuration/shared_widget.cpp configuration/shared_widget.h - configuration/qt_config.cpp - configuration/qt_config.h debugger/console.cpp debugger/console.h debugger/controller.cpp @@ -205,12 +201,8 @@ add_executable(yuzu play_time_manager.cpp play_time_manager.h precompiled_headers.h - qt_common.cpp - qt_common.h startup_checks.cpp startup_checks.h - uisettings.cpp - uisettings.h util/clickable_label.cpp util/clickable_label.h util/controller_navigation.cpp @@ -396,14 +388,17 @@ elseif(WIN32) endif() endif() -target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core) target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json) +target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common) target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) if (NOT WIN32) target_include_directories(yuzu PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) endif() + +target_link_libraries(yuzu PRIVATE Vulkan::Headers) + if (UNIX AND NOT APPLE) target_link_libraries(yuzu PRIVATE Qt6::DBus) @@ -501,8 +496,4 @@ if (YUZU_ROOM) target_link_libraries(yuzu PRIVATE yuzu-room) endif() -# Extra deps -add_subdirectory(externals) -target_link_libraries(yuzu PRIVATE QuaZip::QuaZip) - create_target_directory_groups(yuzu) diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 63e7a74003..7b5f2a314f 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -12,7 +12,7 @@ #include #include "common/settings_enums.h" -#include "uisettings.h" +#include "qt_common/uisettings.h" #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA #include #include @@ -58,7 +58,7 @@ #include "video_core/renderer_base.h" #include "yuzu/bootmanager.h" #include "yuzu/main.h" -#include "yuzu/qt_common.h" +#include "qt_common/qt_common.h" class QObject; class QPaintEngine; @@ -234,7 +234,7 @@ class DummyContext : public Core::Frontend::GraphicsContext {}; class RenderWidget : public QWidget { public: - explicit RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) { + explicit RenderWidget(GRenderWindow* parent) : QWidget(parent) { setAttribute(Qt::WA_NativeWindow); setAttribute(Qt::WA_PaintOnScreen); if (QtCommon::GetWindowSystemType() == Core::Frontend::WindowSystemType::Wayland) { @@ -247,9 +247,6 @@ public: QPaintEngine* paintEngine() const override { return nullptr; } - -private: - GRenderWindow* render_window; }; struct OpenGLRenderWidget : public RenderWidget { diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index c235b0fca4..a7ebae91f8 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -16,9 +19,9 @@ #include "ui_configure_audio.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_audio.h" -#include "yuzu/configuration/shared_translation.h" +#include "qt_common/shared_translation.h" #include "yuzu/configuration/shared_widget.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" ConfigureAudio::ConfigureAudio(const Core::System& system_, std::shared_ptr> group_, diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h index 098e0e397b..bbc6096f9b 100644 --- a/src/yuzu/configuration/configure_cpu.h +++ b/src/yuzu/configuration/configure_cpu.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,7 +10,7 @@ #include #include #include "yuzu/configuration/configuration_shared.h" -#include "yuzu/configuration/shared_translation.h" +#include "qt_common/shared_translation.h" class QComboBox; diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 733c419c4b..18f629f639 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -15,7 +15,7 @@ #include "ui_configure_debug.h" #include "yuzu/configuration/configure_debug.h" #include "yuzu/debugger/console.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) : QScrollArea(parent), ui{std::make_unique()}, system{system_} { diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 987b1462db..0816782282 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -27,7 +30,7 @@ #include "yuzu/configuration/configure_ui.h" #include "yuzu/configuration/configure_web.h" #include "yuzu/hotkeys.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, InputCommon::InputSubsystem* input_subsystem, diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 0b6b285678..c1e148fc8e 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,7 +11,7 @@ #include #include "configuration/shared_widget.h" #include "yuzu/configuration/configuration_shared.h" -#include "yuzu/configuration/shared_translation.h" +#include "qt_common/shared_translation.h" #include "yuzu/vk_device_info.h" namespace Core { diff --git a/src/yuzu/configuration/configure_filesystem.cpp b/src/yuzu/configuration/configure_filesystem.cpp index f25f14708b..d461bd2cc9 100644 --- a/src/yuzu/configuration/configure_filesystem.cpp +++ b/src/yuzu/configuration/configure_filesystem.cpp @@ -1,14 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "yuzu/configuration/configure_filesystem.h" #include #include #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/settings.h" +#include "qt_common/qt_common.h" +#include "qt_common/qt_game_util.h" +#include "qt_common/uisettings.h" #include "ui_configure_filesystem.h" -#include "yuzu/configuration/configure_filesystem.h" -#include "yuzu/uisettings.h" ConfigureFilesystem::ConfigureFilesystem(QWidget* parent) : QWidget(parent), ui(std::make_unique()) { @@ -126,20 +131,7 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) } void ConfigureFilesystem::ResetMetadata() { - if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / - "game_list/")) { - QMessageBox::information(this, tr("Reset Metadata Cache"), - tr("The metadata cache is already empty.")); - } else if (Common::FS::RemoveDirRecursively( - Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list")) { - QMessageBox::information(this, tr("Reset Metadata Cache"), - tr("The operation completed successfully.")); - UISettings::values.is_game_list_reload_pending.exchange(true); - } else { - QMessageBox::warning( - this, tr("Reset Metadata Cache"), - tr("The metadata cache couldn't be deleted. It might be in use or non-existent.")); - } + QtCommon::Game::ResetMetadata(); } void ConfigureFilesystem::UpdateEnabledControls() { diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 918fa15d4d..18c2c9cb77 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -11,7 +14,7 @@ #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_general.h" #include "yuzu/configuration/shared_widget.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" ConfigureGeneral::ConfigureGeneral(const Core::System& system_, std::shared_ptr> group_, diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 16846878f9..cd806e5ef8 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -40,8 +43,8 @@ #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" #include "yuzu/configuration/shared_widget.h" -#include "yuzu/qt_common.h" -#include "yuzu/uisettings.h" +#include "qt_common/qt_common.h" +#include "qt_common/uisettings.h" #include "yuzu/vk_device_info.h" static const std::vector default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index b92b4496ba..38d97aae80 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,7 +18,7 @@ #include #include "common/common_types.h" #include "common/settings_enums.h" -#include "configuration/shared_translation.h" +#include "qt_common/shared_translation.h" #include "vk_device_info.h" #include "yuzu/configuration/configuration_shared.h" diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 4db18673d4..e07ad8c466 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -9,7 +12,7 @@ #include "ui_configure_graphics_advanced.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics_advanced.h" -#include "yuzu/configuration/shared_translation.h" +#include "qt_common/shared_translation.h" #include "yuzu/configuration/shared_widget.h" ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced( diff --git a/src/yuzu/configuration/configure_graphics_extensions.cpp b/src/yuzu/configuration/configure_graphics_extensions.cpp index 322fa9ea08..f165e703d9 100644 --- a/src/yuzu/configuration/configure_graphics_extensions.cpp +++ b/src/yuzu/configuration/configure_graphics_extensions.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -11,7 +14,7 @@ #include "ui_configure_graphics_extensions.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics_extensions.h" -#include "yuzu/configuration/shared_translation.h" +#include "qt_common/shared_translation.h" #include "yuzu/configuration/shared_widget.h" ConfigureGraphicsExtensions::ConfigureGraphicsExtensions( diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index 3f68de12d8..a5c1ee009a 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,7 +16,7 @@ #include "ui_configure_hotkeys.h" #include "yuzu/configuration/configure_hotkeys.h" #include "yuzu/hotkeys.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #include "yuzu/util/sequence_dialog/sequence_dialog.h" constexpr int name_column = 0; diff --git a/src/yuzu/configuration/configure_input_per_game.h b/src/yuzu/configuration/configure_input_per_game.h index 4420e856cb..dcd6dd841b 100644 --- a/src/yuzu/configuration/configure_input_per_game.h +++ b/src/yuzu/configuration/configure_input_per_game.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -9,7 +12,7 @@ #include "ui_configure_input_per_game.h" #include "yuzu/configuration/input_profiles.h" -#include "yuzu/configuration/qt_config.h" +#include "qt_common/qt_config.h" class QComboBox; diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index d0dc0ff44c..6afa8320a2 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -14,7 +14,7 @@ #include #include "common/assert.h" #include "common/param_package.h" -#include "configuration/qt_config.h" +#include "qt_common/qt_config.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" #include "hid_core/hid_types.h" diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 031a8d4c2d..b51ede0de8 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -38,7 +41,7 @@ #include "yuzu/configuration/configure_per_game.h" #include "yuzu/configuration/configure_per_game_addons.h" #include "yuzu/configuration/configure_system.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #include "yuzu/util/util.h" #include "yuzu/vk_device_info.h" diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h index e0f4e5cd67..83afc27f3d 100644 --- a/src/yuzu/configuration/configure_per_game.h +++ b/src/yuzu/configuration/configure_per_game.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,8 +18,8 @@ #include "frontend_common/config.h" #include "vk_device_info.h" #include "yuzu/configuration/configuration_shared.h" -#include "yuzu/configuration/qt_config.h" -#include "yuzu/configuration/shared_translation.h" +#include "qt_common/qt_config.h" +#include "qt_common/shared_translation.h" namespace Core { class System; diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 078f2e8288..ed4cbc1147 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -21,7 +24,7 @@ #include "ui_configure_per_game_addons.h" #include "yuzu/configuration/configure_input.h" #include "yuzu/configuration/configure_per_game_addons.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* parent) : QWidget(parent), ui{std::make_unique()}, system{system_} { diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp index f7249be97e..795ad1a85e 100644 --- a/src/yuzu/configuration/configure_ringcon.cpp +++ b/src/yuzu/configuration/configure_ringcon.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,7 +11,7 @@ #include #include -#include "configuration/qt_config.h" +#include "qt_common/qt_config.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" #include "input_common/drivers/keyboard.h" diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index 773658bf22..898a1a3e59 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp @@ -1,3 +1,6 @@ +// 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 @@ -8,7 +11,7 @@ #include "common/settings.h" #include "ui_configure_tas.h" #include "yuzu/configuration/configure_tas.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" ConfigureTasDialog::ConfigureTasDialog(QWidget* parent) : QDialog(parent), ui(std::make_unique()) { diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 8dafee628b..ac6d6e34aa 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -27,7 +27,7 @@ #include "core/core.h" #include "core/frontend/framebuffer_layout.h" #include "ui_configure_ui.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" namespace { constexpr std::array default_game_icon_sizes{ diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index d62b5b0853..15a0029901 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp @@ -17,7 +17,7 @@ #include #include "common/settings.h" #include "ui_configure_web.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" ConfigureWeb::ConfigureWeb(QWidget* parent) : QWidget(parent) diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h index 023ec74a63..dba5ce1318 100644 --- a/src/yuzu/configuration/input_profiles.h +++ b/src/yuzu/configuration/input_profiles.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,7 +9,7 @@ #include #include -#include "configuration/qt_config.h" +#include "qt_common/qt_config.h" namespace Core { class System; diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp index e23d86dc69..e30ecc4848 100644 --- a/src/yuzu/configuration/shared_widget.cpp +++ b/src/yuzu/configuration/shared_widget.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -42,7 +45,7 @@ #include "common/logging/log.h" #include "common/settings.h" #include "common/settings_common.h" -#include "yuzu/configuration/shared_translation.h" +#include "qt_common/shared_translation.h" namespace ConfigurationShared { diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h index 5c67d83542..9e718098a3 100644 --- a/src/yuzu/configuration/shared_widget.h +++ b/src/yuzu/configuration/shared_widget.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -11,7 +14,7 @@ #include #include #include -#include "yuzu/configuration/shared_translation.h" +#include "qt_common/shared_translation.h" class QCheckBox; class QComboBox; diff --git a/src/yuzu/debugger/console.cpp b/src/yuzu/debugger/console.cpp index 1c1342ff18..8fb22db192 100644 --- a/src/yuzu/debugger/console.cpp +++ b/src/yuzu/debugger/console.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -9,7 +12,7 @@ #include "common/logging/backend.h" #include "yuzu/debugger/console.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" namespace Debugger { void ToggleConsole() { diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 9f9e21bc28..feb814b25e 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,7 +8,7 @@ #include #include "yuzu/debugger/wait_tree.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #include "core/arm/debug.h" #include "core/core.h" diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 1ecef4af92..fa61cdfb1f 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include +#include "yuzu/game_list.h" #include #include #include @@ -13,19 +13,20 @@ #include #include #include -#include #include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" +#include "qt_common/qt_game_util.h" +#include "qt_common/uisettings.h" #include "yuzu/compatibility_list.h" -#include "yuzu/game_list.h" #include "yuzu/game_list_p.h" #include "yuzu/game_list_worker.h" #include "yuzu/main.h" -#include "yuzu/uisettings.h" #include "yuzu/util/controller_navigation.h" +#include +#include GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent) : QObject(parent), gamelist{gamelist_} {} @@ -608,30 +609,30 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri connect(open_transferable_shader_cache, &QAction::triggered, [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); }); connect(remove_all_content, &QAction::triggered, [this, program_id]() { - emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Game); + emit RemoveInstalledEntryRequested(program_id, QtCommon::Game::InstalledEntryType::Game); }); connect(remove_update, &QAction::triggered, [this, program_id]() { - emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::Update); + emit RemoveInstalledEntryRequested(program_id, QtCommon::Game::InstalledEntryType::Update); }); connect(remove_dlc, &QAction::triggered, [this, program_id]() { - emit RemoveInstalledEntryRequested(program_id, InstalledEntryType::AddOnContent); + emit RemoveInstalledEntryRequested(program_id, QtCommon::Game::InstalledEntryType::AddOnContent); }); connect(remove_gl_shader_cache, &QAction::triggered, [this, program_id, path]() { - emit RemoveFileRequested(program_id, GameListRemoveTarget::GlShaderCache, path); + emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::GlShaderCache, path); }); connect(remove_vk_shader_cache, &QAction::triggered, [this, program_id, path]() { - emit RemoveFileRequested(program_id, GameListRemoveTarget::VkShaderCache, path); + emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::VkShaderCache, path); }); connect(remove_shader_cache, &QAction::triggered, [this, program_id, path]() { - emit RemoveFileRequested(program_id, GameListRemoveTarget::AllShaderCache, path); + emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::AllShaderCache, path); }); connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { - emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); + emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::CustomConfiguration, path); }); connect(remove_play_time_data, &QAction::triggered, [this, program_id]() { emit RemovePlayTimeRequested(program_id); }); connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { - emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); + emit RemoveFileRequested(program_id, QtCommon::Game::GameListRemoveTarget::CacheStorage, path); }); connect(dump_romfs, &QAction::triggered, [this, program_id, path]() { emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::Normal); @@ -649,10 +650,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri // TODO: Implement shortcut creation for macOS #if !defined(__APPLE__) connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { - emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); + emit CreateShortcut(program_id, path, QtCommon::Game::ShortcutTarget::Desktop); }); connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { - emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); + emit CreateShortcut(program_id, path, QtCommon::Game::ShortcutTarget::Applications); }); #endif connect(properties, &QAction::triggered, @@ -820,7 +821,7 @@ QStandardItemModel* GameList::GetModel() const { return item_model; } -void GameList::PopulateAsync(QVector& game_dirs, const bool cached) +void GameList::PopulateAsync(QVector& game_dirs) { tree_view->setEnabled(false); @@ -843,8 +844,7 @@ void GameList::PopulateAsync(QVector& game_dirs, const bool game_dirs, compatibility_list, play_time_manager, - system, - cached); + system); // Get events from the worker as data becomes available connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent, @@ -873,14 +873,6 @@ const QStringList GameList::supported_file_extensions = { QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; -void GameList::ForceRefreshGameDirectory() -{ - if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { - LOG_INFO(Frontend, "Force-reloading game list per user request."); - PopulateAsync(UISettings::values.game_dirs, false); - } -} - void GameList::RefreshGameDirectory() { if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 7c492bc19f..94e7b2dc42 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -20,7 +20,8 @@ #include "common/common_types.h" #include "core/core.h" -#include "uisettings.h" +#include "qt_common/uisettings.h" +#include "qt_common/qt_game_util.h" #include "yuzu/compatibility_list.h" #include "yuzu/play_time_manager.h" @@ -46,30 +47,11 @@ enum class GameListOpenTarget { ModData, }; -enum class GameListRemoveTarget { - GlShaderCache, - VkShaderCache, - AllShaderCache, - CustomConfiguration, - CacheStorage, -}; - enum class DumpRomFSTarget { Normal, SDMC, }; -enum class GameListShortcutTarget { - Desktop, - Applications, -}; - -enum class InstalledEntryType { - Game, - Update, - AddOnContent, -}; - class GameList : public QWidget { Q_OBJECT @@ -97,7 +79,7 @@ public: bool IsEmpty() const; void LoadCompatibilityList(); - void PopulateAsync(QVector& game_dirs, const bool cached = true); + void PopulateAsync(QVector& game_dirs); void SaveInterfaceLayout(); void LoadInterfaceLayout(); @@ -110,7 +92,6 @@ public: static const QStringList supported_file_extensions; public slots: - void ForceRefreshGameDirectory(); void RefreshGameDirectory(); signals: @@ -119,15 +100,15 @@ signals: void OpenFolderRequested(u64 program_id, GameListOpenTarget target, const std::string& game_path); void OpenTransferableShaderCacheRequested(u64 program_id); - void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); - void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, + void RemoveInstalledEntryRequested(u64 program_id, QtCommon::Game::InstalledEntryType type); + void RemoveFileRequested(u64 program_id, QtCommon::Game::GameListRemoveTarget target, const std::string& game_path); void RemovePlayTimeRequested(u64 program_id); void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); void VerifyIntegrityRequested(const std::string& game_path); void CopyTIDRequested(u64 program_id); void CreateShortcut(u64 program_id, const std::string& game_path, - GameListShortcutTarget target); + const QtCommon::Game::ShortcutTarget target); void NavigateToGamedbEntryRequested(u64 program_id, const CompatibilityList& compatibility_list); void OpenPerGameGeneralRequested(const std::string& file); diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index c330b574f9..5a3b5829f5 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2015 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -19,7 +22,7 @@ #include "common/logging/log.h" #include "common/string_util.h" #include "yuzu/play_time_manager.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #include "yuzu/util/util.h" enum class GameListItemType { diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 60109769bf..538c7ab822 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -27,7 +30,7 @@ #include "yuzu/game_list.h" #include "yuzu/game_list_p.h" #include "yuzu/game_list_worker.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" namespace { @@ -199,8 +202,7 @@ QList MakeGameListEntry(const std::string& path, u64 program_id, const CompatibilityList& compatibility_list, const PlayTime::PlayTimeManager& play_time_manager, - const FileSys::PatchManager& patch, - const bool cached) + const FileSys::PatchManager& patch) { const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -224,14 +226,10 @@ QList MakeGameListEntry(const std::string& path, QString patch_versions; - if (cached) { - patch_versions = GetGameListCachedObject( - fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { - return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); - }); - } else { - patch_versions = FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); - } + patch_versions = GetGameListCachedObject( + fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] { + return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable()); + }); list.insert(2, new GameListItem(patch_versions)); @@ -244,15 +242,13 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, QVector& game_dirs_, const CompatibilityList& compatibility_list_, const PlayTime::PlayTimeManager& play_time_manager_, - Core::System& system_, - const bool cached_) + Core::System& system_) : vfs{std::move(vfs_)} , provider{provider_} , game_dirs{game_dirs_} , compatibility_list{compatibility_list_} , play_time_manager{play_time_manager_} , system{system_} - , cached{cached_} { // We want the game list to manage our lifetime. setAutoDelete(false); @@ -355,8 +351,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { program_id, compatibility_list, play_time_manager, - patch, - cached); + patch); RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } @@ -439,8 +434,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa id, compatibility_list, play_time_manager, - patch, - cached); + patch); RecordEvent( [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); @@ -463,8 +457,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa program_id, compatibility_list, play_time_manager, - patch, - cached); + patch); RecordEvent( [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 0afd7c7849..f5d5f6341b 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,7 +18,7 @@ #include "common/thread.h" #include "core/file_sys/registered_cache.h" -#include "uisettings.h" +#include "qt_common/uisettings.h" #include "yuzu/compatibility_list.h" #include "yuzu/play_time_manager.h" @@ -45,8 +48,7 @@ public: QVector& game_dirs_, const CompatibilityList& compatibility_list_, const PlayTime::PlayTimeManager& play_time_manager_, - Core::System& system_, - const bool cached = true); + Core::System& system_); ~GameListWorker() override; /// Starts the processing of directory tree information. @@ -95,6 +97,4 @@ private: Common::Event processing_completed; Core::System& system; - - const bool cached; }; diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index 1931dcd1f6..31932e6f43 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,7 +11,7 @@ #include "hid_core/frontend/emulated_controller.h" #include "yuzu/hotkeys.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" HotkeyRegistry::HotkeyRegistry() = default; HotkeyRegistry::~HotkeyRegistry() = default; diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp index 673bbaa836..e6f1392ce0 100644 --- a/src/yuzu/install_dialog.cpp +++ b/src/yuzu/install_dialog.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,7 +11,7 @@ #include #include #include "yuzu/install_dialog.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialog(parent) { file_list = new QListWidget(this); diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e23e9a6a48..d7d4e94ab7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1,18 +1,19 @@ // SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include -#include -#include -#include -#include -#include #include "core/hle/service/am/applet_manager.h" #include "core/loader/nca.h" #include "core/tools/renderdoc.h" #include "frontend_common/firmware_manager.h" - -#include +#include "qt_common/qt_common.h" +#include "qt_common/qt_content_util.h" +#include "qt_common/qt_game_util.h" +#include "qt_common/qt_meta.h" +#include "qt_common/qt_path_util.h" +#include +#include +#include +#include #ifdef __APPLE__ #include // for chdir @@ -39,21 +40,16 @@ #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" #include "configuration/configure_tas.h" -#include "core/core_timing.h" #include "core/file_sys/romfs_factory.h" -#include "core/file_sys/vfs/vfs.h" -#include "core/file_sys/vfs/vfs_real.h" #include "core/frontend/applets/cabinet.h" #include "core/frontend/applets/controller.h" #include "core/frontend/applets/general.h" #include "core/frontend/applets/mii_edit.h" #include "core/frontend/applets/software_keyboard.h" -#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/frontend/applets.h" -#include "core/hle/service/set/system_settings_server.h" #include "frontend_common/content_manager.h" -#include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" +#include "hid_core/frontend/emulated_controller.h" #include "yuzu/multiplayer/state.h" #include "yuzu/util/controller_navigation.h" @@ -67,9 +63,8 @@ // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. -static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( - const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::OpenMode mode) { - return vfs->CreateDirectory(path, mode); +static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(const std::string& path, FileSys::OpenMode mode) { + return QtCommon::vfs->CreateDirectory(path, mode); } static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::VirtualDir& dir, @@ -118,7 +113,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "common/scm_rev.h" #include "common/scope_exit.h" #ifdef _WIN32 -#include +#include "core/core_timing.h" #include "common/windows/timer_resolution.h" #endif #ifdef ARCHITECTURE_x86_64 @@ -137,6 +132,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/file_sys/savedata_factory.h" #include "core/file_sys/submission_package.h" #include "core/hle/kernel/k_process.h" +#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/am.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/sm/sm.h" @@ -156,7 +152,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/compatibility_list.h" #include "yuzu/configuration/configure_dialog.h" #include "yuzu/configuration/configure_input_per_game.h" -#include "yuzu/configuration/qt_config.h" +#include "qt_common/qt_config.h" #include "yuzu/debugger/console.h" #include "yuzu/debugger/controller.h" #include "yuzu/debugger/wait_tree.h" @@ -164,13 +160,12 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/discord.h" #include "yuzu/game_list.h" #include "yuzu/game_list_p.h" -#include "yuzu/hotkeys.h" #include "yuzu/install_dialog.h" #include "yuzu/loading_screen.h" #include "yuzu/main.h" #include "yuzu/play_time_manager.h" #include "yuzu/startup_checks.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #include "yuzu/util/clickable_label.h" #include "yuzu/vk_device_info.h" @@ -299,14 +294,6 @@ enum class CalloutFlag : uint32_t { DRDDeprecation = 0x2, }; -/** - * Some games perform worse or straight-up don't work with updates, - * so this tracks which games are bad in this regard. - */ -constexpr std::array bad_update_games{ - 0x0100F2C0115B6000 // Tears of the Kingdom -}; - const int GMainWindow::max_recent_files_item; static void RemoveCachedContents() { @@ -389,6 +376,7 @@ static void OverrideWindowsFont() { #endif #ifndef _WIN32 +// TODO(crueter): carboxyl does this, is it needed in qml? inline static bool isDarkMode() { #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) const auto scheme = QGuiApplication::styleHints()->colorScheme(); @@ -403,10 +391,10 @@ inline static bool isDarkMode() { #endif // _WIN32 GMainWindow::GMainWindow(bool has_broken_vulkan) - : ui{std::make_unique()}, system{std::make_unique()}, - input_subsystem{std::make_shared()}, user_data_migrator{this}, - vfs{std::make_shared()}, - provider{std::make_unique()} { + : ui{std::make_unique()}, + input_subsystem{std::make_shared()}, user_data_migrator{this} { + QtCommon::Init(this); + Common::FS::CreateEdenPaths(); this->config = std::make_unique(); @@ -437,7 +425,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) UISettings::RestoreWindowState(config); - system->Initialize(); + QtCommon::system->Initialize(); Common::Log::Initialize(); Common::Log::Start(); @@ -459,11 +447,11 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); discord_rpc->Update(); - play_time_manager = std::make_unique(system->GetProfileManager()); + play_time_manager = std::make_unique(QtCommon::system->GetProfileManager()); Network::Init(); - RegisterMetaTypes(); + QtCommon::Meta::RegisterMetaTypes(); InitializeWidgets(); InitializeDebugWidgets(); @@ -476,7 +464,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) ConnectMenuEvents(); ConnectWidgetEvents(); - system->HIDCore().ReloadInputDevices(); + QtCommon::system->HIDCore().ReloadInputDevices(); controller_dialog->refreshConfiguration(); const auto branch_name = std::string(Common::g_scm_branch); @@ -520,7 +508,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) std::chrono::duration_cast>( Common::Windows::SetCurrentTimerResolutionToMaximum()) .count()); - system->CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution()); + QtCommon::system->CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution()); #endif UpdateWindowTitle(); @@ -546,10 +534,10 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) } #endif - system->SetContentProvider(std::make_unique()); - system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, - provider.get()); - system->GetFileSystemController().CreateFactories(*vfs); + QtCommon::system->SetContentProvider(std::make_unique()); + QtCommon::system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, + QtCommon::provider.get()); + QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); // Remove cached contents generated during the previous session RemoveCachedContents(); @@ -559,7 +547,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) game_list->LoadCompatibilityList(); // force reload on first load to ensure add-ons get updated - game_list->PopulateAsync(UISettings::values.game_dirs, false); + game_list->PopulateAsync(UISettings::values.game_dirs); // make sure menubar has the arrow cursor instead of inheriting from this ui->menubar->setCursor(QCursor()); @@ -663,7 +651,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) if (!argument_ok) { // try to look it up by username, only finds the first username that matches. const std::string user_arg_str = args[user_arg_idx].toStdString(); - const auto user_idx = system->GetProfileManager().GetUserIndex(user_arg_str); + const auto user_idx = QtCommon::system->GetProfileManager().GetUserIndex(user_arg_str); if (user_idx == std::nullopt) { LOG_ERROR(Frontend, "Invalid user argument"); @@ -673,7 +661,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) selected_user = user_idx.value(); } - if (!system->GetProfileManager().UserExistsIndex(selected_user)) { + if (!QtCommon::system->GetProfileManager().UserExistsIndex(selected_user)) { LOG_ERROR(Frontend, "Selected user doesn't exist"); continue; } @@ -734,65 +722,6 @@ GMainWindow::~GMainWindow() { #endif } -void GMainWindow::RegisterMetaTypes() { - // Register integral and floating point types - qRegisterMetaType("u8"); - qRegisterMetaType("u16"); - qRegisterMetaType("u32"); - qRegisterMetaType("u64"); - qRegisterMetaType("u128"); - qRegisterMetaType("s8"); - qRegisterMetaType("s16"); - qRegisterMetaType("s32"); - qRegisterMetaType("s64"); - qRegisterMetaType("f32"); - qRegisterMetaType("f64"); - - // Register string types - qRegisterMetaType("std::string"); - qRegisterMetaType("std::wstring"); - qRegisterMetaType("std::u8string"); - qRegisterMetaType("std::u16string"); - qRegisterMetaType("std::u32string"); - qRegisterMetaType("std::string_view"); - qRegisterMetaType("std::wstring_view"); - qRegisterMetaType("std::u8string_view"); - qRegisterMetaType("std::u16string_view"); - qRegisterMetaType("std::u32string_view"); - - // Register applet types - - // Cabinet Applet - qRegisterMetaType("Core::Frontend::CabinetParameters"); - qRegisterMetaType>( - "std::shared_ptr"); - - // Controller Applet - qRegisterMetaType("Core::Frontend::ControllerParameters"); - - // Profile Select Applet - qRegisterMetaType( - "Core::Frontend::ProfileSelectParameters"); - - // Software Keyboard Applet - qRegisterMetaType( - "Core::Frontend::KeyboardInitializeParameters"); - qRegisterMetaType( - "Core::Frontend::InlineAppearParameters"); - qRegisterMetaType("Core::Frontend::InlineTextParameters"); - qRegisterMetaType("Service::AM::Frontend::SwkbdResult"); - qRegisterMetaType( - "Service::AM::Frontend::SwkbdTextCheckResult"); - qRegisterMetaType( - "Service::AM::Frontend::SwkbdReplyType"); - - // Web Browser Applet - qRegisterMetaType("Service::AM::Frontend::WebExitReason"); - - // Register loader types - qRegisterMetaType("Core::SystemResultStatus"); -} - void GMainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, std::shared_ptr nfp_device) { cabinet_applet = @@ -823,7 +752,7 @@ void GMainWindow::AmiiboSettingsRequestExit() { void GMainWindow::ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters) { controller_applet = - new QtControllerSelectorDialog(this, parameters, input_subsystem.get(), *system); + new QtControllerSelectorDialog(this, parameters, input_subsystem.get(), *QtCommon::system); SCOPE_EXIT { controller_applet->deleteLater(); controller_applet = nullptr; @@ -836,8 +765,8 @@ void GMainWindow::ControllerSelectorReconfigureControllers( bool is_success = controller_applet->exec() != QDialog::Rejected; // Don't forget to apply settings. - system->HIDCore().DisableAllControllerConfiguration(); - system->ApplySettings(); + QtCommon::system->HIDCore().DisableAllControllerConfiguration(); + QtCommon::system->ApplySettings(); config->SaveAllValues(); UpdateStatusButtons(); @@ -853,7 +782,7 @@ void GMainWindow::ControllerSelectorRequestExit() { void GMainWindow::ProfileSelectorSelectProfile( const Core::Frontend::ProfileSelectParameters& parameters) { - profile_select_applet = new QtProfileSelectionDialog(*system, this, parameters); + profile_select_applet = new QtProfileSelectionDialog(*QtCommon::system, this, parameters); SCOPE_EXIT { profile_select_applet->deleteLater(); profile_select_applet = nullptr; @@ -868,7 +797,7 @@ void GMainWindow::ProfileSelectorSelectProfile( return; } - const auto uuid = system->GetProfileManager().GetUser( + const auto uuid = QtCommon::system->GetProfileManager().GetUser( static_cast(profile_select_applet->GetIndex())); if (!uuid.has_value()) { emit ProfileSelectorFinishedSelection(std::nullopt); @@ -891,7 +820,7 @@ void GMainWindow::SoftwareKeyboardInitialize( return; } - software_keyboard = new QtSoftwareKeyboardDialog(render_window, *system, is_inline, + software_keyboard = new QtSoftwareKeyboardDialog(render_window, *QtCommon::system, is_inline, std::move(initialize_parameters)); if (is_inline) { @@ -1008,7 +937,7 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, return; } - web_applet = new QtNXWebEngineView(this, *system, input_subsystem.get()); + web_applet = new QtNXWebEngineView(this, *QtCommon::system, input_subsystem.get()); ui->action_Pause->setEnabled(false); ui->action_Restart->setEnabled(false); @@ -1152,10 +1081,10 @@ void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING ui->action_Report_Compatibility->setVisible(true); #endif - render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); + render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *QtCommon::system); render_window->hide(); - game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this); + game_list = new GameList(QtCommon::vfs, QtCommon::provider.get(), *play_time_manager, *QtCommon::system, this); ui->horizontalLayout->addWidget(game_list); game_list_placeholder = new GameListPlaceholder(this); @@ -1165,7 +1094,7 @@ void GMainWindow::InitializeWidgets() { loading_screen = new LoadingScreen(this); loading_screen->hide(); ui->horizontalLayout->addWidget(loading_screen); - connect(loading_screen, &LoadingScreen::Hidden, [&] { + connect(loading_screen, &LoadingScreen::Hidden, this, [&] { loading_screen->Clear(); if (emulation_running) { render_window->show(); @@ -1174,7 +1103,7 @@ void GMainWindow::InitializeWidgets() { }); multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room, - ui->action_Show_Room, *system); + ui->action_Show_Room, *QtCommon::system); multiplayer_state->setVisible(false); // Create status bar @@ -1428,12 +1357,12 @@ void GMainWindow::InitializeWidgets() { void GMainWindow::InitializeDebugWidgets() { QMenu* debug_menu = ui->menu_View_Debugging; - waitTreeWidget = new WaitTreeWidget(*system, this); + waitTreeWidget = new WaitTreeWidget(*QtCommon::system, this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); debug_menu->addAction(waitTreeWidget->toggleViewAction()); - controller_dialog = new ControllerDialog(system->HIDCore(), input_subsystem, this); + controller_dialog = new ControllerDialog(QtCommon::system->HIDCore(), input_subsystem, this); controller_dialog->hide(); debug_menu->addAction(controller_dialog->toggleViewAction()); @@ -1473,7 +1402,7 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name this->addAction(action); - auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* controller = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); const auto* controller_hotkey = hotkey_registry.GetControllerHotkey(main_window, action_name.toStdString(), controller); connect( @@ -1518,7 +1447,7 @@ void GMainWindow::InitializeHotkeys() { const auto connect_shortcut = [&](const QString& action_name, const Fn& function) { const auto* hotkey = hotkey_registry.GetHotkey(main_window.toStdString(), action_name.toStdString(), this); - auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* controller = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); const auto* controller_hotkey = hotkey_registry.GetControllerHotkey( main_window.toStdString(), action_name.toStdString(), controller); connect(hotkey, &QShortcut::activated, this, function); @@ -1542,9 +1471,9 @@ void GMainWindow::InitializeHotkeys() { connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); }); - connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] { + connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [] { if (Settings::values.enable_renderdoc_hotkey) { - system->GetRenderdocAPI().ToggleCapture(); + QtCommon::system->GetRenderdocAPI().ToggleCapture(); } }); connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { @@ -1634,8 +1563,9 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory); connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); - connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, - &GMainWindow::OnTransferableShaderCacheOpenFile); + connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, [this](u64 program_id) { + QtCommon::Path::OpenShaderCache(program_id, this); + }); connect(game_list, &GameList::RemoveInstalledEntryRequested, this, &GMainWindow::OnGameListRemoveInstalledEntry); connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); @@ -1949,13 +1879,13 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa return false; } - system->SetFilesystem(vfs); + QtCommon::system->SetFilesystem(QtCommon::vfs); if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) { - system->GetUserChannel().clear(); + QtCommon::system->GetUserChannel().clear(); } - system->SetFrontendAppletSet({ + QtCommon::system->SetFrontendAppletSet({ std::make_unique(*this), // Amiibo Settings (UISettings::values.controller_applet_disabled.GetValue() == true) ? nullptr @@ -1970,89 +1900,21 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa nullptr, // Net Connect }); - /** Game Updates check */ + /** firmware check */ - // yuzu's configuration doesn't actually support lists so this is a bit hacky - QSettings settings; - QStringList currentIgnored = settings.value("ignoredBadUpdates", {}).toStringList(); - - if (std::find(bad_update_games.begin(), bad_update_games.end(), params.program_id) != - bad_update_games.end() && - !currentIgnored.contains(QString::number(params.program_id))) { - QMessageBox* msg = new QMessageBox(this); - msg->setWindowTitle(tr("Game Updates Warning")); - msg->setIcon(QMessageBox::Warning); - msg->setText( - tr("The game you are trying to launch is known to have performance or booting " - "issues when updates are applied. Please try increasing the memory layout to " - "6GB or 8GB if any issues occur.

Press \"OK\" to continue launching, or " - "\"Cancel\" to cancel the launch.")); - - msg->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - - QCheckBox* dontShowAgain = new QCheckBox(msg); - dontShowAgain->setText(tr("Don't show again for this game")); - msg->setCheckBox(dontShowAgain); - - int result = msg->exec(); - - // wtf - QMessageBox::ButtonRole role = - msg->buttonRole(msg->button((QMessageBox::StandardButton)result)); - - switch (role) { - case QMessageBox::RejectRole: - return false; - case QMessageBox::AcceptRole: - default: - if (dontShowAgain->isChecked()) { - currentIgnored << QString::number(params.program_id); - settings.setValue("ignoredBadUpdates", currentIgnored); - settings.sync(); - } - break; - } - } - - if (FirmwareManager::GameRequiresFirmware(params.program_id) && - !FirmwareManager::CheckFirmwarePresence(*system)) { - QMessageBox* msg = new QMessageBox(this); - msg->setWindowTitle(tr("Game Requires Firmware")); - msg->setIcon(QMessageBox::Warning); - msg->setText( - tr("The game you are trying to launch requires firmware to boot or to get past the " - "opening menu. Please " - "dump and install firmware, or press \"OK\" to launch anyways.")); - - msg->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - - int exec_result = msg->exec(); - - QMessageBox::ButtonRole role = - msg->buttonRole(msg->button((QMessageBox::StandardButton)exec_result)); - - switch (role) { - case QMessageBox::RejectRole: - return false; - case QMessageBox::AcceptRole: - default: - break; - } - } - - if (!OnCheckNcaVerification()) { + if (!QtCommon::Content::CheckGameFirmware(params.program_id, this)) { return false; } /** Exec */ const Core::SystemResultStatus result{ - system->Load(*render_window, filename.toStdString(), params)}; + QtCommon::system->Load(*render_window, filename.toStdString(), params)}; const auto drd_callout = (UISettings::values.callout_flags.GetValue() & static_cast(CalloutFlag::DRDDeprecation)) == 0; if (result == Core::SystemResultStatus::Success && - system->GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory && + QtCommon::system->GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory && drd_callout) { UISettings::values.callout_flags = UISettings::values.callout_flags.GetValue() | static_cast(CalloutFlag::DRDDeprecation); @@ -2117,7 +1979,7 @@ bool GMainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPa bool GMainWindow::SelectAndSetCurrentUser( const Core::Frontend::ProfileSelectParameters& parameters) { - QtProfileSelectionDialog dialog(*system, this, parameters); + QtProfileSelectionDialog dialog(*QtCommon::system, this, parameters); dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); @@ -2132,12 +1994,12 @@ bool GMainWindow::SelectAndSetCurrentUser( void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) { // Ensure all NCAs are registered before launching the game - const auto file = vfs->OpenFile(filepath, FileSys::OpenMode::Read); + const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read); if (!file) { return; } - auto loader = Loader::GetLoader(*system, file); + auto loader = Loader::GetLoader(*QtCommon::system, file); if (!loader) { return; } @@ -2150,7 +2012,7 @@ void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) { u64 program_id = 0; const auto res2 = loader->ReadProgramId(program_id); if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { - provider->AddEntry(FileSys::TitleType::Application, + QtCommon::provider->AddEntry(FileSys::TitleType::Application, FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), program_id, file); } else if (res2 == Loader::ResultStatus::Success && @@ -2160,7 +2022,7 @@ void GMainWindow::ConfigureFilesystemProvider(const std::string& filepath) { : FileSys::XCI{file}.GetSecurePartitionNSP(); for (const auto& title : nsp->GetNCAs()) { for (const auto& entry : title.second) { - provider->AddEntry(entry.first.first, entry.first.second, title.first, + QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first, entry.second->GetBaseFile()); } } @@ -2186,8 +2048,8 @@ void GMainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletP last_filename_booted = filename; ConfigureFilesystemProvider(filename.toStdString()); - const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); - const auto loader = Loader::GetLoader(*system, v_file, params.program_id, params.program_index); + const auto v_file = Core::GetGameFileFromPath(QtCommon::vfs, filename.toUtf8().constData()); + const auto loader = Loader::GetLoader(*QtCommon::system, v_file, params.program_id, params.program_index); if (loader != nullptr && loader->ReadProgramId(title_id) == Loader::ResultStatus::Success && type == StartGameType::Normal) { @@ -2198,8 +2060,8 @@ void GMainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletP ? Common::FS::PathToUTF8String(file_path.filename()) : fmt::format("{:016X}", title_id); QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig); - system->HIDCore().ReloadInputDevices(); - system->ApplySettings(); + QtCommon::system->HIDCore().ReloadInputDevices(); + QtCommon::system->ApplySettings(); } Settings::LogSettings(); @@ -2225,19 +2087,19 @@ void GMainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletP return; } - system->SetShuttingDown(false); + QtCommon::system->SetShuttingDown(false); game_list->setDisabled(true); // Create and start the emulation thread - emu_thread = std::make_unique(*system); + emu_thread = std::make_unique(*QtCommon::system); emit EmulationStarting(emu_thread.get()); emu_thread->start(); // Register an ExecuteProgram callback such that Core can execute a sub-program - system->RegisterExecuteProgramCallback( + QtCommon::system->RegisterExecuteProgramCallback( [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); }); - system->RegisterExitCallback([this] { + QtCommon::system->RegisterExitCallback([this] { emu_thread->ForceStop(); render_window->Exit(); }); @@ -2277,11 +2139,11 @@ void GMainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletP std::string title_name; std::string title_version; - const auto res = system->GetGameName(title_name); + const auto res = QtCommon::system->GetGameName(title_name); - const auto metadata = [this, title_id] { - const FileSys::PatchManager pm(title_id, system->GetFileSystemController(), - system->GetContentProvider()); + const auto metadata = [title_id] { + const FileSys::PatchManager pm(title_id, QtCommon::system->GetFileSystemController(), + QtCommon::system->GetContentProvider()); return pm.GetControlMetadata(); }(); if (metadata.first != nullptr) { @@ -2293,16 +2155,16 @@ void GMainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletP std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())} .filename()); } - const bool is_64bit = system->Kernel().ApplicationProcess()->Is64Bit(); + const bool is_64bit = QtCommon::system->Kernel().ApplicationProcess()->Is64Bit(); const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)"); title_name = tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit") .arg(QString::fromStdString(title_name), instruction_set_suffix) .toStdString(); LOG_INFO(Frontend, "Booting game: {:016X} | {} | {}", title_id, title_name, title_version); - const auto gpu_vendor = system->GPU().Renderer().GetDeviceVendor(); + const auto gpu_vendor = QtCommon::system->GPU().Renderer().GetDeviceVendor(); UpdateWindowTitle(title_name, title_version, gpu_vendor); - loading_screen->Prepare(system->GetAppLoader()); + loading_screen->Prepare(QtCommon::system->GetAppLoader()); loading_screen->show(); emulation_running = true; @@ -2330,11 +2192,11 @@ bool GMainWindow::OnShutdownBegin() { // Disable unlimited frame rate Settings::values.use_speed_limit.SetValue(true); - if (system->IsShuttingDown()) { + if (QtCommon::system->IsShuttingDown()) { return false; } - system->SetShuttingDown(true); + QtCommon::system->SetShuttingDown(true); discord_rpc->Pause(); RequestGameExit(); @@ -2345,9 +2207,9 @@ bool GMainWindow::OnShutdownBegin() { int shutdown_time = 1000; - if (system->DebuggerEnabled()) { + if (QtCommon::system->DebuggerEnabled()) { shutdown_time = 0; - } else if (system->GetExitLocked()) { + } else if (QtCommon::system->GetExitLocked()) { shutdown_time = 5000; } @@ -2365,7 +2227,7 @@ bool GMainWindow::OnShutdownBegin() { } void GMainWindow::OnShutdownBeginDialog() { - shutdown_dialog = new OverlayDialog(this, *system, QString{}, tr("Closing software..."), + shutdown_dialog = new OverlayDialog(this, *QtCommon::system, QString{}, tr("Closing software..."), QString{}, QString{}, Qt::AlignHCenter | Qt::AlignVCenter); shutdown_dialog->open(); } @@ -2417,10 +2279,10 @@ void GMainWindow::OnEmulationStopped() { OnTasStateChanged(); render_window->FinalizeCamera(); - system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::None); + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::None); // Enable all controllers - system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); + QtCommon::system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); render_window->removeEventFilter(render_window); render_window->setAttribute(Qt::WA_Hover, false); @@ -2449,8 +2311,8 @@ void GMainWindow::OnEmulationStopped() { // Enable game list game_list->setEnabled(true); - Settings::RestoreGlobalState(system->IsPoweredOn()); - system->HIDCore().ReloadInputDevices(); + Settings::RestoreGlobalState(QtCommon::system->IsPoweredOn()); + QtCommon::system->HIDCore().ReloadInputDevices(); UpdateStatusButtons(); } @@ -2459,6 +2321,7 @@ void GMainWindow::ShutdownGame() { return; } + // TODO(crueter): make this common as well (frontend_common?) play_time_manager->Stop(); OnShutdownBegin(); OnEmulationStopTimeExpired(); @@ -2503,21 +2366,22 @@ void GMainWindow::OnGameListLoadFile(QString game_path, u64 program_id) { BootGame(game_path, params); } +// TODO(crueter): Common profile selector void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, const std::string& game_path) { std::filesystem::path path; QString open_target; - const auto [user_save_size, device_save_size] = [this, &game_path, &program_id] { - const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), - system->GetContentProvider()}; + const auto [user_save_size, device_save_size] = [&game_path, &program_id] { + const FileSys::PatchManager pm{program_id, QtCommon::system->GetFileSystemController(), + QtCommon::system->GetContentProvider()}; const auto control = pm.GetControlMetadata().first; if (control != nullptr) { return std::make_pair(control->GetDefaultNormalSaveSize(), control->GetDeviceSaveDataSize()); } else { - const auto file = Core::GetGameFileFromPath(vfs, game_path); - const auto loader = Loader::GetLoader(*system, file); + const auto file = Core::GetGameFileFromPath(QtCommon::vfs, game_path); + const auto loader = Loader::GetLoader(*QtCommon::system, file); FileSys::NACP nacp{}; loader->ReadControlData(nacp); @@ -2536,7 +2400,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target open_target = tr("Save Data"); const auto nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir); auto vfs_nand_dir = - vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read); + QtCommon::vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read); if (has_user_save) { // User save data @@ -2547,7 +2411,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target .display_options = {}, .purpose = Service::AM::Frontend::UserSelectionPurpose::General, }; - QtProfileSelectionDialog dialog(*system, this, parameters); + QtProfileSelectionDialog dialog(*QtCommon::system, this, parameters); dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); @@ -2565,7 +2429,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target } const auto user_id = - system->GetProfileManager().GetUser(static_cast(index)); + QtCommon::system->GetProfileManager().GetUser(static_cast(index)); ASSERT(user_id); const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( @@ -2611,19 +2475,6 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); } -void GMainWindow::OnTransferableShaderCacheOpenFile(u64 program_id) { - const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); - const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)}; - if (!Common::FS::CreateDirs(shader_cache_folder_path)) { - QMessageBox::warning(this, tr("Error Opening Transferable Shader Cache"), - tr("Failed to create the shader cache directory for this title.")); - return; - } - const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)}; - const auto qt_shader_cache_path = QString::fromStdString(shader_path_string); - QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path)); -} - static bool RomFSRawCopy(size_t total_size, size_t& read_size, QProgressDialog& dialog, const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, bool full) { @@ -2686,26 +2537,17 @@ static bool RomFSRawCopy(size_t total_size, size_t& read_size, QProgressDialog& return true; } -QString GMainWindow::GetGameListErrorRemoving(InstalledEntryType type) const { - switch (type) { - case InstalledEntryType::Game: - return tr("Error Removing Contents"); - case InstalledEntryType::Update: - return tr("Error Removing Update"); - case InstalledEntryType::AddOnContent: - return tr("Error Removing DLC"); - default: - return QStringLiteral("Error Removing "); - } -} -void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { +// TODO(crueter): All this can be transfered to qt_common +// Aldoe I need to decide re: message boxes for QML +// translations_common? strings_common? qt_strings? who knows +void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, QtCommon::Game::InstalledEntryType type) { const QString entry_question = [type] { switch (type) { - case InstalledEntryType::Game: + case QtCommon::Game::InstalledEntryType::Game: return tr("Remove Installed Game Contents?"); - case InstalledEntryType::Update: + case QtCommon::Game::InstalledEntryType::Update: return tr("Remove Installed Game Update?"); - case InstalledEntryType::AddOnContent: + case QtCommon::Game::InstalledEntryType::AddOnContent: return tr("Remove Installed Game DLC?"); default: return QStringLiteral("Remove Installed Game ?"); @@ -2717,18 +2559,19 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT return; } + // TODO(crueter): move this to QtCommon (populate async?) switch (type) { - case InstalledEntryType::Game: - RemoveBaseContent(program_id, type); + case QtCommon::Game::InstalledEntryType::Game: + QtCommon::Game::RemoveBaseContent(program_id, type); [[fallthrough]]; - case InstalledEntryType::Update: - RemoveUpdateContent(program_id, type); - if (type != InstalledEntryType::Game) { + case QtCommon::Game::InstalledEntryType::Update: + QtCommon::Game::RemoveUpdateContent(program_id, type); + if (type != QtCommon::Game::InstalledEntryType::Game) { break; } [[fallthrough]]; - case InstalledEntryType::AddOnContent: - RemoveAddOnContent(program_id, type); + case QtCommon::Game::InstalledEntryType::AddOnContent: + QtCommon::Game::RemoveAddOnContent(program_id, type); break; } Common::FS::RemoveDirRecursively(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / @@ -2736,55 +2579,19 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT game_list->PopulateAsync(UISettings::values.game_dirs); } -void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) { - const auto res = - ContentManager::RemoveBaseContent(system->GetFileSystemController(), program_id); - if (res) { - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed the installed base game.")); - } else { - QMessageBox::warning( - this, GetGameListErrorRemoving(type), - tr("The base game is not installed in the NAND and cannot be removed.")); - } -} - -void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) { - const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id); - if (res) { - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed the installed update.")); - } else { - QMessageBox::warning(this, GetGameListErrorRemoving(type), - tr("There is no update installed for this title.")); - } -} - -void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) { - const size_t count = ContentManager::RemoveAllDLC(*system, program_id); - if (count == 0) { - QMessageBox::warning(this, GetGameListErrorRemoving(type), - tr("There are no DLC installed for this title.")); - return; - } - - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed %1 installed DLC.").arg(count)); -} - -void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, +void GMainWindow::OnGameListRemoveFile(u64 program_id, QtCommon::Game::GameListRemoveTarget target, const std::string& game_path) { const QString question = [target] { switch (target) { - case GameListRemoveTarget::GlShaderCache: + case QtCommon::Game::GameListRemoveTarget::GlShaderCache: return tr("Delete OpenGL Transferable Shader Cache?"); - case GameListRemoveTarget::VkShaderCache: + case QtCommon::Game::GameListRemoveTarget::VkShaderCache: return tr("Delete Vulkan Transferable Shader Cache?"); - case GameListRemoveTarget::AllShaderCache: + case QtCommon::Game::GameListRemoveTarget::AllShaderCache: return tr("Delete All Transferable Shader Caches?"); - case GameListRemoveTarget::CustomConfiguration: + case QtCommon::Game::GameListRemoveTarget::CustomConfiguration: return tr("Remove Custom Game Configuration?"); - case GameListRemoveTarget::CacheStorage: + case QtCommon::Game::GameListRemoveTarget::CacheStorage: return tr("Remove Cache Storage?"); default: return QString{}; @@ -2797,20 +2604,20 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ } switch (target) { - case GameListRemoveTarget::VkShaderCache: - RemoveVulkanDriverPipelineCache(program_id); + case QtCommon::Game::GameListRemoveTarget::VkShaderCache: + QtCommon::Game::RemoveVulkanDriverPipelineCache(program_id); [[fallthrough]]; - case GameListRemoveTarget::GlShaderCache: - RemoveTransferableShaderCache(program_id, target); + case QtCommon::Game::GameListRemoveTarget::GlShaderCache: + QtCommon::Game::RemoveTransferableShaderCache(program_id, target); break; - case GameListRemoveTarget::AllShaderCache: - RemoveAllTransferableShaderCaches(program_id); + case QtCommon::Game::GameListRemoveTarget::AllShaderCache: + QtCommon::Game::RemoveAllTransferableShaderCaches(program_id); break; - case GameListRemoveTarget::CustomConfiguration: - RemoveCustomConfiguration(program_id, game_path); + case QtCommon::Game::GameListRemoveTarget::CustomConfiguration: + QtCommon::Game::RemoveCustomConfiguration(program_id, game_path); break; - case GameListRemoveTarget::CacheStorage: - RemoveCacheStorage(program_id); + case QtCommon::Game::GameListRemoveTarget::CacheStorage: + QtCommon::Game::RemoveCacheStorage(program_id); break; } } @@ -2826,107 +2633,6 @@ void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) { game_list->PopulateAsync(UISettings::values.game_dirs); } -void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { - const auto target_file_name = [target] { - switch (target) { - case GameListRemoveTarget::GlShaderCache: - return "opengl.bin"; - case GameListRemoveTarget::VkShaderCache: - return "vulkan.bin"; - default: - return ""; - } - }(); - const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); - const auto shader_cache_folder_path = shader_cache_dir / fmt::format("{:016x}", program_id); - const auto target_file = shader_cache_folder_path / target_file_name; - - if (!Common::FS::Exists(target_file)) { - QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), - tr("A shader cache for this title does not exist.")); - return; - } - if (Common::FS::RemoveFile(target_file)) { - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed the transferable shader cache.")); - } else { - QMessageBox::warning(this, tr("Error Removing Transferable Shader Cache"), - tr("Failed to remove the transferable shader cache.")); - } -} - -void GMainWindow::RemoveVulkanDriverPipelineCache(u64 program_id) { - static constexpr std::string_view target_file_name = "vulkan_pipelines.bin"; - - const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); - const auto shader_cache_folder_path = shader_cache_dir / fmt::format("{:016x}", program_id); - const auto target_file = shader_cache_folder_path / target_file_name; - - if (!Common::FS::Exists(target_file)) { - return; - } - if (!Common::FS::RemoveFile(target_file)) { - QMessageBox::warning(this, tr("Error Removing Vulkan Driver Pipeline Cache"), - tr("Failed to remove the driver pipeline cache.")); - } -} - -void GMainWindow::RemoveAllTransferableShaderCaches(u64 program_id) { - const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); - const auto program_shader_cache_dir = shader_cache_dir / fmt::format("{:016x}", program_id); - - if (!Common::FS::Exists(program_shader_cache_dir)) { - QMessageBox::warning(this, tr("Error Removing Transferable Shader Caches"), - tr("A shader cache for this title does not exist.")); - return; - } - if (Common::FS::RemoveDirRecursively(program_shader_cache_dir)) { - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed the transferable shader caches.")); - } else { - QMessageBox::warning(this, tr("Error Removing Transferable Shader Caches"), - tr("Failed to remove the transferable shader cache directory.")); - } -} - -void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& game_path) { - const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path)); - const auto config_file_name = - program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini") - : fmt::format("{:016X}.ini", program_id); - const auto custom_config_file_path = - Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir) / "custom" / config_file_name; - - if (!Common::FS::Exists(custom_config_file_path)) { - QMessageBox::warning(this, tr("Error Removing Custom Configuration"), - tr("A custom configuration for this title does not exist.")); - return; - } - - if (Common::FS::RemoveFile(custom_config_file_path)) { - QMessageBox::information(this, tr("Successfully Removed"), - tr("Successfully removed the custom game configuration.")); - } else { - QMessageBox::warning(this, tr("Error Removing Custom Configuration"), - tr("Failed to remove the custom game configuration.")); - } -} - -void GMainWindow::RemoveCacheStorage(u64 program_id) { - const auto nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir); - auto vfs_nand_dir = - vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read); - - const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath( - {}, vfs_nand_dir, FileSys::SaveDataSpaceId::User, FileSys::SaveDataType::Cache, - 0 /* program_id */, {}, 0); - - const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path); - - // Not an error if it wasn't cleared. - Common::FS::RemoveDirRecursively(path); -} - void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target) { const auto failed = [this] { @@ -2936,7 +2642,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa }; const auto loader = - Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read)); + Loader::GetLoader(*QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read)); if (loader == nullptr) { failed(); return; @@ -2945,7 +2651,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa FileSys::VirtualFile packed_update_raw{}; loader->ReadUpdateRaw(packed_update_raw); - const auto& installed = system->GetContentProvider(); + const auto& installed = QtCommon::system->GetContentProvider(); u64 title_id{}; u8 raw_type{}; @@ -2977,14 +2683,14 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa const auto path = Common::FS::PathToUTF8String(dump_dir / romfs_dir); - const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), installed}; + const FileSys::PatchManager pm{title_id, QtCommon::system->GetFileSystemController(), installed}; auto romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, packed_update_raw, false); - const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::OpenMode::ReadWrite); + const auto out = VfsFilesystemCreateDirectoryWrapper(path, FileSys::OpenMode::ReadWrite); if (out == nullptr) { failed(); - vfs->DeleteDirectory(path); + QtCommon::vfs->DeleteDirectory(path); return; } @@ -2998,7 +2704,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa selections, 0, false, &ok); if (!ok) { failed(); - vfs->DeleteDirectory(path); + QtCommon::vfs->DeleteDirectory(path); return; } @@ -3038,41 +2744,13 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa } else { progress.close(); failed(); - vfs->DeleteDirectory(path); + QtCommon::vfs->DeleteDirectory(path); } } +// END void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { - const auto NotImplemented = [this] { - QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), - tr("File contents were not checked for validity.")); - }; - - QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); - progress.setWindowModality(Qt::WindowModal); - progress.setMinimumDuration(100); - progress.setAutoClose(false); - progress.setAutoReset(false); - - const auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { - progress.setValue(static_cast((processed_size * 100) / total_size)); - return progress.wasCanceled(); - }; - - const auto result = ContentManager::VerifyGameContents(*system, game_path, QtProgressCallback); - progress.close(); - switch (result) { - case ContentManager::GameVerificationResult::Success: - QMessageBox::information(this, tr("Integrity verification succeeded!"), - tr("The operation completed successfully.")); - break; - case ContentManager::GameVerificationResult::Failed: - QMessageBox::critical(this, tr("Integrity verification failed!"), - tr("File contents may be corrupt.")); - break; - case ContentManager::GameVerificationResult::NotImplemented: - NotImplemented(); - } + QtCommon::Content::VerifyGameContents(game_path); } void GMainWindow::OnGameListCopyTID(u64 program_id) { @@ -3093,172 +2771,12 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, QUrl(QStringLiteral("https://eden-emulator.github.io/game/") + directory)); } -bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path, - const std::string& comment, - const std::filesystem::path& icon_path, - const std::filesystem::path& command, - const std::string& arguments, const std::string& categories, - const std::string& keywords, const std::string& name) try { -#ifdef _WIN32 // Windows - HRESULT hr = CoInitialize(nullptr); - if (FAILED(hr)) { - LOG_ERROR(Frontend, "CoInitialize failed"); - return false; - } - SCOPE_EXIT { - CoUninitialize(); - }; - IShellLinkW* ps1 = nullptr; - IPersistFile* persist_file = nullptr; - SCOPE_EXIT { - if (persist_file != nullptr) { - persist_file->Release(); - } - if (ps1 != nullptr) { - ps1->Release(); - } - }; - HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW, - reinterpret_cast(&ps1)); - if (FAILED(hres)) { - LOG_ERROR(Frontend, "Failed to create IShellLinkW instance"); - return false; - } - hres = ps1->SetPath(command.c_str()); - if (FAILED(hres)) { - LOG_ERROR(Frontend, "Failed to set path"); - return false; - } - if (!arguments.empty()) { - hres = ps1->SetArguments(Common::UTF8ToUTF16W(arguments).data()); - if (FAILED(hres)) { - LOG_ERROR(Frontend, "Failed to set arguments"); - return false; - } - } - if (!comment.empty()) { - hres = ps1->SetDescription(Common::UTF8ToUTF16W(comment).data()); - if (FAILED(hres)) { - LOG_ERROR(Frontend, "Failed to set description"); - return false; - } - } - if (std::filesystem::is_regular_file(icon_path)) { - hres = ps1->SetIconLocation(icon_path.c_str(), 0); - if (FAILED(hres)) { - LOG_ERROR(Frontend, "Failed to set icon location"); - return false; - } - } - hres = ps1->QueryInterface(IID_IPersistFile, reinterpret_cast(&persist_file)); - if (FAILED(hres)) { - LOG_ERROR(Frontend, "Failed to get IPersistFile interface"); - return false; - } - hres = persist_file->Save(std::filesystem::path{shortcut_path / (name + ".lnk")}.c_str(), TRUE); - if (FAILED(hres)) { - LOG_ERROR(Frontend, "Failed to save shortcut"); - return false; - } - return true; -#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) // Any desktop NIX - std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop"); - std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc); - if (!shortcut_stream.is_open()) { - LOG_ERROR(Frontend, "Failed to create shortcut"); - return false; - } - // TODO: Migrate fmt::print to std::print in futures STD C++ 23. - fmt::print(shortcut_stream, "[Desktop Entry]\n"); - fmt::print(shortcut_stream, "Type=Application\n"); - fmt::print(shortcut_stream, "Version=1.0\n"); - fmt::print(shortcut_stream, "Name={}\n", name); - if (!comment.empty()) { - fmt::print(shortcut_stream, "Comment={}\n", comment); - } - if (std::filesystem::is_regular_file(icon_path)) { - fmt::print(shortcut_stream, "Icon={}\n", icon_path.string()); - } - fmt::print(shortcut_stream, "TryExec={}\n", command.string()); - fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments); - if (!categories.empty()) { - fmt::print(shortcut_stream, "Categories={}\n", categories); - } - if (!keywords.empty()) { - fmt::print(shortcut_stream, "Keywords={}\n", keywords); - } - return true; -#else // Unsupported platform - return false; -#endif -} catch (const std::exception& e) { - LOG_ERROR(Frontend, "Failed to create shortcut: {}", e.what()); - return false; -} -// Messages in pre-defined message boxes for less code spaghetti -bool GMainWindow::CreateShortcutMessagesGUI(QWidget* parent, int imsg, const QString& game_title) { - int result = 0; - QMessageBox::StandardButtons buttons; - switch (imsg) { - case GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES: - buttons = QMessageBox::Yes | QMessageBox::No; - result = - QMessageBox::information(parent, tr("Create Shortcut"), - tr("Do you want to launch the game in fullscreen?"), buttons); - return result == QMessageBox::Yes; - case GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS: - QMessageBox::information(parent, tr("Create Shortcut"), - tr("Successfully created a shortcut to %1").arg(game_title)); - return false; - case GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING: - buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel; - result = - QMessageBox::warning(this, tr("Create Shortcut"), - tr("This will create a shortcut to the current AppImage. This may " - "not work well if you update. Continue?"), - buttons); - return result == QMessageBox::Ok; - default: - buttons = QMessageBox::Ok; - QMessageBox::critical(parent, tr("Create Shortcut"), - tr("Failed to create a shortcut to %1").arg(game_title), buttons); - return false; - } -} - -bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, - std::filesystem::path& out_icon_path) { - // Get path to Yuzu icons directory & icon extension - std::string ico_extension = "png"; -#if defined(_WIN32) - out_icon_path = Common::FS::GetEdenPath(Common::FS::EdenPath::IconsDir); - ico_extension = "ico"; -#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) - out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256"; -#endif - // Create icons directory if it doesn't exist - if (!Common::FS::CreateDirs(out_icon_path)) { - QMessageBox::critical( - this, tr("Create Icon"), - tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.") - .arg(QString::fromStdString(out_icon_path.string())), - QMessageBox::StandardButton::Ok); - out_icon_path.clear(); - return false; - } - - // Create icon file path - out_icon_path /= (program_id == 0 ? fmt::format("eden-{}.{}", game_file_name, ico_extension) - : fmt::format("eden-{:016X}.{}", program_id, ico_extension)); - return true; -} - void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, - GameListShortcutTarget target) { + const QtCommon::Game::ShortcutTarget target) { // Create shortcut std::string arguments = fmt::format("-g \"{:s}\"", game_path); - CreateShortcut(game_path, program_id, "", target, arguments, true); + QtCommon::Game::CreateShortcut(game_path, program_id, "", target, arguments, true); } void GMainWindow::OnGameListOpenDirectory(const QString& directory) { @@ -3313,8 +2831,8 @@ void GMainWindow::OnGameListShowList(bool show) { void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { u64 title_id{}; - const auto v_file = Core::GetGameFileFromPath(vfs, file); - const auto loader = Loader::GetLoader(*system, v_file); + const auto v_file = Core::GetGameFileFromPath(QtCommon::vfs, file); + const auto loader = Loader::GetLoader(*QtCommon::system, v_file); if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { QMessageBox::information(this, tr("Properties"), @@ -3441,8 +2959,8 @@ void GMainWindow::OnMenuInstallToNAND() { } return false; }; - future = QtConcurrent::run([this, &file, progress_callback] { - return ContentManager::InstallNSP(*system, *vfs, file.toStdString(), + future = QtConcurrent::run([&file, progress_callback] { + return ContentManager::InstallNSP(*QtCommon::system, *QtCommon::vfs, file.toStdString(), progress_callback); }); @@ -3534,7 +3052,7 @@ ContentManager::InstallResult GMainWindow::InstallNCA(const QString& filename) { } const bool is_application = index >= static_cast(FileSys::TitleType::Application); - const auto& fs_controller = system->GetFileSystemController(); + const auto& fs_controller = QtCommon::system->GetFileSystemController(); auto* registered_cache = is_application ? fs_controller.GetUserNANDContents() : fs_controller.GetSystemNANDContents(); @@ -3545,7 +3063,7 @@ ContentManager::InstallResult GMainWindow::InstallNCA(const QString& filename) { } return false; }; - return ContentManager::InstallNCA(*vfs, filename.toStdString(), *registered_cache, + return ContentManager::InstallNCA(*QtCommon::vfs, filename.toStdString(), *registered_cache, static_cast(index), progress_callback); } @@ -3574,7 +3092,7 @@ void GMainWindow::OnStartGame() { UpdateMenuState(); OnTasStateChanged(); - play_time_manager->SetProgramId(system->GetApplicationProcessProgramID()); + play_time_manager->SetProgramId(QtCommon::system->GetApplicationProcessProgramID()); play_time_manager->Start(); discord_rpc->Update(); @@ -3585,7 +3103,7 @@ void GMainWindow::OnStartGame() { } void GMainWindow::OnRestartGame() { - if (!system->IsPoweredOn()) { + if (!QtCommon::system->IsPoweredOn()) { return; } @@ -3633,7 +3151,7 @@ void GMainWindow::OnStopGame() { bool GMainWindow::ConfirmShutdownGame() { if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) { - if (system->GetExitLocked()) { + if (QtCommon::system->GetExitLocked()) { if (!ConfirmForceLockedExit()) { return false; } @@ -3645,7 +3163,7 @@ bool GMainWindow::ConfirmShutdownGame() { } else { if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game && - system->GetExitLocked()) { + QtCommon::system->GetExitLocked()) { if (!ConfirmForceLockedExit()) { return false; } @@ -3672,12 +3190,12 @@ void GMainWindow::OnExit() { } void GMainWindow::OnSaveConfig() { - system->ApplySettings(); + QtCommon::system->ApplySettings(); config->SaveAllValues(); } void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { - error_applet = new OverlayDialog(render_window, *system, error_code, error_text, QString{}, + error_applet = new OverlayDialog(render_window, *QtCommon::system, error_code, error_text, QString{}, tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); SCOPE_EXIT { error_applet->deleteLater(); @@ -3906,7 +3424,7 @@ void GMainWindow::OnConfigure() { Settings::SetConfiguringGlobal(true); ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), - vk_device_records, *system, + vk_device_records, *QtCommon::system, !multiplayer_state->IsHostingPublicRoom()); connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, &GMainWindow::OnLanguageChanged); @@ -4007,7 +3525,7 @@ void GMainWindow::OnConfigure() { UpdateStatusButtons(); controller_dialog->refreshConfiguration(); - system->ApplySettings(); + QtCommon::system->ApplySettings(); } void GMainWindow::OnConfigureTas() { @@ -4015,7 +3533,7 @@ void GMainWindow::OnConfigureTas() { const auto result = dialog.exec(); if (result != QDialog::Accepted && !UISettings::values.configuration_applied) { - Settings::RestoreGlobalState(system->IsPoweredOn()); + Settings::RestoreGlobalState(QtCommon::system->IsPoweredOn()); return; } else if (result == QDialog::Accepted) { dialog.ApplyConfiguration(); @@ -4029,7 +3547,7 @@ void GMainWindow::OnTasStartStop() { } // Disable system buttons to prevent TAS from executing a hotkey - auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* controller = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); controller->ResetSystemButtons(); input_subsystem->GetTas()->StartStop(); @@ -4045,7 +3563,7 @@ void GMainWindow::OnTasRecord() { } // Disable system buttons to prevent TAS from recording a hotkey - auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* controller = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); controller->ResetSystemButtons(); const bool is_recording = input_subsystem->GetTas()->Record(); @@ -4067,8 +3585,8 @@ void GMainWindow::OnTasReset() { void GMainWindow::OnToggleDockedMode() { const bool is_docked = Settings::IsDockedMode(); - auto* player_1 = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); - auto* handheld = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + auto* player_1 = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + auto* handheld = QtCommon::system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); if (!is_docked && handheld->IsConnected()) { QMessageBox::warning(this, tr("Invalid config detected"), @@ -4083,7 +3601,7 @@ void GMainWindow::OnToggleDockedMode() { Settings::values.use_docked_mode.SetValue(is_docked ? Settings::ConsoleMode::Handheld : Settings::ConsoleMode::Docked); UpdateDockedButton(); - OnDockedModeChanged(is_docked, !is_docked, *system); + OnDockedModeChanged(is_docked, !is_docked, *QtCommon::system); } void GMainWindow::OnToggleGpuAccuracy() { @@ -4100,7 +3618,7 @@ void GMainWindow::OnToggleGpuAccuracy() { } } - system->ApplySettings(); + QtCommon::system->ApplySettings(); UpdateGPUAccuracyButton(); } @@ -4165,20 +3683,20 @@ void GMainWindow::OnToggleGraphicsAPI() { } void GMainWindow::OnConfigurePerGame() { - const u64 title_id = system->GetApplicationProcessProgramID(); + const u64 title_id = QtCommon::system->GetApplicationProcessProgramID(); OpenPerGameConfiguration(title_id, current_game_path.toStdString()); } void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) { - const auto v_file = Core::GetGameFileFromPath(vfs, file_name); + const auto v_file = Core::GetGameFileFromPath(QtCommon::vfs, file_name); Settings::SetConfiguringGlobal(false); - ConfigurePerGame dialog(this, title_id, file_name, vk_device_records, *system); + ConfigurePerGame dialog(this, title_id, file_name, vk_device_records, *QtCommon::system); dialog.LoadFromFile(v_file); const auto result = dialog.exec(); if (result != QDialog::Accepted && !UISettings::values.configuration_applied) { - Settings::RestoreGlobalState(system->IsPoweredOn()); + Settings::RestoreGlobalState(QtCommon::system->IsPoweredOn()); return; } else if (result == QDialog::Accepted) { dialog.ApplyConfiguration(); @@ -4190,9 +3708,9 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file } // Do not cause the global config to write local settings into the config file - const bool is_powered_on = system->IsPoweredOn(); + const bool is_powered_on = QtCommon::system->IsPoweredOn(); Settings::RestoreGlobalState(is_powered_on); - system->HIDCore().ReloadInputDevices(); + QtCommon::system->HIDCore().ReloadInputDevices(); UISettings::values.configuration_applied = false; @@ -4236,6 +3754,7 @@ void GMainWindow::OnLoadAmiibo() { LoadAmiibo(filename); } +// TODO(crueter): does this need to be ported to QML? bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { @@ -4246,7 +3765,7 @@ bool GMainWindow::question(QWidget* parent, const QString& title, const QString& box_dialog->setDefaultButton(defaultButton); ControllerNavigation* controller_navigation = - new ControllerNavigation(system->HIDCore(), box_dialog); + new ControllerNavigation(QtCommon::system->HIDCore(), box_dialog); connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, [box_dialog](Qt::Key key) { QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); @@ -4287,182 +3806,35 @@ void GMainWindow::LoadAmiibo(const QString& filename) { } void GMainWindow::OnOpenRootDataFolder() { - QDesktopServices::openUrl( - QUrl(QString::fromStdString(Common::FS::GetEdenPathString(Common::FS::EdenPath::EdenDir)))); + QtCommon::Game::OpenRootDataFolder(); } -void GMainWindow::OnOpenNANDFolder() { - QDesktopServices::openUrl(QUrl::fromLocalFile( - QString::fromStdString(Common::FS::GetEdenPathString(Common::FS::EdenPath::NANDDir)))); +void GMainWindow::OnOpenNANDFolder() +{ + QtCommon::Game::OpenNANDFolder(); } -void GMainWindow::OnOpenSDMCFolder() { - QDesktopServices::openUrl(QUrl::fromLocalFile( - QString::fromStdString(Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir)))); +void GMainWindow::OnOpenSDMCFolder() +{ + QtCommon::Game::OpenSDMCFolder(); } -void GMainWindow::OnOpenModFolder() { - QDesktopServices::openUrl(QUrl::fromLocalFile( - QString::fromStdString(Common::FS::GetEdenPathString(Common::FS::EdenPath::LoadDir)))); +void GMainWindow::OnOpenModFolder() +{ + QtCommon::Game::OpenModFolder(); } -void GMainWindow::OnOpenLogFolder() { - QDesktopServices::openUrl(QUrl::fromLocalFile( - QString::fromStdString(Common::FS::GetEdenPathString(Common::FS::EdenPath::LogDir)))); +void GMainWindow::OnOpenLogFolder() +{ + QtCommon::Game::OpenLogFolder(); } void GMainWindow::OnVerifyInstalledContents() { - // Initialize a progress dialog. - QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); - progress.setWindowModality(Qt::WindowModal); - progress.setMinimumDuration(100); - progress.setAutoClose(false); - progress.setAutoReset(false); - - // Declare progress callback. - auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { - progress.setValue(static_cast((processed_size * 100) / total_size)); - return progress.wasCanceled(); - }; - - const std::vector result = - ContentManager::VerifyInstalledContents(*system, *provider, QtProgressCallback); - progress.close(); - - if (result.empty()) { - QMessageBox::information(this, tr("Integrity verification succeeded!"), - tr("The operation completed successfully.")); - } else { - const auto failed_names = - QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); - QMessageBox::critical( - this, tr("Integrity verification failed!"), - tr("Verification failed for the following files:\n\n%1").arg(failed_names)); - } + QtCommon::Content::VerifyInstalledContents(); } void GMainWindow::InstallFirmware(const QString& location, bool recursive) { - QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this); - progress.setWindowModality(Qt::WindowModal); - progress.setMinimumDuration(100); - progress.setAutoClose(false); - progress.setAutoReset(false); - progress.show(); - - // Declare progress callback. - auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { - progress.setValue(static_cast((processed_size * 100) / total_size)); - return progress.wasCanceled(); - }; - - LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString()); - - // Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in - // there.) - std::filesystem::path firmware_source_path = location.toStdString(); - if (!Common::FS::IsDir(firmware_source_path)) { - progress.close(); - return; - } - - std::vector out; - const Common::FS::DirEntryCallable callback = - [&out](const std::filesystem::directory_entry& entry) { - if (entry.path().has_extension() && entry.path().extension() == ".nca") { - out.emplace_back(entry.path()); - } - - return true; - }; - - QtProgressCallback(100, 10); - - if (recursive) { - Common::FS::IterateDirEntriesRecursively(firmware_source_path, callback, - Common::FS::DirEntryFilter::File); - } else { - Common::FS::IterateDirEntries(firmware_source_path, callback, - Common::FS::DirEntryFilter::File); - } - - if (out.size() <= 0) { - progress.close(); - QMessageBox::warning(this, tr("Firmware install failed"), - tr("Unable to locate potential firmware NCA files")); - return; - } - - // Locate and erase the content of nand/system/Content/registered/*.nca, if any. - auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory(); - if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { - progress.close(); - QMessageBox::critical(this, tr("Firmware install failed"), - tr("Failed to delete one or more firmware file.")); - return; - } - - LOG_INFO(Frontend, - "Cleaned nand/system/Content/registered folder in preparation for new firmware."); - - QtProgressCallback(100, 20); - - auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered"); - - bool success = true; - int i = 0; - for (const auto& firmware_src_path : out) { - i++; - auto firmware_src_vfile = - vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read); - auto firmware_dst_vfile = - firmware_vdir->CreateFileRelative(firmware_src_path.filename().string()); - - if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) { - LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!", - firmware_src_path.generic_string(), firmware_src_path.filename().string()); - success = false; - } - - if (QtProgressCallback( - 100, 20 + static_cast(((i) / static_cast(out.size())) * 70.0))) { - progress.close(); - QMessageBox::warning( - this, tr("Firmware install failed"), - tr("Firmware installation cancelled, firmware may be in a bad state or corrupted. " - "Restart Eden or re-install firmware.")); - return; - } - } - - if (!success) { - progress.close(); - QMessageBox::critical(this, tr("Firmware install failed"), - tr("One or more firmware files failed to copy into NAND.")); - return; - } - - // Re-scan VFS for the newly placed firmware files. - system->GetFileSystemController().CreateFactories(*vfs); - - auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) { - progress.setValue(90 + static_cast((processed_size * 10) / total_size)); - return progress.wasCanceled(); - }; - - auto result = - ContentManager::VerifyInstalledContents(*system, *provider, VerifyFirmwareCallback, true); - - if (result.size() > 0) { - const auto failed_names = - QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); - progress.close(); - QMessageBox::critical( - this, tr("Firmware integrity verification failed!"), - tr("Verification failed for the following files:\n\n%1").arg(failed_names)); - return; - } - - progress.close(); + QtCommon::Content::InstallFirmware(location, recursive); OnCheckFirmwareDecryption(); } @@ -4509,32 +3881,14 @@ void GMainWindow::OnInstallFirmwareFromZIP() { return; } - namespace fs = std::filesystem; - fs::path tmp{std::filesystem::temp_directory_path()}; + const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location); - if (!std::filesystem::create_directories(tmp / "eden" / "firmware")) { - goto unzipFailed; - } - - { - tmp /= "eden"; - tmp /= "firmware"; - - QString qCacheDir = QString::fromStdString(tmp.string()); - - QFile zip(firmware_zip_location); - - QStringList result = JlCompress::extractDir(&zip, qCacheDir); - if (result.isEmpty()) { - goto unzipFailed; - } - - // In this case, it has to be done recursively, since sometimes people - // will pack it into a subdirectory after dumping + // In this case, it has to be done recursively, since sometimes people + // will pack it into a subdirectory after dumping + if (!qCacheDir.isEmpty()) { InstallFirmware(qCacheDir, true); - std::error_code ec; - std::filesystem::remove_all(tmp, ec); + std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware", ec); if (ec) { QMessageBox::warning(this, tr("Firmware cleanup failed"), @@ -4543,14 +3897,7 @@ void GMainWindow::OnInstallFirmwareFromZIP() { "again.\nOS reported error: %1") .arg(QString::fromStdString(ec.message()))); } - - return; } -unzipFailed: - QMessageBox::critical( - this, tr("Firmware unzip failed"), - tr("Check write permissions in the system temp directory and try again.")); - return; } void GMainWindow::OnInstallDecryptionKeys() { @@ -4559,30 +3906,9 @@ void GMainWindow::OnInstallDecryptionKeys() { return; } - const QString key_source_location = QFileDialog::getOpenFileName( - this, tr("Select Dumped Keys Location"), {}, QStringLiteral("Decryption Keys (*.keys)"), {}, - QFileDialog::ReadOnly); - if (key_source_location.isEmpty()) { - return; - } + QtCommon::Content::InstallKeys(); - FirmwareManager::KeyInstallResult result = - FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys"); - - system->GetFileSystemController().CreateFactories(*vfs); game_list->PopulateAsync(UISettings::values.game_dirs); - - switch (result) { - case FirmwareManager::KeyInstallResult::Success: - QMessageBox::information(this, tr("Decryption Keys install succeeded"), - tr("Decryption Keys were successfully installed")); - break; - default: - QMessageBox::critical(this, tr("Decryption Keys install failed"), - tr(FirmwareManager::GetKeyInstallResultString(result))); - break; - } - OnCheckFirmwareDecryption(); } @@ -4609,18 +3935,20 @@ void GMainWindow::OnToggleStatusBar() { statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); } -void GMainWindow::OnGameListRefresh() { - // force reload add-ons etc - game_list->ForceRefreshGameDirectory(); +void GMainWindow::OnGameListRefresh() +{ + // Resets metadata cache and reloads + QtCommon::Game::ResetMetadata(); + game_list->RefreshGameDirectory(); SetFirmwareVersion(); } void GMainWindow::OnAlbum() { constexpr u64 AlbumId = static_cast(Service::AM::AppletProgramId::PhotoViewer); - auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); if (!bis_system) { QMessageBox::warning(this, tr("No firmware available"), - tr("Please install the firmware to use the Album applet.")); + tr("Please install firmware to use the Album applet.")); return; } @@ -4631,7 +3959,7 @@ void GMainWindow::OnAlbum() { return; } - system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::PhotoViewer); + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::PhotoViewer); const auto filename = QString::fromStdString(album_nca->GetFullPath()); UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); @@ -4640,10 +3968,10 @@ void GMainWindow::OnAlbum() { void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) { constexpr u64 CabinetId = static_cast(Service::AM::AppletProgramId::Cabinet); - auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); if (!bis_system) { QMessageBox::warning(this, tr("No firmware available"), - tr("Please install the firmware to use the Cabinet applet.")); + tr("Please install firmware to use the Cabinet applet.")); return; } @@ -4654,8 +3982,8 @@ void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) { return; } - system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::Cabinet); - system->GetFrontendAppletHolder().SetCabinetMode(mode); + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::Cabinet); + QtCommon::system->GetFrontendAppletHolder().SetCabinetMode(mode); const auto filename = QString::fromStdString(cabinet_nca->GetFullPath()); UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); @@ -4664,10 +3992,10 @@ void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) { void GMainWindow::OnMiiEdit() { constexpr u64 MiiEditId = static_cast(Service::AM::AppletProgramId::MiiEdit); - auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); if (!bis_system) { QMessageBox::warning(this, tr("No firmware available"), - tr("Please install the firmware to use the Mii editor.")); + tr("Please install firmware to use the Mii editor.")); return; } @@ -4678,7 +4006,7 @@ void GMainWindow::OnMiiEdit() { return; } - system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::MiiEdit); + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::MiiEdit); const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath())); UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); @@ -4687,10 +4015,10 @@ void GMainWindow::OnMiiEdit() { void GMainWindow::OnOpenControllerMenu() { constexpr u64 ControllerAppletId = static_cast(Service::AM::AppletProgramId::Controller); - auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); if (!bis_system) { QMessageBox::warning(this, tr("No firmware available"), - tr("Please install the firmware to use the Controller Menu.")); + tr("Please install firmware to use the Controller Menu.")); return; } @@ -4702,7 +4030,7 @@ void GMainWindow::OnOpenControllerMenu() { return; } - system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::Controller); + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::Controller); const auto filename = QString::fromStdString((controller_applet_nca->GetFullPath())); UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); @@ -4711,7 +4039,7 @@ void GMainWindow::OnOpenControllerMenu() { } void GMainWindow::OnHomeMenu() { - auto result = FirmwareManager::VerifyFirmware(*system.get()); + auto result = FirmwareManager::VerifyFirmware(*QtCommon::system.get()); switch (result) { case FirmwareManager::ErrorFirmwareMissing: @@ -4746,7 +4074,7 @@ void GMainWindow::OnHomeMenu() { } constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); - auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); if (!qlaunch_applet_nca) { @@ -4755,7 +4083,7 @@ void GMainWindow::OnHomeMenu() { return; } - system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::QLaunch); + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::QLaunch); const auto filename = QString::fromStdString((qlaunch_applet_nca->GetFullPath())); UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); @@ -4764,10 +4092,10 @@ void GMainWindow::OnHomeMenu() { void GMainWindow::OnInitialSetup() { constexpr u64 Starter = static_cast(Service::AM::AppletProgramId::Starter); - auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents(); if (!bis_system) { QMessageBox::warning(this, tr("No firmware available"), - tr("Please install the firmware to use Starter.")); + tr("Please install firmware to use Starter.")); return; } @@ -4778,7 +4106,7 @@ void GMainWindow::OnInitialSetup() { return; } - system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::Starter); + QtCommon::system->GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::Starter); const auto filename = QString::fromStdString((qlaunch_nca->GetFullPath())); UISettings::values.roms_path = QFileInfo(filename).path().toStdString(); @@ -4786,158 +4114,11 @@ void GMainWindow::OnInitialSetup() { } void GMainWindow::OnCreateHomeMenuDesktopShortcut() { - OnCreateHomeMenuShortcut(GameListShortcutTarget::Desktop); + QtCommon::Game::CreateHomeMenuShortcut(QtCommon::Game::ShortcutTarget::Desktop); } void GMainWindow::OnCreateHomeMenuApplicationMenuShortcut() { - OnCreateHomeMenuShortcut(GameListShortcutTarget::Applications); -} - -std::filesystem::path GMainWindow::GetEdenCommand() { - std::filesystem::path command; - - QString appimage = QString::fromLocal8Bit(getenv("APPIMAGE")); - if (!appimage.isEmpty()) { - command = std::filesystem::path{appimage.toStdString()}; - } else { - const QStringList args = QApplication::arguments(); - command = args[0].toStdString(); - } - - // If relative path, make it an absolute path - if (command.c_str()[0] == '.') { - command = Common::FS::GetCurrentDir() / command; - } - - return command; -} - -std::filesystem::path GMainWindow::GetShortcutPath(GameListShortcutTarget target) { - std::filesystem::path shortcut_path{}; - if (target == GameListShortcutTarget::Desktop) { - shortcut_path = - QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString(); - } else if (target == GameListShortcutTarget::Applications) { - shortcut_path = - QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString(); - } - - return shortcut_path; -} - -void GMainWindow::CreateShortcut(const std::string& game_path, const u64 program_id, - const std::string& game_title_, GameListShortcutTarget target, - std::string arguments_, const bool needs_title) { - // Get path to yuzu executable - std::filesystem::path command = GetEdenCommand(); - - // Shortcut path - std::filesystem::path shortcut_path = GetShortcutPath(target); - - if (!std::filesystem::exists(shortcut_path)) { - GMainWindow::CreateShortcutMessagesGUI( - this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR, - QString::fromStdString(shortcut_path.generic_string())); - LOG_ERROR(Frontend, "Invalid shortcut target {}", shortcut_path.generic_string()); - return; - } - - const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), - system->GetContentProvider()}; - const auto control = pm.GetControlMetadata(); - const auto loader = - Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::OpenMode::Read)); - - std::string game_title{game_title_}; - - // Delete illegal characters from title - if (needs_title) { - game_title = fmt::format("{:016X}", program_id); - if (control.first != nullptr) { - game_title = control.first->GetApplicationName(); - } else { - loader->ReadTitle(game_title); - } - } - - const std::string illegal_chars = "<>:\"/\\|?*."; - for (auto it = game_title.rbegin(); it != game_title.rend(); ++it) { - if (illegal_chars.find(*it) != std::string::npos) { - game_title.erase(it.base() - 1); - } - } - - const QString qgame_title = QString::fromStdString(game_title); - - // Get icon from game file - std::vector icon_image_file{}; - if (control.second != nullptr) { - icon_image_file = control.second->ReadAllBytes(); - } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) { - LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); - } - - QImage icon_data = - QImage::fromData(icon_image_file.data(), static_cast(icon_image_file.size())); - std::filesystem::path out_icon_path; - if (GMainWindow::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) { - if (!SaveIconToFile(out_icon_path, icon_data)) { - LOG_ERROR(Frontend, "Could not write icon to file"); - } - } - -#if defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__) - // Special case for AppImages - // Warn once if we are making a shortcut to a volatile AppImage - if (command.string().ends_with(".AppImage") && !UISettings::values.shortcut_already_warned) { - if (!GMainWindow::CreateShortcutMessagesGUI( - this, GMainWindow::CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, qgame_title)) { - return; - } - UISettings::values.shortcut_already_warned = true; - } -#endif - - // Create shortcut - std::string arguments{arguments_}; - if (GMainWindow::CreateShortcutMessagesGUI( - this, GMainWindow::CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, qgame_title)) { - arguments = "-f " + arguments; - } - const std::string comment = fmt::format("Start {:s} with the eden Emulator", game_title); - const std::string categories = "Game;Emulator;Qt;"; - const std::string keywords = "Switch;Nintendo;"; - - if (GMainWindow::CreateShortcutLink(shortcut_path, comment, out_icon_path, command, arguments, - categories, keywords, game_title)) { - GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_SUCCESS, - qgame_title); - return; - } - GMainWindow::CreateShortcutMessagesGUI(this, GMainWindow::CREATE_SHORTCUT_MSGBOX_ERROR, - qgame_title); -} - -void GMainWindow::OnCreateHomeMenuShortcut(GameListShortcutTarget target) { - constexpr u64 QLaunchId = static_cast(Service::AM::AppletProgramId::QLaunch); - auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); - if (!bis_system) { - QMessageBox::warning(this, tr("No firmware available"), - tr("Please install firmware to use the home menu.")); - return; - } - - auto qlaunch_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); - if (!qlaunch_nca) { - QMessageBox::warning(this, tr("Home Menu Applet"), - tr("Home Menu is not available. Please reinstall firmware.")); - return; - } - - auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program); - const auto game_path = qlaunch_applet_nca->GetFullPath(); - - CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false); + QtCommon::Game::CreateHomeMenuShortcut(QtCommon::Game::ShortcutTarget::Applications); } void GMainWindow::OnCaptureScreenshot() { @@ -4945,7 +4126,7 @@ void GMainWindow::OnCaptureScreenshot() { return; } - const u64 title_id = system->GetApplicationProcessProgramID(); + const u64 title_id = QtCommon::system->GetApplicationProcessProgramID(); const auto screenshot_path = QString::fromStdString(Common::FS::GetEdenPathString(Common::FS::EdenPath::ScreenshotsDir)); const auto date = @@ -5107,7 +4288,7 @@ void GMainWindow::OnTasStateChanged() { } void GMainWindow::UpdateStatusBar() { - if (emu_thread == nullptr || !system->IsPoweredOn()) { + if (emu_thread == nullptr || !QtCommon::system->IsPoweredOn()) { status_bar_update_timer.stop(); return; } @@ -5118,8 +4299,8 @@ void GMainWindow::UpdateStatusBar() { tas_label->clear(); } - auto results = system->GetAndResetPerfStats(); - auto& shader_notify = system->GPU().ShaderNotify(); + auto results = QtCommon::system->GetAndResetPerfStats(); + auto& shader_notify = QtCommon::system->GPU().ShaderNotify(); const int shaders_building = shader_notify.ShadersBuilding(); if (shaders_building > 0) { @@ -5265,7 +4446,6 @@ void GMainWindow::OnMouseActivity() { } void GMainWindow::OnCheckFirmwareDecryption() { - system->GetFileSystemController().CreateFactories(*vfs); if (!ContentManager::AreKeysPresent()) { QMessageBox::warning(this, tr("Derivation Components Missing"), tr("Encryption keys are missing.")); @@ -5274,47 +4454,12 @@ void GMainWindow::OnCheckFirmwareDecryption() { UpdateMenuState(); } -bool GMainWindow::OnCheckNcaVerification() { - if (!Settings::values.disable_nca_verification.GetValue()) - return true; - - const bool currently_hidden = Settings::values.hide_nca_verification_popup.GetValue(); - LOG_INFO(Frontend, "NCA Verification is disabled. Popup State={}", currently_hidden); - if (currently_hidden) - return true; - - QMessageBox msgbox(this); - msgbox.setWindowTitle(tr("NCA Verification Disabled")); - msgbox.setText(tr("NCA Verification is disabled.\n" - "This is required to run new games and updates.\n" - "Running without verification can cause instability or crashes if NCA files " - "are corrupt, modified, or tampered.\n" - "If unsure, re-enable verification in Eden's Settings and use firmware " - "version 19.0.1 or below.")); - msgbox.setIcon(QMessageBox::Warning); - msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - msgbox.setDefaultButton(QMessageBox::Ok); - - QCheckBox* cb = new QCheckBox(tr("Don't show again"), &msgbox); - cb->setChecked(currently_hidden); - msgbox.setCheckBox(cb); - - int result = msgbox.exec(); - - const bool hide = cb->isChecked(); - if (hide != currently_hidden) { - Settings::values.hide_nca_verification_popup.SetValue(hide); - } - - return result == static_cast(QMessageBox::Ok); -} - bool GMainWindow::CheckFirmwarePresence() { - return FirmwareManager::CheckFirmwarePresence(*system.get()); + return FirmwareManager::CheckFirmwarePresence(*QtCommon::system.get()); } void GMainWindow::SetFirmwareVersion() { - const auto pair = FirmwareManager::GetFirmwareVersion(*system.get()); + const auto pair = FirmwareManager::GetFirmwareVersion(*QtCommon::system.get()); const auto firmware_data = pair.first; const auto result = pair.second; @@ -5400,7 +4545,7 @@ bool GMainWindow::ConfirmClose() { UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) { return true; } - if (!system->GetExitLocked() && + if (!QtCommon::system->GetExitLocked() && UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game) { return true; } @@ -5430,7 +4575,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { render_window->close(); multiplayer_state->Close(); - system->HIDCore().UnloadInputDevices(); + QtCommon::system->HIDCore().UnloadInputDevices(); Network::Shutdown(); QWidget::closeEvent(event); @@ -5501,12 +4646,12 @@ bool GMainWindow::ConfirmForceLockedExit() { } void GMainWindow::RequestGameExit() { - if (!system->IsPoweredOn()) { + if (!QtCommon::system->IsPoweredOn()) { return; } - system->SetExitRequested(true); - system->GetAppletManager().RequestExit(); + QtCommon::system->SetExitRequested(true); + QtCommon::system->GetAppletManager().RequestExit(); } void GMainWindow::filterBarSetChecked(bool state) { @@ -5616,7 +4761,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) { void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { #ifdef USE_DISCORD_PRESENCE if (state) { - discord_rpc = std::make_unique(*system); + discord_rpc = std::make_unique(*QtCommon::system); } else { discord_rpc = std::make_unique(); } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 7857788fcf..e3922759b0 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -17,9 +17,10 @@ #include #include "common/common_types.h" -#include "configuration/qt_config.h" #include "frontend_common/content_manager.h" #include "input_common/drivers/tas_input.h" +#include "qt_common/qt_config.h" +#include "qt_common/qt_game_util.h" #include "user_data_migration.h" #include "yuzu/compatibility_list.h" #include "yuzu/hotkeys.h" @@ -53,10 +54,7 @@ class QSlider; class QHBoxLayout; class WaitTreeWidget; enum class GameListOpenTarget; -enum class GameListRemoveTarget; -enum class GameListShortcutTarget; enum class DumpRomFSTarget; -enum class InstalledEntryType; class GameListPlaceholder; class QtAmiiboSettingsDialog; @@ -72,7 +70,6 @@ enum class StartGameType { namespace Core { enum class SystemResultStatus : u32; -class System; } // namespace Core namespace Core::Frontend { @@ -163,13 +160,6 @@ class GMainWindow : public QMainWindow { /// Max number of recently loaded items to keep track of static const int max_recent_files_item = 10; - enum { - CREATE_SHORTCUT_MSGBOX_FULLSCREEN_YES, - CREATE_SHORTCUT_MSGBOX_SUCCESS, - CREATE_SHORTCUT_MSGBOX_ERROR, - CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING, - }; - public: void filterBarSetChecked(bool state); void UpdateUITheme(); @@ -179,7 +169,7 @@ public: bool DropAction(QDropEvent* event); void AcceptDropEvent(QDropEvent* event); - std::filesystem::path GetShortcutPath(GameListShortcutTarget target); + std::filesystem::path GetShortcutPath(QtCommon::Game::ShortcutTarget target); signals: @@ -258,8 +248,6 @@ private: void LinkActionShortcut(QAction* action, const QString& action_name, const bool tas_allowed = false); - void RegisterMetaTypes(); - void InitializeWidgets(); void InitializeDebugWidgets(); void InitializeRecentFileMenuActions(); @@ -353,9 +341,8 @@ private slots: void OnGameListLoadFile(QString game_path, u64 program_id); void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target, const std::string& game_path); - void OnTransferableShaderCacheOpenFile(u64 program_id); - void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); - void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, + void OnGameListRemoveInstalledEntry(u64 program_id, QtCommon::Game::InstalledEntryType type); + void OnGameListRemoveFile(u64 program_id, QtCommon::Game::GameListRemoveTarget target, const std::string& game_path); void OnGameListRemovePlayTimeData(u64 program_id); void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); @@ -363,8 +350,9 @@ private slots: void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); - void OnGameListCreateShortcut(u64 program_id, const std::string& game_path, - GameListShortcutTarget target); + void OnGameListCreateShortcut(u64 program_id, + const std::string& game_path, + const QtCommon::Game::ShortcutTarget target); void OnGameListOpenDirectory(const QString& directory); void OnGameListAddDirectory(); void OnGameListShowList(bool show); @@ -421,7 +409,6 @@ private slots: void OnInitialSetup(); void OnCreateHomeMenuDesktopShortcut(); void OnCreateHomeMenuApplicationMenuShortcut(); - void OnCreateHomeMenuShortcut(GameListShortcutTarget target); void OnCaptureScreenshot(); void OnCheckFirmwareDecryption(); void OnLanguageChanged(const QString& locale); @@ -436,16 +423,7 @@ private slots: #endif private: - QString GetGameListErrorRemoving(InstalledEntryType type) const; - void RemoveBaseContent(u64 program_id, InstalledEntryType type); - void RemoveUpdateContent(u64 program_id, InstalledEntryType type); - void RemoveAddOnContent(u64 program_id, InstalledEntryType type); - void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target); - void RemoveVulkanDriverPipelineCache(u64 program_id); - void RemoveAllTransferableShaderCaches(u64 program_id); - void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); void RemovePlayTimeData(u64 program_id); - void RemoveCacheStorage(u64 program_id); bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, u64* selected_title_id, u8* selected_content_record_type); ContentManager::InstallResult InstallNCA(const QString& filename); @@ -478,15 +456,6 @@ private: QString GetTasStateDescription() const; bool CreateShortcutMessagesGUI(QWidget* parent, int imsg, const QString& game_title); - bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, - std::filesystem::path& out_icon_path); - bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment, - const std::filesystem::path& icon_path, - const std::filesystem::path& command, const std::string& arguments, - const std::string& categories, const std::string& keywords, - const std::string& name); - - bool OnCheckNcaVerification(); /** * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog @@ -501,7 +470,6 @@ private: std::unique_ptr ui; - std::unique_ptr system; std::unique_ptr discord_rpc; std::unique_ptr play_time_manager; std::shared_ptr input_subsystem; @@ -561,10 +529,6 @@ private: QString startup_icon_theme; - // FS - std::shared_ptr vfs; - std::unique_ptr provider; - // Debugger panes WaitTreeWidget* waitTreeWidget; ControllerDialog* controller_dialog; @@ -611,7 +575,7 @@ private: void CreateShortcut(const std::string& game_path, const u64 program_id, const std::string& game_title, - GameListShortcutTarget target, + QtCommon::Game::ShortcutTarget target, std::string arguments, const bool needs_title); diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp index 30f37c2b4a..deac3b9e59 100644 --- a/src/yuzu/multiplayer/direct_connect.cpp +++ b/src/yuzu/multiplayer/direct_connect.cpp @@ -21,7 +21,7 @@ #include "yuzu/multiplayer/message.h" #include "yuzu/multiplayer/state.h" #include "yuzu/multiplayer/validation.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" enum class ConnectionType : u8 { TraversalServer, IP }; diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index d8e63da600..4dd3958550 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -25,7 +25,7 @@ #include "yuzu/multiplayer/message.h" #include "yuzu/multiplayer/state.h" #include "yuzu/multiplayer/validation.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #ifdef ENABLE_WEB_SERVICE #include "web_service/verify_user_jwt.h" #endif diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index ed6ba6a15c..84723041df 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -22,7 +22,7 @@ #include "yuzu/multiplayer/message.h" #include "yuzu/multiplayer/state.h" #include "yuzu/multiplayer/validation.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #ifdef ENABLE_WEB_SERVICE #include "web_service/web_backend.h" #endif diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp index d5529712b0..8ff1d991ec 100644 --- a/src/yuzu/multiplayer/state.cpp +++ b/src/yuzu/multiplayer/state.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -19,7 +22,7 @@ #include "yuzu/multiplayer/lobby.h" #include "yuzu/multiplayer/message.h" #include "yuzu/multiplayer/state.h" -#include "yuzu/uisettings.h" +#include "qt_common/uisettings.h" #include "yuzu/util/clickable_label.h" MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_list_model_, diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp index 8317386816..6d06bc7614 100644 --- a/src/yuzu/play_time_manager.cpp +++ b/src/yuzu/play_time_manager.cpp @@ -1,7 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "common/alignment.h" #include "common/fs/file.h" #include "common/fs/fs.h" #include "common/fs/path_util.h" diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h index 1714b91313..cd81bdb061 100644 --- a/src/yuzu/play_time_manager.h +++ b/src/yuzu/play_time_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -9,8 +12,9 @@ #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/polyfill_thread.h" +#include +// TODO(crueter): Extract this into frontend_common namespace Service::Account { class ProfileManager; } diff --git a/src/yuzu/qt_common.h b/src/yuzu/qt_common.h deleted file mode 100644 index 9c63f08f34..0000000000 --- a/src/yuzu/qt_common.h +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include "core/frontend/emu_window.h" - -namespace QtCommon { - -Core::Frontend::WindowSystemType GetWindowSystemType(); - -Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window); - -} // namespace QtCommon diff --git a/src/yuzu/user_data_migration.h b/src/yuzu/user_data_migration.h index 3684d14916..a3ac2c15d3 100644 --- a/src/yuzu/user_data_migration.h +++ b/src/yuzu/user_data_migration.h @@ -10,6 +10,7 @@ #include #include "migration_worker.h" +// TODO(crueter): Quick implementation class UserDataMigrator { public: UserDataMigrator(QMainWindow* main_window); diff --git a/src/yuzu/vk_device_info.cpp b/src/yuzu/vk_device_info.cpp index ab0d39c256..d961d550a1 100644 --- a/src/yuzu/vk_device_info.cpp +++ b/src/yuzu/vk_device_info.cpp @@ -1,10 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include -#include "yuzu/qt_common.h" +#include "qt_common/qt_common.h" #include "common/dynamic_library.h" #include "common/logging/log.h" diff --git a/tools/cpm-fetch-all.sh b/tools/cpm-fetch-all.sh index eac0f861a4..66f55df94d 100755 --- a/tools/cpm-fetch-all.sh +++ b/tools/cpm-fetch-all.sh @@ -6,6 +6,6 @@ # SPDX-FileCopyrightText: 2025 crueter # SPDX-License-Identifier: GPL-3.0-or-later -LIBS=$(find . externals src/yuzu/externals src/dynarmic -maxdepth 2 -name cpmfile.json -exec jq -j 'keys_unsorted | join(" ")' {} \; -printf " ") +LIBS=$(find . externals src/qt_common src/dynarmic -maxdepth 2 -name cpmfile.json -exec jq -j 'keys_unsorted | join(" ")' {} \; -printf " ") tools/cpm-fetch.sh $LIBS \ No newline at end of file diff --git a/tools/cpm-fetch.sh b/tools/cpm-fetch.sh index 5620996433..996cf76a97 100755 --- a/tools/cpm-fetch.sh +++ b/tools/cpm-fetch.sh @@ -105,7 +105,8 @@ ci_package() { for package in $@ do # prepare for cancer - JSON=$(find . externals src/yuzu/externals externals/ffmpeg src/dynarmic/externals externals/nx_tzdb -maxdepth 1 -name cpmfile.json -exec jq -r ".\"$package\" | select( . != null )" {} \;) + # TODO(crueter): Fetch json once? + JSON=$(find . externals src/qt_common src/dynarmic -maxdepth 1 -name cpmfile.json -exec jq -r ".\"$package\" | select( . != null )" {} \;) [ -z "$JSON" ] && echo "No cpmfile definition for $package" && continue From 80dfc3d76f918665f574f12b05f3e7c57271903e Mon Sep 17 00:00:00 2001 From: MaranBr Date: Mon, 15 Sep 2025 17:47:19 +0200 Subject: [PATCH 07/23] [fs] Remove remaining files from NCA bypass (#495) Fix Android build after PR 94. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/495 Co-authored-by: MaranBr Co-committed-by: MaranBr --- src/android/app/src/main/res/values-de/strings.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index 907b114388..46ae9ba7fe 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -486,8 +486,6 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die RTC-Datum auswählen RTC-Zeit auswählen Benutzerdefinierte Echtzeituhr - NCA-Verifizierung deaktivieren - Deaktiviert die Integritätsprüfung von NCA-Inhaltsarchiven. Dies kann die Ladegeschwindigkeit verbessern, riskiert jedoch Datenbeschädigung oder dass ungültige Dateien unentdeckt bleiben. Ist notwendig, um Spiele und Updates, die Firmware 20+ benötigen, zum Laufen zu bringen. Generieren From 19036c59b571056ac410d3ec46ca07cd08d28c9d Mon Sep 17 00:00:00 2001 From: MaranBr Date: Tue, 16 Sep 2025 18:42:48 +0200 Subject: [PATCH 08/23] [video_core] Simplify DMA options (#525) This simplifies DMA options in a clearer and more objective way. Co-authored-by: PavelBARABANOV Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/525 Reviewed-by: crueter Reviewed-by: Shinmegumi Co-authored-by: MaranBr Co-committed-by: MaranBr --- .../app/src/main/res/values-ar/strings.xml | 9 ++++----- .../app/src/main/res/values-ckb/strings.xml | 9 ++++----- .../app/src/main/res/values-cs/strings.xml | 9 ++++----- .../app/src/main/res/values-de/strings.xml | 9 ++++----- .../app/src/main/res/values-es/strings.xml | 9 ++++----- .../app/src/main/res/values-fa/strings.xml | 9 ++++----- .../app/src/main/res/values-fr/strings.xml | 9 ++++----- .../app/src/main/res/values-he/strings.xml | 9 ++++----- .../app/src/main/res/values-hu/strings.xml | 9 ++++----- .../app/src/main/res/values-id/strings.xml | 9 ++++----- .../app/src/main/res/values-it/strings.xml | 9 ++++----- .../app/src/main/res/values-ja/strings.xml | 9 ++++----- .../app/src/main/res/values-ko/strings.xml | 9 ++++----- .../app/src/main/res/values-nb/strings.xml | 9 ++++----- .../app/src/main/res/values-pl/strings.xml | 9 ++++----- .../app/src/main/res/values-pt-rBR/strings.xml | 9 ++++----- .../app/src/main/res/values-pt-rPT/strings.xml | 9 ++++----- .../app/src/main/res/values-ru/strings.xml | 11 +++++------ .../app/src/main/res/values-sr/strings.xml | 9 ++++----- .../app/src/main/res/values-uk/strings.xml | 9 ++++----- .../app/src/main/res/values-vi/strings.xml | 9 ++++----- .../app/src/main/res/values-zh-rCN/strings.xml | 9 ++++----- .../app/src/main/res/values-zh-rTW/strings.xml | 9 ++++----- src/android/app/src/main/res/values/arrays.xml | 6 ++---- src/android/app/src/main/res/values/strings.xml | 9 ++++----- src/common/settings.cpp | 10 +++++++++- src/common/settings.h | 5 ++++- src/common/settings_enums.h | 2 +- src/core/arm/dynarmic/arm_dynarmic_32.cpp | 2 +- src/qt_common/shared_translation.cpp | 15 ++++++--------- src/video_core/dma_pusher.cpp | 17 ++--------------- 31 files changed, 122 insertions(+), 153 deletions(-) diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml index ed3fc76f3b..a758e1c7cd 100644 --- a/src/android/app/src/main/res/values-ar/strings.xml +++ b/src/android/app/src/main/res/values-ar/strings.xml @@ -119,8 +119,8 @@ يتخطى بعض عمليات إبطال ذاكرة التخزين المؤقت أثناء تحديثات الذاكرة، مما يقلل استخدام المعالج ويحسن أدائه. قد يسبب هذا أعطالاً أو تعطلًا في بعض الألعاب. تمكين محاكاة MMU المضيف يعمل هذا التحسين على تسريع وصول الذاكرة بواسطة البرنامج الضيف. يؤدي تمكينه إلى إجراء عمليات قراءة/كتابة ذاكرة الضيف مباشرة في الذاكرة والاستفادة من MMU المضيف. يؤدي تعطيل هذا إلى إجبار جميع عمليات الوصول إلى الذاكرة على استخدام محاكاة MMU البرمجية. - مستوى DMA - يتحكم في دقة تحديد مستوى DMA. الدقة الأعلى يمكنها إصلاح بعض المشاكل في بعض الألعاب، ولكنها قد تؤثر أيضًا على الأداء في بعض الحالات. إذا كنت غير متأكد، اتركه على الوضع الافتراضي. + دقة DMA + يتحكم في دقة تحديد DMA. يمكن أن تصلح الدقة الآمنة المشاكل في بعض الألعاب، ولكنها قد تؤثر أيضًا على الأداء في بعض الحالات. إذا كنت غير متأكد، اترك هذا على الوضع الافتراضي. 4 جيجابايت (موصى به) @@ -792,9 +792,8 @@ افتراضي - عادي - عالي - مفرط + غير آمن (سريع) + آمن (مستقر) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-ckb/strings.xml b/src/android/app/src/main/res/values-ckb/strings.xml index 34b1ae6252..fe94f97dc5 100644 --- a/src/android/app/src/main/res/values-ckb/strings.xml +++ b/src/android/app/src/main/res/values-ckb/strings.xml @@ -128,8 +128,8 @@ هەندێک لە بازنەکردنەکانی هەڵگر لە کاتی نوێکردنەوەی بیرگە دەنێرێت، کەمکردنەوەی بەکارهێنانی CPU و باشترکردنی کارایی. لەوانەیە لە هەندێک یاری کێشە درووست بکات. چالاککردنی میمیکردنی MMU میواندە ئەم باشکردنە خێرایی دەستکەوتنی بیرگە لەلایەن پرۆگرامی میوانەکە زیاد دەکات. چالاککردنی وای لێدەکات کە خوێندنەوە/نووسینەکانی بیرگەی میوانەکە ڕاستەوخۆ لە بیرگە ئەنجام بدرێت و میمیکردنی MMU میواندە بەکاربهێنێت. ناچالاککردنی ئەمە هەموو دەستکەوتنەکانی بیرگە ڕەت دەکاتەوە لە بەکارهێنانی میمیکردنی MMU نەرمەکاڵا. - ئاستی DMA - کۆنتڕۆڵی وردی ڕێکخستنی DMA دەکات. وردی زیاتر دەتوانێ هەندێک کێشە لە هەندێک یاری چارەسەر بکات، بەڵام لە هەندێک حاڵەتدا کاریگەری لەسەر کارایی هەیە. ئەگەر دڵنیا نیت، بە ڕێکخستنی بنەڕەتی بێڵە. + وردیی DMA + کۆنتڕۆڵی وردیی وردیی DMA دەکات. وردییی پارێزراو دەتوانێت کێشەکان لە هەندێک یاری چارەسەر بکات، بەڵام لە هەندێک حاڵەتدا کاریگەری لەسەر کارایی هەیە. ئەگەر دڵنیا نیت، ئەمە بە سەر ڕەھەوادا بهێڵە. 4GB (پێشنیارکراو) 6GB (نائاسایش) @@ -761,9 +761,8 @@ بنەڕەتی - ئاسایی - بەرز - زۆر بەرز + نەپارێزراو (خێرا) + پارێزراو (جێگیر) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-cs/strings.xml b/src/android/app/src/main/res/values-cs/strings.xml index 293524271e..785f96b84c 100644 --- a/src/android/app/src/main/res/values-cs/strings.xml +++ b/src/android/app/src/main/res/values-cs/strings.xml @@ -127,8 +127,8 @@ Přeskočí některé invalidace mezipaměti na straně CPU během aktualizací paměti, čímž sníží zatížení CPU a zlepší jeho výkon. Může způsobit chyby nebo pády v některých hrách. Povolit emulaci hostitelské MMU Tato optimalizace zrychluje přístup do paměti hostovaného programu. Její povolení způsobí, že čtení a zápisy do paměti hosta se provádějí přímo v paměti a využívají hostitelskou MMU. Zakázání této funkce vynutí použití softwarové emulace MMU pro všechny přístupy do paměti. - Úroveň DMA - Ovládá přesnost DMA. Vyšší přesnost může opravit problémy v některých hrách, ale může také ovlivnit výkon. Pokud si nejste jisti, ponechejte výchozí nastavení. + Přesnost DMA + Ovládá přesnost DMA. Bezpečná přesnost může opravit problémy v některých hrách, ale v některých případech může také ovlivnit výkon. Pokud si nejste jisti, ponechte to na výchozím nastavení. 4GB (Doporučeno) 6GB (Nebezpečné) @@ -735,9 +735,8 @@ Výchozí - Normální - Vysoká - Extrémní + Nebezpečné (rychlé) + Bezpečné (stabilní) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index 46ae9ba7fe..495804e328 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -128,8 +128,8 @@ Überspringt bestimmte Cache-Invalidierungen auf CPU-Seite während Speicherupdates, reduziert die CPU-Auslastung und verbessert die Leistung. Kann in einigen Spielen zu Fehlern oder Abstürzen führen. Host-MMU-Emulation aktivieren Diese Optimierung beschleunigt Speicherzugriffe durch das Gastprogramm. Wenn aktiviert, erfolgen Speicherlese- und -schreibvorgänge des Gastes direkt im Speicher und nutzen die MMU des Hosts. Das Deaktivieren erzwingt die Verwendung der Software-MMU-Emulation für alle Speicherzugriffe. - DMA-Level - Steuert die DMA-Präzisionsgenauigkeit. Eine höhere Präzision kann Probleme in einigen Spielen beheben, kann aber in einigen Fällen auch die Leistung beeinträchtigen. Im Zweifel auf „Standard“ belassen. + DMA-Genauigkeit + Steuert die DMA-Präzisionsgenauigkeit. Sichere Präzision kann Probleme in einigen Spielen beheben, kann aber in einigen Fällen auch die Leistung beeinträchtigen. Im Zweifel lassen Sie dies auf Standard stehen. 4 GB (Empfohlen) 6 GB (Unsicher) @@ -827,9 +827,8 @@ Wirklich fortfahren?
Standard - Normal - Hoch - Extrem + Unsicher (schnell) + Sicher (stabil) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 8712f455de..2ee0e1783a 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -128,8 +128,8 @@ Omite ciertas invalidaciones de caché durante actualizaciones de memoria, reduciendo el uso de CPU y mejorando su rendimiento. Puede causar fallos en algunos juegos. Habilitar emulación de MMU del host Esta optimización acelera los accesos a la memoria por parte del programa invitado. Al habilitarla, las lecturas/escrituras de memoria del invitado se realizan directamente en la memoria y utilizan la MMU del host. Deshabilitar esto obliga a que todos los accesos a la memoria utilicen la emulación de MMU por software. - Nivel de DMA - Controla la precisión del DMA. Una mayor precisión puede solucionar problemas en algunos juegos, pero también puede afectar el rendimiento en algunos casos. Si no está seguro, déjelo en Predeterminado. + Precisión de DMA + Controla la precisión de DMA. La precisión segura puede solucionar problemas en algunos juegos, pero también puede afectar al rendimiento en algunos casos. Si no está seguro, déjelo en Predeterminado. 4GB (Recomendado) 6GB (Inseguro) @@ -870,9 +870,8 @@ Predeterminado - Normal - Alto - Extremo + Inseguro (rápido) + Seguro (estable) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-fa/strings.xml b/src/android/app/src/main/res/values-fa/strings.xml index 07ff8ff4e0..79cf5f49e6 100644 --- a/src/android/app/src/main/res/values-fa/strings.xml +++ b/src/android/app/src/main/res/values-fa/strings.xml @@ -128,8 +128,8 @@ بعضی ابطال‌های حافظه نهان در هنگام به‌روزرسانی‌های حافظه را رد می‌کند، استفاده از CPU را کاهش داده و عملکرد آن را بهبود می‌بخشد. ممکن است در برخی بازی‌ها باعث مشکلات یا خرابی شود. فعال‌سازی شبیه‌سازی MMU میزبان این بهینه‌سازی دسترسی‌های حافظه توسط برنامه میهمان را تسریع می‌کند. فعال‌سازی آن باعث می‌شود خواندن/نوشتن حافظه میهمان مستقیماً در حافظه انجام شود و از MMU میزبان استفاده کند. غیرفعال کردن این قابلیت، همه دسترسی‌های حافظه را مجبور به استفاده از شبیه‌سازی نرم‌افزاری MMU می‌کند. - سطح DMA - دقت صحت DMA را کنترل می کند. دقت بالاتر می تواند مشکلات برخی بازی ها را برطرف کند، اما در برخی موارد نیز می تواند بر عملکرد تأثیر بگذارد. اگر مطمئن نیستید، آن را روی پیش فرض بگذارید. + دقت DMA + دقت صحت DMA را کنترل می کند. دقت ایمن می تواند مشکلات برخی بازی ها را برطرف کند، اما در برخی موارد نیز ممکن است بر عملکرد تأثیر بگذارد. اگر مطمئن نیستید، این گزینه را روی پیش فرض بگذارید. 4 گیگابایت (توصیه شده) 6 گیگابایت (ناامن) @@ -869,9 +869,8 @@ پیش فرض - معمولی - بالا - فوق العاده + ناایمن (سریع) + ایمن (پایدار) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index 2e06ac98e1..e9df08a4de 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -128,8 +128,8 @@ Ignore certaines invalidations de cache côté CPU lors des mises à jour mémoire, réduisant l\'utilisation du CPU et améliorant ses performances. Peut causer des bugs ou plantages sur certains jeux. Activer l\'émulation de la MMU hôte Cette optimisation accélère les accès mémoire par le programme invité. L\'activer entraîne que les lectures/écritures mémoire de l\'invité sont effectuées directement en mémoire et utilisent la MMU de l\'hôte. Désactiver cela force tous les accès mémoire à utiliser l\'émulation logicielle de la MMU. - Niveau DMA - Contrôle la précision du DMA. Une précision plus élevée peut résoudre les problèmes dans certains jeux, mais peut aussi affecter les performances dans certains cas. Si vous n\'êtes pas sûr, laissez-la sur Défaut. + Précision DMA + Contrôle la précision du DMA. Une précision sûre peut résoudre les problèmes dans certains jeux, mais peut aussi affecter les performances dans certains cas. Si vous n\'êtes pas sûr, laissez ce paramètre sur Par défaut. 4 Go (Recommandé) 6 Go (Dangereux) @@ -918,9 +918,8 @@ Défaut - Normal - Élevé - Extrême + Dangereux (rapide) + Sûr (stable) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-he/strings.xml b/src/android/app/src/main/res/values-he/strings.xml index c0c835d633..4e1624a556 100644 --- a/src/android/app/src/main/res/values-he/strings.xml +++ b/src/android/app/src/main/res/values-he/strings.xml @@ -129,8 +129,8 @@ מדלג על איפוסי מטמון מסוימים במהלך עדכוני זיכרון, מפחית שימוש במעבד ומשפר ביצועים. עלול לגרום לתקלות או קריסות בחלק מהמשחקים. הפעל אמולציית MMU מארח אופטימיזציה זו מאיצה את גישת הזיכרון על ידי התוכנית האורחת. הפעלתה גורמת לכך שפעולות קריאה/כתיבה לזיכרון האורח מתבצעות ישירות לזיכרון ומשתמשות ב-MMU של המארח. השבתת זאת מאלצת את כל גישות הזיכרון להשתמש באמולציית MMU תוכנתית. - רמת DMA - שולטת בדיוק הדיוק של DMA. דיוק גבוה יותר יכול לתקן בעיות בחלק מהמשחקים, אך הוא עלול גם להשפיע על הביצועים במקרים מסוימים. אם אינך בטוח, השאר ברירת מחדל. + דיוק DMA + שולט בדיוק הדיוק של DMA. דיוק בטוח יכול לתקן בעיות בחלק מהמשחקים, אך הוא עלול גם להשפיע על הביצועים במקרים מסוימים. אם אינך בטוח, השאר זאת על ברירת מחדל. 4GB (מומלץ) 6GB (לא בטוח) @@ -800,9 +800,8 @@ ברירת מחדל - רגיל - גבוה - קיצוני + לא בטוח (מהיר) + בטוח (יציב) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-hu/strings.xml b/src/android/app/src/main/res/values-hu/strings.xml index 46a5ac7cce..061ac07388 100644 --- a/src/android/app/src/main/res/values-hu/strings.xml +++ b/src/android/app/src/main/res/values-hu/strings.xml @@ -128,8 +128,8 @@ Kihagy néhány CPU-oldali gyorsítótár-érvénytelenítést memóriafrissítések közben, csökkentve a CPU használatát és javítva a teljesítményt. Néhány játékban hibákat vagy összeomlást okozhat. Gazda MMU emuláció engedélyezése Ez az optimalizáció gyorsítja a vendégprogram memória-hozzáférését. Engedélyezése esetén a vendég memóriaolvasási/írási műveletei közvetlenül a memóriában történnek, és kihasználják a gazda MMU-ját. Letiltás esetén minden memória-hozzáférés a szoftveres MMU emulációt használja. - DMA szint - Szabályozza a DMA pontosságát. A magasabb pontosság megoldhat néhány játék problémáit, de bizonyos esetekben befolyásolhatja a teljesítményt. Ha bizonytalan, hagyja Alapértelmezett beállításnál. + DMA pontosság + Szabályozza a DMA pontosságát. A biztonságos pontosság megoldhat néhány játék problémáit, de bizonyos esetekben befolyásolhatja a teljesítményt. Ha bizonytalan, hagyja Alapértelmezett beállításon. 4GB (Ajánlott) 6GB (Nem biztonságos) @@ -907,9 +907,8 @@ Alapértelmezett - Normál - Magas - Extrém + Nem biztonságos (gyors) + Biztonságos (stabil) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-id/strings.xml b/src/android/app/src/main/res/values-id/strings.xml index cffb526ad5..6e3b64953f 100644 --- a/src/android/app/src/main/res/values-id/strings.xml +++ b/src/android/app/src/main/res/values-id/strings.xml @@ -128,8 +128,8 @@ Melewati beberapa pembatalan cache sisi CPU selama pembaruan memori, mengurangi penggunaan CPU dan meningkatkan kinerjanya. Mungkin menyebabkan gangguan atau crash pada beberapa game. Aktifkan Emulasi MMU Host Optimasi ini mempercepat akses memori oleh program tamu. Mengaktifkannya menyebabkan pembacaan/penulisan memori tamu dilakukan langsung ke memori dan memanfaatkan MMU Host. Menonaktifkan ini memaksa semua akses memori menggunakan Emulasi MMU Perangkat Lunak. - Level DMA - Mengontrol akurasi presisi DMA. Presisi yang lebih tinggi dapat memperbaiki masalah di beberapa game, tetapi juga dapat memengaruhi performa dalam beberapa kasus. Jika tidak yakin, biarkan di Bawaan. + Akurasi DMA + Mengontrol keakuratan presisi DMA. Presisi aman dapat memperbaiki masalah di beberapa game, tetapi juga dapat memengaruhi kinerja dalam beberapa kasus. Jika tidak yakin, biarkan ini pada Bawaan. 4GB (Direkomendasikan) 6GB (Tidak Aman) @@ -862,9 +862,8 @@ Bawaan - Normal - Tinggi - Ekstrem + Tidak Aman (cepat) + Aman (stabil) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml index cb234cf61e..38a82b3c11 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml @@ -128,8 +128,8 @@ Salta alcuni invalidamenti della cache lato CPU durante gli aggiornamenti di memoria, riducendo l\'uso della CPU e migliorandone le prestazioni. Potrebbe causare glitch o crash in alcuni giochi. Abilita emulazione MMU host Questa ottimizzazione accelera gli accessi alla memoria da parte del programma guest. Abilitandola, le letture/scritture della memoria guest vengono eseguite direttamente in memoria e sfruttano la MMU host. Disabilitandola, tutti gli accessi alla memoria sono costretti a utilizzare l\'emulazione software della MMU. - Livello DMA - Controlla la precisione del DMA. Una precisione più alta può risolvere problemi in alcuni giochi, ma in alcuni casi può influire sulle prestazioni. Se non sei sicuro, lascia su Predefinito. + Precisione DMA + Controlla la precisione del DMA. La precisione sicura può risolvere problemi in alcuni giochi, ma in alcuni casi può anche influire sulle prestazioni. In caso di dubbi, lascia questo su Predefinito. 4GB (Consigliato) 6GB (Non sicuro) @@ -831,9 +831,8 @@ Predefinito - Normale - Alto - Estremo + Non sicuro (veloce) + Sicuro (stabile) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml index abedb1e0bc..179601f182 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml @@ -128,8 +128,8 @@ メモリ更新時のCPU側キャッシュ無効化をスキップし、CPU使用率を減らして性能を向上させます。一部のゲームで不具合やクラッシュが発生する可能性があります。 ホストMMUエミュレーションを有効化 この最適化により、ゲストプログラムによるメモリアクセスが高速化されます。有効にすると、ゲストのメモリ読み書きが直接メモリ内で実行され、ホストのMMUを利用します。無効にすると、すべてのメモリアクセスでソフトウェアMMUエミュレーションが使用されます。 - DMAレベル - DMAの精度を制御します。精度を高くすると一部のゲームの問題が修正される場合がありますが、場合によってはパフォーマンスに影響を与える可能性もあります。不明な場合は、デフォルトのままにしてください。 + DMA精度 + DMAの精度を制御します。安全な精度は一部のゲームの問題を修正できる場合がありますが、場合によってはパフォーマンスに影響を与える可能性もあります。不明な場合は、これをデフォルトのままにしてください。 4GB (推奨) 6GB (安全でない) @@ -790,9 +790,8 @@ デフォルト - 標準 - - 最高 + 安全でない(高速) + 安全(安定) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml index c6d9457744..6f4dd42af2 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml @@ -128,8 +128,8 @@ 메모리 업데이트 시 일부 CPU 측 캐시 무효화를 건너뛰어 CPU 사용량을 줄이고 성능을 향상시킵니다. 일부 게임에서 오류 또는 충돌을 일으킬 수 있습니다. 호스트 MMU 에뮬레이션 사용 이 최적화는 게스트 프로그램의 메모리 접근 속도를 높입니다. 활성화하면 게스트의 메모리 읽기/쓰기가 메모리에서 직접 수행되고 호스트의 MMU를 활용합니다. 비활성화하면 모든 메모리 접근에 소프트웨어 MMU 에뮬레이션을 사용하게 됩니다. - DMA 수준 - DMA 정밀도를 제어합니다. 높은 정밀도는 일부 게임의 문제를 해결할 수 있지만 경우에 따라 성능에 영향을 미칠 수도 있습니다. 확실하지 않다면 기본값으로 두세요. + DMA 정확도 + DMA 정밀도 정확도를 제어합니다. 안전한 정밀도는 일부 게임의 문제를 해결할 수 있지만 경우에 따라 성능에 영향을 미칠 수도 있습니다. 확실하지 않은 경우 기본값으로 두십시오. 4GB (권장) 6GB (안전하지 않음) @@ -861,9 +861,8 @@ 기본값 - 보통 - 높음 - 극단적 + 안전하지 않음(빠름) + 안전함(안정적) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml index 3cc4c6d12c..7f0cffc7c4 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml @@ -128,8 +128,8 @@ Hopper over enkelte CPU-side cache-invalideringer under minneoppdateringer, reduserer CPU-bruk og forbedrer ytelsen. Kan forårsake feil eller krasj i noen spill. Aktiver verts-MMU-emulering Denne optimaliseringen fremskynder minnetilgang av gjesteprogrammet. Hvis aktivert, utføres gjestens minnelesing/skriving direkte i minnet og bruker vertens MMU. Deaktivering tvinger alle minnetilganger til å bruke programvarebasert MMU-emulering. - DMA-nivå - Styrer DMA-presisjonsnøyaktigheten. Høyere presisjon kan fikse problemer i noen spill, men kan også påvirke ytelsen i noen tilfeller. Hvis du er usikker, la den stå på Standard. + DMA-nøyaktighet + Kontrollerer DMA-presisjonsnøyaktigheten. Sikker presisjon kan fikse problemer i noen spill, men kan også påvirke ytelsen i noen tilfeller. Hvis du er usikker, la dette stå på Standard. 4GB (Anbefalt) 6GB (Usikkert) @@ -771,9 +771,8 @@ Standard - Normal - Høy - Ekstrem + Usikker (rask) + Sikker (stabil) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index b9858838e8..de9b8f47fc 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -128,8 +128,8 @@ Pomija niektóre unieważnienia pamięci podręcznej po stronie CPU podczas aktualizacji pamięci, zmniejszając użycie CPU i poprawiając jego wydajność. Może powodować błędy lub awarie w niektórych grach. Włącz emulację MMU hosta Ta optymalizacja przyspiesza dostęp do pamięci przez program gościa. Włączenie powoduje, że odczyty/zapisy pamięci gościa są wykonywane bezpośrednio w pamięci i wykorzystują MMU hosta. Wyłączenie wymusza użycie programowej emulacji MMU dla wszystkich dostępów do pamięci. - Poziom DMA - Kontroluje dokładność precyzji DMA. Wyższy poziom może naprawić problemy w niektórych grach, ale może również wpłynąć na wydajność. Jeśli nie jesteś pewien, pozostaw wartość «Domyślny». + Dokładność DMA + Kontroluje dokładność precyzji DMA. Bezpieczna precyzja może naprawić problemy w niektórych grach, ale w niektórych przypadkach może również wpłynąć na wydajność. Jeśli nie jesteś pewien, pozostaw wartość Domyślną. 4GB (Zalecane) 6GB (Niebezpieczne) @@ -768,9 +768,8 @@ 預設 - 普通 - - 極高 + Niezabezpieczone (szybkie) + Bezpieczne (stabilne) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml index 1296fad889..eec3fdf715 100644 --- a/src/android/app/src/main/res/values-pt-rBR/strings.xml +++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml @@ -128,8 +128,8 @@ Ignora algumas invalidações de cache do lado da CPU durante atualizações de memória, reduzindo o uso da CPU e melhorando seu desempenho. Pode causar falhas ou travamentos em alguns jogos. Ativar Emulação de MMU do Host Esta otimização acelera os acessos à memória pelo programa convidado. Ativar isso faz com que as leituras/gravações de memória do convidado sejam feitas diretamente na memória e utilizem a MMU do Host. Desativar isso força todos os acessos à memória a usarem a Emulação de MMU por Software. - Nível DMA - Controla a precisão do DMA. Maior precisão pode corrigir problemas em alguns jogos, mas também pode impactar o desempenho em alguns casos. Se não tiver certeza, deixe em Padrão. + Precisão de DMA + Controla a precisão do DMA. A precisão segura pode corrigir problemas em alguns jogos, mas também pode afetar o desempenho em alguns casos. Se não tiver certeza, deixe isso como Padrão. 4GB (Recomendado) 6GB (Inseguro) @@ -919,9 +919,8 @@ uma tentativa de mapeamento automático Padrão - Normal - Alto - Extremo + Inseguro (rápido) + Seguro (estável) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml index a166907877..d45bf4bfc9 100644 --- a/src/android/app/src/main/res/values-pt-rPT/strings.xml +++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml @@ -128,8 +128,8 @@ Ignora algumas invalidações de cache do lado da CPU durante atualizações de memória, reduzindo a utilização da CPU e melhorando o desempenho. Pode causar falhas ou crashes em alguns jogos. Ativar Emulação de MMU do Anfitrião Esta otimização acelera os acessos à memória pelo programa convidado. Ativar faz com que as leituras/escritas de memória do convidado sejam efetuadas diretamente na memória e utilizem a MMU do Anfitrião. Desativar força todos os acessos à memória a usar a Emulação de MMU por Software. - Nível DMA - Controla a precisão do DMA. Maior precisão pode corrigir problemas em alguns jogos, mas também pode afetar o desempenho nalguns casos. Se não tiver a certeza, deixe em Predefinido. + Precisão da DMA + Controla a precisão da DMA. A precisão segura pode resolver problemas em alguns jogos, mas também pode afetar o desempenho nalguns casos. Se não tiver a certeza, deixe esta opção em Predefinido. 4GB (Recomendado) 6GB (Inseguro) @@ -919,9 +919,8 @@ uma tentativa de mapeamento automático Predefinido - Normal - Alto - Extremo + Inseguro (rápido) + Seguro (estável) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index dc68c7b817..2f7714257f 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -128,8 +128,8 @@ Пропускает некоторые инвалидации кэша на стороне ЦП при обновлениях памяти, уменьшая нагрузку на процессор и повышая производительность. Может вызывать сбои в некоторых играх. Включить эмуляцию MMU хоста Эта оптимизация ускоряет доступ к памяти гостевой программой. При включении операции чтения/записи памяти гостя выполняются напрямую в памяти с использованием MMU хоста. Отключение заставляет все обращения к памяти использовать программную эмуляцию MMU. - Уровень DMA - Управляет точностью DMA. Более высокий уровень может исправить проблемы в некоторых играх, но также может повлиять на производительность. Если не уверены, оставьте значение «По умолчанию». + Точность DMA + Управляет точностью DMA. Безопасная точность может исправить проблемы в некоторых играх, но в некоторых случаях также может повлиять на производительность. Если не уверены, оставьте значение По умолчанию. 4 ГБ (Рекомендуется) 6 ГБ (Небезопасно) @@ -920,9 +920,8 @@ По умолчанию - Нормальный - Высокий - Экстремальный + Небезопасно (быстро) + Безопасно (стабильно) 0.25X (180p/270p) @@ -956,7 +955,7 @@ Авто Альбомная (сенсор) - Пейзаж + Альбомная Обратная альбомная Портретная (сенсор) Портрет diff --git a/src/android/app/src/main/res/values-sr/strings.xml b/src/android/app/src/main/res/values-sr/strings.xml index c547b3f761..e261772fc4 100644 --- a/src/android/app/src/main/res/values-sr/strings.xml +++ b/src/android/app/src/main/res/values-sr/strings.xml @@ -121,8 +121,8 @@ Preskače određena poništavanja keša na strani CPU-a tokom ažuriranja memorije, smanjujući opterećenje procesora i poboljšavajući performanse. Može izazvati greške u nekim igrama. Омогући емулацију MMU домаћина Ова оптимизација убрзава приступ меморији од стране гостујућег програма. Укључивање изазива да се читања/уписа меморије госта обављају директно у меморији и користе MMU домаћина. Искључивање присиљава све приступе меморији да користе софтверску емулацију MMU. - DMA ниво - Контролише тачност DMA прецизности. Виши ниво може да поправи проблеме у неким играма, али може и да утиче на перформансе. Ако нисте сигурни, оставите на «Подразумевано». + DMA тачност + Управља прецизношћу DMA-а. Сигурна прецизност може да исправи проблеме у неким играма, али у неким случајевима може да утиче и на перформансе. Ако нисте сигурни, оставите ово на Подразумевано. Схадер Бацкенд @@ -915,9 +915,8 @@ Подразумевано - Нормално - Високо - Екстремно + Небезбедно (брзо) + Безбедно (стабилно) АСТЦ метода декодирања diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index b48a8a4a58..2222402a25 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -128,8 +128,8 @@ Пропускає деякі інвалідації кешу на стороні CPU під час оновлення пам\'яті, зменшуючи навантаження на процесор і покращуючи продуктивність. Може спричинити збої в деяких іграх. Увімкнути емуляцію MMU хоста Ця оптимізація пришвидшує доступ до пам\'яті гостьовою програмою. Увімкнення призводить до того, що читання/запис пам\'яті гостя виконуються безпосередньо в пам\'яті та використовують MMU хоста. Вимкнення змушує всі звернення до пам\'яті використовувати програмну емуляцію MMU. - Рівень DMA - Керує точністю DMA. Вищий рівень може виправити проблеми в деяких іграх, але також може вплинути на продуктивність. Якщо не впевнені, залиште значення «Типово». + Точність DMA + Керує точністю DMA. Безпечна точність може виправити проблеми в деяких іграх, але в деяких випадках також може вплинути на продуктивність. Якщо не впевнені, залиште це значення за замовчуванням. 4 ГБ (Рекомендовано) 6 ГБ (Небезпечно) @@ -809,9 +809,8 @@ Типово - Нормальний - Високий - Екстремальний + Небезпечно (швидко) + Безпечно (стабільно) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-vi/strings.xml b/src/android/app/src/main/res/values-vi/strings.xml index b19d437ceb..784b2dec14 100644 --- a/src/android/app/src/main/res/values-vi/strings.xml +++ b/src/android/app/src/main/res/values-vi/strings.xml @@ -128,8 +128,8 @@ Bỏ qua một số lần vô hiệu hóa bộ nhớ đệm phía CPU trong khi cập nhật bộ nhớ, giảm mức sử dụng CPU và cải thiện hiệu suất. Có thể gây ra lỗi hoặc treo máy trong một số trò chơi. Bật giả lập MMU Máy chủ Tối ưu hóa này tăng tốc độ truy cập bộ nhớ của chương trình khách. Bật nó lên khiến các thao tác đọc/ghi bộ nhớ khách được thực hiện trực tiếp vào bộ nhớ và sử dụng MMU của Máy chủ. Tắt tính năng này buộc tất cả quyền truy cập bộ nhớ phải sử dụng Giả lập MMU Phần mềm. - Cấp độ DMA - Điều khiển độ chính xác của DMA. Độ chính xác cao hơn có thể sửa lỗi trong một số trò chơi, nhưng cũng có thể ảnh hưởng đến hiệu suất trong một số trường hợp. Nếu không chắc chắn, hãy để ở Mặc định. + Độ chính xác DMA + Điều khiển độ chính xác của DMA. Độ chính xác an toàn có thể khắc phục sự cố trong một số trò chơi, nhưng trong một số trường hợp cũng có thể ảnh hưởng đến hiệu suất. Nếu không chắc chắn, hãy để giá trị này ở Mặc định. 4GB (Được đề xuất) 6GB (Không an toàn) @@ -774,9 +774,8 @@ Mặc định - Bình thường - Cao - Cực cao + Không an toàn (nhanh) + An toàn (ổn định) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index 95ab14abd0..bfdc3af3d3 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -127,8 +127,8 @@ 在内存更新期间跳过某些CPU端缓存无效化,减少CPU使用率并提高其性能。可能会导致某些游戏出现故障或崩溃。 启用主机 MMU 模拟 此优化可加速来宾程序的内存访问。启用后,来宾内存读取/写入将直接在内存中执行并利用主机的 MMU。禁用此功能将强制所有内存访问使用软件 MMU 模拟。 - DMA 级别 - 控制 DMA 精度。更高的精度可以修复某些游戏中的问题,但在某些情况下也可能影响性能。如果不确定,请保留为“默认”。 + DMA 精度 + 控制 DMA 精度。安全精度可以修复某些游戏中的问题,但在某些情况下也可能影响性能。如果不确定,请保留为“默认”。 4GB (推荐) 6GB (不安全) @@ -912,9 +912,8 @@ 默认 - 普通 - - 极高 + 不安全(快速) + 安全(稳定) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml index 8640875f2c..e64aaa9a54 100644 --- a/src/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml @@ -120,8 +120,8 @@ 在記憶體更新期間跳過某些CPU端快取無效化,減少CPU使用率並提高其性能。可能會導致某些遊戲出現故障或崩潰。 啟用主機 MMU 模擬 此最佳化可加速來賓程式的記憶體存取。啟用後,來賓記憶體讀取/寫入將直接在記憶體中執行並利用主機的 MMU。停用此功能將強制所有記憶體存取使用軟體 MMU 模擬。 - DMA 級別 - 控制 DMA 精確度。更高的精確度可以修復某些遊戲中的問題,但在某些情況下也可能影響效能。如果不確定,請保留為「預設」。 + DMA 精度 + 控制 DMA 精度。安全精度可以修復某些遊戲中的問題,但在某些情況下也可能影響效能。如果不確定,請保留為「預設」。 4GB (推薦) @@ -917,9 +917,8 @@ 預設 - 普通 - - 極高 + 不安全(快速) + 安全(穩定) 0.25X (180p/270p) diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 2f0392675d..08ca53ad81 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -454,15 +454,13 @@ @string/dma_accuracy_default - @string/dma_accuracy_normal - @string/dma_accuracy_high - @string/dma_accuracy_extreme + @string/dma_accuracy_unsafe + @string/dma_accuracy_safe 0 1 2 - 3 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index d99776b440..2cef5903cb 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -115,8 +115,8 @@ Use Boost (1700MHz) to run at the Switch\'s highest native clock, or Fast (2000MHz) to run at 2x clock. Memory Layout (EXPERIMENTAL) Change the emulated memory layout. This setting will not increase performance, but may help with games utilizing high resolutions via mods. Do not use on phones with 8GB of RAM or less. Only works on the Dynarmic (JIT) backend. - DMA Level - Controls the DMA precision accuracy. Higher precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave it at Default. + DMA Accuracy + Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default. Shader Backend @@ -940,9 +940,8 @@ Default - Normal - High - Extreme + Unsafe (fast) + Safe (stable) ASTC Decoding Method diff --git a/src/common/settings.cpp b/src/common/settings.cpp index d4f16f4853..b41f4c75f5 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -154,11 +154,19 @@ bool IsGPULevelHigh() { values.current_gpu_accuracy == GpuAccuracy::High; } +bool IsDMALevelDefault() { + return values.dma_accuracy.GetValue() == DmaAccuracy::Default; +} + +bool IsDMALevelSafe() { + return values.dma_accuracy.GetValue() == DmaAccuracy::Safe; +} + bool IsFastmemEnabled() { if (values.cpu_debug_mode) { return static_cast(values.cpuopt_fastmem); } - if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Unsafe) { + if (values.cpu_accuracy.GetValue() == CpuAccuracy::Unsafe) { return static_cast(values.cpuopt_unsafe_host_mmu); } #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun__) diff --git a/src/common/settings.h b/src/common/settings.h index fafd765804..8605445837 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -443,7 +443,7 @@ struct Values { SwitchableSetting dma_accuracy{linkage, DmaAccuracy::Default, DmaAccuracy::Default, - DmaAccuracy::Extreme, + DmaAccuracy::Safe, "dma_accuracy", Category::RendererAdvanced, Specialization::Default, @@ -783,6 +783,9 @@ void UpdateGPUAccuracy(); bool IsGPULevelExtreme(); bool IsGPULevelHigh(); +bool IsDMALevelDefault(); +bool IsDMALevelSafe(); + bool IsFastmemEnabled(); void SetNceEnabled(bool is_64bit); bool IsNceEnabled(); diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 52b4a128f7..41133a7819 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -136,7 +136,7 @@ ENUM(ShaderBackend, Glsl, Glasm, SpirV); ENUM(GpuAccuracy, Normal, High, Extreme); -ENUM(DmaAccuracy, Default, Normal, High, Extreme); +ENUM(DmaAccuracy, Default, Unsafe, Safe); ENUM(CpuBackend, Dynarmic, Nce); diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index 2c2c54a1ad..d2035d0fe0 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -305,7 +305,7 @@ std::shared_ptr ArmDynarmic32::MakeJit(Common::PageTable* pa config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor; } - // Paranoid mode for debugging optimizations + // Paranoia mode for debugging optimizations if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Paranoid) { config.unsafe_optimizations = false; config.optimizations = Dynarmic::no_optimizations; diff --git a/src/qt_common/shared_translation.cpp b/src/qt_common/shared_translation.cpp index 8f31e07154..cdc05e60e0 100644 --- a/src/qt_common/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -288,16 +288,14 @@ std::unique_ptr InitializeTranslations(QObject* parent) "and safe to set at 16x on most GPUs.")); INSERT(Settings, gpu_accuracy, - tr("GPU Level:"), + tr("GPU Accuracy:"), tr("Controls the GPU emulation accuracy.\nMost games render fine with Normal, but High is still " "required for some.\nParticles tend to only render correctly with High " - "accuracy.\nExtreme should only be used for debugging.\nThis option can " - "be changed while playing.\nSome games may require booting on high to render " - "properly.")); + "accuracy.\nExtreme should only be used as a last resort.")); INSERT(Settings, dma_accuracy, - tr("DMA Level:"), - tr("Controls the DMA precision accuracy. Higher precision can fix issues in some games, but it can also impact performance in some cases.\nIf unsure, leave it at Default.")); + tr("DMA Accuracy:"), + tr("Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases.\nIf unsure, leave this on Default.")); INSERT(Settings, use_asynchronous_shaders, tr("Use asynchronous shader building (Hack)"), @@ -529,9 +527,8 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) translations->insert({Settings::EnumMetadata::Index(), { PAIR(DmaAccuracy, Default, tr("Default")), - PAIR(DmaAccuracy, Normal, tr("Normal")), - PAIR(DmaAccuracy, High, tr("High")), - PAIR(DmaAccuracy, Extreme, tr("Extreme")), + PAIR(DmaAccuracy, Unsafe, tr("Unsafe (fast)")), + PAIR(DmaAccuracy, Safe, tr("Safe (stable)")), }}); translations->insert( {Settings::EnumMetadata::Index(), diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 4b9a506cdf..a9bcd150e6 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -102,23 +102,10 @@ bool DmaPusher::Step() { ProcessCommands(headers); }; - const Settings::DmaAccuracy accuracy = Settings::values.dma_accuracy.GetValue(); - const bool use_gpu_accuracy = accuracy == Settings::DmaAccuracy::Default; + const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe(); - // reduces eye bleeding but also macros are dumb so idk -#define CHECK_LEVEL(level) use_gpu_accuracy ? Settings::IsGPULevel##level() : accuracy == Settings::DmaAccuracy::level; - const bool force_safe = CHECK_LEVEL(Extreme) - const bool unsafe_compute = CHECK_LEVEL(High) -#undef CHECK_LEVEL - - if (force_safe) { + if (use_safe) { safe_process(); - } else if (unsafe_compute) { - if (dma_state.method >= MacroRegistersStart) { - unsafe_process(); - } else { - safe_process(); - } } else { unsafe_process(); } From 6699361b7e76590409ba973e28978ffe0e9cc143 Mon Sep 17 00:00:00 2001 From: crueter Date: Tue, 16 Sep 2025 18:44:19 +0200 Subject: [PATCH 09/23] [cmake] fix freebsd and openbsd, remove CPMUtil Eden copyright (#496) CPMUtil is technically a completely separate project created and maintained solely by me, so it should actually be copyrighted to me Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/496 Reviewed-by: MaranBr --- CMakeLists.txt | 36 ++++++++++++++++++++++++++++----- CMakeModules/CPMUtil.cmake | 23 +++++---------------- externals/libusb/CMakeLists.txt | 9 +++++++-- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fdf8900775..673aab9e6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,13 @@ if (PLATFORM_SUN) endif() endif() +# Needed for FFmpeg w/ VAAPI and DRM +if (PLATFORM_OPENBSD) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/X11R6/include") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I/usr/X11R6/include") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/X11R6/lib") +endif() + # Detect current compilation architecture and create standard definitions # ======================================================================= @@ -88,7 +95,7 @@ message(STATUS "Target architecture: ${ARCHITECTURE}") if (MSVC AND ARCHITECTURE_x86) message(FATAL_ERROR "Attempting to build with the x86 environment is not supported. \ - This can typically happen if you used the Developer Command Prompt from the start menu;\ + This can typically happen if you used the Developer Command Prompt from the start menu; \ instead, run vcvars64.bat directly, located at C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat") endif() @@ -122,7 +129,7 @@ include(CMakeDependentOption) include(CTest) # Disable Warnings as Errors for MSVC -if (CXX_CL) +if (MSVC AND NOT CXX_CLANG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /WX-") endif() @@ -485,13 +492,32 @@ else() find_package(Opus 1.3 MODULE REQUIRED) find_package(ZLIB 1.2 REQUIRED) find_package(zstd 1.5 REQUIRED MODULE) - find_package(Boost 1.57.0 REQUIRED context system fiber) - find_package(MbedTLS 3) + + # wow + if (PLATFORM_LINUX) + find_package(Boost 1.57.0 REQUIRED headers context system fiber) + else() + find_package(Boost 1.57.0 REQUIRED) + endif() + + # OpenBSD does not package mbedtls3 (only 2) + if (PLATFORM_OPENBSD) + AddJsonPackage(mbedtls) + else() + find_package(MbedTLS 3 REQUIRED) + endif() find_package(VulkanUtilityLibraries REQUIRED) find_package(VulkanHeaders 1.3.274 REQUIRED) + + # FreeBSD does not package spirv-headers + if (PLATFORM_FREEBSD) + AddJsonPackage(spirv-headers) + else() + find_package(SPIRV-Headers 1.3.274 REQUIRED) + endif() + find_package(SPIRV-Tools MODULE REQUIRED) - find_package(SPIRV-Headers 1.3.274 REQUIRED) if (YUZU_TESTS) find_package(Catch2 3.0.1 REQUIRED) diff --git a/CMakeModules/CPMUtil.cmake b/CMakeModules/CPMUtil.cmake index db9cce4c66..f76a16c103 100644 --- a/CMakeModules/CPMUtil.cmake +++ b/CMakeModules/CPMUtil.cmake @@ -1,17 +1,6 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - # SPDX-FileCopyrightText: Copyright 2025 crueter # SPDX-License-Identifier: GPL-3.0-or-later -# Created-By: crueter -# Docs will come at a later date, mostly this is to just reduce boilerplate -# and some cmake magic to allow for runtime viewing of dependency versions - -# Future crueter: Wow this was a lie and a half, at this point I might as well make my own CPN -# haha just kidding... unless? - -# TODO(crueter): Remember to get more than 6 hours of sleep whenever making giant cmake changes if (MSVC OR ANDROID) set(BUNDLED_DEFAULT ON) else() @@ -27,6 +16,7 @@ option(CPMUTIL_FORCE_SYSTEM cmake_minimum_required(VERSION 3.22) include(CPM) +# cpmfile parsing set(CPMUTIL_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cpmfile.json") if (EXISTS ${CPMUTIL_JSON_FILE}) @@ -35,12 +25,11 @@ else() message(WARNING "[CPMUtil] cpmfile ${CPMUTIL_JSON_FILE} does not exist, AddJsonPackage will be a no-op") endif() -# utility +# Utility stuff function(cpm_utils_message level name message) message(${level} "[CPMUtil] ${name}: ${message}") endfunction() -# utility function(array_to_list array length out) math(EXPR range "${length} - 1") @@ -53,7 +42,6 @@ function(array_to_list array length out) set("${out}" "${NEW_LIST}" PARENT_SCOPE) endfunction() -# utility function(get_json_element object out member default) string(JSON out_type ERROR_VARIABLE err TYPE "${object}" ${member}) @@ -73,14 +61,13 @@ function(get_json_element object out member default) set("${out}" "${outvar}" PARENT_SCOPE) endfunction() -# Kinda cancerous but whatever +# The preferred usage function(AddJsonPackage) set(oneValueArgs NAME # these are overrides that can be generated at runtime, so can be defined separately from the json DOWNLOAD_ONLY - SYSTEM_PACKAGE BUNDLED_PACKAGE ) @@ -90,6 +77,7 @@ function(AddJsonPackage) "${ARGN}") list(LENGTH ARGN argnLength) + # single name argument if(argnLength EQUAL 1) set(JSON_NAME "${ARGV0}") @@ -199,7 +187,6 @@ function(AddJsonPackage) endif() set(options ${options} ${JSON_OPTIONS}) - # end options # system/bundled @@ -241,7 +228,7 @@ endfunction() function(AddPackage) cpm_set_policies() - # TODO(crueter): docs, git clone + # TODO(crueter): git clone? #[[ URL configurations, descending order of precedence: diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt index cfa9a02a2a..4bf2421c53 100644 --- a/externals/libusb/CMakeLists.txt +++ b/externals/libusb/CMakeLists.txt @@ -1,15 +1,19 @@ +# SPDX-FileCopyrightText: 2025 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2020 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later include(CPMUtil) -if (PLATFORM_SUN OR PLATFORM_OPENBSD OR PLATFORM_FREEBSD) +# we love our libraries don't we folks +if (PLATFORM_SUN) set(libusb_bundled ON) else() set(libusb_bundled OFF) endif() -# TODO(crueter): Fix on *BSD/Solaris +# TODO(crueter): Fix on Solaris AddJsonPackage( NAME libusb BUNDLED_PACKAGE ${libusb_bundled} @@ -19,6 +23,7 @@ if (NOT libusb_ADDED) return() endif() +# TODO: *BSD fails to compile--may need different configs/symbols if (MINGW OR PLATFORM_LINUX OR APPLE) set(LIBUSB_FOUND ON CACHE BOOL "libusb is present" FORCE) set(LIBUSB_VERSION "1.0.24" CACHE STRING "libusb version string" FORCE) From 3ca0bde0e9c8cc408610b0838aeb4c1fa8aeda64 Mon Sep 17 00:00:00 2001 From: SDK-Chan Date: Tue, 16 Sep 2025 19:41:52 +0200 Subject: [PATCH 10/23] [core/nvnflinger] Rewrite GetBufferHistory (#528) This rewrite should improve performance with the buffer history by changing the complexity level to O(1). Replace std::vector with std::array to ensure that elements are allocated on the stack rather than on the free store. Avoid expensive resizing at runtime. Adjust buffer states at the right locations. Tightly pack the BufferHistoryInfo struct to ensure that it only occupies 28 bytes. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/528 Co-authored-by: SDK-Chan Co-committed-by: SDK-Chan --- .../nvnflinger/buffer_queue_consumer.cpp | 19 ++--- .../service/nvnflinger/buffer_queue_core.cpp | 20 ++++-- .../service/nvnflinger/buffer_queue_core.h | 28 ++++---- .../nvnflinger/buffer_queue_producer.cpp | 71 ++++++++++++------- .../nvnflinger/buffer_queue_producer.h | 3 + src/core/hle/service/nvnflinger/buffer_slot.h | 5 +- 6 files changed, 94 insertions(+), 52 deletions(-) diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp index a9b0f9d2f3..2913d25819 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -97,18 +100,18 @@ Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer, slots[slot].needs_cleanup_on_release = false; slots[slot].buffer_state = BufferState::Acquired; + // Mark tracked buffer history records as acquired + for (auto& buffer_history_record : core->buffer_history) { + if (buffer_history_record.frame_number == core->frame_counter) { + buffer_history_record.state = BufferState::Acquired; + break; + } + } + // TODO: for now, avoid resetting the fence, so that when we next return this // slot to the producer, it will wait for the fence to pass. We should fix this // by properly waiting for the fence in the BufferItemConsumer. // slots[slot].fence = Fence::NoFence(); - - const auto target_frame_number = slots[slot].frame_number; - for (size_t i = 0; i < core->history.size(); i++) { - if (core->history[i].frame_number == target_frame_number) { - core->history[i].state = BufferState::Acquired; - break; - } - } } // If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp index 27ac930f96..6120d8eae1 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_core.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,12 +13,19 @@ namespace Service::android { -BufferQueueCore::BufferQueueCore() { - history.resize(8); -}; - +BufferQueueCore::BufferQueueCore() = default; BufferQueueCore::~BufferQueueCore() = default; +void BufferQueueCore::PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state) { + buffer_history_pos = (buffer_history_pos + 1) % BUFFER_HISTORY_SIZE; + buffer_history[buffer_history_pos] = BufferHistoryInfo{ + .frame_number = frame_number, + .queue_time = queue_time, + .presentation_time = presentation_time, + .state = state, + }; +} + void BufferQueueCore::SignalDequeueCondition() { dequeue_possible.store(true); dequeue_condition.notify_all(); @@ -47,7 +57,7 @@ s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const { s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const { const auto min_buffer_count = GetMinMaxBufferCountLocked(async); - auto max_buffer_count = (std::max)(default_max_buffer_count, min_buffer_count); + auto max_buffer_count = std::max(default_max_buffer_count, min_buffer_count); if (override_max_buffer_count != 0) { ASSERT(override_max_buffer_count >= min_buffer_count); diff --git a/src/core/hle/service/nvnflinger/buffer_queue_core.h b/src/core/hle/service/nvnflinger/buffer_queue_core.h index 341634352b..ed7d4b4069 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_core.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -15,6 +18,7 @@ #include "core/hle/service/nvnflinger/buffer_item.h" #include "core/hle/service/nvnflinger/buffer_queue_defs.h" +#include "core/hle/service/nvnflinger/buffer_slot.h" #include "core/hle/service/nvnflinger/pixel_format.h" #include "core/hle/service/nvnflinger/status.h" #include "core/hle/service/nvnflinger/window.h" @@ -23,22 +27,19 @@ namespace Service::android { #ifdef _MSC_VER #pragma pack(push, 1) +struct BufferHistoryInfo { +#elif defined(__GNUC__) || defined(__clang__) +struct __attribute__((packed)) BufferHistoryInfo { #endif -struct BufferInfo { u64 frame_number; s64 queue_time; - s64 presentation_time{}; - BufferState state{BufferState::Free}; -} -#if defined(__GNUC__) || defined(__clang__) -__attribute__((packed)) -#endif -; + s64 presentation_time; + BufferState state; +}; #ifdef _MSC_VER #pragma pack(pop) #endif -static_assert(sizeof(BufferInfo) == 0x1C, - "BufferInfo is an invalid size"); +static_assert(sizeof(BufferHistoryInfo) == 0x1C, "BufferHistoryInfo must be 28 bytes"); class IConsumerListener; class IProducerListener; @@ -49,10 +50,13 @@ class BufferQueueCore final { public: static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT; + static constexpr u32 BUFFER_HISTORY_SIZE = 8; BufferQueueCore(); ~BufferQueueCore(); + void PushHistory(u64 frame_number, s64 queue_time, s64 presentation_time, BufferState state); + private: void SignalDequeueCondition(); bool WaitForDequeueCondition(std::unique_lock& lk); @@ -88,11 +92,11 @@ private: const s32 max_acquired_buffer_count{}; // This is always zero on HOS bool buffer_has_been_queued{}; u64 frame_counter{}; + std::array buffer_history{}; + u32 buffer_history_pos{BUFFER_HISTORY_SIZE-1}; u32 transform_hint{}; bool is_allocating{}; mutable std::condition_variable_any is_allocating_condition; - - std::vector history{8}; }; } // namespace Service::android diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index f9e1dba965..bc3076d20b 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -530,11 +533,6 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, item.is_droppable = core->dequeue_buffer_cannot_block || async; item.swap_interval = swap_interval; - position = (position + 1) % 8; - core->history[position] = {.frame_number = core->frame_counter, - .queue_time = slots[slot].queue_time, - .state = BufferState::Queued}; - sticky_transform = sticky_transform_; if (core->queue.empty()) { @@ -551,6 +549,15 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, // mark it as freed if (core->StillTracking(*front)) { slots[front->slot].buffer_state = BufferState::Free; + + // Mark tracked buffer history records as free + for (auto& buffer_history_record : core->buffer_history) { + if (buffer_history_record.frame_number == front->frame_number) { + buffer_history_record.state = BufferState::Free; + break; + } + } + // Reset the frame number of the freed buffer so that it is the first in line to // be dequeued again slots[front->slot].frame_number = 0; @@ -564,6 +571,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, } } + core->PushHistory(core->frame_counter, slots[slot].queue_time, slots[slot].presentation_time, BufferState::Queued); core->buffer_has_been_queued = true; core->SignalDequeueCondition(); output->Inflate(core->default_width, core->default_height, core->transform_hint, @@ -938,33 +946,46 @@ void BufferQueueProducer::Transact(u32 code, std::span parcel_data, break; } case TransactionId::GetBufferHistory: { - LOG_WARNING(Service_Nvnflinger, "called, transaction=GetBufferHistory"); + LOG_DEBUG(Service_Nvnflinger, "called, transaction=GetBufferHistory"); - std::scoped_lock lock{core->mutex}; - - auto buffer_history_count = (std::min)(parcel_in.Read(), (s32)core->history.size()); - - if (buffer_history_count <= 0) { + const s32 request = parcel_in.Read(); + if (request <= 0) { parcel_out.Write(Status::BadValue); parcel_out.Write(0); - status = Status::None; break; } - auto info = new BufferInfo[buffer_history_count]; - auto pos = position; - for (int i = 0; i < buffer_history_count; i++) { - info[i] = core->history[(pos - i) % core->history.size()]; - LOG_WARNING(Service_Nvnflinger, "frame_number={}, state={}", - core->history[(pos - i) % core->history.size()].frame_number, - (u32)core->history[(pos - i) % core->history.size()].state); - pos--; + constexpr u32 history_max = BufferQueueCore::BUFFER_HISTORY_SIZE; + std::array buffer_history_snapshot{}; + s32 valid_index{}; + { + std::scoped_lock lk(core->mutex); + + const u32 current_history_pos = core->buffer_history_pos; + u32 index_reversed{}; + for (u32 i = 0; i < history_max; ++i) { + // Wrap values backwards e.g. 7, 6, 5, etc. in the range of 0-7 + index_reversed = (current_history_pos + history_max - i) % history_max; + const auto& current_history_buffer = core->buffer_history[index_reversed]; + + // Here we use the frame number as a terminator. + // Because a buffer without frame_number is not considered complete + if (current_history_buffer.frame_number == 0) { + break; + } + + buffer_history_snapshot[valid_index] = current_history_buffer; + ++valid_index; + } } + const s32 limit = std::min(request, valid_index); parcel_out.Write(Status::NoError); - parcel_out.Write(buffer_history_count); - parcel_out.WriteFlattenedObject(info); - status = Status::None; + parcel_out.Write(limit); + for (s32 i = 0; i < limit; ++i) { + parcel_out.Write(buffer_history_snapshot[i]); + } + break; } default: @@ -972,9 +993,7 @@ void BufferQueueProducer::Transact(u32 code, std::span parcel_data, break; } - if (status != Status::None) { - parcel_out.Write(status); - } + parcel_out.Write(status); const auto serialized = parcel_out.Serialize(); std::memcpy(parcel_reply.data(), serialized.data(), diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.h b/src/core/hle/service/nvnflinger/buffer_queue_producer.h index 28195cd3c5..6610e0853a 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h index 5b5cbb6fbd..d348b331cb 100644 --- a/src/core/hle/service/nvnflinger/buffer_slot.h +++ b/src/core/hle/service/nvnflinger/buffer_slot.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -15,7 +18,7 @@ namespace Service::android { class GraphicBuffer; -enum class BufferState : s32 { +enum class BufferState : u32 { Free = 0, Dequeued = 1, Queued = 2, From 3b3278f44bc10c9e6b39ea6cfcd1b8a256e8373c Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 16 Sep 2025 20:54:00 +0200 Subject: [PATCH 11/23] [jit, exception] use shared mutex to reduce thread contention of cached code blocks in fastmem trap handler (#320) ankerl::unordered_dense::map will provide better lookup times, theoretically. fastmem trap handler usually had mutex contention for non-linkable series of blocks (block linking failures); so just use shared_mutex Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/320 Reviewed-by: MaranBr Reviewed-by: crueter Co-authored-by: lizzie Co-committed-by: lizzie --- .../backend/exception_handler_posix.cpp | 220 +++++++----------- 1 file changed, 87 insertions(+), 133 deletions(-) diff --git a/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp b/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp index 7695df57d2..d0653eceab 100644 --- a/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp +++ b/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp @@ -6,8 +6,13 @@ * SPDX-License-Identifier: 0BSD */ -#include "dynarmic/backend/exception_handler.h" - +#include +#include +#include +#include +#include +#include +#include #ifdef __APPLE__ # include # include @@ -21,17 +26,10 @@ # endif #endif -#include -#include -#include -#include -#include -#include +#include -#include "dynarmic/common/assert.h" -#include +#include "dynarmic/backend/exception_handler.h" #include "dynarmic/common/common_types.h" - #if defined(MCL_ARCHITECTURE_X86_64) # include "dynarmic/backend/x64/block_of_code.h" #elif defined(MCL_ARCHITECTURE_ARM64) @@ -43,42 +41,80 @@ #else # error "Invalid architecture" #endif +#include namespace Dynarmic::Backend { namespace { struct CodeBlockInfo { - u64 code_begin, code_end; + u64 size; std::function cb; }; class SigHandler { -public: - SigHandler(); - ~SigHandler(); - - void AddCodeBlock(CodeBlockInfo info); - void RemoveCodeBlock(u64 host_pc); - - bool SupportsFastmem() const { return supports_fast_mem; } - -private: - auto FindCodeBlockInfo(u64 host_pc) { - return std::find_if(code_block_infos.begin(), code_block_infos.end(), [&](const auto& x) { return x.code_begin <= host_pc && x.code_end > host_pc; }); + auto FindCodeBlockInfo(u64 offset) noexcept { + return std::find_if(code_block_infos.begin(), code_block_infos.end(), [&](auto const& e) { + return e.first <= offset && e.first + e.second.size > offset; + }); } + static void SigAction(int sig, siginfo_t* info, void* raw_context); bool supports_fast_mem = true; - void* signal_stack_memory = nullptr; - - std::vector code_block_infos; - std::mutex code_block_infos_mutex; - + ankerl::unordered_dense::map code_block_infos; + std::shared_mutex code_block_infos_mutex; struct sigaction old_sa_segv; struct sigaction old_sa_bus; + std::size_t signal_stack_size; +public: + SigHandler() noexcept { + signal_stack_size = std::max(SIGSTKSZ, 2 * 1024 * 1024); + signal_stack_memory = mmap(nullptr, signal_stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - static void SigAction(int sig, siginfo_t* info, void* raw_context); + stack_t signal_stack{}; + signal_stack.ss_sp = signal_stack_memory; + signal_stack.ss_size = signal_stack_size; + signal_stack.ss_flags = 0; + if (sigaltstack(&signal_stack, nullptr) != 0) { + fmt::print(stderr, "dynarmic: POSIX SigHandler: init failure at sigaltstack\n"); + supports_fast_mem = false; + return; + } + + struct sigaction sa{}; + sa.sa_handler = nullptr; + sa.sa_sigaction = &SigHandler::SigAction; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGSEGV, &sa, &old_sa_segv) != 0) { + fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGSEGV handler\n"); + supports_fast_mem = false; + return; + } +#ifdef __APPLE__ + if (sigaction(SIGBUS, &sa, &old_sa_bus) != 0) { + fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGBUS handler\n"); + supports_fast_mem = false; + return; + } +#endif + } + + ~SigHandler() noexcept { + munmap(signal_stack_memory, signal_stack_size); + } + + void AddCodeBlock(u64 offset, CodeBlockInfo cbi) noexcept { + std::unique_lock guard(code_block_infos_mutex); + code_block_infos.insert_or_assign(offset, cbi); + } + void RemoveCodeBlock(u64 offset) noexcept { + std::unique_lock guard(code_block_infos_mutex); + code_block_infos.erase(offset); + } + + bool SupportsFastmem() const noexcept { return supports_fast_mem; } }; std::mutex handler_lock; @@ -91,64 +127,8 @@ void RegisterHandler() { } } -SigHandler::SigHandler() { - const size_t signal_stack_size = std::max(SIGSTKSZ, 2 * 1024 * 1024); - - signal_stack_memory = std::malloc(signal_stack_size); - - stack_t signal_stack; - signal_stack.ss_sp = signal_stack_memory; - signal_stack.ss_size = signal_stack_size; - signal_stack.ss_flags = 0; - if (sigaltstack(&signal_stack, nullptr) != 0) { - fmt::print(stderr, "dynarmic: POSIX SigHandler: init failure at sigaltstack\n"); - supports_fast_mem = false; - return; - } - - struct sigaction sa; - sa.sa_handler = nullptr; - sa.sa_sigaction = &SigHandler::SigAction; - sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; - sigemptyset(&sa.sa_mask); - if (sigaction(SIGSEGV, &sa, &old_sa_segv) != 0) { - fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGSEGV handler\n"); - supports_fast_mem = false; - return; - } -#ifdef __APPLE__ - if (sigaction(SIGBUS, &sa, &old_sa_bus) != 0) { - fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGBUS handler\n"); - supports_fast_mem = false; - return; - } -#endif -} - -SigHandler::~SigHandler() { - std::free(signal_stack_memory); -} - -void SigHandler::AddCodeBlock(CodeBlockInfo cbi) { - std::lock_guard guard(code_block_infos_mutex); - if (auto iter = FindCodeBlockInfo(cbi.code_begin); iter != code_block_infos.end()) { - code_block_infos.erase(iter); - } - code_block_infos.push_back(cbi); -} - -void SigHandler::RemoveCodeBlock(u64 host_pc) { - std::lock_guard guard(code_block_infos_mutex); - const auto iter = FindCodeBlockInfo(host_pc); - if (iter == code_block_infos.end()) { - return; - } - code_block_infos.erase(iter); -} - void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) { - ASSERT(sig == SIGSEGV || sig == SIGBUS); - + DEBUG_ASSERT(sig == SIGSEGV || sig == SIGBUS); #ifndef MCL_ARCHITECTURE_RISCV ucontext_t* ucontext = reinterpret_cast(raw_context); #ifndef __OpenBSD__ @@ -157,7 +137,6 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) { #endif #if defined(MCL_ARCHITECTURE_X86_64) - # if defined(__APPLE__) # define CTX_RIP (mctx->__ss.__rip) # define CTX_RSP (mctx->__ss.__rsp) @@ -179,26 +158,18 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) { # else # error "Unknown platform" # endif - { - std::lock_guard guard(sig_handler->code_block_infos_mutex); - - const auto iter = sig_handler->FindCodeBlockInfo(CTX_RIP); - if (iter != sig_handler->code_block_infos.end()) { - FakeCall fc = iter->cb(CTX_RIP); - + std::shared_lock guard(sig_handler->code_block_infos_mutex); + if (auto const iter = sig_handler->FindCodeBlockInfo(CTX_RIP); iter != sig_handler->code_block_infos.end()) { + FakeCall fc = iter->second.cb(CTX_RIP); CTX_RSP -= sizeof(u64); *mcl::bit_cast(CTX_RSP) = fc.ret_rip; CTX_RIP = fc.call_rip; - return; } } - fmt::print(stderr, "Unhandled {} at rip {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_RIP); - #elif defined(MCL_ARCHITECTURE_ARM64) - # if defined(__APPLE__) # define CTX_PC (mctx->__ss.__pc) # define CTX_SP (mctx->__ss.__sp) @@ -240,30 +211,19 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) { # else # error "Unknown platform" # endif - { - std::lock_guard guard(sig_handler->code_block_infos_mutex); - - const auto iter = sig_handler->FindCodeBlockInfo(CTX_PC); - if (iter != sig_handler->code_block_infos.end()) { - FakeCall fc = iter->cb(CTX_PC); - + std::shared_lock guard(sig_handler->code_block_infos_mutex); + if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) { + FakeCall fc = iter->second.cb(CTX_PC); CTX_PC = fc.call_pc; - return; } } - fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC); - #elif defined(MCL_ARCHITECTURE_RISCV) - ASSERT_FALSE("Unimplemented"); - #else - # error "Invalid architecture" - #endif struct sigaction* retry_sa = sig == SIGSEGV ? &sig_handler->old_sa_segv : &sig_handler->old_sa_bus; @@ -284,26 +244,26 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) { } // anonymous namespace struct ExceptionHandler::Impl final { - Impl(u64 code_begin_, u64 code_end_) - : code_begin(code_begin_) - , code_end(code_end_) { + Impl(u64 offset_, u64 size_) + : offset(offset_) + , size(size_) { RegisterHandler(); } void SetCallback(std::function cb) { - CodeBlockInfo cbi; - cbi.code_begin = code_begin; - cbi.code_end = code_end; - cbi.cb = cb; - sig_handler->AddCodeBlock(cbi); + sig_handler->AddCodeBlock(offset, CodeBlockInfo{ + .size = size, + .cb = cb + }); } ~Impl() { - sig_handler->RemoveCodeBlock(code_begin); + sig_handler->RemoveCodeBlock(offset); } private: - u64 code_begin, code_end; + u64 offset; + u64 size; }; ExceptionHandler::ExceptionHandler() = default; @@ -311,28 +271,22 @@ ExceptionHandler::~ExceptionHandler() = default; #if defined(MCL_ARCHITECTURE_X86_64) void ExceptionHandler::Register(X64::BlockOfCode& code) { - const u64 code_begin = mcl::bit_cast(code.getCode()); - const u64 code_end = code_begin + code.GetTotalCodeSize(); - impl = std::make_unique(code_begin, code_end); + impl = std::make_unique(mcl::bit_cast(code.getCode()), code.GetTotalCodeSize()); } #elif defined(MCL_ARCHITECTURE_ARM64) void ExceptionHandler::Register(oaknut::CodeBlock& mem, std::size_t size) { - const u64 code_begin = mcl::bit_cast(mem.ptr()); - const u64 code_end = code_begin + size; - impl = std::make_unique(code_begin, code_end); + impl = std::make_unique(mcl::bit_cast(mem.ptr()), size); } #elif defined(MCL_ARCHITECTURE_RISCV) void ExceptionHandler::Register(RV64::CodeBlock& mem, std::size_t size) { - const u64 code_begin = mcl::bit_cast(mem.ptr()); - const u64 code_end = code_begin + size; - impl = std::make_unique(code_begin, code_end); + impl = std::make_unique(mcl::bit_cast(mem.ptr()), size); } #else # error "Invalid architecture" #endif bool ExceptionHandler::SupportsFastmem() const noexcept { - return static_cast(impl) && sig_handler->SupportsFastmem(); + return bool(impl) && sig_handler->SupportsFastmem(); } void ExceptionHandler::SetFastmemCallback(std::function cb) { From dac2efc4c83b3db7d57f66b6bf18cfa86d2d12fe Mon Sep 17 00:00:00 2001 From: wildcard Date: Wed, 17 Sep 2025 02:27:03 +0200 Subject: [PATCH 12/23] [Shader Recompiler] Caching optimization for Texture_Pass (#481) Add Caching per texture_pass and optimize sorting to single pass. Use Logical shift instead of arithmetic shift. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/481 Reviewed-by: Shinmegumi Reviewed-by: crueter Co-authored-by: wildcard Co-committed-by: wildcard --- src/shader_recompiler/ir_opt/texture_pass.cpp | 137 +++++++++++++++--- 1 file changed, 114 insertions(+), 23 deletions(-) diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 9f04c0afaf..7ff1961172 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -7,7 +7,9 @@ #include #include #include - +#include +#include +#include #include #include "shader_recompiler/environment.h" @@ -177,6 +179,93 @@ bool IsBindless(const IR::Inst& inst) { bool IsTextureInstruction(const IR::Inst& inst) { return IndexedInstruction(inst) != IR::Opcode::Void; } +// Per-pass caches + struct CbufWordKey { + u32 index; + u32 offset; + bool operator==(const CbufWordKey& o) const noexcept { + return index == o.index && offset == o.offset; + } + }; + struct CbufWordKeyHash { + size_t operator()(const CbufWordKey& k) const noexcept { + return (static_cast(k.index) << 32) ^ k.offset; + } + }; + + struct HandleKey { + u32 index, offset, shift_left; + u32 sec_index, sec_offset, sec_shift_left; + bool has_secondary; + bool operator==(const HandleKey& o) const noexcept { + return std::tie(index, offset, shift_left, + sec_index, sec_offset, sec_shift_left, has_secondary) + == std::tie(o.index, o.offset, o.shift_left, + o.sec_index, o.sec_offset, o.sec_shift_left, o.has_secondary); + } + }; + struct HandleKeyHash { + size_t operator()(const HandleKey& k) const noexcept { + size_t h = (static_cast(k.index) << 32) ^ k.offset; + h ^= (static_cast(k.shift_left) << 1); + h ^= (static_cast(k.sec_index) << 33) ^ (static_cast(k.sec_offset) << 2); + h ^= (static_cast(k.sec_shift_left) << 3); + h ^= k.has_secondary ? 0x9e3779b97f4a7c15ULL : 0ULL; + return h; + } + }; + +// Thread-local(may implement multithreading in future *wink*) + thread_local std::unordered_map g_cbuf_word_cache; + thread_local std::unordered_map g_handle_cache; + thread_local std::unordered_map g_track_cache; + + static inline u32 ReadCbufCached(Environment& env, u32 index, u32 offset) { + const CbufWordKey k{index, offset}; + if (auto it = g_cbuf_word_cache.find(k); it != g_cbuf_word_cache.end()) return it->second; + const u32 v = env.ReadCbufValue(index, offset); + g_cbuf_word_cache.emplace(k, v); + return v; + } + + static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr& cbuf) { + const u32 sec_idx = cbuf.has_secondary ? cbuf.secondary_index : cbuf.index; + const u32 sec_off = cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset; + const HandleKey hk{cbuf.index, cbuf.offset, cbuf.shift_left, + sec_idx, sec_off, cbuf.secondary_shift_left, cbuf.has_secondary}; + if (auto it = g_handle_cache.find(hk); it != g_handle_cache.end()) return it->second; + + const u32 lhs = ReadCbufCached(env, cbuf.index, cbuf.offset) << cbuf.shift_left; + const u32 rhs = ReadCbufCached(env, sec_idx, sec_off) << cbuf.secondary_shift_left; + const u32 handle = lhs | rhs; + g_handle_cache.emplace(hk, handle); + return handle; + } + +// Cached variants of existing helpers + static inline TextureType ReadTextureTypeCached(Environment& env, const ConstBufferAddr& cbuf) { + return env.ReadTextureType(GetTextureHandleCached(env, cbuf)); + } + static inline TexturePixelFormat ReadTexturePixelFormatCached(Environment& env, + const ConstBufferAddr& cbuf) { + return env.ReadTexturePixelFormat(GetTextureHandleCached(env, cbuf)); + } + static inline bool IsTexturePixelFormatIntegerCached(Environment& env, + const ConstBufferAddr& cbuf) { + return env.IsTexturePixelFormatInteger(GetTextureHandleCached(env, cbuf)); + } + + + std::optional Track(const IR::Value& value, Environment& env); + static inline std::optional TrackCached(const IR::Value& v, Environment& env) { + if (const IR::Inst* key = v.InstRecursive()) { + if (auto it = g_track_cache.find(key); it != g_track_cache.end()) return it->second; + auto found = Track(v, env); + if (found) g_track_cache.emplace(key, *found); + return found; + } + return Track(v, env); + } std::optional TryGetConstBuffer(const IR::Inst* inst, Environment& env); @@ -203,7 +292,7 @@ std::optional TryGetConstant(IR::Value& value, Environment& env) { return std::nullopt; } const auto offset_number = offset.U32(); - return env.ReadCbufValue(index_number, offset_number); + return ReadCbufCached(env, index_number, offset_number); } std::optional TryGetConstBuffer(const IR::Inst* inst, Environment& env) { @@ -211,8 +300,8 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme default: return std::nullopt; case IR::Opcode::BitwiseOr32: { - std::optional lhs{Track(inst->Arg(0), env)}; - std::optional rhs{Track(inst->Arg(1), env)}; + std::optional lhs{TrackCached(inst->Arg(0), env)}; + std::optional rhs{TrackCached(inst->Arg(1), env)}; if (!lhs || !rhs) { return std::nullopt; } @@ -242,7 +331,7 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme if (!shift.IsImmediate()) { return std::nullopt; } - std::optional lhs{Track(inst->Arg(0), env)}; + std::optional lhs{TrackCached(inst->Arg(0), env)}; if (lhs) { lhs->shift_left = shift.U32(); } @@ -271,7 +360,7 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme return std::nullopt; } while (false); } - std::optional lhs{Track(op1, env)}; + std::optional lhs{TrackCached(op1, env)}; if (lhs) { lhs->shift_left = static_cast(std::countr_zero(op2.U32())); } @@ -346,7 +435,7 @@ static ConstBufferAddr last_valid_addr = ConstBufferAddr{ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) { ConstBufferAddr addr; if (IsBindless(inst)) { - const std::optional track_addr{Track(inst.Arg(0), env)}; + const std::optional track_addr{TrackCached(inst.Arg(0), env)}; if (!track_addr) { //throw NotImplementedException("Failed to track bindless texture constant buffer"); @@ -384,15 +473,15 @@ u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) { return lhs_raw | rhs_raw; } -TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) { + [[maybe_unused]]TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) { return env.ReadTextureType(GetTextureHandle(env, cbuf)); } -TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) { + [[maybe_unused]]TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) { return env.ReadTexturePixelFormat(GetTextureHandle(env, cbuf)); } -bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf) { + [[maybe_unused]]bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf) { return env.IsTexturePixelFormatInteger(GetTextureHandle(env, cbuf)); } @@ -543,6 +632,10 @@ void PatchTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_ } // Anonymous namespace void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo& host_info) { + // reset per-pass caches + g_cbuf_word_cache.clear(); + g_handle_cache.clear(); + g_track_cache.clear(); TextureInstVector to_replace; for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { @@ -553,11 +646,9 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo } } // Sort instructions to visit textures by constant buffer index, then by offset - std::ranges::sort(to_replace, [](const auto& lhs, const auto& rhs) { - return lhs.cbuf.offset < rhs.cbuf.offset; - }); - std::stable_sort(to_replace.begin(), to_replace.end(), [](const auto& lhs, const auto& rhs) { - return lhs.cbuf.index < rhs.cbuf.index; + std::ranges::sort(to_replace, [](const auto& a, const auto& b) { + if (a.cbuf.index != b.cbuf.index) return a.cbuf.index < b.cbuf.index; + return a.cbuf.offset < b.cbuf.offset; }); Descriptors descriptors{ program.info.texture_buffer_descriptors, @@ -575,14 +666,14 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo bool is_multisample{false}; switch (inst->GetOpcode()) { case IR::Opcode::ImageQueryDimensions: - flags.type.Assign(ReadTextureType(env, cbuf)); + flags.type.Assign(ReadTextureTypeCached(env, cbuf)); inst->SetFlags(flags); break; case IR::Opcode::ImageSampleImplicitLod: if (flags.type != TextureType::Color2D) { break; } - if (ReadTextureType(env, cbuf) == TextureType::Color2DRect) { + if (ReadTextureTypeCached(env, cbuf) == TextureType::Color2DRect) { PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst); } break; @@ -596,7 +687,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo if (flags.type != TextureType::Color1D) { break; } - if (ReadTextureType(env, cbuf) == TextureType::Buffer) { + if (ReadTextureTypeCached(env, cbuf) == TextureType::Buffer) { // Replace with the bound texture type only when it's a texture buffer // If the instruction is 1D and the bound type is 2D, don't change the code and let // the rasterizer robustness handle it @@ -627,7 +718,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo } const bool is_written{inst->GetOpcode() != IR::Opcode::ImageRead}; const bool is_read{inst->GetOpcode() != IR::Opcode::ImageWrite}; - const bool is_integer{IsTexturePixelFormatInteger(env, cbuf)}; + const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)}; if (flags.type == TextureType::Buffer) { index = descriptors.Add(ImageBufferDescriptor{ .format = flags.image_format, @@ -691,16 +782,16 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo if (cbuf.count > 1) { const auto insert_point{IR::Block::InstructionList::s_iterator_to(*inst)}; IR::IREmitter ir{*texture_inst.block, insert_point}; - const IR::U32 shift{ir.Imm32(std::countr_zero(DESCRIPTOR_SIZE))}; - inst->SetArg(0, ir.UMin(ir.ShiftRightArithmetic(cbuf.dynamic_offset, shift), - ir.Imm32(DESCRIPTOR_SIZE - 1))); + const IR::U32 shift{ir.Imm32(DESCRIPTOR_SIZE_SHIFT)}; + inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift), + ir.Imm32(DESCRIPTOR_SIZE - 1))); } else { inst->SetArg(0, IR::Value{}); } if (!host_info.support_snorm_render_buffer && inst->GetOpcode() == IR::Opcode::ImageFetch && flags.type == TextureType::Buffer) { - const auto pixel_format = ReadTexturePixelFormat(env, cbuf); + const auto pixel_format = ReadTexturePixelFormatCached(env, cbuf); if (IsPixelFormatSNorm(pixel_format)) { PatchTexelFetch(*texture_inst.block, *texture_inst.inst, pixel_format); } From cda69581119c967362c0170f094c66358b0b7925 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 17 Sep 2025 02:31:44 +0200 Subject: [PATCH 13/23] [host_memory] decrease latency of mapping on linux (#232) Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/232 Reviewed-by: MaranBr Reviewed-by: Shinmegumi Co-authored-by: lizzie Co-committed-by: lizzie --- src/common/host_memory.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 1b7532b6b9..2e36d59569 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -598,12 +598,17 @@ public: bool ClearBackingRegion(size_t physical_offset, size_t length) { #ifdef __linux__ - // Set MADV_REMOVE on backing map to destroy it instantly. - // This also deletes the area from the backing file. - int ret = madvise(backing_base + physical_offset, length, MADV_REMOVE); - ASSERT_MSG(ret == 0, "madvise failed: {}", strerror(errno)); - - return true; + // Only incur syscall cost IF memset would be slower (theshold = 16MiB) + // TODO(lizzie): Smarter way to dynamically get this threshold (broadwell != raptor lake) for example + if (length >= 2097152UL * 8) { + // Set MADV_REMOVE on backing map to destroy it instantly. + // This also deletes the area from the backing file. + int ret = madvise(backing_base + physical_offset, length, MADV_REMOVE); + ASSERT_MSG(ret == 0, "madvise failed: {}", strerror(errno)); + return true; + } else { + return false; + } #else return false; #endif From 8ac495aceea5460d7641ffad77c9f6f4a5167b07 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Wed, 17 Sep 2025 17:45:52 +0200 Subject: [PATCH 14/23] [fs] Remove remaining files from NCA bypass and fix some asserts (#2502) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2502 Reviewed-by: crueter Co-authored-by: MaranBr Co-committed-by: MaranBr --- src/core/CMakeLists.txt | 1 - .../fssystem/fssystem_bucket_tree.cpp | 1 - .../fssystem_nca_file_system_driver.cpp | 2 -- .../file_sys/fssystem/fssystem_nca_header.cpp | 6 ++-- .../fssystem/fssystem_passthrough_storage.h | 32 ------------------- 5 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 src/core/file_sys/fssystem/fssystem_passthrough_storage.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 89c97eb1aa..4d9566a60f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -107,7 +107,6 @@ add_library(core STATIC file_sys/fssystem/fssystem_nca_header.cpp file_sys/fssystem/fssystem_nca_header.h file_sys/fssystem/fssystem_nca_reader.cpp - file_sys/fssystem/fssystem_passthrough_storage.h file_sys/fssystem/fssystem_pooled_buffer.cpp file_sys/fssystem/fssystem_pooled_buffer.h file_sys/fssystem/fssystem_sparse_storage.cpp diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp index 71ba458cef..f58b154968 100644 --- a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp @@ -4,7 +4,6 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "common/settings.h" #include "core/file_sys/errors.h" #include "core/file_sys/fssystem/fssystem_bucket_tree.h" #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp index 37fb71e9e3..25036b02c1 100644 --- a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp @@ -4,7 +4,6 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "common/settings.h" #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" @@ -14,7 +13,6 @@ #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" #include "core/file_sys/fssystem/fssystem_indirect_storage.h" #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" -#include "core/file_sys/fssystem/fssystem_passthrough_storage.h" #include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h" #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" #include "core/file_sys/fssystem/fssystem_sparse_storage.h" diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.cpp b/src/core/file_sys/fssystem/fssystem_nca_header.cpp index 2226c087c0..77042dfd43 100644 --- a/src/core/file_sys/fssystem/fssystem_nca_header.cpp +++ b/src/core/file_sys/fssystem/fssystem_nca_header.cpp @@ -13,13 +13,11 @@ u8 NcaHeader::GetProperKeyGeneration() const { } bool NcaPatchInfo::HasIndirectTable() const { - static constexpr unsigned char BKTR[4] = {'B', 'K', 'T', 'R'}; - return std::memcmp(indirect_header.data(), BKTR, sizeof(BKTR)) == 0; + return this->indirect_size != 0; } bool NcaPatchInfo::HasAesCtrExTable() const { - static constexpr unsigned char BKTR[4] = {'B', 'K', 'T', 'R'}; - return std::memcmp(aes_ctr_ex_header.data(), BKTR, sizeof(BKTR)) == 0; + return this->aes_ctr_ex_size != 0; } } // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_passthrough_storage.h b/src/core/file_sys/fssystem/fssystem_passthrough_storage.h deleted file mode 100644 index 8fc6f4962a..0000000000 --- a/src/core/file_sys/fssystem/fssystem_passthrough_storage.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once -#include "core/file_sys/fssystem/fs_i_storage.h" -#include "core/file_sys/vfs/vfs.h" - -namespace FileSys { - -//TODO: No integrity verification. -class PassthroughStorage final : public IReadOnlyStorage { - YUZU_NON_COPYABLE(PassthroughStorage); - YUZU_NON_MOVEABLE(PassthroughStorage); - -public: - explicit PassthroughStorage(VirtualFile base) : base_(std::move(base)) {} - ~PassthroughStorage() override = default; - - size_t Read(u8* buffer, size_t size, size_t offset) const override { - if (!base_ || size == 0) - return 0; - return base_->Read(buffer, size, offset); - } - size_t GetSize() const override { - return base_ ? base_->GetSize() : 0; - } - -private: - VirtualFile base_{}; -}; - -} // namespace FileSys From 249e006667966b7709b74709a801c46714aecec1 Mon Sep 17 00:00:00 2001 From: wildcard Date: Wed, 17 Sep 2025 21:40:09 +0200 Subject: [PATCH 15/23] [VMA] Use Host cached and Host coherent for Download operations (#482) Increase read speeds by using appropriate usage flags Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/482 Reviewed-by: crueter Reviewed-by: Shinmegumi Co-authored-by: wildcard Co-committed-by: wildcard --- src/video_core/vulkan_common/vulkan_memory_allocator.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 675dede61c..119b4be1c8 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -49,6 +49,9 @@ namespace Vulkan { } [[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) { + if (usage == MemoryUsage::Download) { + return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + } return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT : VkMemoryPropertyFlagBits{}; } From e1ffeec21211f2fc3a255172aced9c51d659b896 Mon Sep 17 00:00:00 2001 From: crueter Date: Thu, 18 Sep 2025 02:37:02 +0200 Subject: [PATCH 16/23] [docs] refactor: full rewrite, generalization + dedup (#488) "docs but awesome" Combines most of the stuff that was repeated thrice over verbatim into a single common Build Instructions page, with additional caveats marked elsewhere. Prettifies some stuff too because why not. cc: @Lizzie @DraVee @MaranBr @SDK-Chan Co-authored-by: Caio Oliveira Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/488 Reviewed-by: Lizzie Reviewed-by: MaranBr --- README.md | 8 +- docs/Build.md | 160 +++++++++++++++++++++++++ docs/CPM.md | 2 +- docs/Caveats.md | 52 ++++++++ docs/Deps.md | 214 +++++++++++++++++++++++++++++++++ docs/Development.md | 24 +--- docs/Options.md | 69 +++++++++++ docs/README.md | 10 ++ docs/build/Android.md | 7 +- docs/build/FreeBSD.md | 81 ------------- docs/build/Linux.md | 138 --------------------- docs/build/OpenBSD.md | 10 -- docs/build/Solaris.md | 51 -------- docs/build/Windows.md | 259 ---------------------------------------- docs/build/macOS.md | 78 ------------ docs/img/creator-1.png | Bin 0 -> 64671 bytes docs/scripts/Linux.md | 31 +++++ docs/scripts/Windows.md | 29 +++++ 18 files changed, 573 insertions(+), 650 deletions(-) create mode 100644 docs/Build.md create mode 100644 docs/Caveats.md create mode 100644 docs/Deps.md create mode 100644 docs/Options.md create mode 100644 docs/README.md delete mode 100644 docs/build/FreeBSD.md delete mode 100644 docs/build/Linux.md delete mode 100644 docs/build/OpenBSD.md delete mode 100644 docs/build/Solaris.md delete mode 100644 docs/build/Windows.md delete mode 100644 docs/build/macOS.md create mode 100644 docs/img/creator-1.png create mode 100644 docs/scripts/Linux.md create mode 100644 docs/scripts/Windows.md diff --git a/README.md b/README.md index e1f0b50b37..959b903385 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,7 @@ If you would like to contribute, we are open to new developers and pull requests ## Building -* **Windows**: [Windows Building Guide](./docs/build/Windows.md) -* **Linux**: [Linux Building Guide](./docs/build/Linux.md) -* **Android**: [Android Building Guide](./docs/build/Android.md) -* **Solaris**: [Solaris Building Guide](./docs/build/Solaris.md) -* **FreeBSD**: [FreeBSD Building Guide](./docs/build/FreeBSD.md) -* **macOS**: [macOS Building Guide](./docs/build/macOS.md) -* **OpenBSD**: [OpenBSD Building Guide](./docs/build/OpenBSD.md) +See the [General Build Guide](docs/Build.md) ## Download diff --git a/docs/Build.md b/docs/Build.md new file mode 100644 index 0000000000..52a671ab1e --- /dev/null +++ b/docs/Build.md @@ -0,0 +1,160 @@ +# Building Eden + +> [!WARNING] +> This guide is intended for developers ONLY. If you are not a developer or packager, you are unlikely to receive support. + +This is a full-fledged guide to build Eden on all supported platforms. + +## Dependencies +First, you must [install some dependencies](Deps.md). + +## Clone +Next, you will want to clone Eden via the terminal: + +```sh +git clone https://git.eden-emu.dev/eden-emu/eden.git +cd eden +``` + +Or use Qt Creator (Create Project -> Import Project -> Git Clone). + +## Android + +Android has a completely different build process than other platforms. See its [dedicated page](build/Android.md). + +## Initial Configuration + +If the configure phase fails, see the `Troubleshooting` section below. Usually, as long as you followed the dependencies guide, the defaults *should* successfully configure and build. + +### Qt Creator + +This is the recommended GUI method for Linux, macOS, and Windows. + +
+Click to Open + +> [!WARNING] +> On MSYS2, to use Qt Creator you are recommended to *also* install Qt from the online installer, ensuring to select the "MinGW" version. + +Open the CMakeLists.txt file in your cloned directory via File -> Open File or Project (Ctrl+O), if you didn't clone Eden via the project import tool. + +Select your desired "kit" (usually, the default is okay). RelWithDebInfo or Release is recommended: + +![Qt Creator kits](img/creator-1.png) + +Hit "Configure Project", then wait for CMake to finish configuring (may take a while on Windows). + +
+ +### Command Line + +This is recommended for *BSD, Solaris, Linux, and MSYS2. MSVC is possible, but not recommended. + +
+Click to Open + +Note that CMake must be in your PATH, and you must be in the cloned Eden directory. On Windows, you must also set up a Visual C++ development environment. This can be done by running `C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat` in the same terminal. + +Recommended generators: + +- MSYS2: `MSYS Makefiles` +- MSVC: Install **[ninja](https://ninja-build.org/)** and use `Ninja`, OR use `Visual Studio 17 2022` +- macOS: `Ninja` (preferred) or `Xcode` +- Others: `Ninja` (preferred) or `UNIX Makefiles` + +BUILD_TYPE should usually be `Release` or `RelWithDebInfo` (debug symbols--compiled executable will be large). If you are using a debugger and annoyed with stuff getting optimized out, try `Debug`. + +Also see the [Options](Options.md) page for additional CMake options. + +```sh +cmake -S . -B build -G "GENERATOR" -DCMAKE_BUILD_TYPE= -DYUZU_TESTS=OFF +``` + +If you are on Windows and prefer to use Clang: + +```sh +cmake -S . -B build -G "GENERATOR" -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl +``` + +
+ +### [CLion](https://www.jetbrains.com/clion/) + +
+Click to Open + +* Clone the Repository: + + + + + +--- + +### Building & Setup + +* Once Cloned, You will be taken to a prompt like the image below: + + + +* Set the settings to the image below: +* Change `Build type: Release` +* Change `Name: Release` +* Change `Toolchain Visual Studio` +* Change `Generator: Let CMake decide` +* Change `Build directory: build` + + + +* Click OK; now Clion will build a directory and index your code to allow for IntelliSense. Please be patient. +* Once this process has been completed (No loading bar bottom right), you can now build eden +* In the top right, click on the drop-down menu, select all configurations, then select eden + + + +* Now run by clicking the play button or pressing Shift+F10, and eden will auto-launch once built. + + +
+ +## Troubleshooting + +If your initial configure failed: +- *Carefully* re-read the [dependencies guide](Deps.md) +- Clear the CPM cache (`.cache/cpm`) and CMake cache (`/CMakeCache.txt`) +- Evaluate the error and find any related settings +- See the [CPM docs](CPM.md) to see if you may need to forcefully bundle any packages + +Otherwise, feel free to ask for help in Revolt or Discord. + +## Caveats + +Many platforms have quirks, bugs, and other fun stuff that may cause issues when building OR running. See the [Caveats page](Caveats.md) before continuing. + +## Building & Running + +### Qt Creator + +Simply hit Ctrl+B, or the "hammer" icon in the bottom left. To run, hit the "play" icon, or Ctrl+R. + +### Command Line + +If you are not on Windows and are using the `UNIX Makefiles` generator, you must also add `-j$(nproc)` to this command. + +``` +cmake --build build +``` + +Your compiled executable will be in: +- `build/bin/eden.exe` for Windows, +- `build/bin/eden.app/Contents/MacOS/eden` for macOS, +- and `build/bin/eden` for others. + +## Scripts + +Some platforms have convenience scripts provided for building. + +- **[Linux](scripts/Linux.md)** +- **[Windows](scripts/Windows.md)** + +macOS scripts will come soon. \ No newline at end of file diff --git a/docs/CPM.md b/docs/CPM.md index bce224da40..03d8a039f9 100644 --- a/docs/CPM.md +++ b/docs/CPM.md @@ -177,7 +177,7 @@ If `ci` is `true`: ### Examples -In order: OpenSSL CI, Boost (tag + artifact), discord-rpc (sha + options + patches), Opus (options + find_args) +In order: OpenSSL CI, Boost (tag + artifact), Opus (options + find_args), discord-rpc (sha + options + patches) ```json { diff --git a/docs/Caveats.md b/docs/Caveats.md new file mode 100644 index 0000000000..7bc2428bab --- /dev/null +++ b/docs/Caveats.md @@ -0,0 +1,52 @@ +# Caveats + +## Arch Linux + +- httplib AUR package is broken. Set `httplib_FORCE_BUNDLED=ON` if you have it installed. +- Eden is also available as an [AUR package](https://aur.archlinux.org/packages/eden-git). If you are unable to build, either use that or compare your process to the PKGBUILD. + +## Gentoo Linux + +Do not use the system sirit or xbyak packages. + +## macOS + +macOS is largely untested. Expect crashes, significant Vulkan issues, and other fun stuff. + +## Solaris + +Qt Widgets appears to be broken. For now, add `-DENABLE_QT=OFF` to your configure command. In the meantime, a Qt Quick frontend is in the works--check back later! + +This is needed for some dependencies that call cc directly (tz): + +```sh +echo '#!/bin/sh' >cc +echo 'gcc $@' >>cc +chmod +x cc +export PATH="$PATH:$PWD" +``` + +Default MESA is a bit outdated, the following environment variables should be set for a smoother experience: +```sh +export MESA_GL_VERSION_OVERRIDE=4.6 +export MESA_GLSL_VERSION_OVERRIDE=460 +export MESA_EXTENSION_MAX_YEAR=2025 +export MESA_DEBUG=1 +export MESA_VK_VERSION_OVERRIDE=1.3 +# Only if nvidia/intel drm drivers cause crashes, will severely hinder performance +export LIBGL_ALWAYS_SOFTWARE=1 +``` + +- Modify the generated ffmpeg.make (in build dir) if using multiple threads (base system `make` doesn't use `-j4`, so change for `gmake`). +- If using OpenIndiana, due to a bug in SDL2's CMake configuration, audio driver defaults to SunOS ``, which does not exist on OpenIndiana. Using external or bundled SDL2 may solve this. +- System OpenSSL generally does not work. Instead, use `-DYUZU_USE_BUNDLED_OPENSSL=ON` to use a bundled static OpenSSL, or build a system dependency from source. + +## OpenBSD + +After configuration, you may need to modify `externals/ffmpeg/CMakeFiles/ffmpeg-build/build.make` to use `-j$(nproc)` instead of just `-j`. + +## FreeBSD + +Eden is not currently available as a port on FreeBSD, though it is in the works. For now, the recommended method of usage is to compile it yourself. + +The available OpenSSL port (3.0.17) is out-of-date, and using a bundled static library instead is recommended; to do so, add `-DYUZU_USE_BUNDLED_OPENSSL=ON` to your CMake configure command. \ No newline at end of file diff --git a/docs/Deps.md b/docs/Deps.md new file mode 100644 index 0000000000..cfc6f0365b --- /dev/null +++ b/docs/Deps.md @@ -0,0 +1,214 @@ +# Dependencies + +To build Eden, you MUST have a C++ compiler. +* On Linux, this is usually [GCC](https://gcc.gnu.org/) 11+ or [Clang](https://clang.llvm.org/) v14+ + - GCC 12 also requires Clang 14+ +* On Windows, this is either: + - **[MSVC](https://visualstudio.microsoft.com/downloads/)**, + * *A convenience script to install the **minimal** version (Visual Build Tools) is provided in `.ci/windows/install-msvc.ps1`* + - clang-cl - can be downloaded from the MSVC installer, + - or **[MSYS2](https://www.msys2.org)** +* On macOS, this is Apple Clang + - This can be installed with `xcode-select --install` + +The following additional tools are also required: + +* **[CMake](https://www.cmake.org/)** 3.22+ - already included with the Android SDK +* **[Git](https://git-scm.com/)** for version control + - **[Windows installer](https://gitforwindows.org)** +* On Windows, you must install the **[Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows)** as well + - *A convenience script to install the latest SDK is provided in `.ci/windows/install-vulkan-sdk.ps1`* + +If you are on desktop and plan to use the Qt frontend, you *must* install Qt 6, and optionally Qt Creator (the recommended IDE for building) +* On Linux, *BSD and macOS, this can be done by the package manager + - If you wish to use Qt Creator, append `qtcreator` or `qt-creator` to the commands seen below. +* MSVC/clang-cl users on Windows must install through the [official installer](https://www.qt.io/download-qt-installer-oss) +* Linux and macOS users may choose to use the installer as well. +* MSYS2 can also install Qt 6 via the package manager + +If you are on Windows and NOT building with MSYS2, you may go [back home](Build.md) and continue. + +## Externals +The following are handled by Eden's externals: + +* [FFmpeg](https://ffmpeg.org/) (should use `-DYUZU_USE_EXTERNAL_FFMPEG=ON`) +* [SDL2](https://www.libsdl.org/download-2.0.php) 2.0.18+ (should use `-DYUZU_USE_EXTERNAL_SDL2=ON` OR `-DYUZU_USE_BUNDLED_SDL2=ON` to reduce compile time) + +All other dependencies will be downloaded and built by [CPM](https://github.com/cpm-cmake/CPM.cmake/) if `YUZU_USE_CPM` is on, but will always use system dependencies if available (UNIX-like only): + +* [Boost](https://www.boost.org/users/download/) 1.57.0+ +* [Catch2](https://github.com/catchorg/Catch2) 3.0.1 if `YUZU_TESTS` or `DYNARMIC_TESTS` are on +* [fmt](https://fmt.dev/) 8.0.1+ +* [lz4](http://www.lz4.org) +* [nlohmann\_json](https://github.com/nlohmann/json) 3.8+ +* [OpenSSL](https://www.openssl.org/source/) 1.1.1+ +* [ZLIB](https://www.zlib.net/) 1.2+ +* [zstd](https://facebook.github.io/zstd/) 1.5+ +* [enet](http://enet.bespin.org/) 1.3+ +* [Opus](https://opus-codec.org/) 1.3+ +* [MbedTLS](https://github.com/Mbed-TLS/mbedtls) 3+ + +Vulkan 1.3.274+ is also needed: +* [VulkanUtilityLibraries](https://github.com/KhronosGroup/Vulkan-Utility-Libraries) +* [VulkanHeaders](https://github.com/KhronosGroup/Vulkan-Headers) +* [SPIRV-Tools](https://github.com/KhronosGroup/SPIRV-Tools) +* [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers) + +Certain other dependencies will be fetched by CPM regardless. System packages *can* be used for these libraries, but many are either not packaged by most distributions OR have issues when used by the system: + +* [SimpleIni](https://github.com/brofield/simpleini) +* [DiscordRPC](https://github.com/eden-emulator/discord-rpc) +* [cubeb](https://github.com/mozilla/cubeb) +* [libusb](https://github.com/libusb/libusb) +* [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) +* [sirit](https://github.com/eden-emulator/sirit) +* [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_QT_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on +* [cpp-jwt](https://github.com/arun11299/cpp-jwt) 1.4+ - if `ENABLE_WEB_SERVICE` is on +* [unordered-dense](https://github.com/martinus/unordered_dense) +* [mcl](https://github.com/azahar-emu/mcl) - subject to removal + +On amd64: +* [xbyak](https://github.com/herumi/xbyak) - 7.22 or earlier is recommended +* [zycore](https://github.com/zyantific/zycore-c) +* [zydis](https://github.com/zyantific/zydis) 4+ +* Note: zydis and zycore-c MUST match. Using one as a system dependency and the other as a bundled dependency WILL break things + +On aarch64 OR if `DYNARMIC_TESTS` is on: +* [oaknut](https://github.com/merryhime/oaknut) 2.0.1+ + +On riscv64: +* [biscuit](https://github.com/lioncash/biscuit) 0.9.1+ + +## Commands + +These are commands to install all necessary dependencies on various Linux and BSD distributions, as well as macOS. Always review what you're running before you hit Enter! + +Click on the arrows to expand. + +
+Arch Linux + +```sh +sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 mbedtls ninja nlohmann-json openssl opus qt6-base qt6-multimedia sdl2 zlib zstd zip unzip zydis zycore vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers +``` + +* Building with QT Web Engine requires `qt6-webengine` as well. +* Proper Wayland support requires `qt6-wayland` +* GCC 11 or later is required. +
+ +
+Ubuntu, Debian, Mint Linux + +```sh +sudo apt-get install autoconf cmake g++ gcc git glslang-tools libasound2 libboost-context-dev libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev libmbedtls-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev libzydis-dev zydis-tools libzycore-dev +``` + +* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required. +* To enable QT Web Engine, add `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake. +
+ +
+Fedora Linux + +```sh +sudo dnf install autoconf ccache cmake fmt-devel gcc{,-c++} glslang hidapi-devel json-devel libtool libusb1-devel libzstd-devel lz4-devel nasm ninja-build openssl-devel pulseaudio-libs-devel qt6-linguist qt6-qtbase{-private,}-devel qt6-qtwebengine-devel qt6-qtmultimedia-devel speexdsp-devel wayland-devel zlib-devel ffmpeg-devel libXext-devel +``` + +* Force system libraries via CMake arguments: + * SDL2: `-DYUZU_USE_BUNDLED_SDL2=OFF -DYUZU_USE_EXTERNAL_SDL2=OFF` + * FFmpeg: `-DYUZU_USE_EXTERNAL_FFMPEG=OFF` +* [RPM Fusion](https://rpmfusion.org/) is required for `ffmpeg-devel` +* Fedora 32 or later is required. +* Fedora 36+ users with GCC 12 need Clang and should configure CMake with: +
+ +
+macOS + +Install dependencies from **[Homebrew](https://brew.sh/)** + +```sh +brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl2 speexdsp zlib zstd cmake Catch2 molten-vk vulkan-loader spirv-tools +``` + +If you are compiling on Intel Mac, or are using a Rosetta Homebrew installation, you must replace all references of `/opt/homebrew` with `/usr/local`. + +To run with MoltenVK, install additional dependencies: +```sh +brew install molten-vk vulkan-loader +``` + +
+ +
+FreeBSD + +``` +devel/cmake +devel/sdl20 +devel/boost-libs +devel/catch2 +devel/libfmt +devel/nlohmann-json +devel/ninja +devel/nasm +devel/autoconf +devel/pkgconf +devel/qt6-base + +net/enet + +multimedia/ffnvcodec-headers +multimedia/ffmpeg + +audio/opus + +archivers/liblz4 + +lang/gcc12 + +graphics/glslang +graphics/vulkan-utility-libraries +``` + +If using FreeBSD 12 or prior, use `devel/pkg-config` instead. +
+ +
+OpenBSD + +```sh +pkg_add -u +pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake llvm-19.1.7p3 qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl2 libusb1.1.0.27 +``` +
+ +
+Solaris / OpenIndiana + +Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability. + +Run the usual update + install of essential toolings: `sudo pkg update && sudo pkg install git cmake`. + +- **gcc**: `sudo pkg install developer/gcc-14`. +- **clang**: Version 20 is broken, use `sudo pkg install developer/clang-19`. + +Then install the libraries: `sudo pkg install qt6 boost glslang libzip library/lz4 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt`. +
+ +
+MSYS2 + +* Open the `MSYS2 MinGW 64-bit` shell (`mingw64.exe`) +* Download and install all dependencies using: + * `pacman -Syu git make mingw-w64-x86_64-SDL2 mingw-w64-x86_64-cmake mingw-w64-x86_64-python-pip mingw-w64-x86_64-qt6 mingw-w64-x86_64-toolchain autoconf libtool automake-wrapper` +* Add MinGW binaries to the PATH: + * `echo 'PATH=/mingw64/bin:$PATH' >> ~/.bashrc` +* Add VulkanSDK to the PATH: + * `echo 'PATH=$(readlink -e /c/VulkanSDK/*/Bin/):$PATH' >> ~/.bashrc` +
+ +## All Done + +You may now return to the **[root build guide](Build.md)**. \ No newline at end of file diff --git a/docs/Development.md b/docs/Development.md index e4816cd1ec..d170818d10 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -1,23 +1,3 @@ -# Development - -* **Windows**: [Windows Building Guide](./build/Windows.md) -* **Linux**: [Linux Building Guide](./build/Linux.md) -* **Android**: [Android Building Guide](./build/Android.md) -* **Solaris**: [Solaris Building Guide](./build/Solaris.md) -* **FreeBSD**: [FreeBSD Building Guide](./build/FreeBSD.md) -* **macOS**: [macOS Building Guide](./build/macOS.md) -* **OpenBSD**: [OpenBSD Building Guide](./build/OpenBSD.md) - -# CPM - -CPM (CMake Package Manager) is the preferred method of managing dependencies within Eden. Documentation on adding dependencies/using CPMUtil is in the works. - -Notes: -- `YUZU_USE_CPM` is set by default on MSVC and Android. Other platforms should use this if certain "required" system dependencies (e.g. OpenSSL) are broken or missing -- `CPMUTIL_DEFAULT_SYSTEM` can be set to `OFF` to force the usage of bundled dependencies. This can marginally decrease the final package size. -- When adding new prebuilt dependencies a la OpenSSL, SDL2, or FFmpeg, there *must* be a CMake option made available to forcefully download this bundle. See the OpenSSL implementation in the root CMakeLists for an example. - * This is necessary to allow for creation of fully-qualified source packs that allow for offline builds after download (some package managers and distros enforce this) - # Guidelines ## License Headers @@ -37,6 +17,8 @@ FIX=true .ci/license-header.sh git commit --amend -a --no-edit ``` +If the work is licensed/vendored from other people or projects, you may omit the license headers. Additionally, if you wish to retain authorship over a piece of code, you may attribute it to yourself; however, the code may be changed at any given point and brought under the attribution of Eden. + ## Pull Requests Pull requests are only to be merged by core developers when properly tested and discussions conclude on Discord or other communication channels. Labels are recommended but not required. However, all PRs MUST be namespaced and optionally typed: ``` @@ -49,7 +31,7 @@ Pull requests are only to be merged by core developers when properly tested and - The level of namespacing is generally left to the committer's choice. - However, we never recommend going more than two levels *except* in `hle`, in which case you may go as many as four levels depending on the specificity of your changes. -- Ocassionally, up to two namespaces may be provided for more clarity. +- Ocassionally, up to two additional namespaces may be provided for more clarity. * Changes that affect the entire project (sans CMake changes) should be namespaced as `meta`. - Maintainers are permitted to change namespaces at will. - Commits within PRs are not required to be namespaced, but it is highly recommended. diff --git a/docs/Options.md b/docs/Options.md new file mode 100644 index 0000000000..d19aab63f6 --- /dev/null +++ b/docs/Options.md @@ -0,0 +1,69 @@ +# CMake Options + +To change these options, add `-DOPTION_NAME=NEWVALUE` to the command line. + +- On Qt Creator, go to Project -> Current Configuration + +Notes: +- Defaults are marked per-platform. +- "Non-UNIX" just means Windows/MSVC and Android (yes, macOS is UNIX +- Android generally doesn't need to change anything; if you do, go to `src/android/app/build.gradle.kts` +- To set a boolean variable to on, use `ON` for the value; to turn it off, use `OFF` +- If a variable is mentioned as being e.g. "ON" for a specific platform(s), that means it is defaulted to OFF on others +- TYPE is always boolean unless otherwise specified +- Format: + * `OPTION_NAME` (TYPE DEFAULT) DESCRIPTION + +## Options + +- `YUZU_USE_CPM` (ON for non-UNIX) Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched. See the [CPM](CPM.md) and [Deps](Deps.md) docs for more info. +- `ENABLE_WEB_SERVICE` (ON) Enable multiplayer service +- `ENABLE_WIFI_SCAN` (OFF) Enable WiFi scanning (requires iw on Linux) - experimental +- `YUZU_USE_BUNDLED_FFMPEG` (ON for non-UNIX) Download (Windows, Android) or build (UNIX) bundled FFmpeg +- `ENABLE_CUBEB` (ON) Enables the cubeb audio backend +- `YUZU_TESTS` (ON) Compile tests - requires Catch2 +- `YUZU_USE_PRECOMPILED_HEADERS` (ON for non-UNIX) Use precompiled headers +- `YUZU_DOWNLOAD_ANDROID_VVL` (ON) Download validation layer binary for Android +- `YUZU_ENABLE_LTO` (OFF) Enable link-time optimization + * Not recommended on Windows + * UNIX may be better off appending `-flto=thin` to compiler args +- `YUZU_DOWNLOAD_TIME_ZONE_DATA` (ON) Always download time zone binaries + * Currently, build fails without this +- `YUZU_USE_FASTER_LD` (ON) Check if a faster linker is available + * Only available on UNIX +- `USE_SYSTEM_MOLTENVK` (OFF, macOS only) Use the system MoltenVK lib (instead of the bundled one) +- `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS) +- `ENABLE_OPENSSL` (ON for Linux and *BSD) Enable OpenSSL backend for the ssl service + * Always enabled if the web service is enabled +- `YUZU_USE_BUNDLED_OPENSSL` (ON for MSVC) Download bundled OpenSSL build + * Always on for Android + * Unavailable on OpenBSD + +The following options are desktop only: +- `ENABLE_SDL2` (ON) Enable the SDL2 desktop, audio, and input frontend (HIGHLY RECOMMENDED!) + * Unavailable on Android +- `YUZU_USE_EXTERNAL_SDL2` (ON for non-UNIX) Compiles SDL2 from source +- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2 + * Unavailable on OpenBSD + * Only enabled if YUZU_USE_CPM and ENABLE_SDL2 are both ON +- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input frontend (HIGHLY RECOMMENDED) +- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics frontend + * Unavailable on Windows/ARM64 and Android +- `ENABLE_QT` (ON) Enable the Qt frontend (recommended) +- `ENABLE_QT_TRANSLATION` (OFF) Enable translations for the Qt frontend +- `ENABLE_QT_UPDATE_CHECKER` (OFF) Enable update checker for the Qt frontend +- `YUZU_USE_BUNDLED_QT` (ON for MSVC) Download bundled Qt binaries + * Note that using **system Qt** requires you to include the Qt CMake directory in `CMAKE_PREFIX_PATH`, e.g: + * `-DCMAKE_PREFIX_PATH=C:/Qt/6.9.0/msvc2022_64/lib/cmake/Qt6` +- `YUZU_QT_MIRROR` (string) What mirror to use for downloading the bundled Qt libraries +- `YUZU_USE_QT_MULTIMEDIA` (OFF) Use QtMultimedia for camera support +- `YUZU_USE_QT_WEB_ENGINE` (OFF) Use QtWebEngine for web applet implementation (requires the huge QtWebEngine dependency; not recommended) +- `USE_DISCORD_PRESENCE` (OFF) Enables Discord Rich Presence (Qt frontend only) +- `YUZU_ROOM` (ON) Enable dedicated room functionality +- `YUZU_ROOM_STANDALONE` (ON) Enable standalone room executable (eden-room) + * Requires `YUZU_ROOM` +- `YUZU_CMD` (ON) Compile the SDL2 frontend (eden-cli) - requires SDL2 +- `YUZU_CRASH_DUMPS` Compile crash dump (Minidump) support" + * Currently only available on Windows and Linux + +See `src/dynarmic/CMakeLists.txt` for additional options--usually, these don't need changed \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..71e79e15ea --- /dev/null +++ b/docs/README.md @@ -0,0 +1,10 @@ +# Eden Build Documentation + +This contains documentation created by developers. This contains build instructions, guidelines, instructions/layouts for [cool stuff we made](CPM.md), and more. + +- **[General Build Instructions](Build.md)** +- **[Development Guidelines](Development.md)** +- **[Dependencies](Deps.md)** +- **[CPM - CMake Package Manager](CPM.md)** +- **[Platform-Specific Caveats](Caveats.md)** +- **[User Directory Handling](User.md)** \ No newline at end of file diff --git a/docs/build/Android.md b/docs/build/Android.md index 0538d351ea..c8ff3a3b1e 100644 --- a/docs/build/Android.md +++ b/docs/build/Android.md @@ -2,7 +2,7 @@ ## Dependencies * [Android Studio](https://developer.android.com/studio) -* [NDK 25.2.9519653 and CMake 3.22.1](https://developer.android.com/studio/projects/install-ndk#default-version) +* [NDK 27+ and CMake 3.22.1](https://developer.android.com/studio/projects/install-ndk#default-version) * [Git](https://git-scm.com/download) ### WINDOWS ONLY - Additional Dependencies @@ -16,8 +16,7 @@ git clone --recursive https://git.eden-emu.dev/eden-emu/eden.git ``` Eden by default will be cloned into - * `C:\Users\\eden` on Windows -* `~/eden` on Linux -* And wherever on macOS +* `~/eden` on Linux and macOS ## Building 1. Start Android Studio, on the startup dialog select `Open`. @@ -32,7 +31,7 @@ Eden by default will be cloned into - `export ANDROID_SDK_ROOT=path/to/sdk` `export ANDROID_NDK_ROOT=path/to/ndk`. 4. Navigate to `eden/src/android`. -5. Then Build with `./gradlew assemblerelWithDebInfo`. +5. Then Build with `./gradlew assembleRelWithDebInfo`. 6. To build the optimised build use `./gradlew assembleGenshinSpoofRelWithDebInfo`. ### Script diff --git a/docs/build/FreeBSD.md b/docs/build/FreeBSD.md deleted file mode 100644 index 97eef8f9d8..0000000000 --- a/docs/build/FreeBSD.md +++ /dev/null @@ -1,81 +0,0 @@ -Eden is not currently available as a port on FreeBSD, though it is in the works. For now, the recommended method of usage is to compile it yourself. Check back often, as the build process frequently changes. - -## Dependencies. -Eden needs the following dependencies: - -``` -devel/cmake -devel/sdl20 -devel/boost-libs -devel/catch2 -devel/libfmt -devel/nlohmann-json -devel/ninja -devel/nasm -devel/autoconf -devel/pkgconf -devel/qt6-base - -net/enet - -multimedia/ffnvcodec-headers -multimedia/ffmpeg - -audio/opus - -archivers/liblz4 - -lang/gcc12 - -graphics/glslang -graphics/vulkan-utility-libraries -``` - -If using FreeBSD 12 or prior, use `devel/pkg-config` instead. - ---- - -### Build preparations: -Run the following command to clone eden with git: -```sh -git clone --recursive https://git.eden-emu.dev/eden-emu/eden -``` -You usually want to add the `--recursive` parameter as it also takes care of the external dependencies for you. - -Now change into the eden directory and create a build directory there: -```sh -cd eden -mkdir build -``` - -Change into that build directory: -```sh -cd build -``` - -#### 1. Building in Release Mode (usually preferred and the most performant choice): -```sh -cmake .. -GNinja -DYUZU_TESTS=OFF -``` - -#### 2. Building in Release Mode with debugging symbols (useful if you want to debug errors for a eventual fix): -```sh -cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=ON -``` - -Build the emulator locally: -```sh -ninja -``` - -Optional: If you wish to install eden globally onto your system issue the following command: -```sh -sudo ninja install -``` -OR -```sh -doas -- ninja install -``` - -## OpenSSL -The available OpenSSL port (3.0.17) is out-of-date, and using a bundled static library instead is recommended; to do so, add `-DYUZU_USE_CPM=ON` to your CMake configure command. \ No newline at end of file diff --git a/docs/build/Linux.md b/docs/build/Linux.md deleted file mode 100644 index a6b7b2dda7..0000000000 --- a/docs/build/Linux.md +++ /dev/null @@ -1,138 +0,0 @@ -### Dependencies - -You'll need to download and install the following to build Eden: - - * [GCC](https://gcc.gnu.org/) v11+ (for C++20 support) & misc - * If GCC 12 is installed, [Clang](https://clang.llvm.org/) v14+ is required for compiling - * [CMake](https://www.cmake.org/) 3.22+ - -The following are handled by Eden's externals: - - * [FFmpeg](https://ffmpeg.org/) - * [SDL2](https://www.libsdl.org/download-2.0.php) 2.0.18+ - * [opus](https://opus-codec.org/downloads/) 1.3+ - -All other dependencies will be downloaded and built by [CPM](https://github.com/cpm-cmake/CPM.cmake/) if `YUZU_USE_CPM` is on, but will always use system dependencies if available: - - * [Boost](https://www.boost.org/users/download/) 1.79.0+ - * [Catch2](https://github.com/catchorg/Catch2) 2.13.7 - 2.13.9 - * [fmt](https://fmt.dev/) 8.0.1+ - * [lz4](http://www.lz4.org) 1.8+ - * [nlohmann_json](https://github.com/nlohmann/json) 3.8+ - * [OpenSSL](https://www.openssl.org/source/) 1.1.1+ - * [ZLIB](https://www.zlib.net/) 1.2+ - * [zstd](https://facebook.github.io/zstd/) 1.5+ - * [enet](http://enet.bespin.org/) 1.3+ - * [cubeb](https://github.com/mozilla/cubeb) - * [SimpleIni](https://github.com/brofield/simpleini) - -Certain other dependencies (httplib, jwt, sirit, etc.) will be fetched by CPM regardless. System packages *can* be used for these libraries but this is generally not recommended. - -Dependencies are listed here as commands that can be copied/pasted. Of course, they should be inspected before being run. - -- Arch / Manjaro: - - `sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 mbedtls ninja nlohmann-json openssl opus qt6-base qt6-multimedia sdl2 zlib zstd zip unzip` - - Building with QT Web Engine requires `qt6-webengine` as well. - - Proper wayland support requires `qt6-wayland` - - GCC 11 or later is required. - -- Ubuntu / Linux Mint / Debian: - - `sudo apt-get install autoconf cmake g++ gcc git glslang-tools libasound2 libboost-context-dev libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev libmbedtls-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev` - - Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required. - - Users need to manually specify building with QT Web Engine enabled. This is done using the parameter `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake. - - Users need to manually disable building SDL2 from externals if they intend to use the version provided by their system by adding the parameters `-DYUZU_USE_EXTERNAL_SDL2=OFF` - -```sh -git submodule update --init --recursive -cmake .. -GNinja -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -``` - -- Fedora: - - `sudo dnf install autoconf ccache cmake fmt-devel gcc{,-c++} glslang hidapi-devel json-devel libtool libusb1-devel libzstd-devel lz4-devel nasm ninja-build openssl-devel pulseaudio-libs-devel qt6-linguist qt6-qtbase{-private,}-devel qt6-qtwebengine-devel qt6-qtmultimedia-devel speexdsp-devel wayland-devel zlib-devel ffmpeg-devel libXext-devel` - - Fedora 32 or later is required. - - Due to GCC 12, Fedora 36 or later users need to install `clang`, and configure CMake to use it via `-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang` - - CMake arguments to force system libraries: - - SDL2: `-DYUZU_USE_BUNDLED_SDL2=OFF -DYUZU_USE_EXTERNAL_SDL2=OFF` - - FFmpeg: `-DYUZU_USE_EXTERNAL_FFMPEG=OFF` - - [RPM Fusion](https://rpmfusion.org/) (free) is required to install `ffmpeg-devel` - -### Cloning Eden with Git - -**Master:** - -```bash -git clone --recursive https://git.eden-emu.dev/eden-emu/eden -cd eden -``` - -The `--recursive` option automatically clones the required Git submodules. - -### Building Eden in Release Mode (Optimised) - -If you need to run ctests, you can disable `-DYUZU_TESTS=OFF` and install Catch2. - -```bash -mkdir build && cd build -cmake .. -GNinja -DYUZU_TESTS=OFF -ninja -sudo ninja install -``` -You may also want to include support for Discord Rich Presence by adding `-DUSE_DISCORD_PRESENCE=ON` after `cmake ..` - -`-DYUZU_USE_EXTERNAL_VULKAN_SPIRV_TOOLS=OFF` might be needed if ninja command failed with `undefined reference to symbol 'spvOptimizerOptionsCreate`, reason currently unknown - -Optionally, you can use `cmake-gui ..` to adjust various options (e.g. disable the Qt GUI). - -### Building Eden in Debug Mode (Slow) - -```bash -mkdir build && cd build -cmake .. -GNinja -DCMAKE_BUILD_TYPE=Debug -DYUZU_TESTS=OFF -ninja -``` - -### Building with debug symbols - -```bash -mkdir build && cd build -cmake .. -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU -DYUZU_TESTS=OFF -ninja -``` - -### Building with Scripts -A convenience script for building is provided in `.ci/linux/build.sh`. You must provide an arch target for optimization, e.g. `.ci/linux/build.sh amd64`. Valid targets: -- `legacy`: x86_64 generic, only needed for CPUs older than 2013 or so -- `amd64`: x86_64-v3, for CPUs newer than 2013 or so -- `steamdeck` / `zen2`: For Steam Deck or Zen >= 2 AMD CPUs (untested on Intel) -- `rog-ally` / `allyx` / `zen4`: For ROG Ally X or Zen >= 4 AMD CPUs (untested on Intel) -- `aarch64`: For armv8-a CPUs, older than mid-2021 or so -- `armv9`: For armv9-a CPUs, newer than mid-2021 or so -- `native`: Optimize to your native host architecture - -Extra flags to pass to CMake should be passed after the arch target. - -Additional environment variables can be used to control building: -- `NPROC`: Number of threads to use for compilation (defaults to all) -- `TARGET`: Set to `appimage` to disable standalone `eden-cli` and `eden-room` executables -- `BUILD_TYPE`: Sets the build type to use. Defaults to `Release` - -The following environment variables are boolean flags. Set to `true` to enable or `false` to disable: -- `DEVEL` (default FALSE): Disable Qt update checker -- `USE_WEBENGINE` (default FALSE): Enable Qt WebEngine -- `USE_MULTIMEDIA` (default TRUE): Enable Qt Multimedia - -After building, an AppImage can be packaged via `.ci/linux/package.sh`. This script takes the same arch targets as the build script. If the build was created in a different directory, you can specify its path relative to the source directory, e.g. `.ci/linux/package.sh amd64 build-appimage`. Additionally, set the `DEVEL` environment variable to `true` to change the app name to `Eden Nightly`. - -### Running without installing - -After building, the binaries `eden` and `eden-cmd` (depending on your build options) will end up in `build/bin/`. - -```bash -# SDL -cd build/bin/ -./eden-cmd - -# Qt -cd build/bin/ -./eden -``` diff --git a/docs/build/OpenBSD.md b/docs/build/OpenBSD.md deleted file mode 100644 index 6a55fd269d..0000000000 --- a/docs/build/OpenBSD.md +++ /dev/null @@ -1,10 +0,0 @@ -# Building for OpenBSD - -```sh -pkg_add -u -pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake llvm-19.1.7p3 qt6 jq -git --recursive https://git.eden-emu.dev/eden-emu/eden -cmake -DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19 -DDYNARMIC_USE_PRECOMPILED_HEADERS=OFF -DCMAKE_BUILD_TYPE=Debug -DENABLE_QT=OFF -DENABLE_OPENSSL=OFF -DENABLE_WEB_SERVICE=OFF -B /usr/obj/eden -``` - -- Modify `externals/ffmpeg/CMakeFiles/ffmpeg-build/build.make` to use `-j$(nproc)` instead of just `-j`. diff --git a/docs/build/Solaris.md b/docs/build/Solaris.md deleted file mode 100644 index f7174c2869..0000000000 --- a/docs/build/Solaris.md +++ /dev/null @@ -1,51 +0,0 @@ -# Building for Solaris - -## Dependencies. -Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability. - -Run the usual update + install of essential toolings: `sudo pkg update && sudo pkg install git cmake`. - -- **gcc**: `sudo pkg install developer/gcc-14`. -- **clang**: Version 20 is broken, use `sudo pkg install developer/clang-19`. - -Then install the libraies: `sudo pkg install qt6 boost glslang libzip library/lz4 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt`. - -### Building - -Clone eden with git `git clone --recursive https://git.eden-emu.dev/eden-emu/eden` - -```sh -# Needed for some dependencies that call cc directly (tz) -echo '#!/bin/sh' >cc -echo 'gcc $@' >>cc -chmod +x cc -export PATH="$PATH:$PWD" -``` - -Patch for FFmpeg: -```sh -sed -i 's/ make / gmake /' externals/ffmpeg/CMakeFiles/ffmpeg-build.dir/build.make -``` - -- **Configure**: `cmake -B build -DYUZU_USE_CPM=ON -DCMAKE_CXX_FLAGS="-I/usr/include/SDL2" -DCMAKE_C_FLAGS="-I/usr/include/SDL2"`. -- **Build**: `cmake --build build`. -- **Installing**: `sudo cmake --install build`. - -### Running - -Default Mesa is a bit outdated, the following environment variables should be set for a smoother experience: -```sh -export MESA_GL_VERSION_OVERRIDE=4.6 -export MESA_GLSL_VERSION_OVERRIDE=460 -export MESA_EXTENSION_MAX_YEAR=2025 -export MESA_DEBUG=1 -export MESA_VK_VERSION_OVERRIDE=1.3 -# Only if nvidia/intel drm drivers cause crashes, will severely hinder performance -export LIBGL_ALWAYS_SOFTWARE=1 -``` - -### Notes - -- Modify the generated ffmpeg.make (in build dir) if using multiple threads (base system `make` doesn't use `-j4`, so change for `gmake`). -- If using OpenIndiana, due to a bug in SDL2 cmake configuration; Audio driver defaults to SunOS ``, which does not exist on OpenIndiana. -- System OpenSSL generally does not work. Instead, use `-DYUZU_USE_CPM=ON` to use a bundled static OpenSSL, or build a system dependency from source. \ No newline at end of file diff --git a/docs/build/Windows.md b/docs/build/Windows.md deleted file mode 100644 index 76602e6d69..0000000000 --- a/docs/build/Windows.md +++ /dev/null @@ -1,259 +0,0 @@ -# ⚠️ This guide is for developers ONLY! Support will be provided to developers ONLY. - -## 📋 Current building methods: - -* [ Minimal Dependencies](#minimal-dependencies) -* [⚡ Method I: MSVC Build for Windows](#method-i-msvc-build-for-windows) -* [🐧 Method II: MinGW-w64 Build with MSYS2](#method-ii-mingw-w64-build-with-msys2) -* [🖥️ Method III: CLion Environment Setup](#method-iii-clion-environment-setup) -* [💻 Building from the command line with MSVC](#building-from-the-command-line-with-msvc) -* [📜 Building with Scripts](#building-with-scripts) - ---- - - -## Minimal Dependencies - -On Windows, **all** library dependencies are **automatically included** within the `externals` folder. - -You still need to install: - -* **[CMake](https://cmake.org/download/)** - Used to generate Visual Studio project files. -* **[Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows)** - Make sure to select **Latest SDK**. - - * *A convenience script to install the latest SDK is provided in `.ci/windows/install-vulkan-sdk.ps1`* -* **[Git for Windows](https://gitforwindows.org)** - We recommend installing Git for command line use and version control integration. - - - - * *While installing Git Bash, select "Git from the command line and also from 3rd-party software". If missed, manually set `git.exe` path in CMake.* - ---- - -## ⚡ Method I: MSVC Build for Windows - -### a. Prerequisites to MSVC Build - -* **[Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)** - Make sure to **select C++ support** in the installer, or **update to the latest version** if already installed. - - * *A convenience script to install the **minimal** version (Visual Build Tools) is provided in `.ci/windows/install-msvc.ps1`* - ---- - -### b. Clone the eden repository with Git - -Open Terminal on - -```cmd -git clone https://git.eden-emu.dev/eden-emu/eden -cd eden -``` - -* *By default `eden` downloads to `C:\Users\\eden`* - ---- - -### c. Building - -* Open the CMake GUI application and point it to the `eden` - - - -* For the build directory, use a `build/` subdirectory inside the source directory or some other directory of your choice. (Tell CMake to create it.) - -* Click the "Configure" button and choose `Visual Studio 17 2022`, with `x64` for the optional platform. - - - - * *(You may also want to disable `YUZU_TESTS` in this case since Catch2 is not yet supported with this.)* - - - -* Click "Generate" to create the project files. - - - -* Open the solution file `yuzu.sln` in Visual Studio 2022, which is located in the build folder. - - - -* * Depending on whether you want a graphical user interface or not, select in the Solution Explorer: - * `eden` (GUI) - * `eden-cmd` (command-line only) - * Then **right-click** and choose `Set as StartUp Project`. - - - - -* Select the appropriate build type, `Debug` for debug purposes or `Release` for performance (in case of doubt choose `Release`). - - - -* **Right-click** the project you want to build and press **Build** in the submenu or press `F5`. - - - ---- - -## 🐧 Method II: MinGW-w64 Build with MSYS2 - -### a. Prerequisites to MinGW-w64 - -* **[MSYS2](https://www.msys2.org)** - A versatile and up-to-date development environment for Windows, providing a Unix-like shell, package manager, and toolchain. - ---- - -### b. Install eden dependencies for MinGW-w64 - -* Open the `MSYS2 MinGW 64-bit` shell (`mingw64.exe`) -* Download and install all dependencies using: - * `pacman -Syu git make mingw-w64-x86_64-SDL2 mingw-w64-x86_64-cmake mingw-w64-x86_64-python-pip mingw-w64-x86_64-qt6 mingw-w64-x86_64-toolchain autoconf libtool automake-wrapper` -* Add MinGW binaries to the PATH: - * `echo 'PATH=/mingw64/bin:$PATH' >> ~/.bashrc` -* Add VulkanSDK to the PATH: - * `echo 'PATH=$(readlink -e /c/VulkanSDK/*/Bin/):$PATH' >> ~/.bashrc` - ---- - -### c. Clone the eden repository with Git - -```cmd -git clone https://git.eden-emu.dev/eden-emu/eden -cd eden -``` - ---- - -### d. Building dynamically-linked eden - -* This process will generate a *dynamically* linked build - -```bash -# Make build dir and enter -mkdir build && cd build - -# Generate CMake Makefiles -cmake .. -G "MSYS Makefiles" -DYUZU_TESTS=OFF - -# Build -make -j$(nproc) - -# Run eden! -./bin/eden.exe -``` - -* *Warning: This build is not a **static** build meaning that you **need** to include all of the DLLs with the .exe in order to use it or other systems!* - ---- - -### Additional notes - - -* Eden doesn't require the rather large Qt dependency, but you will lack a GUI frontend - -```bash -# ... - -# Generate CMake Makefiles (withou QT) -cmake .. -G "MSYS Makefiles" -DYUZU_TESTS=OFF -DENABLE_QT=no - -$ ... -``` - -* Running programs from inside `MSYS2 MinGW x64` shell has a different `%PATH%` than directly from explorer. - * This different `%PATH%` has the locations of the other DLLs required. - - - ---- - -## 🖥️ Method III: CLion Environment Setup - -### a. Prerequisites to CLion - -* [CLion](https://www.jetbrains.com/clion/) - This IDE is not free; for a free alternative, check Method I - ---- - -### b. Cloning eden with CLion - -* Clone the Repository: - - - - - ---- - -### c. Building & Setup - -* Once Cloned, You will be taken to a prompt like the image below: - - - -* Set the settings to the image below: -* Change `Build type: Release` -* Change `Name: Release` -* Change `Toolchain Visual Studio` -* Change `Generator: Let CMake decide` -* Change `Build directory: build` - - - -* Click OK; now Clion will build a directory and index your code to allow for IntelliSense. Please be patient. -* Once this process has been completed (No loading bar bottom right), you can now build eden -* In the top right, click on the drop-down menu, select all configurations, then select eden - - - -* Now run by clicking the play button or pressing Shift+F10, and eden will auto-launch once built. - - - ---- - -## 💻 Building from the command line with MSVC - -```cmd -# Clone eden and enter -git clone https://git.eden-emu.dev/eden-emu/eden -cd eden - -# Make build dir and enter -mkdir build && cd build - -# Generate CMake Makefiles -cmake .. -G "Visual Studio 17 2022" -A x64 -DYUZU_TESTS=OFF - -# Build -cmake --build . --config Release -``` - -## 📜 Building with Scripts - -* A convenience script for building is provided in `.ci/windows/build.sh`. -* You must run this with Bash, e.g. Git Bash or MinGW TTY. -* To use this script, you must have `windeployqt` installed (usually bundled with Qt) and set the `WINDEPLOYQT` environment variable to its canonical Bash location: - * `WINDEPLOYQT="/c/Qt/6.9.1/msvc2022_64/bin/windeployqt6.exe" .ci/windows/build.sh`. -* You can use `aqtinstall`, more info on and - - -* Extra CMake flags should be placed in the arguments of the script. - -#### Additional environment variables can be used to control building: - -* `BUILD_TYPE` (default `Release`): Sets the build type to use. - -* The following environment variables are boolean flags. Set to `true` to enable or `false` to disable: - - * `DEVEL` (default FALSE): Disable Qt update checker - * `USE_WEBENGINE` (default FALSE): Enable Qt WebEngine - * `USE_MULTIMEDIA` (default TRUE): Enable Qt Multimedia - * `BUNDLE_QT` (default FALSE): Use bundled Qt - - * Note that using **system Qt** requires you to include the Qt CMake directory in `CMAKE_PREFIX_PATH` - * `.ci/windows/build.sh -DCMAKE_PREFIX_PATH=C:/Qt/6.9.0/msvc2022_64/lib/cmake/Qt6` - -* After building, a zip can be packaged via `.ci/windows/package.sh`. You must have 7-zip installed and in your PATH. - * The resulting zip will be placed into `artifacts` in the source directory. - diff --git a/docs/build/macOS.md b/docs/build/macOS.md deleted file mode 100644 index fd1873b849..0000000000 --- a/docs/build/macOS.md +++ /dev/null @@ -1,78 +0,0 @@ -Please note this article is intended for development, and Eden on macOS is not currently ready for regular use. - -This article was written for developers. Eden support for macOS is not ready for casual use. - -## Dependencies -Install dependencies from Homebrew: -```sh -brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl2 speexdsp zlib zstd cmake Catch2 molten-vk vulkan-loader spirv-tools -``` - -If you are compiling on Intel Mac, or are using a Rosetta Homebrew installation, you must replace all references of `/opt/homebrew` with `/usr/local`. - -Now, clone the repo: -```sh -git clone --recursive https://git.eden-emu.dev/eden-emu/eden -cd eden -``` - -## Method I: ninja - ---- -Build for release -```sh -export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" -export LIBVULKAN_PATH=/opt/homebrew/lib/libvulkan.dylib -cmake -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=ON -DENABLE_LIBUSB=OFF -DCLANG_FORMAT=ON -DSDL2_DISABLE_INSTALL=ON -DSDL_ALTIVEC=ON -ninja -``` - -You may also want to include support for Discord Rich Presence by adding `-DUSE_DISCORD_PRESENCE=ON` -```sh -export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" -cmake -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF -ninja -``` - -Run the output: -``` -bin/eden.app/Contents/MacOS/eden -``` - -## Method II: Xcode - ---- -Build for release -```sh -export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" -export LIBVULKAN_PATH=/opt/homebrew/lib/libvulkan.dylib -# Only if having errors about Xcode 15.0 -sudo /usr/bin/xcode-select --switch /Users/admin/Downloads/Xcode.ap -cmake -B build -GXcode -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=ON -DENABLE_LIBUSB=OFF -DCLANG_FORMAT=ON -DSDL2_DISABLE_INSTALL=ON -DSDL_ALTIVEC=ON -xcodebuild build -project yuzu.xcodeproj -scheme "yuzu" -configuration "RelWithDebInfo" -``` - -Build with debug symbols: -```sh -export Qt6_DIR="/opt/homebrew/opt/qt@6/lib/cmake" -cmake -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DYUZU_TESTS=OFF -DENABLE_WEB_SERVICE=OFF -DENABLE_LIBUSB=OFF -ninja -``` - -Run the output: -``` -bin/eden.app/Contents/MacOS/eden -``` - ---- - -To run with MoltenVK, install additional dependencies: -```sh -brew install molten-vk vulkan-loader -``` - -Run with Vulkan loader path: -```sh -export LIBVULKAN_PATH=/opt/homebrew/lib/libvulkan.dylib -bin/eden.app/Contents/MacOS/eden -``` diff --git a/docs/img/creator-1.png b/docs/img/creator-1.png new file mode 100644 index 0000000000000000000000000000000000000000..14d679cfbb6dabd5bcf7300395aa4d22ae2b1d62 GIT binary patch literal 64671 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μ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%0Ay*v1tt;;D^9?ybazI(tsNvoIkg)qOU2gHM0m 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 literal 0 HcmV?d00001 diff --git a/docs/scripts/Linux.md b/docs/scripts/Linux.md new file mode 100644 index 0000000000..8b18e41ced --- /dev/null +++ b/docs/scripts/Linux.md @@ -0,0 +1,31 @@ +# Linux Build Scripts + +* Provided script: `.ci/linux/build.sh` +* Must specify arch target, e.g.: `.ci/linux/build.sh amd64` +* Valid targets: + * `native`: Optimize to your native host architecture + * `legacy`: x86\_64 generic, only needed for CPUs older than 2013 or so + * `amd64`: x86\_64-v3, for CPUs newer than 2013 or so + * `steamdeck` / `zen2`: For Steam Deck or Zen >= 2 AMD CPUs (untested on Intel) + * `rog-ally` / `allyx` / `zen4`: For ROG Ally X or Zen >= 4 AMD CPUs (untested on Intel) + * `aarch64`: For armv8-a CPUs, older than mid-2021 or so + * `armv9`: For armv9-a CPUs, newer than mid-2021 or so +* Extra CMake flags go after the arch target. + +### Environment Variables + +* `NPROC`: Number of compilation threads (default: all cores) +* `TARGET`: Set `appimage` to disable standalone `eden-cli` and `eden-room` +* `BUILD_TYPE`: Build type (default: `Release`) + +Boolean flags (set `true` to enable, `false` to disable): + +* `DEVEL` (default `FALSE`): Disable Qt update checker +* `USE_WEBENGINE` (default `FALSE`): Enable Qt WebEngine +* `USE_MULTIMEDIA` (default `FALSE`): Enable Qt Multimedia + +* AppImage packaging script: `.ci/linux/package.sh` + + * Accepts same arch targets as build script + * Use `DEVEL=true` to rename app to `Eden Nightly` + * This should generally not be used unless in a tailor-made packaging environment (e.g. Actions/CI) \ No newline at end of file diff --git a/docs/scripts/Windows.md b/docs/scripts/Windows.md new file mode 100644 index 0000000000..e60c2119a2 --- /dev/null +++ b/docs/scripts/Windows.md @@ -0,0 +1,29 @@ +# Windows Build Scripts + +* A convenience script for building is provided in `.ci/windows/build.sh`. +* You must run this with Bash, e.g. Git Bash or the MinGW TTY. +* To use this script, you must have `windeployqt` installed (usually bundled with Qt) and set the `WINDEPLOYQT` environment variable to its canonical Bash location: + * `WINDEPLOYQT="/c/Qt/6.9.1/msvc2022_64/bin/windeployqt6.exe" .ci/windows/build.sh`. +* You can use `aqtinstall`, more info on and + + +* Extra CMake flags should be placed in the arguments of the script. + +#### Additional environment variables can be used to control building: + +* `BUILD_TYPE` (default `Release`): Sets the build type to use. + +* The following environment variables are boolean flags. Set to `true` to enable or `false` to disable: + + * `DEVEL` (default FALSE): Disable Qt update checker + * `USE_WEBENGINE` (default FALSE): Enable Qt WebEngine + * `USE_MULTIMEDIA` (default FALSE): Enable Qt Multimedia + * `BUNDLE_QT` (default FALSE): Use bundled Qt + + * Note that using **system Qt** requires you to include the Qt CMake directory in `CMAKE_PREFIX_PATH` + * `.ci/windows/build.sh -DCMAKE_PREFIX_PATH=C:/Qt/6.9.0/msvc2022_64/lib/cmake/Qt6` + +* After building, a zip can be packaged via `.ci/windows/package.sh`. You must have 7-zip installed and in your PATH. + * The resulting zip will be placed into `artifacts` in the source directory. + + From b1ce3c8dc1259afe8e0a90d8393591f2d33738b7 Mon Sep 17 00:00:00 2001 From: crueter Date: Thu, 18 Sep 2025 02:38:15 +0200 Subject: [PATCH 17/23] [docs] CODEOWNERS (#491) cc: @CamilleLaVey @Lizzie initial guess for everyone, may need more specificity etc. Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/491 Reviewed-by: Shinmegumi --- docs/CODEOWNERS | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/CODEOWNERS diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS new file mode 100644 index 0000000000..503f9ec1fe --- /dev/null +++ b/docs/CODEOWNERS @@ -0,0 +1,26 @@ +# ui stuff +/src/android @AleksandrPopovich @nyxynx @Producdevity +/src/yuzu @crueter +/src/eden @crueter +/src/frontend_common @crueter +/src/qt_common @crueter + +# docs, meta +/docs @Lizzie @crueter +/.ci @crueter + +# cmake +*.cmake @crueter +*/CMakeLists.txt @crueter +*.in @crueter + +# individual stuff +/src/web_service @AleksandrPopovich +/src/dynarmic @Lizzie +/src/core @Lizzie @Maufeat @PavelBARABANOV @MrPurple666 +/src/core/hle @Maufeat @PavelBARABANOV @SDK-Chan +/src/*_room @AleksandrPopovich +/src/video_core @CamilleLaVey @MaranBr @Wildcard @weakboson + +# Global owners/triage +* @CamilleLaVey @Maufeat @crueter @MrPurple666 @MaranBr @Lizzie \ No newline at end of file From cf634d4d6f4d56c464e40d7da9af80bb17d3bab1 Mon Sep 17 00:00:00 2001 From: SDK-Chan Date: Thu, 18 Sep 2025 14:46:53 +0200 Subject: [PATCH 18/23] [gpu/nvdrv] Rewrite ZBC functions (#2501) This rewrite attempts to implement a fully correct ZBC (Zero Bandwith Clear) mechanism. The zbc_mutex attempts to mitigate contention by assuring that only threads which hold the mutex can modify the table. Notify drivers about the index size, I believe some drivers even need the notification. Only add new entries if a entry was not previously available. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2501 Reviewed-by: Shinmegumi Reviewed-by: MaranBr Reviewed-by: crueter Co-authored-by: SDK-Chan Co-committed-by: SDK-Chan --- .../service/nvdrv/devices/nvhost_ctrl_gpu.cpp | 140 ++++++++++++------ .../service/nvdrv/devices/nvhost_ctrl_gpu.h | 39 +++-- 2 files changed, 119 insertions(+), 60 deletions(-) diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index a7551ec154..d7a65ce445 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -256,61 +256,103 @@ NvResult nvhost_ctrl_gpu::ZCullGetInfo(IoctlNvgpuGpuZcullGetInfoArgs& params) { } NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) { - LOG_DEBUG(Service_NVDRV, "called"); - ZbcEntry entry = {}; - std::memset(&entry, 0, sizeof(entry)); - // TODO(ogniK): What does this even actually do? - // TODO(myself): This thing I guess - if (params.type == 1) { - for (auto i = 0; i < 4; ++i) { - entry.color_ds[i] = params.color_ds[i]; - entry.color_l2[i] = params.color_l2[i]; - } - ASSERT(this->max_color_entries < 16); - this->color_entries[this->max_color_entries] = entry; - ++this->max_color_entries; - } else if (params.type == 2) { - entry.depth = params.depth; - ASSERT(this->max_depth_entries < 16); - this->depth_entries[this->max_depth_entries] = entry; - ++this->max_depth_entries; + if (params.type > supported_types) { + LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#X}", params.type); + return NvResult::BadParameter; } + + std::scoped_lock lk(zbc_mutex); + + switch (static_cast(params.type)) { + case ZBCTypes::color: { + ZbcColorEntry color_entry{}; + std::copy_n(std::begin(params.color_ds), color_entry.color_ds.size(), color_entry.color_ds.begin()); + std::copy_n(std::begin(params.color_l2), color_entry.color_l2.size(), color_entry.color_l2.begin()); + color_entry.format = params.format; + color_entry.ref_cnt = 1u; + + auto color_it = std::ranges::find_if(zbc_colors, + [&](const ZbcColorEntry& color_in_question) { + return color_entry.format == color_in_question.format && + color_entry.color_ds == color_in_question.color_ds && + color_entry.color_l2 == color_in_question.color_l2; + }); + + if (color_it != zbc_colors.end()) { + ++color_it->ref_cnt; + LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#X}, ref_cnt={:#X}", + params.format, color_it->ref_cnt); + } else { + zbc_colors.push_back(color_entry); + LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#X}, index={:#X}", + params.format, zbc_colors.size() - 1); + } + break; + } + case ZBCTypes::depth: { + ZbcDepthEntry depth_entry{params.depth, params.format, 1u}; + + auto depth_it = std::ranges::find_if(zbc_depths, + [&](const ZbcDepthEntry& depth_entry_in_question) { + return depth_entry.format == depth_entry_in_question.format && + depth_entry.depth == depth_entry_in_question.depth; + }); + + if (depth_it != zbc_depths.end()) { + ++depth_it->ref_cnt; + LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#X}, ref_cnt={:#X}", + depth_entry.format, depth_it->ref_cnt); + } else { + zbc_depths.push_back(depth_entry); + LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#X}, index={:#X}", + depth_entry.format, zbc_depths.size() - 1); + } + } + } + return NvResult::Success; } NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) { - LOG_DEBUG(Service_NVDRV, "called"); - struct ZbcQueryParams { - u32_le color_ds[4]; - u32_le color_l2[4]; - u32_le depth; - u32_le ref_cnt; - u32_le format; - u32_le type; - u32_le index_size; - } entry = {}; - std::memset(&entry, 0, sizeof(entry)); - auto const index = params.index_size; - if (params.type == 0) { //no - entry.index_size = 15; - } else if (params.type == 1) { //color - ASSERT(index < 16); - for (auto i = 0; i < 4; ++i) { - params.color_ds[i] = this->color_entries[index].color_ds[i]; - params.color_l2[i] = this->color_entries[index].color_l2[i]; - } - // TODO: Only if no error thrown (otherwise dont modify) - params.format = this->color_entries[index].format; - //params.ref_cnt = this->color_entries[index].ref_cnt; - } else if (params.type == 2) { //depth - ASSERT(index < 16); - params.depth = this->depth_entries[index].depth; - // TODO: Only if no error thrown (otherwise dont modify) - params.format = this->depth_entries[index].format; - //params.ref_cnt = this->depth_entries[index].ref_cnt; - } else { - UNREACHABLE(); + if (params.type > supported_types) { + LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid type {:#X}", params.type); + return NvResult::BadParameter; } + + std::scoped_lock lk(zbc_mutex); + + switch (static_cast(params.type)) { + case ZBCTypes::color: { + if (params.index_size >= zbc_colors.size()) { + LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid color index {:#X}", params.index_size); + return NvResult::BadParameter; + } + + const auto& colors = zbc_colors[params.index_size]; + std::copy_n(colors.color_ds.begin(), colors.color_ds.size(), std::begin(params.color_ds)); + std::copy_n(colors.color_l2.begin(), colors.color_l2.size(), std::begin(params.color_l2)); + params.depth = 0; + params.ref_cnt = colors.ref_cnt; + params.format = colors.format; + params.index_size = static_cast(zbc_colors.size()); + break; + } + case ZBCTypes::depth: { + if (params.index_size >= zbc_depths.size()) { + LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#X}", params.index_size); + return NvResult::BadParameter; + } + + const auto& depth_entry = zbc_depths[params.index_size]; + std::fill(std::begin(params.color_ds), std::end(params.color_ds), 0); + std::fill(std::begin(params.color_l2), std::end(params.color_l2), 0); + params.depth = depth_entry.depth; + params.ref_cnt = depth_entry.ref_cnt; + params.format = depth_entry.format; + params.index_size = static_cast(zbc_depths.size()); + } + } + return NvResult::Success; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h index c36fcbaa69..e0603f9a71 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -34,6 +37,11 @@ public: Kernel::KEvent* QueryEvent(u32 event_id) override; private: + enum class ZBCTypes { + color = 1, + depth = 2, + }; + struct IoctlGpuCharacteristics { u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200) u32_le impl; // 0xB (NVGPU_GPU_IMPL_GM20B) @@ -139,6 +147,21 @@ private: }; static_assert(sizeof(IoctlZbcQueryTable) == 52, "IoctlZbcQueryTable is incorrect size"); + struct ZbcColorEntry { + std::array color_ds{}; + std::array color_l2{}; + u32 format{}; + u32 ref_cnt{}; + }; + static_assert(sizeof(ZbcColorEntry) == 40, "ZbcColorEntry is incorrect size"); + + struct ZbcDepthEntry { + u32 depth{}; + u32 format{}; + u32 ref_cnt{}; + }; + static_assert(sizeof(ZbcDepthEntry) == 12, "ZbcDepthEntry is incorrect size"); + struct IoctlFlushL2 { u32_le flush; // l2_flush | l2_invalidate << 1 | fb_flush << 2 u32_le reserved; @@ -182,17 +205,11 @@ private: Kernel::KEvent* error_notifier_event; Kernel::KEvent* unknown_event; - struct ZbcEntry { - u32_le color_ds[4]; - u32_le color_l2[4]; - u32_le depth; - u32_le type; - u32_le format; - }; - std::array color_entries; - std::array depth_entries; - u8 max_color_entries; - u8 max_depth_entries; + // ZBC Tables + std::mutex zbc_mutex{}; + std::vector zbc_colors{}; + std::vector zbc_depths{}; + const u32 supported_types = 2u; }; } // namespace Service::Nvidia::Devices From f51e28c00a99aed325a55e06edc2add5de91dd22 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 29 Aug 2025 23:48:25 +0000 Subject: [PATCH 19/23] [gamemode] Make available on other platforms Signed-off-by: lizzie --- CMakeLists.txt | 2 +- externals/gamemode/gamemode_client.h | 28 +++++++++++++++- src/common/CMakeLists.txt | 8 ++--- src/common/gamemode.cpp | 50 ++++++++++++++++++++++++++++ src/common/gamemode.h | 17 ++++++++++ src/common/linux/gamemode.cpp | 40 ---------------------- src/common/linux/gamemode.h | 24 ------------- src/yuzu/main.cpp | 18 +++------- src/yuzu_cmd/yuzu.cpp | 16 ++------- 9 files changed, 105 insertions(+), 98 deletions(-) create mode 100644 src/common/gamemode.cpp create mode 100644 src/common/gamemode.h delete mode 100644 src/common/linux/gamemode.cpp delete mode 100644 src/common/linux/gamemode.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 673aab9e6e..083db9fcd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -523,7 +523,7 @@ else() find_package(Catch2 3.0.1 REQUIRED) endif() - if (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR ANDROID) + if (PLATFORM_LINUX OR ANDROID) find_package(gamemode 1.7 MODULE) endif() diff --git a/externals/gamemode/gamemode_client.h b/externals/gamemode/gamemode_client.h index b9f64fe460..bfbf61c0c2 100644 --- a/externals/gamemode/gamemode_client.h +++ b/externals/gamemode/gamemode_client.h @@ -1,6 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* -Copyright (c) 2017-2019, Feral Interactive +Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors All rights reserved. Redistribution and use in source and binary forms, with or without @@ -103,6 +106,7 @@ typedef int (*api_call_pid_return_int)(pid_t); static api_call_return_int REAL_internal_gamemode_request_start = NULL; static api_call_return_int REAL_internal_gamemode_request_end = NULL; static api_call_return_int REAL_internal_gamemode_query_status = NULL; +static api_call_return_int REAL_internal_gamemode_request_restart = NULL; static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; @@ -166,6 +170,10 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void) (void **)&REAL_internal_gamemode_query_status, sizeof(REAL_internal_gamemode_query_status), false }, + { "real_gamemode_request_restart", + (void **)&REAL_internal_gamemode_request_restart, + sizeof(REAL_internal_gamemode_request_restart), + false }, { "real_gamemode_error_string", (void **)&REAL_internal_gamemode_error_string, sizeof(REAL_internal_gamemode_error_string), @@ -319,6 +327,24 @@ __attribute__((always_inline)) static inline int gamemode_query_status(void) return REAL_internal_gamemode_query_status(); } +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_request_restart(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_request_restart == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_request_restart missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_request_restart(); +} + /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) { diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 665143900a..3683052c30 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -69,6 +69,8 @@ add_library( fs/fs_util.h fs/path_util.cpp fs/path_util.h + gamemode.cpp + gamemode.h hash.h heap_tracker.cpp heap_tracker.h @@ -187,11 +189,7 @@ if(ANDROID) android/applets/software_keyboard.h) endif() -if(LINUX AND NOT APPLE) - target_sources(common PRIVATE linux/gamemode.cpp linux/gamemode.h) - - target_link_libraries(common PRIVATE gamemode::headers) -endif() +target_link_libraries(common PRIVATE gamemode::headers) if(ARCHITECTURE_x86_64) target_sources( diff --git a/src/common/gamemode.cpp b/src/common/gamemode.cpp new file mode 100644 index 0000000000..a3f0ba37ab --- /dev/null +++ b/src/common/gamemode.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// While technically available on al *NIX platforms, Linux is only available +// as the primary target of libgamemode.so - so warnings are suppressed +#ifdef __unix__ +#include +#endif +#include "common/gamemode.h" +#include "common/logging/log.h" +#include "common/settings.h" + +namespace Common::FeralGamemode { + +void Start() noexcept { + if (Settings::values.enable_gamemode) { +#ifdef __unix__ + if (gamemode_request_start() < 0) { +#ifdef __linux__ + LOG_WARNING(Frontend, "{}", gamemode_error_string()); +#else + LOG_INFO(Frontend, "{}", gamemode_error_string()); +#endif + } else { + LOG_INFO(Frontend, "Done"); + } +#endif + } +} + +void Stop() noexcept { + if (Settings::values.enable_gamemode) { +#ifdef __unix__ + if (gamemode_request_end() < 0) { +#ifdef __linux__ + LOG_WARNING(Frontend, "{}", gamemode_error_string()); +#else + LOG_INFO(Frontend, "{}", gamemode_error_string()); +#endif + } else { + LOG_INFO(Frontend, "Done"); + } +#endif + } +} + +} // namespace Common::Linux diff --git a/src/common/gamemode.h b/src/common/gamemode.h new file mode 100644 index 0000000000..05b1936bb5 --- /dev/null +++ b/src/common/gamemode.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Common::FeralGamemode { + +/// @brief Start the gamemode client +void Start() noexcept; + +/// @brief Stop the gmemode client +void Stop() noexcept; + +} // namespace Common::FeralGamemode diff --git a/src/common/linux/gamemode.cpp b/src/common/linux/gamemode.cpp deleted file mode 100644 index 8d3e2934a6..0000000000 --- a/src/common/linux/gamemode.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "common/linux/gamemode.h" -#include "common/logging/log.h" -#include "common/settings.h" - -namespace Common::Linux { - -void StartGamemode() { - if (Settings::values.enable_gamemode) { - if (gamemode_request_start() < 0) { - LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Started gamemode"); - } - } -} - -void StopGamemode() { - if (Settings::values.enable_gamemode) { - if (gamemode_request_end() < 0) { - LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Stopped gamemode"); - } - } -} - -void SetGamemodeState(bool state) { - if (state) { - StartGamemode(); - } else { - StopGamemode(); - } -} - -} // namespace Common::Linux diff --git a/src/common/linux/gamemode.h b/src/common/linux/gamemode.h deleted file mode 100644 index b80646ae27..0000000000 --- a/src/common/linux/gamemode.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -namespace Common::Linux { - -/** - * Start the (Feral Interactive) Linux gamemode if it is installed and it is activated - */ -void StartGamemode(); - -/** - * Stop the (Feral Interactive) Linux gamemode if it is installed and it is activated - */ -void StopGamemode(); - -/** - * Start or stop the (Feral Interactive) Linux gamemode if it is installed and it is activated - * @param state The new state the gamemode should have - */ -void SetGamemodeState(bool state); - -} // namespace Common::Linux diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index d7d4e94ab7..a1ec954ac8 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -22,9 +22,7 @@ #include #include #endif -#ifdef __linux__ -#include "common/linux/gamemode.h" -#endif +#include "common/gamemode.h" #include @@ -2255,9 +2253,7 @@ void GMainWindow::OnEmulationStopped() { discord_rpc->Update(); -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif + Common::FeralGamemode::Stop(); // The emulation is stopped, so closing the window or not does not matter anymore disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -3096,10 +3092,7 @@ void GMainWindow::OnStartGame() { play_time_manager->Start(); discord_rpc->Update(); - -#ifdef __linux__ - Common::Linux::StartGamemode(); -#endif + Common::FeralGamemode::StartGamemode(); } void GMainWindow::OnRestartGame() { @@ -3120,10 +3113,7 @@ void GMainWindow::OnPauseGame() { play_time_manager->Stop(); UpdateMenuState(); AllowOSSleep(); - -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif + Common::FeralGamemode::Stop(); } void GMainWindow::OnPauseContinueGame() { diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 599582aba9..7400b48e47 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -63,10 +63,7 @@ __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif - -#ifdef __linux__ -#include "common/linux/gamemode.h" -#endif +#include "common/gamemode.h" static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 @@ -435,10 +432,7 @@ int main(int argc, char** argv) { // Just exit right away. exit(0); }); - -#ifdef __linux__ - Common::Linux::StartGamemode(); -#endif + Common::FeralGamemode::StartGamemode(); void(system.Run()); if (system.DebuggerEnabled()) { @@ -450,11 +444,7 @@ int main(int argc, char** argv) { system.DetachDebugger(); void(system.Pause()); system.ShutdownMainProcess(); - -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif - + Common::FeralGamemode::Stop(); detached_tasks.WaitForAllTasks(); return 0; } From 47a3308f92afd536246b7b9a76b492e9c107016a Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 00:04:36 +0000 Subject: [PATCH 20/23] [gamemode] make option available on all nixes Signed-off-by: lizzie --- src/yuzu/main.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index a1ec954ac8..37d9942db6 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -416,10 +416,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) #ifdef __unix__ SetupSigInterrupts(); #endif - -#ifdef __linux__ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); -#endif UISettings::RestoreWindowState(config); @@ -3408,9 +3405,7 @@ void GMainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); const auto old_language_index = Settings::values.language_index.GetValue(); -#ifdef __linux__ const bool old_gamemode = Settings::values.enable_gamemode.GetValue(); -#endif Settings::SetConfiguringGlobal(true); ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), @@ -3470,11 +3465,9 @@ void GMainWindow::OnConfigure() { if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) { SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); } -#ifdef __linux__ if (Settings::values.enable_gamemode.GetValue() != old_gamemode) { SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); } -#endif if (!multiplayer_state->IsHostingPublicRoom()) { multiplayer_state->UpdateCredentials(); @@ -4761,13 +4754,15 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { discord_rpc->Update(); } -#ifdef __linux__ void GMainWindow::SetGamemodeEnabled(bool state) { if (emulation_running) { - Common::Linux::SetGamemodeState(state); + if (state) { + Common::FeralGamemode::Start(); + } else { + Common::FeralGamemode::Stop(); + } } } -#endif void GMainWindow::changeEvent(QEvent* event) { #ifdef __unix__ @@ -4927,7 +4922,9 @@ int main(int argc, char* argv[]) { // the user folder in the Qt Frontend, we need to cd into that working directory const auto bin_path = Common::FS::GetBundleDirectory() / ".."; chdir(Common::FS::PathToUTF8String(bin_path).c_str()); -#elif defined(__unix__) && !defined(__ANDROID__) +#endif + +#ifdef __unix__ // Set the DISPLAY variable in order to open web browsers // TODO (lat9nq): Find a better solution for AppImages to start external applications if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) { From 336373df417b5d29b4f969f3afa3a1ddcc99e11d Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 08:43:59 +0000 Subject: [PATCH 21/23] [gamemode] extra win/lin/macos fixes Signed-off-by: lizzie --- externals/CMakeLists.txt | 2 +- src/yuzu/main.cpp | 2 +- src/yuzu_cmd/yuzu.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index aba5451b6d..2fba5d3570 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -143,7 +143,7 @@ if (ANDROID AND ARCHITECTURE_arm64) AddJsonPackage(libadrenotools) endif() -if (UNIX AND NOT APPLE AND NOT TARGET gamemode::headers) +if (NOT TARGET gamemode::headers) add_library(gamemode INTERFACE) target_include_directories(gamemode INTERFACE gamemode) add_library(gamemode::headers ALIAS gamemode) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 37d9942db6..47de01d128 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3089,7 +3089,7 @@ void GMainWindow::OnStartGame() { play_time_manager->Start(); discord_rpc->Update(); - Common::FeralGamemode::StartGamemode(); + Common::FeralGamemode::Start(); } void GMainWindow::OnRestartGame() { diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 7400b48e47..62ca70850c 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -432,7 +432,7 @@ int main(int argc, char** argv) { // Just exit right away. exit(0); }); - Common::FeralGamemode::StartGamemode(); + Common::FeralGamemode::Start(); void(system.Run()); if (system.DebuggerEnabled()) { From 217943b1ac50aea3ad8b995dca749e563ab35c9a Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 13:40:57 +0000 Subject: [PATCH 22/23] [gamemode] windows fix Signed-off-by: lizzie --- src/yuzu/main.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yuzu/main.h b/src/yuzu/main.h index e3922759b0..c6980968c8 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -315,8 +315,8 @@ private: void SetupSigInterrupts(); static void HandleSigInterrupt(int); void OnSigInterruptNotifierActivated(); - void SetGamemodeEnabled(bool state); #endif + void SetGamemodeEnabled(bool state); Service::AM::FrontendAppletParameters ApplicationAppletParameters(); Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id, From e662fe99ac0a09dbd692137774abf29b66320e25 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 13 Sep 2025 20:48:40 +0000 Subject: [PATCH 23/23] [gamemode] default disable on msvc, move to UI category Signed-off-by: lizzie --- src/common/settings.h | 10 ++++++++-- src/common/settings_common.h | 1 - src/qt_common/shared_translation.cpp | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/common/settings.h b/src/common/settings.h index 8605445837..7b676cb497 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -641,8 +641,14 @@ struct Values { true, true}; - // Linux - SwitchableSetting enable_gamemode{linkage, true, "enable_gamemode", Category::Linux}; + // Linux/MinGW may support (requires libdl support) + SwitchableSetting enable_gamemode{linkage, +#ifndef _MSC_VER + true, +#else + false, +#endif + "enable_gamemode", Category::UiGeneral}; // Controls InputSetting> players; diff --git a/src/common/settings_common.h b/src/common/settings_common.h index af16ec692b..7902cbf945 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h @@ -44,7 +44,6 @@ enum class Category : u32 { Multiplayer, Services, Paths, - Linux, LibraryApplet, MaxEnum, }; diff --git a/src/qt_common/shared_translation.cpp b/src/qt_common/shared_translation.cpp index cdc05e60e0..8da090e6c7 100644 --- a/src/qt_common/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -450,7 +450,10 @@ std::unique_ptr InitializeTranslations(QObject* parent) tr("Whether or not to check for updates upon startup.")); // Linux - INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QString()); + INSERT(UISettings, + enable_gamemode, + tr("Enable Gamemode"), + QString()); // Ui Debugging