mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-28 09:41:36 +00:00
[desktop] More qt_common reorganization (#3916)
Ported from QML branch. Main "big" change is that EmuThread is now a shared state in QtCommon, not individually managed/passed around by GRenderWindow and MainWindow. Signed-off-by: crueter <crueter@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3916 Reviewed-by: Lizzie <lizzie@eden-emu.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// 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"
|
||||
|
||||
@@ -494,5 +495,94 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::str
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(crueter): Port InstallFirmware et al. from QML Branch
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Content
|
||||
|
||||
@@ -37,9 +37,12 @@ inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult
|
||||
}
|
||||
|
||||
void InstallFirmware(const QString& location, bool recursive);
|
||||
|
||||
QString UnzipFirmwareToTmp(const QString& location);
|
||||
|
||||
bool CheckKeys();
|
||||
void InstallFirmware();
|
||||
void InstallFirmwareZip();
|
||||
|
||||
// Keys //
|
||||
void InstallKeys();
|
||||
|
||||
@@ -54,6 +57,9 @@ 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
|
||||
|
||||
+108
-1
@@ -11,7 +11,6 @@
|
||||
#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>
|
||||
@@ -22,6 +21,7 @@
|
||||
#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,6 +367,7 @@ 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;
|
||||
@@ -533,4 +534,110 @@ 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
|
||||
|
||||
@@ -69,6 +69,14 @@ 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
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
#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"
|
||||
|
||||
class QWindow;
|
||||
|
||||
namespace VkDeviceInfo {
|
||||
Record::Record(std::string_view name_, const std::vector<VkPresentModeKHR>& vsync_modes_,
|
||||
bool has_broken_compute_)
|
||||
: name{name_}, vsync_support{vsync_modes_}, has_broken_compute{has_broken_compute_} {}
|
||||
|
||||
Record::~Record() = default;
|
||||
|
||||
void PopulateRecords(std::vector<Record>& records, QWindow* window) try {
|
||||
using namespace Vulkan;
|
||||
|
||||
// Create a test window with a Vulkan surface type for checking present modes.
|
||||
QWindow test_window(window);
|
||||
test_window.setSurfaceType(QWindow::VulkanSurface);
|
||||
test_window.create();
|
||||
auto wsi = QtCommon::GetWindowSystemInfo(&test_window);
|
||||
|
||||
vk::InstanceDispatch dld;
|
||||
const auto library = OpenLibrary();
|
||||
const vk::Instance instance = CreateInstance(*library, dld, VK_API_VERSION_1_1, wsi.type);
|
||||
const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices();
|
||||
vk::SurfaceKHR surface = CreateSurface(instance, wsi);
|
||||
|
||||
records.clear();
|
||||
records.reserve(physical_devices.size());
|
||||
for (const VkPhysicalDevice device : physical_devices) {
|
||||
const auto physical_device = vk::PhysicalDevice(device, dld);
|
||||
std::string name = physical_device.GetProperties().deviceName;
|
||||
|
||||
const std::vector<VkPresentModeKHR> present_modes =
|
||||
physical_device.GetSurfacePresentModesKHR(*surface);
|
||||
|
||||
VkPhysicalDeviceDriverProperties driver_properties{};
|
||||
driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
|
||||
driver_properties.pNext = nullptr;
|
||||
VkPhysicalDeviceProperties2 properties{};
|
||||
properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
|
||||
properties.pNext = &driver_properties;
|
||||
dld.vkGetPhysicalDeviceProperties2(physical_device, &properties);
|
||||
|
||||
const auto driverID = driver_properties.driverID;
|
||||
|
||||
bool has_broken_compute{
|
||||
Vulkan::Device::CheckBrokenCompute(driverID, properties.properties.driverVersion)};
|
||||
|
||||
std::string driver_string = Vulkan::vk::GetDriverName(driver_properties);
|
||||
|
||||
if (driver_string.empty())
|
||||
driver_string = "Unknown";
|
||||
|
||||
name = fmt::format("{} ({})", name, driver_string);
|
||||
|
||||
records.push_back(VkDeviceInfo::Record(name, present_modes, has_broken_compute));
|
||||
}
|
||||
} catch (const Vulkan::vk::Exception& exception) {
|
||||
LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
|
||||
}
|
||||
|
||||
} // namespace VkDeviceInfo
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user