Compare commits

..

2 Commits

Author SHA1 Message Date
lizzie cbb74b3e70 fix
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-05-19 04:28:22 +00:00
lizzie 7da774f766 [hle] make HBLoader work
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-05-19 04:16:52 +00:00
45 changed files with 1033 additions and 1046 deletions
+22 -10
View File
@@ -582,6 +582,8 @@ if (ENABLE_QT)
else()
AddQt(6.9.3)
endif()
set(YUZU_STATIC_BUILD ON)
else()
message(STATUS "Using system Qt")
if (NOT Qt6_DIR)
@@ -590,7 +592,23 @@ if (ENABLE_QT)
list(APPEND CMAKE_PREFIX_PATH "${Qt6_DIR}")
endif()
find_package(Qt6 REQUIRED COMPONENTS Core)
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Charts Concurrent)
if (YUZU_USE_QT_MULTIMEDIA)
find_package(Qt6 REQUIRED COMPONENTS Multimedia)
endif()
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
# yes Qt, we get it
set(QT_NO_PRIVATE_MODULE_WARNING ON)
find_package(Qt6 REQUIRED COMPONENTS DBus OPTIONAL_COMPONENTS GuiPrivate)
elseif (UNIX AND NOT APPLE)
find_package(Qt6 REQUIRED COMPONENTS DBus Gui)
endif()
if (ENABLE_QT_TRANSLATION)
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
endif()
if (NOT DEFINED QT_TARGET_PATH)
get_target_property(qtcore_path Qt6::Core LOCATION_Release)
@@ -613,27 +631,21 @@ if (ENABLE_QT)
## Components ##
# Best practice is to ask for all components at once, so they are from the same version
set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent Gui)
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent)
if (PLATFORM_LINUX)
list(APPEND YUZU_QT_COMPONENTS DBus)
# yes Qt, we get it
set(QT_NO_PRIVATE_MODULE_WARNING ON)
list(APPEND YUZU_QT_OPTIONAL GuiPrivate)
endif()
if (YUZU_USE_QT_MULTIMEDIA)
list(APPEND YUZU_QT_COMPONENTS Multimedia)
endif()
if (YUZU_USE_QT_WEB_ENGINE)
list(APPEND YUZU_QT_COMPONENTS WebEngineCore WebEngineWidgets)
endif()
if (ENABLE_QT_TRANSLATION)
list(APPEND YUZU_QT_COMPONENTS LinguistTools)
endif()
find_package(Qt6 REQUIRED COMPONENTS ${YUZU_QT_COMPONENTS} OPTIONAL_COMPONENTS ${YUZU_QT_OPTIONAL})
find_package(Qt6 REQUIRED COMPONENTS ${YUZU_QT_COMPONENTS})
set(QT_MAJOR_VERSION 6)
# Qt6 sets cxx_std_17 and we need to undo that
set_target_properties(Qt6::Platform PROPERTIES INTERFACE_COMPILE_FEATURES "")
+4 -7
View File
@@ -715,8 +715,8 @@ function(AddCIPackage)
"${${ARTIFACT_PACKAGE}_SOURCE_DIR}" PARENT_SCOPE)
if (PKG_ARGS_MODULE)
list(PREPEND CMAKE_PREFIX_PATH "${${ARTIFACT_PACKAGE}_SOURCE_DIR}")
Propagate(CMAKE_PREFIX_PATH)
list(APPEND CMAKE_PREFIX_PATH "${${ARTIFACT_PACKAGE}_SOURCE_DIR}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endif()
else()
find_package(${ARTIFACT_PACKAGE} ${ARTIFACT_MIN_VERSION} REQUIRED)
@@ -730,7 +730,7 @@ function(AddQt version)
endif()
AddCIPackage(
NAME qt
NAME Qt
PACKAGE Qt6
VERSION ${version}
MIN_VERSION 6
@@ -740,8 +740,5 @@ function(AddQt version)
freebsd-amd64 solaris-amd64 openbsd-amd64
MODULE)
find_package(Qt6 REQUIRED PATHS ${Qt6_SOURCE_DIR} NO_DEFAULT_PATH)
Propagate(CMAKE_PREFIX_PATH)
Propagate(Qt6_SOURCE_DIR)
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
endfunction()
+1 -2
View File
@@ -112,8 +112,7 @@
"options": [
"QUAZIP_QT_MAJOR_VERSION 6",
"QUAZIP_INSTALL OFF",
"QUAZIP_ENABLE_QTEXTCODEC OFF",
"QUAZIP_BZIP2 OFF"
"QUAZIP_ENABLE_QTEXTCODEC OFF"
]
}
}
-12
View File
@@ -435,16 +435,4 @@ void ToggleSlowMode() {
SetSpeedMode(SpeedMode::Standard);
}
bool IsOpenGL() {
const auto backend = Settings::values.renderer_backend.GetValue();
switch (backend) {
case RendererBackend::OpenGL_GLSL:
case RendererBackend::OpenGL_GLASM:
case RendererBackend::OpenGL_SPIRV:
return true;
default:
return false;
}
}
} // namespace Settings
-2
View File
@@ -863,8 +863,6 @@ bool IsFastmemEnabled();
void SetNceEnabled(bool is_64bit);
bool IsNceEnabled();
bool IsOpenGL();
bool IsDockedMode();
float Volume();
-9
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 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
@@ -19,12 +16,6 @@
#include "core/file_sys/fs_filesystem.h"
#include "core/file_sys/vfs/vfs_types.h"
#undef CreateFile
#undef CopyFile
#undef MoveFile
#undef DeleteFile
#undef CreateDirectory
namespace FileSys {
// An enumeration representing what can be at the end of a path in a VfsFilesystem
+101 -83
View File
@@ -6,7 +6,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <bit>
#include <cstring>
#include <mutex>
#include <span>
@@ -123,14 +122,83 @@ struct Memory::Impl {
}
}
[[nodiscard]] inline u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const {
auto const paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
return paddr ? system.DeviceMemory().GetPointer<u8>(paddr + vaddr) : nullptr;
[[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const {
Common::PhysicalAddress const paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
if (paddr)
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
return {};
}
[[nodiscard]] inline u8* GetPointerFromDebugMemory(u64 vaddr) const {
auto const paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
return paddr ? system.DeviceMemory().GetPointer<u8>(paddr + vaddr) : nullptr;
[[nodiscard]] u8* GetPointerFromDebugMemory(u64 vaddr) const {
const Common::PhysicalAddress paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr;
if (paddr != 0)
return system.DeviceMemory().GetPointer<u8>(paddr + vaddr);
return {};
}
u8 Read8(const Common::ProcessAddress addr) {
return Read<u8>(addr);
}
u16 Read16(const Common::ProcessAddress addr) {
if ((addr & 1) == 0) {
return Read<u16_le>(addr);
} else {
const u32 a{Read<u8>(addr)};
const u32 b{Read<u8>(addr + sizeof(u8))};
return static_cast<u16>((b << 8) | a);
}
}
u32 Read32(const Common::ProcessAddress addr) {
if ((addr & 3) == 0) {
return Read<u32_le>(addr);
} else {
const u32 a{Read16(addr)};
const u32 b{Read16(addr + sizeof(u16))};
return (b << 16) | a;
}
}
u64 Read64(const Common::ProcessAddress addr) {
if ((addr & 7) == 0) {
return Read<u64_le>(addr);
} else {
const u32 a{Read32(addr)};
const u32 b{Read32(addr + sizeof(u32))};
return (static_cast<u64>(b) << 32) | a;
}
}
void Write8(const Common::ProcessAddress addr, const u8 data) {
Write<u8>(addr, data);
}
void Write16(const Common::ProcessAddress addr, const u16 data) {
if ((addr & 1) == 0) {
Write<u16_le>(addr, data);
} else {
Write<u8>(addr, static_cast<u8>(data));
Write<u8>(addr + sizeof(u8), static_cast<u8>(data >> 8));
}
}
void Write32(const Common::ProcessAddress addr, const u32 data) {
if ((addr & 3) == 0) {
Write<u32_le>(addr, data);
} else {
Write16(addr, static_cast<u16>(data));
Write16(addr + sizeof(u16), static_cast<u16>(data >> 16));
}
}
void Write64(const Common::ProcessAddress addr, const u64 data) {
if ((addr & 7) == 0) {
Write<u64_le>(addr, data);
} else {
Write32(addr, static_cast<u32>(data));
Write32(addr + sizeof(u32), static_cast<u32>(data >> 32));
}
}
bool WriteExclusive8(const Common::ProcessAddress addr, const u8 data, const u8 expected) {
@@ -496,7 +564,7 @@ struct Memory::Impl {
}
template<typename F, typename G>
[[nodiscard]] inline u8* GetPointerImpl(u64 vaddr, F&& on_unmapped, G&& on_rasterizer) const {
[[nodiscard]] u8* GetPointerImpl(u64 vaddr, F&& on_unmapped, G&& on_rasterizer) const {
// AARCH64 masks the upper 16 bit of all memory accesses
vaddr &= 0xffffffffffffULL;
if (AddressSpaceContains(*current_page_table, vaddr, 1)) [[likely]] {
@@ -551,42 +619,18 @@ struct Memory::Impl {
/// @returns The instance of T read from the specified virtual address.
template <typename T>
inline T Read(Common::ProcessAddress vaddr) noexcept requires(std::is_trivially_copyable_v<T>) {
auto const addr_c1 = GetInteger(vaddr);
if (!(sizeof(T) > 1 && (addr_c1 & 4095) + sizeof(T) > 4096)) {
if (auto const ptr_c1 = GetPointerImpl(addr_c1, [addr_c1] {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr_c1);
}, [&] {
HandleRasterizerDownload(addr_c1, sizeof(T));
}); ptr_c1) {
// It may be tempting to rewrite this particular section to use "reinterpret_cast";
// afterall, it's trivially copyable so surely it can be copied ov- Alignment.
// Remember, alignment. memcpy() will deal with all the alignment extremely fast.
T result{};
std::memcpy(&result, ptr_c1, sizeof(T));
return result;
}
} else {
auto const addr_c2 = (addr_c1 & (~0xfff)) + 0x1000;
// page crossing: say if sizeof(T) = 2, vaddr = 4095
// 4095 + 2 mod 4096 = 1 => 2 - 1 = 1, thus c1=1, c2=1
auto const count_c2 = (addr_c1 + sizeof(T)) & 4095;
auto const count_c1 = sizeof(T) - count_c2;
if (auto const ptr_c1 = GetPointerImpl(addr_c1, [addr_c1] {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr_c1);
}, [&] {
HandleRasterizerDownload(addr_c1, count_c1);
}); ptr_c1) {
if (auto const ptr_c2 = GetPointerImpl(addr_c2, [addr_c2] {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr_c2);
}, [&] {
HandleRasterizerDownload(addr_c2, count_c2);
}); ptr_c2) {
std::array<char, sizeof(T)> result{};
std::memcpy(result.data() + 0, ptr_c1, count_c1);
std::memcpy(result.data() + count_c1, ptr_c2, count_c2);
return std::bit_cast<T>(result);
}
}
const u64 addr = GetInteger(vaddr);
if (auto const ptr = GetPointerImpl(addr, [addr]() {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr);
}, [&]() {
HandleRasterizerDownload(addr, sizeof(T));
}); ptr) [[likely]] {
// It may be tempting to rewrite this particular section to use "reinterpret_cast";
// afterall, it's trivially copyable so surely it can be copied ov- Alignment.
// Remember, alignment. memcpy() will deal with all the alignment extremely fast.
T result{};
std::memcpy(&result, ptr, sizeof(T));
return result;
}
return T{};
}
@@ -596,37 +640,11 @@ struct Memory::Impl {
/// @tparam T The data type to write to memory.
template <typename T>
inline void Write(Common::ProcessAddress vaddr, const T data) noexcept requires(std::is_trivially_copyable_v<T>) {
auto const addr_c1 = GetInteger(vaddr);
if (!(sizeof(T) > 1 && (addr_c1 & 4095) + sizeof(T) > 4096)) {
if (auto const ptr_c1 = GetPointerImpl(addr_c1, [addr_c1] {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr_c1);
}, [&] {
HandleRasterizerWrite(addr_c1, sizeof(T));
}); ptr_c1) {
std::memcpy(ptr_c1, &data, sizeof(T));
}
} else {
auto const addr_c2 = (addr_c1 & (~0xfff)) + 0x1000;
// page crossing: say if sizeof(T) = 2, vaddr = 4095
// 4095 + 2 mod 4096 = 1 => 2 - 1 = 1, thus c1=1, c2=1
auto const count_c2 = (addr_c1 + sizeof(T)) & 4095;
auto const count_c1 = sizeof(T) - count_c2;
if (auto const ptr_c1 = GetPointerImpl(addr_c1, [addr_c1] {
LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X}", sizeof(T) * 8, addr_c1);
}, [&] {
HandleRasterizerWrite(addr_c1, count_c1);
}); ptr_c1) {
if (auto const ptr_c2 = GetPointerImpl(addr_c2, [addr_c2] {
LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X}", sizeof(T) * 8, addr_c2);
}, [&] {
HandleRasterizerWrite(addr_c2, count_c2);
}); ptr_c2) {
std::array<char, sizeof(T)> tmp = std::bit_cast<std::array<char, sizeof(T)>>(data);
std::memcpy(ptr_c1, tmp.data() + 0, count_c1);
std::memcpy(ptr_c2, tmp.data() + count_c1, count_c2);
}
}
}
const u64 addr = GetInteger(vaddr);
if (auto const ptr = GetPointerImpl(addr, [addr, data]() {
LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, addr, u64(data));
}, [&]() { HandleRasterizerWrite(addr, sizeof(T)); }); ptr) [[likely]]
std::memcpy(ptr, &data, sizeof(T));
}
template <typename T>
@@ -824,35 +842,35 @@ const u8* Memory::GetPointer(Common::ProcessAddress vaddr) const {
}
u8 Memory::Read8(const Common::ProcessAddress addr) {
return impl->Read<u8>(addr);
return impl->Read8(addr);
}
u16 Memory::Read16(const Common::ProcessAddress addr) {
return impl->Read<u16_le>(addr);
return impl->Read16(addr);
}
u32 Memory::Read32(const Common::ProcessAddress addr) {
return impl->Read<u32_le>(addr);
return impl->Read32(addr);
}
u64 Memory::Read64(const Common::ProcessAddress addr) {
return impl->Read<u64_le>(addr);
return impl->Read64(addr);
}
void Memory::Write8(Common::ProcessAddress addr, u8 data) {
impl->Write<u8>(addr, data);
impl->Write8(addr, data);
}
void Memory::Write16(Common::ProcessAddress addr, u16 data) {
impl->Write<u16_le>(addr, data);
impl->Write16(addr, data);
}
void Memory::Write32(Common::ProcessAddress addr, u32 data) {
impl->Write<u32_le>(addr, data);
impl->Write32(addr, data);
}
void Memory::Write64(Common::ProcessAddress addr, u64 data) {
impl->Write<u64_le>(addr, data);
impl->Write64(addr, data);
}
bool Memory::WriteExclusive8(Common::ProcessAddress addr, u8 data, u8 expected) {
+2 -1
View File
@@ -7,7 +7,8 @@
add_library(yuzu-room STATIC EXCLUDE_FROM_ALL
yuzu_room.cpp
yuzu_room.h
yuzu_room.rc)
yuzu_room.rc
)
target_link_libraries(yuzu-room PRIVATE common network)
if (ENABLE_WEB_SERVICE)
+5 -6
View File
@@ -26,7 +26,6 @@ add_library(qt_common STATIC
util/compress.h util/compress.cpp
util/fs.h util/fs.cpp
util/mod.h util/mod.cpp
util/vk.h util/vk.cpp
abstract/frontend.h abstract/frontend.cpp
abstract/progress.h abstract/progress.cpp
@@ -34,9 +33,7 @@ add_library(qt_common STATIC
qt_string_lookup.h
qt_compat.h
discord/discord.h
render/context.h
render/emu_thread.h render/emu_thread.cpp)
discord/discord.h)
if (UNIX)
target_sources(qt_common PRIVATE gui_settings.cpp gui_settings.h)
@@ -59,7 +56,9 @@ if (USE_DISCORD_PRESENCE)
endif()
# TODO(crueter)
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
if (ENABLE_QT)
target_link_libraries(qt_common PRIVATE Qt6::Widgets)
endif()
target_compile_definitions(qt_common PUBLIC
# Use QStringBuilder for string concatenation to reduce
@@ -81,7 +80,7 @@ target_compile_definitions(qt_common PUBLIC
)
target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip)
target_link_libraries(qt_common PUBLIC frozen::frozen-headers Vulkan::Headers)
target_link_libraries(qt_common PUBLIC frozen::frozen-headers)
target_link_libraries(qt_common PRIVATE gamemode::headers frontend_common)
if (ENABLE_OPENGL)
+2 -3
View File
@@ -165,9 +165,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
INSERT(Settings, use_disk_shader_cache, tr("Use persistent pipeline cache"),
tr("Allows saving shaders to storage for faster loading on following game "
"boots.\nDisabling it is only intended for debugging."));
INSERT(
Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"),
tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled."));
INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"),
tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for "
"decoding, or perform no decoding at all (black screen on videos).\n"
+36 -180
View File
@@ -1,52 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "common/literals.h"
#include "common/memory_detect.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "hid_core/hid_core.h"
#include "network/network.h"
#include "qt_common.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging.h"
#include "common/scm_rev.h"
#include "core/memory.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h"
#endif
#include "qt_common.h"
#include <QGuiApplication>
#include <QStringLiteral>
#include "common/logging.h"
#include "core/frontend/emu_window.h"
#include "qt_common/util/meta.h"
#include <QFile>
#include <QMessageBox>
#include <thread>
#include <JlCompress.h>
#if !defined(WIN32) && !defined(__APPLE__)
#include <qpa/qplatformnativeinterface.h>
#elif defined(__APPLE__)
#include <objc/message.h>
#include <objc/runtime.h>
#endif
#ifdef _WIN32
#include <QApplication>
#include <QSettings>
#include <windows.h>
#include "common/windows/timer_resolution.h"
#include "core/core_timing.h"
#endif
using namespace Common::Literals;
namespace QtCommon {
QWidget* rootObject = nullptr;
@@ -54,11 +27,6 @@ QWidget* rootObject = nullptr;
std::unique_ptr<Core::System> system = nullptr;
std::shared_ptr<FileSys::RealVfsFilesystem> vfs = nullptr;
std::unique_ptr<FileSys::ManualContentProvider> provider = nullptr;
std::unique_ptr<EmuThread> emu_thread = nullptr;
const QStringList supported_file_extensions = {QStringLiteral("nro"), QStringLiteral("nso"),
QStringLiteral("nca"), QStringLiteral("xci"),
QStringLiteral("nsp"), QStringLiteral("kip")};
Core::Frontend::WindowSystemType GetWindowSystemType() {
// Determine WSI type based on Qt platform.
@@ -90,8 +58,37 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
// Our Win32 Qt external doesn't have the private API.
wsi.render_surface = reinterpret_cast<void*>(window->winId());
#elif defined(__APPLE__)
wsi.render_surface = reinterpret_cast<void* (*)(id, SEL)>(objc_msgSend)(
id layer = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend)(
reinterpret_cast<id>(window->winId()), sel_registerName("layer"));
// In Qt 6, the layer of the NSView might be a QContainerLayer.
// MoltenVK needs a CAMetalLayer. We search for it in sublayers.
Class metal_layer_class = objc_getClass("CAMetalLayer");
id metal_layer = nullptr;
if (layer) {
if (reinterpret_cast<bool (*)(id, SEL, Class)>(objc_msgSend)(
layer, sel_registerName("isKindOfClass:"), metal_layer_class)) {
metal_layer = layer;
} else {
id sublayers = reinterpret_cast<id (*)(id, SEL)>(objc_msgSend)(
layer, sel_registerName("sublayers"));
if (sublayers) {
unsigned long count = reinterpret_cast<unsigned long (*)(id, SEL)>(objc_msgSend)(
sublayers, sel_registerName("count"));
for (unsigned long i = 0; i < count; ++i) {
id sublayer = reinterpret_cast<id (*)(id, SEL, unsigned long)>(objc_msgSend)(
sublayers, sel_registerName("objectAtIndex:"), i);
if (reinterpret_cast<bool (*)(id, SEL, Class)>(objc_msgSend)(
sublayer, sel_registerName("isKindOfClass:"), metal_layer_class)) {
metal_layer = sublayer;
break;
}
}
}
}
}
wsi.render_surface = reinterpret_cast<void*>(metal_layer ? metal_layer : layer);
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);
@@ -106,148 +103,18 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
}
const QString tr(const char* str) {
return rootObject->tr(str);
return QGuiApplication::tr(str);
}
const QString tr(const std::string& str) {
return rootObject->tr(str.c_str());
}
static void LogRuntimes() {
#ifdef _MSC_VER
// It is possible that the name of the dll will change.
// vcruntime140.dll is for 2015 and onwards
static constexpr char runtime_dll_name[] = "vcruntime140.dll";
UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr);
bool runtime_version_inspection_worked = false;
if (sz > 0) {
std::vector<u8> buf(sz);
if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) {
VS_FIXEDFILEINFO* pvi;
sz = sizeof(VS_FIXEDFILEINFO);
if (VerQueryValueA(buf.data(), "\\", reinterpret_cast<LPVOID*>(&pvi), &sz)) {
if (pvi->dwSignature == VS_FFI_SIGNATURE) {
runtime_version_inspection_worked = true;
LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER,
pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF,
pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF);
}
}
}
}
if (!runtime_version_inspection_worked) {
LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name);
}
#endif
LOG_INFO(Frontend, "Qt Compile: {} Runtime: {}", QT_VERSION_STR, qVersion());
}
static QString PrettyProductName() {
#ifdef _WIN32
// After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
// With that notation change they changed the registry key used to denote the current version
QSettings windows_registry(
QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
QSettings::NativeFormat);
const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString();
if (release_id == QStringLiteral("2009")) {
const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt();
const QString display_version =
windows_registry.value(QStringLiteral("DisplayVersion")).toString();
const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt();
u32 version = 10;
if (current_build >= 22000) {
version = 11;
}
return QStringLiteral("Windows %1 Version %2 (Build %3.%4)")
.arg(QString::number(version), display_version, QString::number(current_build),
QString::number(ubr));
}
#endif
return QSysInfo::prettyProductName();
}
static void RemoveCachedContents() {
const auto cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir);
const auto offline_fonts = cache_dir / "fonts";
const auto offline_manual = cache_dir / "offline_web_applet_manual";
const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information";
const auto offline_system_data = cache_dir / "offline_web_applet_system_data";
Common::FS::RemoveDirRecursively(offline_fonts);
Common::FS::RemoveDirRecursively(offline_manual);
Common::FS::RemoveDirRecursively(offline_legal_information);
Common::FS::RemoveDirRecursively(offline_system_data);
return QGuiApplication::tr(str.c_str());
}
void Init(QWidget* root) {
rootObject = root;
system = std::make_unique<Core::System>();
rootObject = root;
vfs = std::make_unique<FileSys::RealVfsFilesystem>();
provider = std::make_unique<FileSys::ManualContentProvider>();
// initialization stuff
Common::FS::CreateEdenPaths();
system->Initialize();
Common::Log::Initialize();
Common::Log::Start();
Network::Init();
QtCommon::Meta::RegisterMetaTypes();
// build version
const auto branch_name = std::string(Common::g_scm_branch);
const auto description = std::string(Common::g_scm_desc);
const auto build_id = std::string(Common::g_build_id);
const auto yuzu_build = fmt::format("Eden Development Build | {}-{}", branch_name, description);
const auto override_build =
fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
const auto processor_count = std::thread::hardware_concurrency();
// info logging
LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version);
LogRuntimes();
#ifdef ARCHITECTURE_x86_64
const auto& caps = Common::GetCPUCaps();
std::string cpu_string = caps.cpu_string;
if (caps.avx || caps.avx2 || caps.avx512f) {
cpu_string += " | AVX";
if (caps.avx512f) {
cpu_string += "512";
} else if (caps.avx2) {
cpu_string += '2';
}
if (caps.fma || caps.fma4) {
cpu_string += " | FMA";
}
}
LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
if (std::optional<int> processor_core = Common::GetProcessorCount()) {
LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core);
}
#endif
LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count);
LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
LOG_INFO(Frontend, "Host RAM: {:.2f} GiB",
Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB});
LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB});
#ifdef _WIN32
LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms",
std::chrono::duration_cast<std::chrono::duration<f64, std::milli>>(
Common::Windows::SetCurrentTimerResolutionToMaximum())
.count());
QtCommon::system->CoreTiming().SetTimerResolutionNs(
Common::Windows::GetCurrentTimerResolution());
#endif
// Remove cached contents generated during the previous session
RemoveCachedContents();
}
std::filesystem::path GetEdenCommand() {
@@ -270,15 +137,4 @@ std::filesystem::path GetEdenCommand() {
return command;
}
void SetupContentProviders() {
system->SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
provider.get());
system->GetFileSystemController().CreateFactories(*vfs);
}
void SetupHID() {
system->HIDCore().ReloadInputDevices();
}
} // namespace QtCommon
+2 -17
View File
@@ -6,31 +6,19 @@
#include <memory>
#include <QWindow>
#include <core/frontend/emu_window.h>
#include "core/core.h"
#include "core/file_sys/registered_cache.h"
#include "core/frontend/emu_window.h"
#include "qt_common/render/emu_thread.h"
#include "core/file_sys/vfs/vfs_real.h"
enum class StartGameType {
Normal, // Can use custom configuration
Global, // Only uses global configuration
};
#include <core/file_sys/vfs/vfs_real.h>
namespace QtCommon {
// TODO: Remove QWidget dependency
extern QWidget* rootObject;
extern std::unique_ptr<Core::System> system;
extern std::shared_ptr<FileSys::RealVfsFilesystem> vfs;
extern std::unique_ptr<FileSys::ManualContentProvider> provider;
extern std::unique_ptr<EmuThread> emu_thread;
extern const QStringList supported_file_extensions;
typedef std::function<bool(std::size_t, std::size_t)> QtProgressCallback;
@@ -40,9 +28,6 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
void Init(QWidget* root);
void SetupContentProviders();
void SetupHID();
const QString tr(const char* str);
const QString tr(const std::string& str);
-107
View File
@@ -1,107 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QSurface>
#include "common/logging.h"
#include "common/settings.h"
#include "core/frontend/graphics_context.h"
#ifdef HAS_OPENGL
#include <QOffscreenSurface>
#include <QOpenGLContext>
#endif
#ifdef HAS_OPENGL
class OpenGLSharedContext : public Core::Frontend::GraphicsContext {
public:
/// Create the original context that should be shared from
explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} {
QSurfaceFormat format;
format.setVersion(4, 6);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
if (Settings::values.renderer_debug) {
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
}
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
format.setSwapInterval(0);
context = std::make_unique<QOpenGLContext>();
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create main openGL context");
}
}
/// Create the shared contexts for rendering and presentation
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
// disable vsync for any shared contexts
auto format = share_context->format();
const int swap_interval =
Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1;
format.setSwapInterval(main_surface ? swap_interval : 0);
context = std::make_unique<QOpenGLContext>();
context->setShareContext(share_context);
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create shared openGL context");
}
if (!main_surface) {
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
offscreen_surface->setFormat(format);
offscreen_surface->create();
surface = offscreen_surface.get();
} else {
surface = main_surface;
}
}
~OpenGLSharedContext() {
DoneCurrent();
}
void SwapBuffers() override {
context->swapBuffers(surface);
}
void MakeCurrent() override {
// We can't track the current state of the underlying context in this wrapper class because
// Qt may make the underlying context not current for one reason or another. In particular,
// the WebBrowser uses GL, so it seems to conflict if we aren't careful.
// Instead of always just making the context current (which does not have any caching to
// check if the underlying context is already current) we can check for the current context
// in the thread local data by calling `currentContext()` and checking if its ours.
if (QOpenGLContext::currentContext() != context.get()) {
context->makeCurrent(surface);
}
}
void DoneCurrent() override {
context->doneCurrent();
}
QOpenGLContext* GetShareContext() {
return context.get();
}
const QOpenGLContext* GetShareContext() const {
return context.get();
}
private:
// Avoid using Qt parent system here since we might move the QObjects to new threads
// As a note, this means we should avoid using slots/signals with the objects too
std::unique_ptr<QOpenGLContext> context;
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
QSurface* surface;
};
#endif
class DummyContext : public Core::Frontend::GraphicsContext {};
-83
View File
@@ -1,83 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <qdebug.h>
#include "core/core.h"
#include "core/cpu_manager.h"
#include "emu_thread.h"
#include "qt_common/qt_common.h"
#include "video_core/gpu.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
EmuThread::EmuThread() {}
EmuThread::~EmuThread() = default;
void EmuThread::run() {
Common::SetCurrentThreadName("EmuControlThread");
auto& gpu = QtCommon::system->GPU();
auto stop_token = m_stop_source.get_token();
QtCommon::system->RegisterHostThread();
// Main process has been loaded. Make the context current to this thread and begin GPU and CPU
// execution.
gpu.ObtainContext();
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
if (Settings::values.use_disk_shader_cache.GetValue()) {
QtCommon::system->Renderer().ReadRasterizer()->LoadDiskResources(
QtCommon::system->GetApplicationProcessProgramID(), stop_token,
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
}
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
gpu.ReleaseContext();
gpu.Start();
QtCommon::system->GetCpuManager().OnGpuReady();
if (QtCommon::system->DebuggerEnabled()) {
QtCommon::system->InitializeDebugger();
}
while (!stop_token.stop_requested()) {
std::unique_lock lk{m_should_run_mutex};
if (m_should_run) {
QtCommon::system->Run();
m_stopped.Reset();
m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; });
} else {
QtCommon::system->Pause();
m_stopped.Set();
EmulationPaused(lk);
m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; });
EmulationResumed(lk);
}
}
// Shutdown the main emulated process
QtCommon::system->DetachDebugger();
QtCommon::system->ShutdownMainProcess();
}
// Unlock while emitting signals so that the main thread can
// continue pumping events.
void EmuThread::EmulationPaused(std::unique_lock<std::mutex>& lk) {
lk.unlock();
emit DebugModeEntered();
lk.lock();
}
void EmuThread::EmulationResumed(std::unique_lock<std::mutex>& lk) {
lk.unlock();
emit DebugModeLeft();
lk.lock();
}
-97
View File
@@ -1,97 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QThread>
#include "common/logging.h"
#include "common/thread.h"
namespace Core {
class System;
} // namespace Core
namespace VideoCore {
enum class LoadCallbackStage;
} // namespace VideoCore
class EmuThread final : public QThread {
Q_OBJECT
public:
explicit EmuThread();
~EmuThread() override;
/**
* Start emulation (on new thread)
* @warning Only call when not running!
*/
void run() override;
/**
* Sets whether the emulation thread should run or not
* @param should_run Boolean value, set the emulation thread to running if true
*/
void SetRunning(bool should_run) {
// TODO: Prevent other threads from modifying the state until we finish.
{
// Notify the running thread to change state.
std::unique_lock run_lk{m_should_run_mutex};
m_should_run = should_run;
m_should_run_cv.notify_one();
}
// Wait until paused, if pausing.
if (!should_run) {
m_stopped.Wait();
}
}
/**
* Check if the emulation thread is running or not
* @return True if the emulation thread is running, otherwise false
*/
bool IsRunning() const {
return m_should_run;
}
/**
* Requests for the emulation thread to immediately stop running
*/
void ForceStop() {
LOG_WARNING(Frontend, "Force stopping EmuThread");
m_stop_source.request_stop();
}
private:
void EmulationPaused(std::unique_lock<std::mutex>& lk);
void EmulationResumed(std::unique_lock<std::mutex>& lk);
private:
std::stop_source m_stop_source;
std::mutex m_should_run_mutex;
std::condition_variable_any m_should_run_cv;
Common::Event m_stopped;
bool m_should_run{true};
signals:
/**
* Emitted when the CPU has halted execution
*
* @warning When connecting to this signal from other threads, make sure to specify either
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
*/
void DebugModeEntered();
/**
* Emitted right before the CPU continues execution
*
* @warning When connecting to this signal from other threads, make sure to specify either
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
*/
void DebugModeLeft();
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
};
+1 -91
View File
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/file_sys/card_image.h"
#include "qt_common/util/content.h"
#include "qt_common/util/game.h"
@@ -495,94 +494,5 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::str
});
}
bool CheckKeys() {
if (!ContentManager::AreKeysPresent()) {
QtCommon::Frontend::Information(
tr("Keys not installed"),
tr("Install decryption keys and restart Eden before attempting to install firmware."));
return false;
}
return true;
}
void InstallFirmware() {
if (!CheckKeys())
return;
const QString firmware_source_location =
QtCommon::Frontend::GetExistingDirectory(tr("Select Dumped Firmware Source Location"), {});
if (!firmware_source_location.isEmpty())
QtCommon::Content::InstallFirmware(firmware_source_location, false);
}
void InstallFirmwareZip() {
if (!CheckKeys())
return;
const QString firmware_zip_location = QtCommon::Frontend::GetOpenFileName(
tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)"));
if (firmware_zip_location.isEmpty())
return;
const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location);
// In this case, it has to be done recursively, since sometimes people
// will pack it into a subdirectory after dumping
if (!qCacheDir.isEmpty()) {
QtCommon::Content::InstallFirmware(qCacheDir, true);
std::error_code ec;
std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware",
ec);
if (ec) {
QtCommon::Frontend::Warning(
tr("Firmware cleanup failed"),
tr("Failed to clean up extracted firmware cache.\n"
"Check write permissions in the system temp directory and try "
"again.\nOS reported error: %1")
.arg(QString::fromStdString(ec.message())));
}
}
}
void configureFilesystemProvider(const std::string& filepath) {
// Ensure all NCAs are registered before launching the game
const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read);
if (!file) {
return;
}
auto loader = Loader::GetLoader(*QtCommon::system, file);
if (!loader) {
return;
}
const auto file_type = loader->GetFileType();
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
return;
}
u64 program_id = 0;
const auto res2 = loader->ReadProgramId(program_id);
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
QtCommon::provider->AddEntry(FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
program_id, file);
} else if (res2 == Loader::ResultStatus::Success &&
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
const auto nsp = file_type == Loader::FileType::NSP
? std::make_shared<FileSys::NSP>(file)
: FileSys::XCI{file}.GetSecurePartitionNSP();
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
}
// TODO(crueter): Port InstallFirmware et al. from QML Branch
} // namespace QtCommon::Content
+1 -7
View File
@@ -37,11 +37,8 @@ inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult
}
void InstallFirmware(const QString& location, bool recursive);
QString UnzipFirmwareToTmp(const QString& location);
bool CheckKeys();
void InstallFirmware();
void InstallFirmwareZip();
QString UnzipFirmwareToTmp(const QString& location);
// Keys //
void InstallKeys();
@@ -57,9 +54,6 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string&
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "",
std::function<void()> callback = {});
// Loader //
void configureFilesystemProvider(const std::string& filepath);
// Profiles //
void FixProfiles();
} // namespace QtCommon::Content
+1 -108
View File
@@ -11,6 +11,7 @@
#include "qt_common/abstract/frontend.h"
#include "qt_common/config/uisettings.h"
#include "qt_common/qt_common.h"
#include "yuzu/util/util.h"
#include <QDesktopServices>
#include <QStandardPaths>
@@ -21,7 +22,6 @@
#include <windows.h>
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "common/fs/file.h"
#else
#include <fstream>
#include "fmt/ostream.h"
@@ -367,7 +367,6 @@ void ResetMetadata(bool show_message) {
// Uhhh //
// TODO(crueter): Make QtCommon::Shortcut
// Messages in pre-defined message boxes for less code spaghetti
inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) {
int result = 0;
@@ -534,110 +533,4 @@ void CreateHomeMenuShortcut(ShortcutTarget target) {
CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false);
}
bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
#if defined(WIN32)
#pragma pack(push, 2)
struct IconDir {
WORD id_reserved;
WORD id_type;
WORD id_count;
};
struct IconDirEntry {
BYTE width;
BYTE height;
BYTE color_count;
BYTE reserved;
WORD planes;
WORD bit_count;
DWORD bytes_in_res;
DWORD image_offset;
};
#pragma pack(pop)
const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
constexpr int bytes_per_pixel = 4;
const IconDir icon_dir{
.id_reserved = 0,
.id_type = 1,
.id_count = static_cast<WORD>(scale_sizes.size()),
};
Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile);
if (!icon_file.IsOpen()) {
return false;
}
if (!icon_file.Write(icon_dir)) {
return false;
}
std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
const IconDirEntry icon_entry{
.width = static_cast<BYTE>(scale_sizes[i]),
.height = static_cast<BYTE>(scale_sizes[i]),
.color_count = 0,
.reserved = 0,
.planes = 1,
.bit_count = bytes_per_pixel * 8,
.bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
.image_offset = static_cast<DWORD>(image_offset),
};
image_offset += icon_entry.bytes_in_res;
if (!icon_file.Write(icon_entry)) {
return false;
}
}
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const QImage scaled_image = source_image.scaled(
scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
const BITMAPINFOHEADER info_header{
.biSize = sizeof(BITMAPINFOHEADER),
.biWidth = scaled_image.width(),
.biHeight = scaled_image.height() * 2,
.biPlanes = 1,
.biBitCount = bytes_per_pixel * 8,
.biCompression = BI_RGB,
.biSizeImage{},
.biXPelsPerMeter{},
.biYPelsPerMeter{},
.biClrUsed{},
.biClrImportant{},
};
if (!icon_file.Write(info_header)) {
return false;
}
for (int y = 0; y < scaled_image.height(); y++) {
const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
std::memcpy(line_data.data(), line, line_data.size());
if (!icon_file.Write(line_data)) {
return false;
}
}
}
icon_file.Close();
return true;
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
// Convert and write the icon as a PNG
if (!image.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
} else {
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
}
return true;
#else
return false;
#endif
}
} // namespace QtCommon::Game
-8
View File
@@ -69,14 +69,6 @@ void CreateShortcut(const std::string& game_path, const u64 program_id,
std::string GetShortcutPath(ShortcutTarget target);
void CreateHomeMenuShortcut(ShortcutTarget target);
/**
* Saves a windows icon to a file
* @param path The icons path
* @param image The image to save
* @return bool If the operation succeeded
*/
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
} // namespace QtCommon::Game
#endif // QT_GAME_UTIL_H
-71
View File
@@ -1,71 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 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
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include "common/common_types.h"
#include "common/settings_enums.h"
#include "vulkan/vulkan_core.h"
static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR,
VK_PRESENT_MODE_FIFO_KHR};
// Converts a setting to a present mode (or vice versa)
static inline constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) {
switch (mode) {
case Settings::VSyncMode::Immediate:
return VK_PRESENT_MODE_IMMEDIATE_KHR;
case Settings::VSyncMode::Mailbox:
return VK_PRESENT_MODE_MAILBOX_KHR;
case Settings::VSyncMode::Fifo:
return VK_PRESENT_MODE_FIFO_KHR;
case Settings::VSyncMode::FifoRelaxed:
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
default:
return VK_PRESENT_MODE_FIFO_KHR;
}
}
static inline constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) {
switch (mode) {
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return Settings::VSyncMode::Immediate;
case VK_PRESENT_MODE_MAILBOX_KHR:
return Settings::VSyncMode::Mailbox;
case VK_PRESENT_MODE_FIFO_KHR:
return Settings::VSyncMode::Fifo;
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
return Settings::VSyncMode::FifoRelaxed;
default:
return Settings::VSyncMode::Fifo;
}
}
class QWindow;
namespace Settings {
enum class VSyncMode : u32;
}
namespace VkDeviceInfo {
// Short class to record Vulkan driver information for configuration purposes
class Record {
public:
explicit Record(std::string_view name, const std::vector<VkPresentModeKHR>& vsync_modes,
bool has_broken_compute);
~Record();
const std::string name;
const std::vector<VkPresentModeKHR> vsync_support;
const bool has_broken_compute;
};
// TODO(crueter): Port some configure_graphics.cpp stuff
void PopulateRecords(std::vector<Record>& records, QWindow* window);
} // namespace VkDeviceInfo
+5 -1
View File
@@ -217,6 +217,8 @@ add_executable(yuzu
util/url_request_interceptor.h
util/util.cpp
util/util.h
vk_device_info.cpp
vk_device_info.h
compatdb.cpp
compatdb.h
user_data_migration.cpp
@@ -243,7 +245,9 @@ add_executable(yuzu
render/performance_overlay.h render/performance_overlay.cpp render/performance_overlay.ui
libqt_common.h libqt_common.cpp
updater/update_dialog.h updater/update_dialog.cpp updater/update_dialog.ui)
updater/update_dialog.h updater/update_dialog.cpp updater/update_dialog.ui
)
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
+196 -17
View File
@@ -6,6 +6,8 @@
#include <cmath>
#include <cstring>
#include <string>
#include <tuple>
#include <type_traits>
#ifdef HAS_OPENGL
#include <glad/glad.h>
@@ -43,13 +45,16 @@
#include <QOpenGLContext>
#endif
#include "common/polyfill_thread.h"
#include "common/scm_rev.h"
#include "common/settings.h"
#include "common/settings_input.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/cpu_manager.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/frontend/graphics_context.h"
#include "input_common/drivers/camera.h"
#include "input_common/drivers/keyboard.h"
#include "input_common/drivers/mouse.h"
#include "input_common/drivers/tas_input.h"
@@ -57,19 +62,189 @@
#include "input_common/main.h"
#include "qt_common/qt_common.h"
#include "video_core/gpu.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
#include "yuzu/bootmanager.h"
#include "yuzu/main_window.h"
#include "qt_common/render/context.h"
#include "qt_common/render/emu_thread.h"
class QObject;
class QPaintEngine;
class QSurface;
constexpr int default_mouse_constrain_timeout = 10;
EmuThread::EmuThread(Core::System& system) : m_system{system} {}
EmuThread::~EmuThread() = default;
void EmuThread::run() {
Common::SetCurrentThreadName("EmuControlThread");
auto& gpu = m_system.GPU();
auto stop_token = m_stop_source.get_token();
m_system.RegisterHostThread();
// Main process has been loaded. Make the context current to this thread and begin GPU and CPU
// execution.
gpu.ObtainContext();
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
if (Settings::values.use_disk_shader_cache.GetValue()) {
m_system.Renderer().ReadRasterizer()->LoadDiskResources(
m_system.GetApplicationProcessProgramID(), stop_token,
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
}
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
gpu.ReleaseContext();
gpu.Start();
m_system.GetCpuManager().OnGpuReady();
if (m_system.DebuggerEnabled()) {
m_system.InitializeDebugger();
}
while (!stop_token.stop_requested()) {
std::unique_lock lk{m_should_run_mutex};
if (m_should_run) {
m_system.Run();
m_stopped.Reset();
m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; });
} else {
m_system.Pause();
m_stopped.Set();
EmulationPaused(lk);
m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; });
EmulationResumed(lk);
}
}
// Shutdown the main emulated process
m_system.DetachDebugger();
m_system.ShutdownMainProcess();
}
// Unlock while emitting signals so that the main thread can
// continue pumping events.
void EmuThread::EmulationPaused(std::unique_lock<std::mutex>& lk) {
lk.unlock();
emit DebugModeEntered();
lk.lock();
}
void EmuThread::EmulationResumed(std::unique_lock<std::mutex>& lk) {
lk.unlock();
emit DebugModeLeft();
lk.lock();
}
#ifdef HAS_OPENGL
class OpenGLSharedContext : public Core::Frontend::GraphicsContext {
public:
/// Create the original context that should be shared from
explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} {
QSurfaceFormat format;
format.setVersion(4, 6);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
if (Settings::values.renderer_debug) {
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
}
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
format.setSwapInterval(0);
context = std::make_unique<QOpenGLContext>();
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create main openGL context");
}
}
/// Create the shared contexts for rendering and presentation
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
// disable vsync for any shared contexts
auto format = share_context->format();
const int swap_interval =
Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1;
format.setSwapInterval(main_surface ? swap_interval : 0);
context = std::make_unique<QOpenGLContext>();
context->setShareContext(share_context);
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create shared openGL context");
}
if (!main_surface) {
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
offscreen_surface->setFormat(format);
offscreen_surface->create();
surface = offscreen_surface.get();
} else {
surface = main_surface;
}
}
~OpenGLSharedContext() {
DoneCurrent();
}
void SwapBuffers() override {
if (auto window = static_cast<QWindow*>(surface)) {
if (!window->isExposed()) {
LOG_DEBUG(Frontend, "SwapBuffers ignored: window not exposed");
return;
}
}
context->swapBuffers(surface);
}
void MakeCurrent() override {
// We can't track the current state of the underlying context in this wrapper class because
// Qt may make the underlying context not current for one reason or another. In particular,
// the WebBrowser uses GL, so it seems to conflict if we aren't careful.
// Instead of always just making the context current (which does not have any caching to
// check if the underlying context is already current) we can check for the current context
// in the thread local data by calling `currentContext()` and checking if its ours.
if (QOpenGLContext::currentContext() != context.get()) {
context->makeCurrent(surface);
}
}
void DoneCurrent() override {
context->doneCurrent();
}
QOpenGLContext* GetShareContext() {
return context.get();
}
const QOpenGLContext* GetShareContext() const {
return context.get();
}
private:
// Avoid using Qt parent system here since we might move the QObjects to new threads
// As a note, this means we should avoid using slots/signals with the objects too
std::unique_ptr<QOpenGLContext> context;
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
QSurface* surface;
};
#endif
class DummyContext : public Core::Frontend::GraphicsContext {};
class RenderWidget : public QWidget {
public:
explicit RenderWidget(GRenderWindow* parent) : QWidget(parent) {
@@ -110,9 +285,11 @@ struct NullRenderWidget : public RenderWidget {
explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {}
};
GRenderWindow::GRenderWindow(MainWindow* parent,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_)
: QWidget(parent), input_subsystem{std::move(input_subsystem_)} {
GRenderWindow::GRenderWindow(MainWindow* parent, EmuThread* emu_thread_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
Core::System& system_)
: QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)},
system{system_} {
setWindowTitle(QStringLiteral("Eden %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name),
QString::fromUtf8(Common::g_scm_branch),
@@ -531,11 +708,10 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
}
void GRenderWindow::ConstrainMouse() {
if (QtCommon::emu_thread == nullptr || !Settings::values.mouse_panning) {
if (emu_thread == nullptr || !Settings::values.mouse_panning) {
mouse_constrain_timer.stop();
return;
}
if (!this->isActiveWindow()) {
mouse_constrain_timer.stop();
return;
@@ -788,7 +964,7 @@ void GRenderWindow::ReleaseRenderTarget() {
}
void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
auto& renderer = QtCommon::system->Renderer();
auto& renderer = system.Renderer();
if (renderer.IsScreenshotPending()) {
LOG_WARNING(Render,
@@ -813,12 +989,7 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
screenshot_image.bits(),
[=, this](bool invert_y) {
const std::string std_screenshot_path = screenshot_path.toStdString();
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
if (screenshot_image.flipped(invert_y ? Qt::Vertical : (Qt::Orientations)0)
.save(screenshot_path)) {
#else
if (screenshot_image.mirrored(false, invert_y).save(screenshot_path)) {
#endif
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
} else {
LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path);
@@ -855,8 +1026,7 @@ bool GRenderWindow::InitializeOpenGL() {
return true;
#else
QMessageBox::warning(this, tr("OpenGL not available!"),
tr("Eden has not been compiled with OpenGL support."));
QMessageBox::warning(this, tr("OpenGL not available!"), tr("Eden has not been compiled with OpenGL support."));
return false;
#endif
}
@@ -902,7 +1072,8 @@ bool GRenderWindow::LoadOpenGL() {
tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
"have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported "
"extensions:<br>%2")
.arg(renderer, missing_ext.join(QStringLiteral("<br>"))));
.arg(renderer)
.arg(missing_ext.join(QStringLiteral("<br>"))));
// Non fatal
}
return true;
@@ -930,6 +1101,14 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
return missing_ext;
}
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread_) {
emu_thread = emu_thread_;
}
void GRenderWindow::OnEmulationStopping() {
emu_thread = nullptr;
}
void GRenderWindow::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
+97 -3
View File
@@ -6,9 +6,12 @@
#pragma once
#include <condition_variable>
#include <cstddef>
#include <memory>
#include <mutex>
#include <utility>
#include <vector>
#include <QByteArray>
#include <QImage>
@@ -24,6 +27,9 @@
#include <qobjectdefs.h>
#include "common/common_types.h"
#include "common/logging.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/frontend/emu_window.h"
class MainWindow;
@@ -40,7 +46,6 @@ class QShowEvent;
class QTouchEvent;
class QWheelEvent;
class EmuThread;
namespace Core {
class System;
} // namespace Core
@@ -58,12 +63,96 @@ namespace VideoCore {
enum class LoadCallbackStage;
} // namespace VideoCore
class EmuThread final : public QThread {
Q_OBJECT
public:
explicit EmuThread(Core::System& system);
~EmuThread() override;
/**
* Start emulation (on new thread)
* @warning Only call when not running!
*/
void run() override;
/**
* Sets whether the emulation thread should run or not
* @param should_run Boolean value, set the emulation thread to running if true
*/
void SetRunning(bool should_run) {
// TODO: Prevent other threads from modifying the state until we finish.
{
// Notify the running thread to change state.
std::unique_lock run_lk{m_should_run_mutex};
m_should_run = should_run;
m_should_run_cv.notify_one();
}
// Wait until paused, if pausing.
if (!should_run) {
m_stopped.Wait();
}
}
/**
* Check if the emulation thread is running or not
* @return True if the emulation thread is running, otherwise false
*/
bool IsRunning() const {
return m_should_run;
}
/**
* Requests for the emulation thread to immediately stop running
*/
void ForceStop() {
LOG_WARNING(Frontend, "Force stopping EmuThread");
m_stop_source.request_stop();
}
private:
void EmulationPaused(std::unique_lock<std::mutex>& lk);
void EmulationResumed(std::unique_lock<std::mutex>& lk);
private:
Core::System& m_system;
std::stop_source m_stop_source;
std::mutex m_should_run_mutex;
std::condition_variable_any m_should_run_cv;
Common::Event m_stopped;
bool m_should_run{true};
signals:
/**
* Emitted when the CPU has halted execution
*
* @warning When connecting to this signal from other threads, make sure to specify either
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
*/
void DebugModeEntered();
/**
* Emitted right before the CPU continues execution
*
* @warning When connecting to this signal from other threads, make sure to specify either
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
*/
void DebugModeLeft();
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
};
class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
Q_OBJECT
public:
explicit GRenderWindow(MainWindow* parent,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_);
explicit GRenderWindow(MainWindow* parent, EmuThread* emu_thread_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
Core::System& system_);
~GRenderWindow() override;
// EmuWindow implementation.
@@ -128,6 +217,8 @@ public:
void Exit();
public slots:
void OnEmulationStarting(EmuThread* emu_thread_);
void OnEmulationStopping();
void OnFramebufferSizeChanged();
signals:
@@ -156,6 +247,7 @@ private:
bool LoadOpenGL();
QStringList GetUnsupportedGLExtensions() const;
EmuThread* emu_thread;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
// Main context that will be shared with all other contexts that are requested.
@@ -185,6 +277,8 @@ private:
QTimer mouse_constrain_timer;
Core::System& system;
protected:
void showEvent(QShowEvent* event) override;
bool eventFilter(QObject* object, QEvent* event) override;
@@ -33,8 +33,8 @@ ModSelectDialog::ModSelectDialog(const QStringList& mods, QWidget* parent)
ui->treeView->expandAll();
int rows = item_model->rowCount();
int height = 4 + ui->treeView->contentsMargins().top() * 4 +
ui->treeView->contentsMargins().bottom() * 4;
int height =
4 + ui->treeView->contentsMargins().top() * 4 + ui->treeView->contentsMargins().bottom() * 4;
int width = 0;
for (int i = 0; i < rows; ++i) {
+1 -2
View File
@@ -76,8 +76,7 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
} else if (setting->Id() == Settings::values.cpu_backend.Id()) {
backend_layout->addWidget(widget);
backend_combobox = widget->combobox;
} else if (setting->Id() == Settings::values.fast_cpu_time.Id() ||
setting->Id() == Settings::values.cpu_ticks.Id()) {
} else if (setting->Id() == Settings::values.fast_cpu_time.Id() || setting->Id() == Settings::values.cpu_ticks.Id()) {
ui->general_layout->addWidget(widget);
} else {
// Presently, all other settings here are unsafe checkboxes
+2 -4
View File
@@ -51,10 +51,8 @@ void ConfigureDebug::SetConfiguration() {
ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue());
ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue());
ui->serial_battery_edit->setText(
QString::fromStdString(std::to_string(Settings::values.serial_battery.GetValue())));
ui->serial_board_edit->setText(
QString::fromStdString(std::to_string(Settings::values.serial_unit.GetValue())));
ui->serial_battery_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_battery.GetValue())));
ui->serial_board_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_unit.GetValue())));
#ifdef YUZU_USE_QT_WEB_ENGINE
ui->disable_web_applet->setChecked(Settings::values.disable_web_applet.GetValue());
#else
+1 -1
View File
@@ -10,8 +10,8 @@
#include "common/settings_enums.h"
#include "core/core.h"
#include "qt_common/config/uisettings.h"
#include "qt_common/util/vk.h"
#include "ui_configure.h"
#include "vk_device_info.h"
#include "yuzu/configuration/configure_applets.h"
#include "yuzu/configuration/configure_audio.h"
#include "yuzu/configuration/configure_cpu.h"
+1 -1
View File
@@ -11,8 +11,8 @@
#include <QDialog>
#include "configuration/shared_widget.h"
#include "qt_common/config/shared_translation.h"
#include "qt_common/util/vk.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/vk_device_info.h"
namespace Core {
class System;
+45 -1
View File
@@ -4,7 +4,14 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <functional>
#include <iosfwd>
#include <iterator>
#include <string>
#include <tuple>
#include <typeinfo>
#include <utility>
#include <vector>
#include <QBoxLayout>
#include <QCheckBox>
@@ -27,15 +34,52 @@
#include <vulkan/vulkan_core.h>
#include "common/common_types.h"
#include "common/dynamic_library.h"
#include "common/logging.h"
#include "common/settings.h"
#include "common/settings_enums.h"
#include "core/core.h"
#include "qt_common/config/uisettings.h"
#include "qt_common/util/vk.h"
#include "qt_common/qt_common.h"
#include "ui_configure_graphics.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics.h"
#include "yuzu/configuration/shared_widget.h"
#include "yuzu/vk_device_info.h"
static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR,
VK_PRESENT_MODE_FIFO_KHR};
// Converts a setting to a present mode (or vice versa)
static constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) {
switch (mode) {
case Settings::VSyncMode::Immediate:
return VK_PRESENT_MODE_IMMEDIATE_KHR;
case Settings::VSyncMode::Mailbox:
return VK_PRESENT_MODE_MAILBOX_KHR;
case Settings::VSyncMode::Fifo:
return VK_PRESENT_MODE_FIFO_KHR;
case Settings::VSyncMode::FifoRelaxed:
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
default:
return VK_PRESENT_MODE_FIFO_KHR;
}
}
static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) {
switch (mode) {
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return Settings::VSyncMode::Immediate;
case VK_PRESENT_MODE_MAILBOX_KHR:
return Settings::VSyncMode::Mailbox;
case VK_PRESENT_MODE_FIFO_KHR:
return Settings::VSyncMode::Fifo;
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
return Settings::VSyncMode::FifoRelaxed;
default:
return Settings::VSyncMode::Fifo;
}
}
ConfigureGraphics::ConfigureGraphics(
const Core::System& system_, std::vector<VkDeviceInfo::Record>& records_,
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
@@ -19,7 +19,7 @@
#include "common/common_types.h"
#include "common/settings_enums.h"
#include "qt_common/config/shared_translation.h"
#include "qt_common/util/vk.h"
#include "vk_device_info.h"
#include "yuzu/configuration/configuration_shared.h"
class QPushButton;
@@ -30,7 +30,6 @@
#include "core/loader/loader.h"
#include "frontend_common/config.h"
#include "qt_common/config/uisettings.h"
#include "qt_common/util/vk.h"
#include "ui_configure_per_game.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_applets.h"
@@ -45,6 +44,7 @@
#include "yuzu/configuration/configure_per_game_addons.h"
#include "yuzu/configuration/configure_system.h"
#include "yuzu/util/util.h"
#include "yuzu/vk_device_info.h"
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name,
std::vector<VkDeviceInfo::Record>& vk_device_records,
+1 -1
View File
@@ -18,7 +18,7 @@
#include "frontend_common/config.h"
#include "qt_common/config/qt_config.h"
#include "qt_common/config/shared_translation.h"
#include "qt_common/util/vk.h"
#include "vk_device_info.h"
#include "yuzu/configuration/configuration_shared.h"
namespace Core {
+7 -12
View File
@@ -63,31 +63,27 @@ DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir,
void DataWidget::clear() {
std::optional<std::string> user_id = selectProfile();
if (!user_id)
return;
if (!user_id) return;
QtCommon::Content::ClearDataDir(m_dir, user_id.value());
scan();
}
void DataWidget::open() {
std::optional<std::string> user_id = selectProfile();
if (!user_id)
return;
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(
FrontendCommon::DataManager::GetDataDirString(m_dir, user_id.value()))));
if (!user_id) return;
QDesktopServices::openUrl(QUrl::fromLocalFile(
QString::fromStdString(FrontendCommon::DataManager::GetDataDirString(m_dir, user_id.value()))));
}
void DataWidget::upload() {
std::optional<std::string> user_id = selectProfile();
if (!user_id)
return;
if (!user_id) return;
QtCommon::Content::ExportDataDir(m_dir, user_id.value(), m_exportName);
}
void DataWidget::download() {
std::optional<std::string> user_id = selectProfile();
if (!user_id)
return;
if (!user_id) return;
QtCommon::Content::ImportDataDir(m_dir, user_id.value(), std::bind(&DataWidget::scan, this));
}
@@ -111,8 +107,7 @@ std::optional<std::string> DataWidget::selectProfile() {
std::string user_id{};
if (m_dir == FrontendCommon::DataManager::DataDir::Saves) {
user_id = GetProfileIDString();
if (user_id.empty())
return std::nullopt;
if (user_id.empty()) return std::nullopt;
}
return user_id;
+1
View File
@@ -11,6 +11,7 @@
#endif
#include "common/logging.h"
#include "yuzu/debugger/console.h"
#include "qt_common/config/uisettings.h"
#include "yuzu/debugger/console.h"
+1 -2
View File
@@ -36,8 +36,7 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option,
const auto x_pos = option.rect.left() - (column * cell_width) + static_cast<int>(relative_x);
// also, add some additional padding here to prevent card overlap
QRect cardRect(x_pos + 4, option.rect.top() + 4, fixed_card_width - 8,
option.rect.height() - margins);
QRect cardRect(x_pos + 4, option.rect.top() + 4, fixed_card_width - 8, option.rect.height() - margins);
// colors
QPalette palette = option.palette;
+4
View File
@@ -1113,6 +1113,10 @@ void GameList::LoadInterfaceLayout() {
header->resizeSection(COLUMN_NAME, 840);
}
const QStringList GameList::supported_file_extensions = {
QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"),
QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
void GameList::RefreshGameDirectory() {
// Reset the externals watcher whenever the game list is reloaded,
// primarily ensures that new titles and external dirs are caught.
+2
View File
@@ -95,6 +95,8 @@ public:
/// Disables events from the emulated controller
void UnloadController();
static const QStringList supported_file_extensions;
bool IsTreeMode();
void ResetViewMode();
+3 -4
View File
@@ -20,18 +20,17 @@
#include "common/settings.h"
#include "core/core.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/fs_filesystem.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/submission_package.h"
#include "core/loader/loader.h"
#include "qt_common/config/uisettings.h"
#include "qt_common/qt_common.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/game/game_list.h"
#include "yuzu/game/game_list_p.h"
@@ -149,7 +148,7 @@ void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const
bool HasSupportedFileExtension(const std::string& file_name) {
const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
return QtCommon::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive);
}
bool IsExtractedNCAMain(const std::string& file_name) {
+315 -81
View File
@@ -2,17 +2,17 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Qt on macOS doesn't define VMA shit
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
#undef VMA_IMPLEMENTATION
#endif
#include <boost/algorithm/string/split.hpp>
#include "common/fs/path_util.h"
#include "common/settings.h"
#include "common/settings_enums.h"
#include "frontend_common/settings_generator.h"
#include "qt_common/qt_string_lookup.h"
#include "render/performance_overlay.h"
#include "updater/update_dialog.h"
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
#undef VMA_IMPLEMENTATION
#endif
#include "common/fs/ryujinx_compat.h"
#include "main_window.h"
@@ -31,10 +31,10 @@
#include "bootmanager.h"
#include "loading_screen.h"
#include "qt_common/util/vk.h"
#include "ryujinx_dialog.h"
#include "set_play_time_dialog.h"
#include "util/util.h"
#include "vk_device_info.h"
#include "yuzu/game/game_list.h"
#include "applets/qt_amiibo_settings.h"
@@ -86,7 +86,6 @@
#include "qt_common/abstract/frontend.h"
#include "qt_common/qt_common.h"
#include "qt_common/qt_string_lookup.h"
#include "qt_common/util/content.h"
#include "qt_common/util/fs.h"
@@ -94,8 +93,6 @@
#include "qt_common/util/mod.h"
#include "qt_common/util/path.h"
#include "qt_common/render/emu_thread.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
// defines.
static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(const std::string& path,
@@ -318,6 +315,73 @@ enum class CalloutFlag : uint32_t {
const int MainWindow::max_recent_files_item;
static void RemoveCachedContents() {
const auto cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir);
const auto offline_fonts = cache_dir / "fonts";
const auto offline_manual = cache_dir / "offline_web_applet_manual";
const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information";
const auto offline_system_data = cache_dir / "offline_web_applet_system_data";
Common::FS::RemoveDirRecursively(offline_fonts);
Common::FS::RemoveDirRecursively(offline_manual);
Common::FS::RemoveDirRecursively(offline_legal_information);
Common::FS::RemoveDirRecursively(offline_system_data);
}
static void LogRuntimes() {
#ifdef _MSC_VER
// It is possible that the name of the dll will change.
// vcruntime140.dll is for 2015 and onwards
static constexpr char runtime_dll_name[] = "vcruntime140.dll";
UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr);
bool runtime_version_inspection_worked = false;
if (sz > 0) {
std::vector<u8> buf(sz);
if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) {
VS_FIXEDFILEINFO* pvi;
sz = sizeof(VS_FIXEDFILEINFO);
if (VerQueryValueA(buf.data(), "\\", reinterpret_cast<LPVOID*>(&pvi), &sz)) {
if (pvi->dwSignature == VS_FFI_SIGNATURE) {
runtime_version_inspection_worked = true;
LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER,
pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF,
pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF);
}
}
}
}
if (!runtime_version_inspection_worked) {
LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name);
}
#endif
LOG_INFO(Frontend, "Qt Compile: {} Runtime: {}", QT_VERSION_STR, qVersion());
}
static QString PrettyProductName() {
#ifdef _WIN32
// After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
// With that notation change they changed the registry key used to denote the current version
QSettings windows_registry(
QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"),
QSettings::NativeFormat);
const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString();
if (release_id == QStringLiteral("2009")) {
const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt();
const QString display_version =
windows_registry.value(QStringLiteral("DisplayVersion")).toString();
const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt();
u32 version = 10;
if (current_build >= 22000) {
version = 11;
}
return QStringLiteral("Windows %1 Version %2 (Build %3.%4)")
.arg(QString::number(version), display_version, QString::number(current_build),
QString::number(ubr));
}
#endif
return QSysInfo::prettyProductName();
}
namespace {
constexpr std::array<std::pair<u32, const char*>, 5> default_game_icon_sizes{
@@ -383,6 +447,11 @@ MainWindow::MainWindow(bool has_broken_vulkan)
UISettings::RestoreWindowState(config);
QtCommon::system->Initialize();
Common::Log::Initialize();
Common::Log::Start();
LoadTranslation();
FrontendCommon::GenerateSettings();
@@ -403,6 +472,10 @@ MainWindow::MainWindow(bool has_broken_vulkan)
play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
Network::Init();
QtCommon::Meta::RegisterMetaTypes();
InitializeWidgets();
InitializeDebugWidgets();
InitializeRecentFileMenuActions();
@@ -414,9 +487,53 @@ MainWindow::MainWindow(bool has_broken_vulkan)
ConnectMenuEvents();
ConnectWidgetEvents();
QtCommon::SetupHID();
QtCommon::system->HIDCore().ReloadInputDevices();
controller_dialog->refreshConfiguration();
const auto branch_name = std::string(Common::g_scm_branch);
const auto description = std::string(Common::g_scm_desc);
const auto build_id = std::string(Common::g_build_id);
const auto yuzu_build = fmt::format("Eden Development Build | {}-{}", branch_name, description);
const auto override_build =
fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
const auto processor_count = std::thread::hardware_concurrency();
LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version);
LogRuntimes();
#ifdef ARCHITECTURE_x86_64
const auto& caps = Common::GetCPUCaps();
std::string cpu_string = caps.cpu_string;
if (caps.avx || caps.avx2 || caps.avx512f) {
cpu_string += " | AVX";
if (caps.avx512f) {
cpu_string += "512";
} else if (caps.avx2) {
cpu_string += '2';
}
if (caps.fma || caps.fma4) {
cpu_string += " | FMA";
}
}
LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
if (std::optional<int> processor_core = Common::GetProcessorCount()) {
LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core);
}
#endif
LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count);
LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
LOG_INFO(Frontend, "Host RAM: {:.2f} GiB",
Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB});
LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB});
#ifdef _WIN32
LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms",
std::chrono::duration_cast<std::chrono::duration<f64, std::milli>>(
Common::Windows::SetCurrentTimerResolutionToMaximum())
.count());
QtCommon::system->CoreTiming().SetTimerResolutionNs(
Common::Windows::GetCurrentTimerResolution());
#endif
UpdateWindowTitle();
show();
@@ -431,8 +548,13 @@ MainWindow::MainWindow(bool has_broken_vulkan)
}
#endif
// Setup content providers.
QtCommon::SetupContentProviders();
QtCommon::system->SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
QtCommon::system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
QtCommon::provider.get());
QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
// Remove cached contents generated during the previous session
RemoveCachedContents();
// Gen keys if necessary
OnCheckFirmwareDecryption();
@@ -957,7 +1079,7 @@ void MainWindow::InitializeWidgets() {
#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
ui->action_Report_Compatibility->setVisible(true);
#endif
render_window = new GRenderWindow(this, input_subsystem);
render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *QtCommon::system);
render_window->hide();
game_list = new GameList(QtCommon::vfs, QtCommon::provider.get(), *play_time_manager,
@@ -1417,12 +1539,11 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
return;
}
if (UISettings::values.pause_when_in_background) {
if (QtCommon::emu_thread->IsRunning() &&
if (emu_thread->IsRunning() &&
(state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) {
auto_paused = true;
OnPauseGame();
} else if (!QtCommon::emu_thread->IsRunning() && auto_paused &&
(state & Qt::ApplicationActive)) {
} else if (!emu_thread->IsRunning() && auto_paused && (state & Qt::ApplicationActive)) {
auto_paused = false;
OnStartGame();
}
@@ -1474,6 +1595,11 @@ void MainWindow::ConnectWidgetEvents() {
connect(this, &MainWindow::UpdateInstallProgress, this, &MainWindow::IncrementInstallProgress);
connect(this, &MainWindow::EmulationStarting, render_window,
&GRenderWindow::OnEmulationStarting);
connect(this, &MainWindow::EmulationStopping, render_window,
&GRenderWindow::OnEmulationStopping);
// Software Keyboard Applet
connect(this, &MainWindow::EmulationStarting, this, &MainWindow::SoftwareKeyboardExit);
connect(this, &MainWindow::EmulationStopping, this, &MainWindow::SoftwareKeyboardExit);
@@ -1627,7 +1753,7 @@ void MainWindow::ConnectMenuEvents() {
}
void MainWindow::UpdateMenuState() {
const bool is_paused = QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning();
const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning();
const bool is_firmware_available = CheckFirmwarePresence();
const std::array running_actions{
@@ -1695,16 +1821,17 @@ void MainWindow::SetupPrepareForSleep() {
}
void MainWindow::OnPrepareForSleep(bool prepare_sleep) {
if (QtCommon::emu_thread == nullptr)
if (emu_thread == nullptr) {
return;
}
if (prepare_sleep) {
if (QtCommon::emu_thread->IsRunning()) {
if (emu_thread->IsRunning()) {
auto_paused = true;
OnPauseGame();
}
} else {
if (!QtCommon::emu_thread->IsRunning() && auto_paused) {
if (!emu_thread->IsRunning() && auto_paused) {
auto_paused = false;
OnStartGame();
}
@@ -1777,16 +1904,19 @@ void MainWindow::AllowOSSleep() {
bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletParameters params) {
// Shutdown previous session if the emu thread is still active...
if (QtCommon::emu_thread != nullptr)
if (emu_thread != nullptr) {
ShutdownGame();
}
if (!render_window->InitRenderTarget())
if (!render_window->InitRenderTarget()) {
return false;
}
QtCommon::system->SetFilesystem(QtCommon::vfs);
if (params.launch_type == Service::AM::LaunchType::FrontendInitiated)
if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) {
QtCommon::system->GetUserChannel().clear();
}
QtCommon::system->SetFrontendAppletSet({
std::make_unique<QtAmiiboSettings>(*this), // Amiibo Settings
@@ -1893,6 +2023,36 @@ bool MainWindow::SelectAndSetCurrentUser(
return true;
}
void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) {
// Ensure all NCAs are registered before launching the game
const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read);
if (!file) {
return;
}
if (QtCommon::provider->AddEntriesFromContainer(file)) {
return;
}
auto loader = Loader::GetLoader(*QtCommon::system, file);
if (!loader) {
return;
}
const auto file_type = loader->GetFileType();
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
return;
}
u64 program_id = 0;
const auto res2 = loader->ReadProgramId(program_id);
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
QtCommon::provider->AddEntry(FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
program_id, file);
}
}
void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletParameters params,
StartGameType type) {
LOG_INFO(Frontend, "Eden starting...");
@@ -1911,7 +2071,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa
last_filename_booted = filename;
QtCommon::Content::configureFilesystemProvider(filename.toStdString());
ConfigureFilesystemProvider(filename.toStdString());
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);
@@ -1956,23 +2116,23 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa
game_list->setDisabled(true);
// Create and start the emulation thread
QtCommon::emu_thread = std::make_unique<EmuThread>();
emit EmulationStarting();
QtCommon::emu_thread->start();
emu_thread = std::make_unique<EmuThread>(*QtCommon::system);
emit EmulationStarting(emu_thread.get());
emu_thread->start();
// Register an ExecuteProgram callback such that Core can execute a sub-program
QtCommon::system->RegisterExecuteProgramCallback(
[this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); });
QtCommon::system->RegisterExitCallback([this] {
QtCommon::emu_thread->ForceStop();
emu_thread->ForceStop();
render_window->Exit();
});
connect(render_window, &GRenderWindow::Closed, this, &MainWindow::OnStopGame);
connect(render_window, &GRenderWindow::MouseActivity, this, &MainWindow::OnMouseActivity);
connect(QtCommon::emu_thread.get(), &EmuThread::LoadProgress, loading_screen,
connect(emu_thread.get(), &EmuThread::LoadProgress, loading_screen,
&LoadingScreen::OnLoadProgress, Qt::QueuedConnection);
// Update the GUI
@@ -2067,10 +2227,9 @@ bool MainWindow::OnShutdownBegin() {
discord_rpc->Pause();
RequestGameExit();
QtCommon::emu_thread->disconnect();
QtCommon::emu_thread->SetRunning(true);
emu_thread->disconnect();
emu_thread->SetRunning(true);
connect(QtCommon::emu_thread.get(), &QThread::finished, this, &MainWindow::OnEmulationStopped);
emit EmulationStopping();
int shutdown_time = 1000;
@@ -2084,6 +2243,7 @@ bool MainWindow::OnShutdownBegin() {
shutdown_timer.setSingleShot(true);
shutdown_timer.start(shutdown_time);
connect(&shutdown_timer, &QTimer::timeout, this, &MainWindow::OnEmulationStopTimeExpired);
connect(emu_thread.get(), &QThread::finished, this, &MainWindow::OnEmulationStopped);
// Disable everything to prevent anything from being triggered here
ui->action_Pause->setEnabled(false);
@@ -2101,17 +2261,17 @@ void MainWindow::OnShutdownBeginDialog() {
}
void MainWindow::OnEmulationStopTimeExpired() {
if (QtCommon::emu_thread) {
QtCommon::emu_thread->ForceStop();
if (emu_thread) {
emu_thread->ForceStop();
}
}
void MainWindow::OnEmulationStopped() {
shutdown_timer.stop();
if (QtCommon::emu_thread) {
QtCommon::emu_thread->disconnect();
QtCommon::emu_thread->wait();
QtCommon::emu_thread.reset();
if (emu_thread) {
emu_thread->disconnect();
emu_thread->wait();
emu_thread.reset();
}
if (shutdown_dialog) {
@@ -2752,7 +2912,7 @@ void MainWindow::OnMenuLoadFile() {
is_load_file_select_active = true;
const QString extensions =
QStringLiteral("*.")
.append(QtCommon::supported_file_extensions.join(QStringLiteral(" *.")))
.append(GameList::supported_file_extensions.join(QStringLiteral(" *.")))
.append(QStringLiteral(" main"));
const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)",
"%1 is an identifier for the Switch executable file extensions.")
@@ -2988,7 +3148,7 @@ void MainWindow::OnMenuRecentFile() {
void MainWindow::OnStartGame() {
PreventOSSleep();
QtCommon::emu_thread->SetRunning(true);
emu_thread->SetRunning(true);
UpdateMenuState();
OnTasStateChanged();
@@ -3014,7 +3174,7 @@ void MainWindow::OnRestartGame() {
}
void MainWindow::OnPauseGame() {
QtCommon::emu_thread->SetRunning(false);
emu_thread->SetRunning(false);
play_time_manager->Stop();
UpdateMenuState();
AllowOSSleep();
@@ -3023,7 +3183,7 @@ void MainWindow::OnPauseGame() {
void MainWindow::OnPauseContinueGame() {
if (emulation_running) {
if (QtCommon::emu_thread->IsRunning()) {
if (emu_thread->IsRunning()) {
OnPauseGame();
} else {
OnStartGame();
@@ -3703,9 +3863,12 @@ void MainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_
}
void MainWindow::OnLoadAmiibo() {
if (QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning() ||
is_amiibo_file_select_active)
if (emu_thread == nullptr || !emu_thread->IsRunning()) {
return;
}
if (is_amiibo_file_select_active) {
return;
}
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
@@ -3809,21 +3972,79 @@ void MainWindow::OnVerifyInstalledContents() {
QtCommon::Content::VerifyInstalledContents();
}
void MainWindow::OnInstallFirmware() {
QtCommon::Content::InstallFirmware();
void MainWindow::InstallFirmware(const QString& location, bool recursive) {
QtCommon::Content::InstallFirmware(location, recursive);
OnCheckFirmwareDecryption();
}
void MainWindow::OnInstallFirmware() {
// Don't do this while emulation is running, that'd probably be a bad idea.
if (emu_thread != nullptr && emu_thread->IsRunning()) {
return;
}
// Check for installed keys, error out, suggest restart?
if (!ContentManager::AreKeysPresent()) {
QMessageBox::information(
this, tr("Keys not installed"),
tr("Install decryption keys and restart Eden before attempting to install firmware."));
return;
}
const QString firmware_source_location = QFileDialog::getExistingDirectory(
this, tr("Select Dumped Firmware Source Location"), {}, QFileDialog::ShowDirsOnly);
if (firmware_source_location.isEmpty()) {
return;
}
InstallFirmware(firmware_source_location);
}
void MainWindow::OnInstallFirmwareFromZIP() {
QtCommon::Content::InstallFirmwareZip();
OnCheckFirmwareDecryption();
// Don't do this while emulation is running, that'd probably be a bad idea.
if (emu_thread != nullptr && emu_thread->IsRunning()) {
return;
}
// Check for installed keys, error out, suggest restart?
if (!ContentManager::AreKeysPresent()) {
QMessageBox::information(
this, tr("Keys not installed"),
tr("Install decryption keys and restart Eden before attempting to install firmware."));
return;
}
const QString firmware_zip_location = QFileDialog::getOpenFileName(
this, tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)"));
if (firmware_zip_location.isEmpty()) {
return;
}
const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location);
// 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(std::filesystem::temp_directory_path() / "eden" / "firmware",
ec);
if (ec) {
QMessageBox::warning(this, tr("Firmware cleanup failed"),
tr("Failed to clean up extracted firmware cache.\n"
"Check write permissions in the system temp directory and try "
"again.\nOS reported error: %1")
.arg(QString::fromStdString(ec.message())));
}
}
}
// TODO(crueter): QtCommon this: game list populate can be a signal?
void MainWindow::OnInstallDecryptionKeys() {
// Don't do this while emulation is running.
if (QtCommon::emu_thread != nullptr && QtCommon::emu_thread->IsRunning())
if (emu_thread != nullptr && emu_thread->IsRunning()) {
return;
}
QtCommon::Content::InstallKeys();
@@ -3851,10 +4072,11 @@ void MainWindow::OnDataDialog() {
void MainWindow::OnToggleFilterBar() {
game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked());
if (ui->action_Show_Filter_Bar->isChecked())
if (ui->action_Show_Filter_Bar->isChecked()) {
game_list->SetFilterFocus();
else
} else {
game_list->ClearFilter();
}
}
void MainWindow::OnToggleStatusBar() {
@@ -3862,8 +4084,9 @@ void MainWindow::OnToggleStatusBar() {
}
void MainWindow::OnTogglePerfOverlay() {
if (perf_overlay)
if (perf_overlay) {
perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked());
}
}
void MainWindow::OnGameListRefresh() {
@@ -3968,8 +4191,9 @@ void MainWindow::OnCreateHomeMenuApplicationMenuShortcut() {
}
void MainWindow::OnCaptureScreenshot() {
if (QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning())
if (emu_thread == nullptr || !emu_thread->IsRunning()) {
return;
}
const u64 title_id = QtCommon::system->GetApplicationProcessProgramID();
const auto screenshot_path =
@@ -3981,8 +4205,9 @@ void MainWindow::OnCaptureScreenshot() {
.arg(title_id, 16, 16, QLatin1Char{'0'})
.arg(date);
if (!Common::FS::CreateDir(screenshot_path.toStdString()))
if (!Common::FS::CreateDir(screenshot_path.toStdString())) {
return;
}
#ifdef _WIN32
if (UISettings::values.enable_screenshot_save_as) {
@@ -4088,15 +4313,16 @@ void MainWindow::OnTasStateChanged() {
}
void MainWindow::UpdateStatusBar() {
if (QtCommon::emu_thread == nullptr || !QtCommon::system->IsPoweredOn()) {
if (emu_thread == nullptr || !QtCommon::system->IsPoweredOn()) {
status_bar_update_timer.stop();
return;
}
if (Settings::values.tas_enable)
if (Settings::values.tas_enable) {
tas_label->setText(GetTasStateDescription());
else
} else {
tas_label->clear();
}
auto results = QtCommon::system->GetAndResetPerfStats();
auto& shader_notify = QtCommon::system->GPU().ShaderNotify();
@@ -4218,13 +4444,14 @@ void MainWindow::UpdateUISettings() {
}
void MainWindow::UpdateInputDrivers() {
if (!input_subsystem)
if (!input_subsystem) {
return;
}
input_subsystem->PumpEvents();
}
void MainWindow::HideMouseCursor() {
if (QtCommon::emu_thread == nullptr && UISettings::values.hide_mouse) {
if (emu_thread == nullptr && UISettings::values.hide_mouse) {
mouse_hide_timer.stop();
ShowMouseCursor();
return;
@@ -4234,8 +4461,9 @@ void MainWindow::HideMouseCursor() {
void MainWindow::ShowMouseCursor() {
render_window->unsetCursor();
if (QtCommon::emu_thread != nullptr && UISettings::values.hide_mouse)
if (emu_thread != nullptr && UISettings::values.hide_mouse) {
mouse_hide_timer.start();
}
}
void MainWindow::OnMouseActivity() {
@@ -4415,14 +4643,14 @@ bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed
}
bool MainWindow::ConfirmClose() {
if (QtCommon::emu_thread == nullptr ||
UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never)
if (emu_thread == nullptr ||
UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) {
return true;
}
if (!QtCommon::system->GetExitLocked() &&
UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game)
UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game) {
return true;
}
const auto text = tr("Are you sure you want to close Eden?");
return question(this, tr("Eden"), text);
}
@@ -4443,8 +4671,9 @@ void MainWindow::closeEvent(QCloseEvent* event) {
game_list->UnloadController();
// Shutdown session if the emu thread is active...
if (QtCommon::emu_thread != nullptr)
if (emu_thread != nullptr) {
ShutdownGame();
}
render_window->close();
multiplayer_state->Close();
@@ -4507,7 +4736,7 @@ void MainWindow::dragMoveEvent(QDragMoveEvent* event) {
}
bool MainWindow::ConfirmChangeGame() {
if (QtCommon::emu_thread == nullptr)
if (emu_thread == nullptr)
return true;
// Use custom question to link controller navigation
@@ -4518,9 +4747,9 @@ bool MainWindow::ConfirmChangeGame() {
}
bool MainWindow::ConfirmForceLockedExit() {
if (QtCommon::emu_thread == nullptr)
if (emu_thread == nullptr) {
return true;
}
const auto text = tr("The currently running application has requested Eden to not exit.\n\n"
"Would you like to bypass this and exit anyway?");
@@ -4528,8 +4757,9 @@ bool MainWindow::ConfirmForceLockedExit() {
}
void MainWindow::RequestGameExit() {
if (!QtCommon::system->IsPoweredOn())
if (!QtCommon::system->IsPoweredOn()) {
return;
}
QtCommon::system->SetExitRequested(true);
QtCommon::system->GetAppletManager().RequestExit();
@@ -4542,14 +4772,14 @@ void MainWindow::filterBarSetChecked(bool state) {
static void AdjustLinkColor() {
QPalette new_pal(qApp->palette());
if (UISettings::IsDarkTheme())
if (UISettings::IsDarkTheme()) {
new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255));
else
} else {
new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255));
if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link))
}
if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) {
qApp->setPalette(new_pal);
}
}
void MainWindow::UpdateUITheme() {
@@ -4557,8 +4787,9 @@ void MainWindow::UpdateUITheme() {
UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second);
QString current_theme = QString::fromStdString(UISettings::values.theme);
if (current_theme.isEmpty())
if (current_theme.isEmpty()) {
current_theme = default_theme;
}
#ifdef _WIN32
QIcon::setThemeName(current_theme);
@@ -4619,15 +4850,17 @@ void MainWindow::LoadTranslation() {
QStringLiteral(":/languages/"));
}
if (loaded)
if (loaded) {
qApp->installTranslator(&translator);
else
} else {
UISettings::values.language = std::string("en");
}
}
void MainWindow::OnLanguageChanged(const QString& locale) {
if (UISettings::values.language.GetValue() != std::string("en"))
if (UISettings::values.language.GetValue() != std::string("en")) {
qApp->removeTranslator(&translator);
}
QList<QAction*> actions = game_size_actions->actions();
for (size_t i = 0; i < default_game_icon_sizes.size(); i++) {
@@ -4643,10 +4876,11 @@ void MainWindow::OnLanguageChanged(const QString& locale) {
void MainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
#ifdef USE_DISCORD_PRESENCE
if (state)
if (state) {
discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>(*QtCommon::system);
else
} else {
discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
}
#else
discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
#endif
+14 -3
View File
@@ -20,9 +20,9 @@
#include "common/common_types.h"
#include "common/settings_enums.h"
#include "frontend_common/content_manager.h"
#include "frontend_common/update_checker.h"
#include "input_common/drivers/tas_input.h"
#include "qt_common/config/qt_config.h"
#include "qt_common/qt_common.h"
#include "qt_common/util/game.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
@@ -38,7 +38,6 @@
#ifdef ENABLE_UPDATE_CHECKER
#include <QFuture>
#include <QFutureWatcher>
#include "common/net/net.h"
#endif
class QtConfig;
@@ -67,6 +66,11 @@ class QtProfileSelectionDialog;
class QtSoftwareKeyboardDialog;
class QtNXWebEngineView;
enum class StartGameType {
Normal, // Can use custom configuration
Global, // Only uses global configuration
};
namespace VideoCore {
class ShaderNotify;
}
@@ -180,8 +184,11 @@ signals:
* Signal that is emitted when a new EmuThread has been created and an emulation session is
* about to start. At this time, the core system emulation has been initialized, and all
* emulation handles and memory should be valid.
*
* @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to
* access/change emulation state).
*/
void EmulationStarting();
void EmulationStarting(EmuThread* emu_thread);
/**
* Signal that is emitted when emulation is about to stop. At this time, the EmuThread and core
@@ -458,6 +465,7 @@ private:
bool CheckFirmwarePresence();
void SetFirmwareVersion();
void SetFPSSuffix();
void ConfigureFilesystemProvider(const std::string& filepath);
/**
* Open (or not) the right confirm dialog based on current setting and game exit lock
* @returns true if the player confirmed or the settings do no require it
@@ -530,6 +538,7 @@ private:
// Whether emulation is currently running in yuzu.
bool emulation_running = false;
std::unique_ptr<EmuThread> emu_thread;
// The path to the game currently running
QString current_game_path;
// Whether a user was set on the command line (skips UserSelector if it's forced to show up)
@@ -590,6 +599,8 @@ private:
const std::string& game_title, QtCommon::Game::ShortcutTarget target,
std::string arguments, const bool needs_title);
void InstallFirmware(const QString& location, bool recursive = false);
protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
+2 -2
View File
@@ -15,7 +15,7 @@ class UpdateDialog : public QDialog {
Q_OBJECT
public:
explicit UpdateDialog(const Common::Net::Release& release, QWidget* parent = nullptr);
explicit UpdateDialog(const Common::Net::Release &release, QWidget* parent = nullptr);
~UpdateDialog();
private slots:
@@ -23,6 +23,6 @@ private slots:
private:
Ui::UpdateDialog* ui;
QList<QRadioButton*> m_buttons;
QList<QRadioButton *> m_buttons;
Common::Net::Asset m_asset;
};
+105
View File
@@ -43,6 +43,111 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
return circle_pixmap;
}
bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
#if defined(WIN32)
#pragma pack(push, 2)
struct IconDir {
WORD id_reserved;
WORD id_type;
WORD id_count;
};
struct IconDirEntry {
BYTE width;
BYTE height;
BYTE color_count;
BYTE reserved;
WORD planes;
WORD bit_count;
DWORD bytes_in_res;
DWORD image_offset;
};
#pragma pack(pop)
const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
constexpr int bytes_per_pixel = 4;
const IconDir icon_dir{
.id_reserved = 0,
.id_type = 1,
.id_count = static_cast<WORD>(scale_sizes.size()),
};
Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile);
if (!icon_file.IsOpen()) {
return false;
}
if (!icon_file.Write(icon_dir)) {
return false;
}
std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
const IconDirEntry icon_entry{
.width = static_cast<BYTE>(scale_sizes[i]),
.height = static_cast<BYTE>(scale_sizes[i]),
.color_count = 0,
.reserved = 0,
.planes = 1,
.bit_count = bytes_per_pixel * 8,
.bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
.image_offset = static_cast<DWORD>(image_offset),
};
image_offset += icon_entry.bytes_in_res;
if (!icon_file.Write(icon_entry)) {
return false;
}
}
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const QImage scaled_image = source_image.scaled(
scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
const BITMAPINFOHEADER info_header{
.biSize = sizeof(BITMAPINFOHEADER),
.biWidth = scaled_image.width(),
.biHeight = scaled_image.height() * 2,
.biPlanes = 1,
.biBitCount = bytes_per_pixel * 8,
.biCompression = BI_RGB,
.biSizeImage{},
.biXPelsPerMeter{},
.biYPelsPerMeter{},
.biClrUsed{},
.biClrImportant{},
};
if (!icon_file.Write(info_header)) {
return false;
}
for (int y = 0; y < scaled_image.height(); y++) {
const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
std::memcpy(line_data.data(), line, line_data.size());
if (!icon_file.Write(line_data)) {
return false;
}
}
}
icon_file.Close();
return true;
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
// Convert and write the icon as a PNG
if (!image.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
} else {
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
}
return true;
#else
return false;
#endif
}
const std::optional<Common::UUID> GetProfileID() {
// if there's only a single profile, the user probably wants to use that... right?
const auto& profiles = QtCommon::system->GetProfileManager().FindExistingProfileUUIDs();
+10 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
@@ -6,6 +6,7 @@
#pragma once
#include <filesystem>
#include <QFont>
#include <QString>
#include "common/uuid.h"
@@ -23,6 +24,14 @@
*/
[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
/**
* Saves a windows icon to a file
* @param path The icons path
* @param image The image to save
* @return bool If the operation succeeded
*/
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
/**
* Prompt the user for a profile ID. If there is only one valid profile, returns that profile.
* @return The selected profile, or an std::nullopt if none were selected
@@ -11,13 +11,13 @@
#include "common/dynamic_library.h"
#include "common/logging.h"
#include "qt_common/util/vk.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_library.h"
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "vulkan/vulkan_core.h"
#include "yuzu/vk_device_info.h"
class QWindow;
+36
View File
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <iterator>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "common/common_types.h"
#include "vulkan/vulkan_core.h"
class QWindow;
namespace Settings {
enum class VSyncMode : u32;
}
// #include "common/settings.h"
namespace VkDeviceInfo {
// Short class to record Vulkan driver information for configuration purposes
class Record {
public:
explicit Record(std::string_view name, const std::vector<VkPresentModeKHR>& vsync_modes,
bool has_broken_compute);
~Record();
const std::string name;
const std::vector<VkPresentModeKHR> vsync_support;
const bool has_broken_compute;
};
void PopulateRecords(std::vector<Record>& records, QWindow* window);
} // namespace VkDeviceInfo