mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-10 03:18:55 +02:00
Compare commits
5 commits
1457123bcc
...
1c57cb5603
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c57cb5603 | ||
|
|
8678cb06eb | ||
|
|
769edbfea3 | ||
|
|
0ff1d215c8 | ||
|
|
07e3a2aa46 |
151 changed files with 5364 additions and 4200 deletions
|
|
@ -115,7 +115,7 @@ for file in $FILES; do
|
|||
*.cmake|*.sh|*CMakeLists.txt)
|
||||
begin="#"
|
||||
;;
|
||||
*.kt*|*.cpp|*.h)
|
||||
*.kt*|*.cpp|*.h|*.qml)
|
||||
begin="//"
|
||||
;;
|
||||
*)
|
||||
|
|
@ -193,7 +193,7 @@ if [ "$UPDATE" = "true" ]; then
|
|||
begin="#"
|
||||
shell=true
|
||||
;;
|
||||
*.kt*|*.cpp|*.h)
|
||||
*)
|
||||
begin="//"
|
||||
shell="false"
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -242,7 +242,6 @@ if (YUZU_CMD)
|
|||
endif()
|
||||
|
||||
if (ENABLE_QT)
|
||||
add_definitions(-DYUZU_QT_WIDGETS)
|
||||
add_subdirectory(qt_common)
|
||||
add_subdirectory(yuzu)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ class AddonAdapter(val addonViewModel: AddonViewModel) :
|
|||
}
|
||||
binding.title.text = model.name
|
||||
binding.version.text = model.version
|
||||
|
||||
binding.addonSwitch.setOnCheckedChangeListener(null)
|
||||
binding.addonSwitch.isChecked = model.enabled
|
||||
|
||||
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
|
||||
|
|
@ -40,7 +42,8 @@ class AddonAdapter(val addonViewModel: AddonViewModel) :
|
|||
}
|
||||
}
|
||||
|
||||
val canDelete = model.isRemovable
|
||||
val canDelete = model.isRemovable && !model.isCheat()
|
||||
|
||||
binding.deleteCard.isEnabled = canDelete
|
||||
binding.buttonDelete.isEnabled = canDelete
|
||||
binding.deleteCard.alpha = if (canDelete) 1f else 0.38f
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class AddonsFragment : Fragment() {
|
|||
}
|
||||
|
||||
addonViewModel.addonList.collect(viewLifecycleOwner) {
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it.toList())
|
||||
}
|
||||
addonViewModel.showModInstallPicker.collect(
|
||||
viewLifecycleOwner,
|
||||
|
|
|
|||
|
|
@ -31,12 +31,17 @@ class AddonViewModel : ViewModel() {
|
|||
val addonToDelete = _addonToDelete.asStateFlow()
|
||||
|
||||
var game: Game? = null
|
||||
private set
|
||||
|
||||
private var loadedGameKey: String? = null
|
||||
|
||||
private val isRefreshing = AtomicBoolean(false)
|
||||
private val pendingRefresh = AtomicBoolean(false)
|
||||
|
||||
fun onAddonsViewCreated(game: Game) {
|
||||
if (this.game?.programId == game.programId && _patchList.value.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
this.game = game
|
||||
refreshAddons(commitEmpty = false)
|
||||
}
|
||||
|
|
@ -66,8 +71,7 @@ class AddonViewModel : ViewModel() {
|
|||
NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId)
|
||||
} ?: return@launch
|
||||
|
||||
val patchList = patches.toMutableList()
|
||||
patchList.sortBy { it.name }
|
||||
val patchList = sortPatchesWithCheatsGrouped(patches.toMutableList())
|
||||
|
||||
// Ensure only one update is enabled
|
||||
ensureSingleUpdateEnabled(patchList)
|
||||
|
|
@ -146,6 +150,7 @@ class AddonViewModel : ViewModel() {
|
|||
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
|
||||
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
|
||||
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
|
||||
PatchType.Cheat -> {}
|
||||
}
|
||||
refreshAddons(force = true)
|
||||
}
|
||||
|
|
@ -179,7 +184,7 @@ class AddonViewModel : ViewModel() {
|
|||
it.name
|
||||
}
|
||||
} else {
|
||||
it.name
|
||||
it.getStorageKey()
|
||||
}
|
||||
}
|
||||
}.toTypedArray()
|
||||
|
|
@ -201,4 +206,28 @@ class AddonViewModel : ViewModel() {
|
|||
private fun gameKey(game: Game): String {
|
||||
return "${game.programId}|${game.path}"
|
||||
}
|
||||
|
||||
private fun sortPatchesWithCheatsGrouped(patches: MutableList<Patch>): MutableList<Patch> {
|
||||
val individualCheats = patches.filter { it.isCheat() }
|
||||
val nonCheats = patches.filter { !it.isCheat() }.sortedBy { it.name }
|
||||
|
||||
val cheatsByParent = individualCheats.groupBy { it.parentName }
|
||||
|
||||
val result = mutableListOf<Patch>()
|
||||
for (patch in nonCheats) {
|
||||
result.add(patch)
|
||||
cheatsByParent[patch.name]?.sortedBy { it.name }?.let { childCheats ->
|
||||
result.addAll(childCheats)
|
||||
}
|
||||
}
|
||||
|
||||
val knownParents = nonCheats.map { it.name }.toSet()
|
||||
for ((parentName, orphanCheats) in cheatsByParent) {
|
||||
if (parentName !in knownParents) {
|
||||
result.addAll(orphanCheats.sortedBy { it.name })
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ data class Patch(
|
|||
val programId: String,
|
||||
val titleId: String,
|
||||
val numericVersion: Long = 0,
|
||||
val source: Int = 0
|
||||
val source: Int = 0,
|
||||
val parentName: String = "" // For cheats: name of the mod folder containing them
|
||||
) {
|
||||
companion object {
|
||||
const val SOURCE_UNKNOWN = 0
|
||||
|
|
@ -29,4 +30,22 @@ data class Patch(
|
|||
|
||||
val isRemovable: Boolean
|
||||
get() = source != SOURCE_EXTERNAL && source != SOURCE_PACKED
|
||||
|
||||
/**
|
||||
* Returns the storage key used for saving enabled/disabled state.
|
||||
* For cheats with a parent, returns "ParentName::CheatName".
|
||||
*/
|
||||
fun getStorageKey(): String {
|
||||
return if (parentName.isNotEmpty()) {
|
||||
"$parentName::$name"
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this patch is an individual cheat entry (not a cheat mod).
|
||||
* Individual cheats have type=Cheat and a parent mod name.
|
||||
*/
|
||||
fun isCheat(): Boolean = type == PatchType.Cheat.int && parentName.isNotEmpty()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -6,7 +9,8 @@ package org.yuzu.yuzu_emu.model
|
|||
enum class PatchType(val int: Int) {
|
||||
Update(0),
|
||||
DLC(1),
|
||||
Mod(2);
|
||||
Mod(2),
|
||||
Cheat(3);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): PatchType = entries.firstOrNull { it.int == int } ?: Update
|
||||
|
|
|
|||
|
|
@ -1396,7 +1396,10 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
|
|||
FileSys::VirtualFile update_raw;
|
||||
loader->ReadUpdateRaw(update_raw);
|
||||
|
||||
auto patches = pm.GetPatches(update_raw);
|
||||
// Get build ID for individual cheat enumeration
|
||||
const auto build_id = pm.GetBuildID(update_raw);
|
||||
|
||||
auto patches = pm.GetPatches(update_raw, build_id);
|
||||
jobjectArray jpatchArray =
|
||||
env->NewObjectArray(patches.size(), Common::Android::GetPatchClass(), nullptr);
|
||||
int i = 0;
|
||||
|
|
@ -1407,7 +1410,8 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
|
|||
Common::Android::ToJString(env, patch.version), static_cast<jint>(patch.type),
|
||||
Common::Android::ToJString(env, std::to_string(patch.program_id)),
|
||||
Common::Android::ToJString(env, std::to_string(patch.title_id)),
|
||||
static_cast<jlong>(patch.numeric_version), static_cast<jint>(patch.source));
|
||||
static_cast<jlong>(patch.numeric_version), static_cast<jint>(patch.source),
|
||||
Common::Android::ToJString(env, patch.parent_name));
|
||||
env->SetObjectArrayElement(jpatchArray, i, jpatch);
|
||||
++i;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ namespace Common::Android {
|
|||
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
|
||||
s_patch_constructor = env->GetMethodID(
|
||||
patch_class, "<init>",
|
||||
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)V");
|
||||
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;JI)V");
|
||||
s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
|
||||
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
|
||||
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
#include "common/settings.h"
|
||||
#include "common/time_zone.h"
|
||||
|
||||
#if defined(__linux__ ) && defined(ARCHITECTURE_arm64)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Settings {
|
||||
|
||||
// Clang 14 and earlier have errors when explicitly instantiating these classes
|
||||
|
|
@ -178,7 +182,11 @@ bool IsFastmemEnabled() {
|
|||
return bool(values.cpuopt_fastmem);
|
||||
else if (values.cpu_accuracy.GetValue() == CpuAccuracy::Unsafe)
|
||||
return bool(values.cpuopt_unsafe_host_mmu);
|
||||
#if !defined(__APPLE__) && !defined(__linux__) && !defined(__ANDROID__) && !defined(_WIN32)
|
||||
#if defined(__linux__) && defined(ARCHITECTURE_arm64)
|
||||
// Only 4kb systems support host MMU right now
|
||||
// TODO: Support this
|
||||
return getpagesize() == 4096;
|
||||
#elif !defined(__APPLE__) && !defined(__ANDROID__) && !defined(_WIN32) && !defined(__linux__)
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -616,6 +616,18 @@ add_library(core STATIC
|
|||
hle/service/caps/caps_u.h
|
||||
hle/service/cmif_serialization.h
|
||||
hle/service/cmif_types.h
|
||||
hle/service/dmnt/cheat_interface.cpp
|
||||
hle/service/dmnt/cheat_interface.h
|
||||
hle/service/dmnt/cheat_parser.cpp
|
||||
hle/service/dmnt/cheat_parser.h
|
||||
hle/service/dmnt/cheat_process_manager.cpp
|
||||
hle/service/dmnt/cheat_process_manager.h
|
||||
hle/service/dmnt/cheat_virtual_machine.cpp
|
||||
hle/service/dmnt/cheat_virtual_machine.h
|
||||
hle/service/dmnt/dmnt.cpp
|
||||
hle/service/dmnt/dmnt.h
|
||||
hle/service/dmnt/dmnt_results.h
|
||||
hle/service/dmnt/dmnt_types.h
|
||||
hle/service/erpt/erpt.cpp
|
||||
hle/service/erpt/erpt.h
|
||||
hle/service/es/es.cpp
|
||||
|
|
@ -1153,11 +1165,6 @@ add_library(core STATIC
|
|||
loader/xci.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory/cheat_engine.cpp
|
||||
memory/cheat_engine.h
|
||||
memory/dmnt_cheat_types.h
|
||||
memory/dmnt_cheat_vm.cpp
|
||||
memory/dmnt_cheat_vm.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
reporter.cpp
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include "core/hle/service/am/frontend/applets.h"
|
||||
#include "core/hle/service/am/process_creation.h"
|
||||
#include "core/hle/service/apm/apm_controller.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/glue/glue_manager.h"
|
||||
#include "core/hle/service/glue/time/static.h"
|
||||
|
|
@ -54,7 +55,6 @@
|
|||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/reporter.h"
|
||||
#include "core/tools/freezer.h"
|
||||
|
|
@ -278,8 +278,17 @@ struct System::Impl {
|
|||
audio_core.emplace(system);
|
||||
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
|
||||
|
||||
// Create cheat_manager BEFORE services, as DMNT::LoopProcess needs it
|
||||
cheat_manager = std::make_unique<Service::DMNT::CheatProcessManager>(system);
|
||||
|
||||
services.emplace(service_manager, system, stop_event.get_token());
|
||||
|
||||
// Apply any pending cheats that were registered before cheat_manager was initialized
|
||||
if (pending_cheats.has_pending) {
|
||||
ApplyPendingCheats(system);
|
||||
}
|
||||
|
||||
is_powered_on = true;
|
||||
exit_locked = false;
|
||||
exit_requested = false;
|
||||
|
|
@ -340,11 +349,6 @@ struct System::Impl {
|
|||
return init_result;
|
||||
}
|
||||
|
||||
// Initialize cheat engine
|
||||
if (cheat_engine) {
|
||||
cheat_engine->Initialize();
|
||||
}
|
||||
|
||||
// Register with applet manager
|
||||
// All threads are started, begin main process execution, now that we're in the clear
|
||||
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params);
|
||||
|
|
@ -405,7 +409,6 @@ struct System::Impl {
|
|||
services.reset();
|
||||
service_manager.reset();
|
||||
fs_controller.Reset();
|
||||
cheat_engine.reset();
|
||||
core_timing.ClearPendingEvents();
|
||||
app_loader.reset();
|
||||
audio_core.reset();
|
||||
|
|
@ -467,7 +470,6 @@ struct System::Impl {
|
|||
Core::SpeedLimiter speed_limiter;
|
||||
ExecuteProgramCallback execute_program_callback;
|
||||
ExitCallback exit_callback;
|
||||
|
||||
std::optional<Service::Services> services;
|
||||
std::optional<Core::Debugger> debugger;
|
||||
std::optional<Service::KernelHelpers::ServiceContext> general_channel_context;
|
||||
|
|
@ -476,7 +478,6 @@ struct System::Impl {
|
|||
std::optional<Tegra::Host1x::Host1x> host1x_core;
|
||||
std::optional<Core::DeviceMemory> device_memory;
|
||||
std::optional<AudioCore::AudioCore> audio_core;
|
||||
std::optional<Memory::CheatEngine> cheat_engine;
|
||||
std::optional<Tools::Freezer> memory_freezer;
|
||||
std::optional<Tools::RenderdocAPI> renderdoc_api;
|
||||
|
||||
|
|
@ -496,6 +497,17 @@ struct System::Impl {
|
|||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::stop_source stop_event;
|
||||
|
||||
/// Cheat Manager (DMNT)
|
||||
std::unique_ptr<Service::DMNT::CheatProcessManager> cheat_manager;
|
||||
/// Pending cheats to register after cheat_manager is initialized
|
||||
struct PendingCheats {
|
||||
std::vector<Service::DMNT::CheatEntry> list;
|
||||
std::array<u8, 32> build_id{};
|
||||
u64 main_region_begin{};
|
||||
u64 main_region_size{};
|
||||
bool has_pending{false};
|
||||
} pending_cheats;
|
||||
|
||||
mutable std::mutex suspend_guard;
|
||||
std::mutex general_channel_mutex;
|
||||
std::atomic_bool is_paused{};
|
||||
|
|
@ -513,6 +525,61 @@ struct System::Impl {
|
|||
general_channel_event.emplace(*general_channel_context);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyPendingCheats(System& system) {
|
||||
if (!pending_cheats.has_pending || !cheat_manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Applying {} pending cheats", pending_cheats.list.size());
|
||||
|
||||
const auto result = cheat_manager->AttachToApplicationProcess(
|
||||
pending_cheats.build_id, pending_cheats.main_region_begin,
|
||||
pending_cheats.main_region_size);
|
||||
|
||||
if (result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to attach cheat process: result={}", result.raw);
|
||||
pending_cheats = {};
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Cheat process attached successfully");
|
||||
|
||||
for (const auto& entry : pending_cheats.list) {
|
||||
if (entry.cheat_id == 0 && entry.definition.num_opcodes != 0) {
|
||||
LOG_DEBUG(Core, "Setting master cheat '{}' with {} opcodes",
|
||||
entry.definition.readable_name.data(), entry.definition.num_opcodes);
|
||||
const auto set_result = cheat_manager->SetMasterCheat(entry.definition);
|
||||
if (set_result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to set master cheat: result={}", set_result.raw);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add normal cheats (cheat_id != 0)
|
||||
for (const auto& entry : pending_cheats.list) {
|
||||
if (entry.cheat_id == 0 || entry.definition.num_opcodes == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 assigned_id = 0;
|
||||
LOG_DEBUG(Core, "Adding cheat '{}' (enabled={}, {} opcodes)",
|
||||
entry.definition.readable_name.data(), entry.enabled,
|
||||
entry.definition.num_opcodes);
|
||||
const auto add_result = cheat_manager->AddCheat(assigned_id, entry.enabled,
|
||||
entry.definition);
|
||||
if (add_result.IsError()) {
|
||||
LOG_WARNING(Core,
|
||||
"Failed to add cheat (original_id={} enabled={} name='{}'): result={}",
|
||||
entry.cheat_id, entry.enabled,
|
||||
entry.definition.readable_name.data(), add_result.raw);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear pending cheats
|
||||
pending_cheats = {};
|
||||
}
|
||||
};
|
||||
|
||||
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
||||
|
|
@ -750,11 +817,61 @@ FileSys::VirtualFilesystem System::GetFilesystem() const {
|
|||
return impl->virtual_filesystem;
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
void System::RegisterCheatList(const std::vector<Service::DMNT::CheatEntry>& list,
|
||||
const std::array<u8, 32>& build_id, u64 main_region_begin,
|
||||
u64 main_region_size) {
|
||||
impl->cheat_engine.emplace(*this, list, build_id);
|
||||
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
|
||||
// If cheat_manager is not yet initialized, cache the cheats for later
|
||||
if (!impl->cheat_manager) {
|
||||
impl->pending_cheats.list = list;
|
||||
impl->pending_cheats.build_id = build_id;
|
||||
impl->pending_cheats.main_region_begin = main_region_begin;
|
||||
impl->pending_cheats.main_region_size = main_region_size;
|
||||
impl->pending_cheats.has_pending = true;
|
||||
LOG_INFO(Core, "Cached {} cheats for later registration", list.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// Attach cheat process to the current application process
|
||||
const auto result = impl->cheat_manager->AttachToApplicationProcess(build_id, main_region_begin,
|
||||
main_region_size);
|
||||
if (result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to attach cheat process: result={}", result.raw);
|
||||
return;
|
||||
}
|
||||
|
||||
// Empty list: nothing more to do
|
||||
if (list.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set master cheat if present (cheat_id == 0)
|
||||
for (const auto& entry : list) {
|
||||
if (entry.cheat_id == 0 && entry.definition.num_opcodes != 0) {
|
||||
const auto set_result = impl->cheat_manager->SetMasterCheat(entry.definition);
|
||||
if (set_result.IsError()) {
|
||||
LOG_WARNING(Core, "Failed to set master cheat: result={}", set_result.raw);
|
||||
}
|
||||
// Only one master cheat allowed
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add normal cheats (cheat_id != 0)
|
||||
for (const auto& entry : list) {
|
||||
if (entry.cheat_id == 0 || entry.definition.num_opcodes == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 assigned_id = 0;
|
||||
const auto add_result = impl->cheat_manager->AddCheat(assigned_id, entry.enabled,
|
||||
entry.definition);
|
||||
if (add_result.IsError()) {
|
||||
LOG_WARNING(Core,
|
||||
"Failed to add cheat (original_id={} enabled={} name='{}'): result={}",
|
||||
entry.cheat_id, entry.enabled,
|
||||
entry.definition.readable_name.data(), add_result.raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
|
||||
|
|
@ -894,6 +1011,14 @@ Tools::RenderdocAPI& System::GetRenderdocAPI() {
|
|||
return *impl->renderdoc_api;
|
||||
}
|
||||
|
||||
Service::DMNT::CheatProcessManager& System::GetCheatManager() {
|
||||
return *impl->cheat_manager;
|
||||
}
|
||||
|
||||
const Service::DMNT::CheatProcessManager& System::GetCheatManager() const {
|
||||
return *impl->cheat_manager;
|
||||
}
|
||||
|
||||
void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
|
||||
return impl->kernel.RunServer(std::move(server_manager));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
#include "core/hle/service/os/event.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
|
||||
|
|
@ -45,10 +46,14 @@ enum class ResultStatus : u16;
|
|||
} // namespace Loader
|
||||
|
||||
namespace Core::Memory {
|
||||
struct CheatEntry;
|
||||
class Memory;
|
||||
} // namespace Core::Memory
|
||||
|
||||
namespace Service::DMNT {
|
||||
class CheatProcessManager;
|
||||
struct CheatEntry;
|
||||
}
|
||||
|
||||
namespace Service {
|
||||
|
||||
namespace Account {
|
||||
|
|
@ -335,7 +340,7 @@ public:
|
|||
|
||||
[[nodiscard]] FileSys::VirtualFilesystem GetFilesystem() const;
|
||||
|
||||
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
|
||||
void RegisterCheatList(const std::vector<Service::DMNT::CheatEntry>& list,
|
||||
const std::array<u8, 0x20>& build_id, u64 main_region_begin,
|
||||
u64 main_region_size);
|
||||
|
||||
|
|
@ -376,6 +381,9 @@ public:
|
|||
|
||||
[[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
|
||||
|
||||
[[nodiscard]] Service::DMNT::CheatProcessManager& GetCheatManager();
|
||||
[[nodiscard]] const Service::DMNT::CheatProcessManager& GetCheatManager() const;
|
||||
|
||||
void SetExitLocked(bool locked);
|
||||
bool GetExitLocked() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,12 +27,13 @@
|
|||
#include "core/file_sys/vfs/vfs_cached.h"
|
||||
#include "core/file_sys/vfs/vfs_layered.h"
|
||||
#include "core/file_sys/vfs/vfs_vector.h"
|
||||
#include "core/hle/service/dmnt/cheat_parser.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/ns/language.h"
|
||||
#include "core/hle/service/set/settings_server.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
|
||||
namespace FileSys {
|
||||
namespace {
|
||||
|
|
@ -64,16 +65,15 @@ std::string FormatTitleVersion(u32 version,
|
|||
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
||||
}
|
||||
|
||||
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
|
||||
// doesn't have a directory with name.
|
||||
VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
|
||||
// Returns a directory with name matching case-insensitively.
|
||||
// Returns nullptr if directory doesn't contain a subdirectory with the given name.
|
||||
VirtualDir FindSubdirectoryCaseless(const VirtualDir& dir, std::string_view name) {
|
||||
#ifdef _WIN32
|
||||
return dir->GetSubdirectory(name);
|
||||
#else
|
||||
const auto subdirs = dir->GetSubdirectories();
|
||||
for (const auto& subdir : subdirs) {
|
||||
std::string dir_name = Common::ToLower(subdir->GetName());
|
||||
if (dir_name == name) {
|
||||
const auto target = Common::ToLower(std::string(name));
|
||||
for (const auto& subdir : dir->GetSubdirectories()) {
|
||||
if (Common::ToLower(subdir->GetName()) == target) {
|
||||
return subdir;
|
||||
}
|
||||
}
|
||||
|
|
@ -82,36 +82,35 @@ VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name)
|
|||
#endif
|
||||
}
|
||||
|
||||
std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
|
||||
std::optional<std::vector<Service::DMNT::CheatEntry>> ReadCheatFileFromFolder(
|
||||
u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) {
|
||||
|
||||
const auto build_id_raw = Common::HexToString(build_id_, upper);
|
||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||
const auto build_id = build_id_raw.substr(0, std::min(build_id_raw.size(), sizeof(u64) * 2));
|
||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||
|
||||
if (file == nullptr) {
|
||||
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
LOG_DEBUG(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != data.size()) {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
LOG_WARNING(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
const Service::DMNT::CheatParser parser;
|
||||
return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
}
|
||||
|
||||
void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
|
||||
if (to.empty()) {
|
||||
to += with;
|
||||
} else {
|
||||
if (!to.empty()) {
|
||||
to += ", ";
|
||||
to += with;
|
||||
}
|
||||
to += with;
|
||||
}
|
||||
|
||||
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
|
|
@ -468,7 +467,7 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name)
|
|||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
|
||||
std::vector<Service::DMNT::CheatEntry> PatchManager::CreateCheatList(const BuildID& build_id_) const {
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||
|
|
@ -477,35 +476,69 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(const BuildI
|
|||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); });
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
// <mod dir> / <folder> / cheats / <build id>.txt
|
||||
std::vector<Core::Memory::CheatEntry> out;
|
||||
// Load cheats from: <mod dir>/<folder>/cheats/<build_id>.txt
|
||||
std::vector<Service::DMNT::CheatEntry> out;
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) == disabled.cend()) {
|
||||
if (auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); cheats_dir != nullptr) {
|
||||
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true))
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
if (auto const res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false))
|
||||
std::copy(res->begin(), res->end(), std::back_inserter(out));
|
||||
const auto mod_name = subdir->GetName();
|
||||
|
||||
// Skip entirely disabled mods
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), mod_name) != disabled.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats");
|
||||
if (cheats_dir == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try uppercase build_id first, then lowercase
|
||||
std::optional<std::vector<Service::DMNT::CheatEntry>> cheat_entries;
|
||||
if (auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
|
||||
cheat_entries = std::move(res);
|
||||
} else if (auto res_lower = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
|
||||
cheat_entries = std::move(res_lower);
|
||||
}
|
||||
|
||||
if (cheat_entries) {
|
||||
for (auto& entry : *cheat_entries) {
|
||||
// Check if this individual cheat is disabled
|
||||
const std::string cheat_name = entry.definition.readable_name.data();
|
||||
const std::string cheat_key = mod_name + "::" + cheat_name;
|
||||
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), cheat_key) != disabled.cend()) {
|
||||
// Individual cheat is disabled - mark it as disabled but still include it
|
||||
entry.enabled = false;
|
||||
}
|
||||
|
||||
out.push_back(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Uncareless user-friendly loading of patches (must start with 'cheat_')
|
||||
// <mod dir> / <cheat file>.txt
|
||||
for (auto const& f : load_dir->GetFiles()) {
|
||||
auto const name = f->GetName();
|
||||
if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) {
|
||||
std::vector<u8> data(f->GetSize());
|
||||
if (f->Read(data.data(), data.size()) == data.size()) {
|
||||
const Core::Memory::TextCheatParser parser;
|
||||
auto const res = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
std::copy(res.begin(), res.end(), std::back_inserter(out));
|
||||
} else {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id);
|
||||
}
|
||||
|
||||
// User-friendly cheat loading from: <mod dir>/cheat_*.txt
|
||||
for (const auto& file : load_dir->GetFiles()) {
|
||||
const auto& name = file->GetName();
|
||||
if (!name.starts_with("cheat_")) {
|
||||
continue;
|
||||
}
|
||||
if (std::find(disabled.cbegin(), disabled.cend(), name) != disabled.cend()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != static_cast<size_t>(data.size())) {
|
||||
LOG_WARNING(Common_Filesystem, "Failed to read cheat file '{}' for title_id={:016X}",
|
||||
name, title_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const Service::DMNT::CheatParser parser;
|
||||
auto entries = parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||
out.insert(out.end(), entries.begin(), entries.end());
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
@ -712,7 +745,53 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
|||
return romfs;
|
||||
}
|
||||
|
||||
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
PatchManager::BuildID PatchManager::GetBuildID(VirtualFile update_raw) const {
|
||||
BuildID build_id{};
|
||||
|
||||
// Get the base NCA
|
||||
const auto base_nca = content_provider.GetEntry(title_id, ContentRecordType::Program);
|
||||
if (base_nca == nullptr) {
|
||||
return build_id;
|
||||
}
|
||||
|
||||
// Try to get ExeFS from update first, then base
|
||||
VirtualDir exefs;
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
|
||||
|
||||
if (update != nullptr && update->GetExeFS() != nullptr) {
|
||||
exefs = update->GetExeFS();
|
||||
} else if (update_raw != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update_raw, base_nca.get());
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetExeFS() != nullptr) {
|
||||
exefs = new_nca->GetExeFS();
|
||||
}
|
||||
}
|
||||
|
||||
if (exefs == nullptr) {
|
||||
exefs = base_nca->GetExeFS();
|
||||
}
|
||||
|
||||
if (exefs == nullptr) {
|
||||
return build_id;
|
||||
}
|
||||
|
||||
// Try to read the main NSO header
|
||||
const auto main_file = exefs->GetFile("main");
|
||||
if (main_file == nullptr || main_file->GetSize() < sizeof(Loader::NSOHeader)) {
|
||||
return build_id;
|
||||
}
|
||||
|
||||
Loader::NSOHeader header{};
|
||||
if (main_file->Read(reinterpret_cast<u8*>(&header), sizeof(header)) == sizeof(header)) {
|
||||
build_id = header.build_id;
|
||||
}
|
||||
|
||||
return build_id;
|
||||
}
|
||||
|
||||
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildID& build_id) const {
|
||||
if (title_id == 0) {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -749,7 +828,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
.program_id = title_id,
|
||||
.title_id = update_tid,
|
||||
.source = PatchSource::External,
|
||||
.numeric_version = update_entry.version};
|
||||
.numeric_version = update_entry.version,
|
||||
.parent_name = ""};
|
||||
|
||||
external_update_patches.push_back(update_patch);
|
||||
}
|
||||
|
|
@ -895,26 +975,15 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if we have a valid build_id for cheat enumeration
|
||||
const bool has_build_id = std::any_of(build_id.begin(), build_id.end(), [](u8 b) { return b != 0; });
|
||||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr) {
|
||||
for (auto const& f : mod_dir->GetFiles())
|
||||
if (auto const name = f->GetName(); name.starts_with("cheat_")) {
|
||||
auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end();
|
||||
out.push_back({
|
||||
.enabled = !mod_disabled,
|
||||
.name = name,
|
||||
.version = "Cheats",
|
||||
.type = PatchType::Mod,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.source = PatchSource::Unknown,
|
||||
.location = f->GetFullPath(),
|
||||
});
|
||||
}
|
||||
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
bool has_cheats = false;
|
||||
|
||||
const auto exefs_dir = FindSubdirectoryCaseless(mod, "exefs");
|
||||
if (IsDirValidAndNonEmpty(exefs_dir)) {
|
||||
|
|
@ -943,8 +1012,12 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")) ||
|
||||
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfslite")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "cheats")))
|
||||
|
||||
const auto cheats_dir = FindSubdirectoryCaseless(mod, "cheats");
|
||||
if (IsDirValidAndNonEmpty(cheats_dir)) {
|
||||
has_cheats = true;
|
||||
AppendCommaIfNotEmpty(types, "Cheats");
|
||||
}
|
||||
|
||||
if (types.empty())
|
||||
continue;
|
||||
|
|
@ -957,7 +1030,47 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.source = PatchSource::Unknown,
|
||||
.location = mod->GetFullPath()});
|
||||
.location = mod->GetFullPath(),
|
||||
.parent_name = ""});
|
||||
|
||||
// Add individual cheats as sub-entries
|
||||
if (has_cheats) {
|
||||
// Try to read cheat file with build_id first, fallback to all files
|
||||
std::vector<Service::DMNT::CheatEntry> cheat_entries;
|
||||
|
||||
if (has_build_id) {
|
||||
if (auto res = ReadCheatFileFromFolder(title_id, build_id, cheats_dir, true)) {
|
||||
cheat_entries = std::move(*res);
|
||||
} else if (auto res_lower = ReadCheatFileFromFolder(title_id, build_id, cheats_dir, false)) {
|
||||
cheat_entries = std::move(*res_lower);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& cheat : cheat_entries) {
|
||||
// Skip master cheat (id 0) with no readable name
|
||||
if (cheat.cheat_id == 0 && cheat.definition.readable_name[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string cheat_name = cheat.definition.readable_name.data();
|
||||
if (cheat_name.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create unique key for this cheat: "ModName::CheatName"
|
||||
const std::string cheat_key = mod->GetName() + "::" + cheat_name;
|
||||
const auto cheat_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), cheat_key) != disabled.end();
|
||||
|
||||
out.push_back({.enabled = !cheat_disabled,
|
||||
.name = cheat_name,
|
||||
.version = types,
|
||||
.type = PatchType::Cheat,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.parent_name = mod->GetName()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -982,7 +1095,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
.type = PatchType::Mod,
|
||||
.program_id = title_id,
|
||||
.title_id = title_id,
|
||||
.source = PatchSource::Unknown});
|
||||
.source = PatchSource::Unknown,
|
||||
.parent_name = ""});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1069,7 +1183,8 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||
.type = PatchType::DLC,
|
||||
.program_id = title_id,
|
||||
.title_id = dlc_match.back().title_id,
|
||||
.source = dlc_source});
|
||||
.source = dlc_source,
|
||||
.parent_name = ""});
|
||||
}
|
||||
|
||||
return out;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
#include "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
|
|
@ -23,13 +22,17 @@ namespace Service::FileSystem {
|
|||
class FileSystemController;
|
||||
}
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct CheatEntry;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class ContentProvider;
|
||||
class NCA;
|
||||
class NACP;
|
||||
|
||||
enum class PatchType { Update, DLC, Mod };
|
||||
enum class PatchType { Update, DLC, Mod, Cheat };
|
||||
|
||||
enum class PatchSource {
|
||||
Unknown,
|
||||
|
|
@ -49,6 +52,7 @@ struct Patch {
|
|||
PatchSource source;
|
||||
std::string location;
|
||||
u32 numeric_version{0};
|
||||
std::string parent_name;
|
||||
};
|
||||
|
||||
// A centralized class to manage patches to games.
|
||||
|
|
@ -79,7 +83,7 @@ public:
|
|||
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
|
||||
[[nodiscard]] std::vector<Service::DMNT::CheatEntry> CreateCheatList(
|
||||
const BuildID& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
|
|
@ -90,8 +94,11 @@ public:
|
|||
VirtualFile packed_update_raw = nullptr,
|
||||
bool apply_layeredfs = true) const;
|
||||
|
||||
// Returns a vector of patches
|
||||
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr) const;
|
||||
// Returns a vector of patches including individual cheats
|
||||
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr,
|
||||
const BuildID& build_id = {}) const;
|
||||
|
||||
[[nodiscard]] BuildID GetBuildID(VirtualFile update_raw = nullptr) const;
|
||||
|
||||
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
|
||||
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
|
||||
|
|
|
|||
238
src/core/hle/service/dmnt/cheat_interface.cpp
Normal file
238
src/core/hle/service/dmnt/cheat_interface.cpp
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/dmnt/cheat_interface.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/dmnt/dmnt_results.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
ICheatInterface::ICheatInterface(Core::System& system_, CheatProcessManager& manager)
|
||||
: ServiceFramework{system_, "dmnt:cht"}, cheat_process_manager{manager} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{65000, C<&ICheatInterface::HasCheatProcess>, "HasCheatProcess"},
|
||||
{65001, C<&ICheatInterface::GetCheatProcessEvent>, "GetCheatProcessEvent"},
|
||||
{65002, C<&ICheatInterface::GetCheatProcessMetadata>, "GetCheatProcessMetadata"},
|
||||
{65003, C<&ICheatInterface::ForceOpenCheatProcess>, "ForceOpenCheatProcess"},
|
||||
{65004, C<&ICheatInterface::PauseCheatProcess>, "PauseCheatProcess"},
|
||||
{65005, C<&ICheatInterface::ResumeCheatProcess>, "ResumeCheatProcess"},
|
||||
{65006, C<&ICheatInterface::ForceCloseCheatProcess>, "ForceCloseCheatProcess"},
|
||||
{65100, C<&ICheatInterface::GetCheatProcessMappingCount>, "GetCheatProcessMappingCount"},
|
||||
{65101, C<&ICheatInterface::GetCheatProcessMappings>, "GetCheatProcessMappings"},
|
||||
{65102, C<&ICheatInterface::ReadCheatProcessMemory>, "ReadCheatProcessMemory"},
|
||||
{65103, C<&ICheatInterface::WriteCheatProcessMemory>, "WriteCheatProcessMemory"},
|
||||
{65104, C<&ICheatInterface::QueryCheatProcessMemory>, "QueryCheatProcessMemory"},
|
||||
{65200, C<&ICheatInterface::GetCheatCount>, "GetCheatCount"},
|
||||
{65201, C<&ICheatInterface::GetCheats>, "GetCheats"},
|
||||
{65202, C<&ICheatInterface::GetCheatById>, "GetCheatById"},
|
||||
{65203, C<&ICheatInterface::ToggleCheat>, "ToggleCheat"},
|
||||
{65204, C<&ICheatInterface::AddCheat>, "AddCheat"},
|
||||
{65205, C<&ICheatInterface::RemoveCheat>, "RemoveCheat"},
|
||||
{65206, C<&ICheatInterface::ReadStaticRegister>, "ReadStaticRegister"},
|
||||
{65207, C<&ICheatInterface::WriteStaticRegister>, "WriteStaticRegister"},
|
||||
{65208, C<&ICheatInterface::ResetStaticRegisters>, "ResetStaticRegisters"},
|
||||
{65209, C<&ICheatInterface::SetMasterCheat>, "SetMasterCheat"},
|
||||
{65300, C<&ICheatInterface::GetFrozenAddressCount>, "GetFrozenAddressCount"},
|
||||
{65301, C<&ICheatInterface::GetFrozenAddresses>, "GetFrozenAddresses"},
|
||||
{65302, C<&ICheatInterface::GetFrozenAddress>, "GetFrozenAddress"},
|
||||
{65303, C<&ICheatInterface::EnableFrozenAddress>, "EnableFrozenAddress"},
|
||||
{65304, C<&ICheatInterface::DisableFrozenAddress>, "DisableFrozenAddress"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ICheatInterface::~ICheatInterface() = default;
|
||||
|
||||
Result ICheatInterface::HasCheatProcess(Out<bool> out_has_cheat) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
*out_has_cheat = cheat_process_manager.HasCheatProcess();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
*out_event = &cheat_process_manager.GetCheatProcessEvent();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.GetCheatProcessMetadata(*out_metadata));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ForceOpenCheatProcess() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_UNLESS(R_SUCCEEDED(cheat_process_manager.ForceOpenCheatProcess()), ResultCheatNotAttached);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ICheatInterface::PauseCheatProcess() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.PauseCheatProcess());
|
||||
}
|
||||
|
||||
Result ICheatInterface::ResumeCheatProcess() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.ResumeCheatProcess());
|
||||
}
|
||||
|
||||
Result ICheatInterface::ForceCloseCheatProcess() {
|
||||
LOG_WARNING(CheatEngine, "(STUBBED) called");
|
||||
R_RETURN(cheat_process_manager.ForceCloseCheatProcess());
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessMappingCount(Out<u64> out_count) {
|
||||
LOG_WARNING(CheatEngine, "(STUBBED) called");
|
||||
R_RETURN(cheat_process_manager.GetCheatProcessMappingCount(*out_count));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatProcessMappings(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings) {
|
||||
LOG_INFO(CheatEngine, "called, offset={}", offset);
|
||||
R_UNLESS(!out_mappings.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.GetCheatProcessMappings(*out_count, offset, out_mappings));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ReadCheatProcessMemory(u64 address, u64 size,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer) {
|
||||
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size);
|
||||
R_UNLESS(!out_buffer.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.ReadCheatProcessMemory(address, size, out_buffer));
|
||||
}
|
||||
|
||||
Result ICheatInterface::WriteCheatProcessMemory(u64 address, u64 size,
|
||||
InBuffer<BufferAttr_HipcMapAlias> buffer) {
|
||||
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size);
|
||||
R_UNLESS(!buffer.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.WriteCheatProcessMemory(address, size, buffer));
|
||||
}
|
||||
|
||||
Result ICheatInterface::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping,
|
||||
u64 address) {
|
||||
LOG_WARNING(CheatEngine, "(STUBBED) called, address={}", address);
|
||||
R_RETURN(cheat_process_manager.QueryCheatProcessMemory(out_mapping, address));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatCount(Out<u64> out_count) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.GetCheatCount(*out_count));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheats(Out<u64> out_count, u64 offset,
|
||||
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats) {
|
||||
LOG_INFO(CheatEngine, "called, offset={}", offset);
|
||||
R_UNLESS(!out_cheats.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.GetCheats(*out_count, offset, out_cheats));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat,
|
||||
u32 cheat_id) {
|
||||
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
|
||||
R_RETURN(cheat_process_manager.GetCheatById(out_cheat, cheat_id));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ToggleCheat(u32 cheat_id) {
|
||||
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
|
||||
R_RETURN(cheat_process_manager.ToggleCheat(cheat_id));
|
||||
}
|
||||
|
||||
Result ICheatInterface::AddCheat(
|
||||
Out<u32> out_cheat_id, bool is_enabled,
|
||||
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) {
|
||||
LOG_INFO(CheatEngine, "called, is_enabled={}", is_enabled);
|
||||
R_RETURN(cheat_process_manager.AddCheat(*out_cheat_id, is_enabled, *cheat_definition));
|
||||
}
|
||||
|
||||
Result ICheatInterface::RemoveCheat(u32 cheat_id) {
|
||||
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
|
||||
R_RETURN(cheat_process_manager.RemoveCheat(cheat_id));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ReadStaticRegister(Out<u64> out_value, u8 register_index) {
|
||||
LOG_DEBUG(CheatEngine, "called, register_index={}", register_index);
|
||||
R_RETURN(cheat_process_manager.ReadStaticRegister(*out_value, register_index));
|
||||
}
|
||||
|
||||
Result ICheatInterface::WriteStaticRegister(u8 register_index, u64 value) {
|
||||
LOG_DEBUG(CheatEngine, "called, register_index={}, value={}", register_index, value);
|
||||
R_RETURN(cheat_process_manager.WriteStaticRegister(register_index, value));
|
||||
}
|
||||
|
||||
Result ICheatInterface::ResetStaticRegisters() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.ResetStaticRegisters());
|
||||
}
|
||||
|
||||
Result ICheatInterface::SetMasterCheat(
|
||||
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) {
|
||||
LOG_INFO(CheatEngine, "called, name={}, num_opcodes={}", cheat_definition->readable_name.data(),
|
||||
cheat_definition->num_opcodes);
|
||||
R_RETURN(cheat_process_manager.SetMasterCheat(*cheat_definition));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetFrozenAddressCount(Out<u64> out_count) {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.GetFrozenAddressCount(*out_count));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetFrozenAddresses(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address) {
|
||||
LOG_INFO(CheatEngine, "called, offset={}", offset);
|
||||
R_UNLESS(!out_frozen_address.empty(), ResultCheatNullBuffer);
|
||||
R_RETURN(cheat_process_manager.GetFrozenAddresses(*out_count, offset, out_frozen_address));
|
||||
}
|
||||
|
||||
Result ICheatInterface::GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry,
|
||||
u64 address) {
|
||||
LOG_INFO(CheatEngine, "called, address={}", address);
|
||||
R_RETURN(cheat_process_manager.GetFrozenAddress(*out_frozen_address_entry, address));
|
||||
}
|
||||
|
||||
Result ICheatInterface::EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width) {
|
||||
LOG_INFO(CheatEngine, "called, address={}, width={}", address, width);
|
||||
R_UNLESS(width > 0, ResultFrozenAddressInvalidWidth);
|
||||
R_UNLESS(width <= sizeof(u64), ResultFrozenAddressInvalidWidth);
|
||||
R_UNLESS((width & (width - 1)) == 0, ResultFrozenAddressInvalidWidth);
|
||||
R_RETURN(cheat_process_manager.EnableFrozenAddress(*out_value, address, width));
|
||||
}
|
||||
|
||||
Result ICheatInterface::DisableFrozenAddress(u64 address) {
|
||||
LOG_INFO(CheatEngine, "called, address={}", address);
|
||||
R_RETURN(cheat_process_manager.DisableFrozenAddress(address));
|
||||
}
|
||||
|
||||
void ICheatInterface::InitializeCheatManager() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
}
|
||||
|
||||
Result ICheatInterface::ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data,
|
||||
size_t size) {
|
||||
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size);
|
||||
R_RETURN(cheat_process_manager.ReadCheatProcessMemoryUnsafe(process_addr, &out_data, size));
|
||||
}
|
||||
|
||||
Result ICheatInterface::WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data,
|
||||
size_t size) {
|
||||
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size);
|
||||
R_RETURN(cheat_process_manager.WriteCheatProcessMemoryUnsafe(process_addr, &data, size));
|
||||
}
|
||||
|
||||
Result ICheatInterface::PauseCheatProcessUnsafe() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.PauseCheatProcessUnsafe());
|
||||
}
|
||||
|
||||
Result ICheatInterface::ResumeCheatProcessUnsafe() {
|
||||
LOG_INFO(CheatEngine, "called");
|
||||
R_RETURN(cheat_process_manager.ResumeCheatProcessUnsafe());
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
88
src/core/hle/service/dmnt/cheat_interface.h
Normal file
88
src/core/hle/service/dmnt/cheat_interface.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Kernel::Svc {
|
||||
struct MemoryInfo;
|
||||
}
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct CheatDefinition;
|
||||
struct CheatEntry;
|
||||
struct CheatProcessMetadata;
|
||||
struct FrozenAddressEntry;
|
||||
class CheatProcessManager;
|
||||
|
||||
class ICheatInterface final : public ServiceFramework<ICheatInterface> {
|
||||
public:
|
||||
explicit ICheatInterface(Core::System& system_, CheatProcessManager& manager);
|
||||
~ICheatInterface() override;
|
||||
|
||||
private:
|
||||
Result HasCheatProcess(Out<bool> out_has_cheat);
|
||||
Result GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata);
|
||||
Result ForceOpenCheatProcess();
|
||||
Result PauseCheatProcess();
|
||||
Result ResumeCheatProcess();
|
||||
Result ForceCloseCheatProcess();
|
||||
|
||||
Result GetCheatProcessMappingCount(Out<u64> out_count);
|
||||
Result GetCheatProcessMappings(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings);
|
||||
Result ReadCheatProcessMemory(u64 address, u64 size,
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer);
|
||||
Result WriteCheatProcessMemory(u64 address, u64 size, InBuffer<BufferAttr_HipcMapAlias> buffer);
|
||||
|
||||
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping, u64 address);
|
||||
Result GetCheatCount(Out<u64> out_count);
|
||||
Result GetCheats(Out<u64> out_count, u64 offset,
|
||||
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats);
|
||||
Result GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat, u32 cheat_id);
|
||||
Result ToggleCheat(u32 cheat_id);
|
||||
|
||||
Result AddCheat(Out<u32> out_cheat_id, bool enabled,
|
||||
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition);
|
||||
Result RemoveCheat(u32 cheat_id);
|
||||
Result ReadStaticRegister(Out<u64> out_value, u8 register_index);
|
||||
Result WriteStaticRegister(u8 register_index, u64 value);
|
||||
Result ResetStaticRegisters();
|
||||
Result SetMasterCheat(InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition);
|
||||
Result GetFrozenAddressCount(Out<u64> out_count);
|
||||
Result GetFrozenAddresses(
|
||||
Out<u64> out_count, u64 offset,
|
||||
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address);
|
||||
Result GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry, u64 address);
|
||||
Result EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width);
|
||||
Result DisableFrozenAddress(u64 address);
|
||||
|
||||
private:
|
||||
void InitializeCheatManager();
|
||||
|
||||
Result ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data, size_t size);
|
||||
Result WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data, size_t size);
|
||||
|
||||
Result PauseCheatProcessUnsafe();
|
||||
Result ResumeCheatProcessUnsafe();
|
||||
|
||||
CheatProcessManager& cheat_process_manager;
|
||||
};
|
||||
} // namespace Service::DMNT
|
||||
121
src/core/hle/service/dmnt/cheat_parser.cpp
Normal file
121
src/core/hle/service/dmnt/cheat_parser.cpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "core/hle/service/dmnt/cheat_parser.h"
|
||||
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
CheatParser::CheatParser() {}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
std::vector<CheatEntry> CheatParser::Parse(std::string_view data) const {
|
||||
std::vector<CheatEntry> out(1);
|
||||
std::optional<u64> current_entry;
|
||||
|
||||
for (std::size_t i = 0; i < data.size(); ++i) {
|
||||
if (std::isspace(data[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data[i] == '{') {
|
||||
current_entry = 0;
|
||||
|
||||
if (out[*current_entry].definition.num_opcodes > 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, '}');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (data[i] == '[') {
|
||||
current_entry = out.size();
|
||||
out.emplace_back();
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, ']');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (std::isxdigit(data[i])) {
|
||||
if (!current_entry || out[*current_entry].definition.num_opcodes >=
|
||||
out[*current_entry].definition.opcodes.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto hex = std::string(data.substr(i, 8));
|
||||
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
|
||||
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
|
||||
value;
|
||||
|
||||
i += 7; // 7 because the for loop will increment by 1 more
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out[0].enabled = out[0].definition.num_opcodes > 0;
|
||||
out[0].cheat_id = 0;
|
||||
|
||||
for (u32 i = 1; i < out.size(); ++i) {
|
||||
out[i].enabled = out[i].definition.num_opcodes > 0;
|
||||
out[i].cheat_id = i;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string_view CheatParser::ExtractName(std::size_t& out_name_size, std::string_view data,
|
||||
std::size_t start_index, char match) const {
|
||||
auto end_index = start_index;
|
||||
while (data[end_index] != match) {
|
||||
++end_index;
|
||||
if (end_index > data.size()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out_name_size = end_index - start_index;
|
||||
|
||||
// Clamp name if it's too big
|
||||
if (out_name_size > sizeof(CheatDefinition::readable_name)) {
|
||||
end_index = start_index + sizeof(CheatDefinition::readable_name);
|
||||
}
|
||||
|
||||
return data.substr(start_index, end_index - start_index);
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
26
src/core/hle/service/dmnt/cheat_parser.h
Normal file
26
src/core/hle/service/dmnt/cheat_parser.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct CheatEntry;
|
||||
|
||||
class CheatParser final {
|
||||
public:
|
||||
CheatParser();
|
||||
~CheatParser();
|
||||
|
||||
std::vector<CheatEntry> Parse(std::string_view data) const;
|
||||
|
||||
private:
|
||||
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
|
||||
std::size_t start_index, char match) const;
|
||||
};
|
||||
} // namespace Service::DMNT
|
||||
599
src/core/hle/service/dmnt/cheat_process_manager.cpp
Normal file
599
src/core/hle/service/dmnt/cheat_process_manager.cpp
Normal file
|
|
@ -0,0 +1,599 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/arm/debug.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/cmif_serialization.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
|
||||
#include "core/hle/service/dmnt/dmnt_results.h"
|
||||
#include "core/hle/service/hid/hid_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "hid_core/resource_manager.h"
|
||||
#include "hid_core/resources/npad/npad.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
|
||||
|
||||
CheatProcessManager::CheatProcessManager(Core::System& system_)
|
||||
: system{system_}, service_context{system_, "dmnt:cht"}, core_timing{system_.CoreTiming()} {
|
||||
update_event = Core::Timing::CreateEvent("CheatEngine::FrameCallback",
|
||||
[this](s64 time, std::chrono::nanoseconds ns_late)
|
||||
-> std::optional<std::chrono::nanoseconds> {
|
||||
FrameCallback(ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < MaxCheatCount; i++) {
|
||||
ResetCheatEntry(i);
|
||||
}
|
||||
|
||||
cheat_vm = std::make_unique<CheatVirtualMachine>(*this);
|
||||
|
||||
cheat_process_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent");
|
||||
unsafe_break_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent");
|
||||
}
|
||||
|
||||
CheatProcessManager::~CheatProcessManager() {
|
||||
service_context.CloseEvent(cheat_process_event);
|
||||
service_context.CloseEvent(unsafe_break_event);
|
||||
core_timing.UnscheduleEvent(update_event);
|
||||
}
|
||||
|
||||
void CheatProcessManager::SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm) {
|
||||
if (vm) {
|
||||
cheat_vm = std::move(vm);
|
||||
SetNeedsReloadVm(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool CheatProcessManager::HasActiveCheatProcess() {
|
||||
// Note: This function *MUST* be called only with the cheat lock held.
|
||||
bool has_cheat_process =
|
||||
cheat_process_debug_handle != InvalidHandle &&
|
||||
system.ApplicationProcess()->GetProcessId() == cheat_process_metadata.process_id;
|
||||
|
||||
if (!has_cheat_process) {
|
||||
CloseActiveCheatProcess();
|
||||
}
|
||||
|
||||
return has_cheat_process;
|
||||
}
|
||||
|
||||
void CheatProcessManager::CloseActiveCheatProcess() {
|
||||
if (cheat_process_debug_handle != InvalidHandle) {
|
||||
broken_unsafe = false;
|
||||
unsafe_break_event->Signal();
|
||||
core_timing.UnscheduleEvent(update_event);
|
||||
|
||||
// Close resources.
|
||||
cheat_process_debug_handle = InvalidHandle;
|
||||
|
||||
// Save cheat toggles.
|
||||
if (always_save_cheat_toggles || should_save_cheat_toggles) {
|
||||
// TODO: save cheat toggles
|
||||
should_save_cheat_toggles = false;
|
||||
}
|
||||
|
||||
cheat_process_metadata = {};
|
||||
|
||||
ResetAllCheatEntries();
|
||||
|
||||
{
|
||||
auto it = frozen_addresses_map.begin();
|
||||
while (it != frozen_addresses_map.end()) {
|
||||
it = frozen_addresses_map.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
cheat_process_event->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
Result CheatProcessManager::EnsureCheatProcess() {
|
||||
R_UNLESS(HasActiveCheatProcess(), ResultCheatNotAttached);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void CheatProcessManager::SetNeedsReloadVm(bool reload) {
|
||||
needs_reload_vm = reload;
|
||||
}
|
||||
|
||||
void CheatProcessManager::ResetCheatEntry(size_t i) {
|
||||
if (i < MaxCheatCount) {
|
||||
cheat_entries[i] = {};
|
||||
cheat_entries[i].cheat_id = static_cast<u32>(i);
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatProcessManager::ResetAllCheatEntries() {
|
||||
for (size_t i = 0; i < MaxCheatCount; i++) {
|
||||
ResetCheatEntry(i);
|
||||
}
|
||||
|
||||
cheat_vm->ResetStaticRegisters();
|
||||
}
|
||||
|
||||
CheatEntry* CheatProcessManager::GetCheatEntryById(size_t i) {
|
||||
if (i < MaxCheatCount) {
|
||||
return cheat_entries.data() + i;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheatEntry* CheatProcessManager::GetCheatEntryByReadableName(const char* readable_name) {
|
||||
for (size_t i = 1; i < MaxCheatCount; i++) {
|
||||
if (std::strncmp(cheat_entries[i].definition.readable_name.data(), readable_name,
|
||||
sizeof(cheat_entries[i].definition.readable_name)) == 0) {
|
||||
return cheat_entries.data() + i;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheatEntry* CheatProcessManager::GetFreeCheatEntry() {
|
||||
// Check all non-master cheats for availability.
|
||||
for (size_t i = 1; i < MaxCheatCount; i++) {
|
||||
if (cheat_entries[i].definition.num_opcodes == 0) {
|
||||
return cheat_entries.data() + i;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CheatProcessManager::HasCheatProcess() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
return HasActiveCheatProcess();
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& CheatProcessManager::GetCheatProcessEvent() const {
|
||||
return cheat_process_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::AttachToApplicationProcess(const std::array<u8, 0x20>& build_id,
|
||||
VAddr main_region_begin,
|
||||
u64 main_region_size) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
{
|
||||
if (this->HasActiveCheatProcess()) {
|
||||
this->CloseActiveCheatProcess();
|
||||
}
|
||||
}
|
||||
|
||||
cheat_process_metadata.process_id = system.ApplicationProcess()->GetProcessId();
|
||||
|
||||
{
|
||||
const auto& page_table = system.ApplicationProcess()->GetPageTable();
|
||||
cheat_process_metadata.program_id = system.GetApplicationProcessProgramID();
|
||||
cheat_process_metadata.heap_extents = {
|
||||
.base = GetInteger(page_table.GetHeapRegionStart()),
|
||||
.size = page_table.GetHeapRegionSize(),
|
||||
};
|
||||
cheat_process_metadata.aslr_extents = {
|
||||
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
|
||||
.size = page_table.GetAliasCodeRegionSize(),
|
||||
};
|
||||
cheat_process_metadata.alias_extents = {
|
||||
.base = GetInteger(page_table.GetAliasRegionStart()),
|
||||
.size = page_table.GetAliasRegionSize(),
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
cheat_process_metadata.main_nso_extents = {
|
||||
.base = main_region_begin,
|
||||
.size = main_region_size,
|
||||
};
|
||||
cheat_process_metadata.main_nso_build_id = build_id;
|
||||
}
|
||||
|
||||
cheat_process_debug_handle = cheat_process_metadata.process_id;
|
||||
|
||||
broken_unsafe = false;
|
||||
unsafe_break_event->Signal();
|
||||
|
||||
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, update_event);
|
||||
LOG_INFO(CheatEngine, "Cheat engine started");
|
||||
|
||||
// Signal to our fans.
|
||||
cheat_process_event->Signal();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatProcessMetadata(CheatProcessMetadata& out_metadata) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
out_metadata = cheat_process_metadata;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ForceOpenCheatProcess() {
|
||||
// R_RETURN(AttachToApplicationProcess(false));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::PauseCheatProcess() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(PauseCheatProcessUnsafe());
|
||||
}
|
||||
|
||||
Result CheatProcessManager::PauseCheatProcessUnsafe() {
|
||||
broken_unsafe = true;
|
||||
unsafe_break_event->Clear();
|
||||
if (system.ApplicationProcess()->IsSuspended()) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
R_RETURN(system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused));
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ResumeCheatProcess() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(ResumeCheatProcessUnsafe());
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ResumeCheatProcessUnsafe() {
|
||||
broken_unsafe = true;
|
||||
unsafe_break_event->Clear();
|
||||
if (!system.ApplicationProcess()->IsSuspended()) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ForceCloseCheatProcess() {
|
||||
CloseActiveCheatProcess();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatProcessMappingCount(u64& out_count) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(this->EnsureCheatProcess());
|
||||
|
||||
// TODO: Call svc::QueryDebugProcessMemory
|
||||
|
||||
out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatProcessMappings(
|
||||
u64& out_count, u64 offset, std::span<Kernel::Svc::MemoryInfo> out_mappings) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(this->EnsureCheatProcess());
|
||||
|
||||
// TODO: Call svc::QueryDebugProcessMemory
|
||||
|
||||
out_count = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ReadCheatProcessMemory(u64 process_address, u64 size,
|
||||
std::span<u8> out_data) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(ReadCheatProcessMemoryUnsafe(process_address, &out_data, size));
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data,
|
||||
size_t size) {
|
||||
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) {
|
||||
std::memset(out_data, 0, size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
system.ApplicationMemory().ReadBlock(process_address, out_data, size);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::WriteCheatProcessMemory(u64 process_address, u64 size,
|
||||
std::span<const u8> data) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_RETURN(WriteCheatProcessMemoryUnsafe(process_address, &data, size));
|
||||
}
|
||||
|
||||
Result CheatProcessManager::WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data,
|
||||
size_t size) {
|
||||
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
if (system.ApplicationMemory().WriteBlock(process_address, data, size)) {
|
||||
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), process_address, size);
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping,
|
||||
u64 address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(this->EnsureCheatProcess());
|
||||
|
||||
// TODO: Call svc::QueryDebugProcessMemory
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatCount(u64& out_count) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
out_count = std::count_if(cheat_entries.begin(), cheat_entries.end(),
|
||||
[](const auto& entry) { return entry.definition.num_opcodes != 0; });
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheats(u64& out_count, u64 offset,
|
||||
std::span<CheatEntry> out_cheats) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
size_t count = 0, total_count = 0;
|
||||
for (size_t i = 0; i < MaxCheatCount && count < out_cheats.size(); i++) {
|
||||
if (cheat_entries[i].definition.num_opcodes) {
|
||||
total_count++;
|
||||
if (total_count > offset) {
|
||||
out_cheats[count++] = cheat_entries[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out_count = count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetCheatById(CheatEntry* out_cheat, u32 cheat_id) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const CheatEntry* entry = GetCheatEntryById(cheat_id);
|
||||
R_UNLESS(entry != nullptr, ResultCheatUnknownId);
|
||||
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId);
|
||||
|
||||
*out_cheat = *entry;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ToggleCheat(u32 cheat_id) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
CheatEntry* entry = GetCheatEntryById(cheat_id);
|
||||
R_UNLESS(entry != nullptr, ResultCheatUnknownId);
|
||||
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId);
|
||||
|
||||
R_UNLESS(cheat_id != 0, ResultCheatCannotDisable);
|
||||
|
||||
entry->enabled = !entry->enabled;
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::AddCheat(u32& out_cheat_id, bool enabled,
|
||||
const CheatDefinition& cheat_definition) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid);
|
||||
R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid);
|
||||
|
||||
CheatEntry* new_entry = GetFreeCheatEntry();
|
||||
R_UNLESS(new_entry != nullptr, ResultCheatOutOfResource);
|
||||
|
||||
new_entry->enabled = enabled;
|
||||
new_entry->definition = cheat_definition;
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
|
||||
out_cheat_id = new_entry->cheat_id;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::RemoveCheat(u32 cheat_id) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_UNLESS(cheat_id < MaxCheatCount, ResultCheatUnknownId);
|
||||
|
||||
ResetCheatEntry(cheat_id);
|
||||
SetNeedsReloadVm(true);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ReadStaticRegister(u64& out_value, u64 register_index) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid);
|
||||
|
||||
out_value = cheat_vm->GetStaticRegister(register_index);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::WriteStaticRegister(u64 register_index, u64 value) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid);
|
||||
|
||||
cheat_vm->SetStaticRegister(register_index, value);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::ResetStaticRegisters() {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
cheat_vm->ResetStaticRegisters();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::SetMasterCheat(const CheatDefinition& cheat_definition) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid);
|
||||
R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid);
|
||||
|
||||
cheat_entries[0] = {
|
||||
.enabled = true,
|
||||
.definition = cheat_definition,
|
||||
};
|
||||
|
||||
SetNeedsReloadVm(true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetFrozenAddressCount(u64& out_count) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
out_count = std::distance(frozen_addresses_map.begin(), frozen_addresses_map.end());
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetFrozenAddresses(u64& out_count, u64 offset,
|
||||
std::span<FrozenAddressEntry> out_frozen_address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
u64 total_count = 0, written_count = 0;
|
||||
for (const auto& [address, value] : frozen_addresses_map) {
|
||||
if (written_count >= out_frozen_address.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset <= total_count) {
|
||||
out_frozen_address[written_count].address = address;
|
||||
out_frozen_address[written_count].value = value;
|
||||
written_count++;
|
||||
}
|
||||
total_count++;
|
||||
}
|
||||
|
||||
out_count = written_count;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry,
|
||||
u64 address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const auto it = frozen_addresses_map.find(address);
|
||||
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound);
|
||||
|
||||
out_frozen_address_entry = {
|
||||
.address = it->first,
|
||||
.value = it->second,
|
||||
};
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::EnableFrozenAddress(u64& out_value, u64 address, u64 width) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const auto it = frozen_addresses_map.find(address);
|
||||
R_UNLESS(it == frozen_addresses_map.end(), ResultFrozenAddressAlreadyExists);
|
||||
|
||||
FrozenAddressValue value{};
|
||||
value.width = static_cast<u8>(width);
|
||||
R_TRY(ReadCheatProcessMemoryUnsafe(address, &value.value, width));
|
||||
|
||||
frozen_addresses_map.insert({address, value});
|
||||
out_value = value.value;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result CheatProcessManager::DisableFrozenAddress(u64 address) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
R_TRY(EnsureCheatProcess());
|
||||
|
||||
const auto it = frozen_addresses_map.find(address);
|
||||
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound);
|
||||
|
||||
frozen_addresses_map.erase(it);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
u64 CheatProcessManager::HidKeysDown() const {
|
||||
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid");
|
||||
if (hid == nullptr) {
|
||||
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto applet_resource = hid->GetResourceManager();
|
||||
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) {
|
||||
LOG_WARNING(CheatEngine,
|
||||
"Attempted to read input state, but applet resource is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState();
|
||||
return static_cast<u64>(press_state & Core::HID::NpadButton::All);
|
||||
}
|
||||
|
||||
void CheatProcessManager::DebugLog(u8 id, u64 value) const {
|
||||
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
|
||||
}
|
||||
|
||||
void CheatProcessManager::CommandLog(std::string_view data) const {
|
||||
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
|
||||
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
|
||||
}
|
||||
|
||||
void CheatProcessManager::FrameCallback(std::chrono::nanoseconds ns_late) {
|
||||
std::scoped_lock lk(cheat_lock);
|
||||
|
||||
if (cheat_vm == nullptr) {
|
||||
LOG_DEBUG(CheatEngine, "FrameCallback: VM is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (needs_reload_vm) {
|
||||
LOG_INFO(CheatEngine, "Reloading cheat VM with {} entries", cheat_entries.size());
|
||||
|
||||
size_t enabled_count = 0;
|
||||
for (const auto& entry : cheat_entries) {
|
||||
if (entry.enabled && entry.definition.num_opcodes > 0) {
|
||||
enabled_count++;
|
||||
LOG_INFO(CheatEngine, " Cheat '{}': {} opcodes, enabled={}",
|
||||
entry.definition.readable_name.data(),
|
||||
entry.definition.num_opcodes, entry.enabled);
|
||||
}
|
||||
}
|
||||
LOG_INFO(CheatEngine, "Total enabled cheats: {}", enabled_count);
|
||||
|
||||
cheat_vm->LoadProgram(cheat_entries);
|
||||
LOG_INFO(CheatEngine, "Cheat VM loaded, program size: {}", cheat_vm->GetProgramSize());
|
||||
needs_reload_vm = false;
|
||||
}
|
||||
|
||||
if (cheat_vm->GetProgramSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
cheat_vm->Execute(cheat_process_metadata);
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
126
src/core/hle/service/dmnt/cheat_process_manager.h
Normal file
126
src/core/hle/service/dmnt/cheat_process_manager.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <span>
|
||||
|
||||
#include "core/hle/service/cmif_types.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
#include "common/intrusive_red_black_tree.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Kernel::Svc {
|
||||
struct MemoryInfo;
|
||||
}
|
||||
|
||||
namespace Service::DMNT {
|
||||
class CheatVirtualMachine;
|
||||
|
||||
class CheatProcessManager final {
|
||||
public:
|
||||
static constexpr size_t MaxCheatCount = 0x80;
|
||||
static constexpr size_t MaxFrozenAddressCount = 0x80;
|
||||
|
||||
CheatProcessManager(Core::System& system_);
|
||||
~CheatProcessManager();
|
||||
|
||||
void SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm);
|
||||
|
||||
bool HasCheatProcess();
|
||||
Kernel::KReadableEvent& GetCheatProcessEvent() const;
|
||||
Result GetCheatProcessMetadata(CheatProcessMetadata& out_metadata);
|
||||
Result AttachToApplicationProcess(const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
|
||||
u64 main_region_size);
|
||||
Result ForceOpenCheatProcess();
|
||||
Result PauseCheatProcess();
|
||||
Result PauseCheatProcessUnsafe();
|
||||
Result ResumeCheatProcess();
|
||||
Result ResumeCheatProcessUnsafe();
|
||||
Result ForceCloseCheatProcess();
|
||||
|
||||
Result GetCheatProcessMappingCount(u64& out_count);
|
||||
Result GetCheatProcessMappings(u64& out_count, u64 offset,
|
||||
std::span<Kernel::Svc::MemoryInfo> out_mappings);
|
||||
Result ReadCheatProcessMemory(u64 process_address, u64 size, std::span<u8> out_data);
|
||||
Result ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data, size_t size);
|
||||
Result WriteCheatProcessMemory(u64 process_address, u64 size, std::span<const u8> data);
|
||||
Result WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data, size_t size);
|
||||
|
||||
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping, u64 address);
|
||||
Result GetCheatCount(u64& out_count);
|
||||
Result GetCheats(u64& out_count, u64 offset, std::span<CheatEntry> out_cheats);
|
||||
Result GetCheatById(CheatEntry* out_cheat, u32 cheat_id);
|
||||
Result ToggleCheat(u32 cheat_id);
|
||||
|
||||
Result AddCheat(u32& out_cheat_id, bool enabled, const CheatDefinition& cheat_definition);
|
||||
Result RemoveCheat(u32 cheat_id);
|
||||
Result ReadStaticRegister(u64& out_value, u64 register_index);
|
||||
Result WriteStaticRegister(u64 register_index, u64 value);
|
||||
Result ResetStaticRegisters();
|
||||
Result SetMasterCheat(const CheatDefinition& cheat_definition);
|
||||
Result GetFrozenAddressCount(u64& out_count);
|
||||
Result GetFrozenAddresses(u64& out_count, u64 offset,
|
||||
std::span<FrozenAddressEntry> out_frozen_address);
|
||||
Result GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry, u64 address);
|
||||
Result EnableFrozenAddress(u64& out_value, u64 address, u64 width);
|
||||
Result DisableFrozenAddress(u64 address);
|
||||
|
||||
u64 HidKeysDown() const;
|
||||
void DebugLog(u8 id, u64 value) const;
|
||||
void CommandLog(std::string_view data) const;
|
||||
|
||||
private:
|
||||
bool HasActiveCheatProcess();
|
||||
void CloseActiveCheatProcess();
|
||||
Result EnsureCheatProcess();
|
||||
void SetNeedsReloadVm(bool reload);
|
||||
void ResetCheatEntry(size_t i);
|
||||
void ResetAllCheatEntries();
|
||||
CheatEntry* GetCheatEntryById(size_t i);
|
||||
CheatEntry* GetCheatEntryByReadableName(const char* readable_name);
|
||||
CheatEntry* GetFreeCheatEntry();
|
||||
|
||||
void FrameCallback(std::chrono::nanoseconds ns_late);
|
||||
|
||||
static constexpr u64 InvalidHandle = 0;
|
||||
|
||||
mutable std::mutex cheat_lock;
|
||||
Kernel::KEvent* unsafe_break_event;
|
||||
|
||||
Kernel::KEvent* cheat_process_event;
|
||||
u64 cheat_process_debug_handle = InvalidHandle;
|
||||
CheatProcessMetadata cheat_process_metadata = {};
|
||||
|
||||
bool broken_unsafe = false;
|
||||
bool needs_reload_vm = false;
|
||||
std::unique_ptr<CheatVirtualMachine> cheat_vm;
|
||||
|
||||
bool enable_cheats_by_default = true;
|
||||
bool always_save_cheat_toggles = false;
|
||||
bool should_save_cheat_toggles = false;
|
||||
std::array<CheatEntry, MaxCheatCount> cheat_entries = {};
|
||||
// TODO: Replace with IntrusiveRedBlackTree
|
||||
std::map<u64, FrozenAddressValue> frozen_addresses_map = {};
|
||||
|
||||
Core::System& system;
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
std::shared_ptr<Core::Timing::EventType> update_event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
};
|
||||
} // namespace Service::DMNT
|
||||
1269
src/core/hle/service/dmnt/cheat_virtual_machine.cpp
Normal file
1269
src/core/hle/service/dmnt/cheat_virtual_machine.cpp
Normal file
File diff suppressed because it is too large
Load diff
323
src/core/hle/service/dmnt/cheat_virtual_machine.h
Normal file
323
src/core/hle/service/dmnt/cheat_virtual_machine.h
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <variant>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
class CheatProcessManager;
|
||||
|
||||
enum class CheatVmOpcodeType : u32 {
|
||||
StoreStatic = 0,
|
||||
BeginConditionalBlock = 1,
|
||||
EndConditionalBlock = 2,
|
||||
ControlLoop = 3,
|
||||
LoadRegisterStatic = 4,
|
||||
LoadRegisterMemory = 5,
|
||||
StoreStaticToAddress = 6,
|
||||
PerformArithmeticStatic = 7,
|
||||
BeginKeypressConditionalBlock = 8,
|
||||
|
||||
// These are not implemented by Gateway's VM.
|
||||
PerformArithmeticRegister = 9,
|
||||
StoreRegisterToAddress = 10,
|
||||
Reserved11 = 11,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
ExtendedWidth = 12,
|
||||
|
||||
// Extended width opcodes.
|
||||
BeginRegisterConditionalBlock = 0xC0,
|
||||
SaveRestoreRegister = 0xC1,
|
||||
SaveRestoreRegisterMask = 0xC2,
|
||||
ReadWriteStaticRegister = 0xC3,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
DoubleExtendedWidth = 0xF0,
|
||||
|
||||
// Double-extended width opcodes.
|
||||
PauseProcess = 0xFF0,
|
||||
ResumeProcess = 0xFF1,
|
||||
DebugLog = 0xFFF,
|
||||
};
|
||||
|
||||
enum class MemoryAccessType : u32 {
|
||||
MainNso = 0,
|
||||
Heap = 1,
|
||||
Alias = 2,
|
||||
Aslr = 3,
|
||||
};
|
||||
|
||||
enum class ConditionalComparisonType : u32 {
|
||||
GT = 1,
|
||||
GE = 2,
|
||||
LT = 3,
|
||||
LE = 4,
|
||||
EQ = 5,
|
||||
NE = 6,
|
||||
};
|
||||
|
||||
enum class RegisterArithmeticType : u32 {
|
||||
Addition = 0,
|
||||
Subtraction = 1,
|
||||
Multiplication = 2,
|
||||
LeftShift = 3,
|
||||
RightShift = 4,
|
||||
|
||||
// These are not supported by Gateway's VM.
|
||||
LogicalAnd = 5,
|
||||
LogicalOr = 6,
|
||||
LogicalNot = 7,
|
||||
LogicalXor = 8,
|
||||
|
||||
None = 9,
|
||||
};
|
||||
|
||||
enum class StoreRegisterOffsetType : u32 {
|
||||
None = 0,
|
||||
Reg = 1,
|
||||
Imm = 2,
|
||||
MemReg = 3,
|
||||
MemImm = 4,
|
||||
MemImmReg = 5,
|
||||
};
|
||||
|
||||
enum class CompareRegisterValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
StaticValue = 4,
|
||||
OtherRegister = 5,
|
||||
};
|
||||
|
||||
enum class SaveRestoreRegisterOpType : u32 {
|
||||
Restore = 0,
|
||||
Save = 1,
|
||||
ClearSaved = 2,
|
||||
ClearRegs = 3,
|
||||
};
|
||||
|
||||
enum class DebugLogValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
RegisterValue = 4,
|
||||
};
|
||||
|
||||
union VmInt {
|
||||
u8 bit8;
|
||||
u16 bit16;
|
||||
u32 bit32;
|
||||
u64 bit64;
|
||||
};
|
||||
|
||||
struct StoreStaticOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 offset_register{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct BeginConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct EndConditionalOpcode {
|
||||
bool is_else;
|
||||
};
|
||||
|
||||
struct ControlLoopOpcode {
|
||||
bool start_loop{};
|
||||
u32 reg_index{};
|
||||
u32 num_iters{};
|
||||
};
|
||||
|
||||
struct LoadRegisterStaticOpcode {
|
||||
u32 reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct LoadRegisterMemoryOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 reg_index{};
|
||||
bool load_from_reg{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct StoreStaticToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
bool increment_reg{};
|
||||
bool add_offset_reg{};
|
||||
u32 offset_reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticStaticOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 value{};
|
||||
};
|
||||
|
||||
struct BeginKeypressConditionalOpcode {
|
||||
u32 key_mask{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticRegisterOpcode {
|
||||
u32 bit_width{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 dst_reg_index{};
|
||||
u32 src_reg_1_index{};
|
||||
u32 src_reg_2_index{};
|
||||
bool has_immediate{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct StoreRegisterToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 str_reg_index{};
|
||||
u32 addr_reg_index{};
|
||||
bool increment_reg{};
|
||||
StoreRegisterOffsetType ofs_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct BeginRegisterConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u32 val_reg_index{};
|
||||
CompareRegisterValueType comp_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 other_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterOpcode {
|
||||
u32 dst_index{};
|
||||
u32 src_index{};
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterMaskOpcode {
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
std::array<bool, 0x10> should_operate{};
|
||||
};
|
||||
|
||||
struct ReadWriteStaticRegisterOpcode {
|
||||
u32 static_idx{};
|
||||
u32 idx{};
|
||||
};
|
||||
|
||||
struct PauseProcessOpcode {};
|
||||
|
||||
struct ResumeProcessOpcode {};
|
||||
|
||||
struct DebugLogOpcode {
|
||||
u32 bit_width{};
|
||||
u32 log_id{};
|
||||
DebugLogValueType val_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 val_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct UnrecognizedInstruction {
|
||||
CheatVmOpcodeType opcode{};
|
||||
};
|
||||
|
||||
struct CheatVmOpcode {
|
||||
bool begin_conditional_block{};
|
||||
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
|
||||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
|
||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
|
||||
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
|
||||
opcode{};
|
||||
};
|
||||
|
||||
class CheatVirtualMachine {
|
||||
public:
|
||||
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
|
||||
static constexpr std::size_t NumRegisters = 0x10;
|
||||
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumWritableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumStaticRegisters =
|
||||
NumReadableStaticRegisters + NumWritableStaticRegisters;
|
||||
|
||||
explicit CheatVirtualMachine(CheatProcessManager& cheat_manager);
|
||||
~CheatVirtualMachine();
|
||||
|
||||
std::size_t GetProgramSize() const {
|
||||
return this->num_opcodes;
|
||||
}
|
||||
|
||||
bool LoadProgram(std::span<const CheatEntry> cheats);
|
||||
void Execute(const CheatProcessMetadata& metadata);
|
||||
|
||||
u64 GetStaticRegister(std::size_t register_index) const {
|
||||
return static_registers[register_index];
|
||||
}
|
||||
|
||||
void SetStaticRegister(std::size_t register_index, u64 value) {
|
||||
static_registers[register_index] = value;
|
||||
}
|
||||
|
||||
void ResetStaticRegisters() {
|
||||
static_registers = {};
|
||||
}
|
||||
|
||||
private:
|
||||
bool DecodeNextOpcode(CheatVmOpcode& out);
|
||||
void SkipConditionalBlock(bool is_if);
|
||||
void ResetState();
|
||||
|
||||
// For implementing the DebugLog opcode.
|
||||
void DebugLog(u32 log_id, u64 value) const;
|
||||
|
||||
void LogOpcode(const CheatVmOpcode& opcode) const;
|
||||
|
||||
static u64 GetVmInt(VmInt value, u32 bit_width);
|
||||
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
|
||||
MemoryAccessType mem_type, u64 rel_address);
|
||||
|
||||
CheatProcessManager& manager;
|
||||
|
||||
std::size_t num_opcodes = 0;
|
||||
std::size_t instruction_ptr = 0;
|
||||
std::size_t condition_depth = 0;
|
||||
bool decode_success = false;
|
||||
std::array<u32, MaximumProgramOpcodeCount> program{};
|
||||
std::array<u64, NumRegisters> registers{};
|
||||
std::array<u64, NumRegisters> saved_values{};
|
||||
std::array<u64, NumStaticRegisters> static_registers{};
|
||||
std::array<std::size_t, NumRegisters> loop_tops{};
|
||||
};
|
||||
}// namespace Service::DMNT
|
||||
26
src/core/hle/service/dmnt/dmnt.cpp
Normal file
26
src/core/hle/service/dmnt/dmnt.cpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/dmnt/cheat_interface.h"
|
||||
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
|
||||
#include "core/hle/service/dmnt/dmnt.h"
|
||||
#include "core/hle/service/server_manager.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
auto& cheat_manager = system.GetCheatManager();
|
||||
auto cheat_vm = std::make_unique<CheatVirtualMachine>(cheat_manager);
|
||||
cheat_manager.SetVirtualMachine(std::move(cheat_vm));
|
||||
|
||||
server_manager->RegisterNamedService("dmnt:cht",
|
||||
std::make_shared<ICheatInterface>(system, cheat_manager));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
} // namespace Service::DMNT
|
||||
15
src/core/hle/service/dmnt/dmnt.h
Normal file
15
src/core/hle/service/dmnt/dmnt.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
};
|
||||
|
||||
namespace Service::DMNT {
|
||||
void LoopProcess(Core::System& system);
|
||||
} // namespace Service::DMNT
|
||||
26
src/core/hle/service/dmnt/dmnt_results.h
Normal file
26
src/core/hle/service/dmnt/dmnt_results.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
constexpr Result ResultDebuggingDisabled(ErrorModule::DMNT, 2);
|
||||
|
||||
constexpr Result ResultCheatNotAttached(ErrorModule::DMNT, 6500);
|
||||
constexpr Result ResultCheatNullBuffer(ErrorModule::DMNT, 6501);
|
||||
constexpr Result ResultCheatInvalidBuffer(ErrorModule::DMNT, 6502);
|
||||
constexpr Result ResultCheatUnknownId(ErrorModule::DMNT, 6503);
|
||||
constexpr Result ResultCheatOutOfResource(ErrorModule::DMNT, 6504);
|
||||
constexpr Result ResultCheatInvalid(ErrorModule::DMNT, 6505);
|
||||
constexpr Result ResultCheatCannotDisable(ErrorModule::DMNT, 6506);
|
||||
constexpr Result ResultFrozenAddressInvalidWidth(ErrorModule::DMNT, 6600);
|
||||
constexpr Result ResultFrozenAddressAlreadyExists(ErrorModule::DMNT, 6601);
|
||||
constexpr Result ResultFrozenAddressNotFound(ErrorModule::DMNT, 6602);
|
||||
constexpr Result ResultFrozenAddressOutOfResource(ErrorModule::DMNT, 6603);
|
||||
constexpr Result ResultVirtualMachineInvalidConditionDepth(ErrorModule::DMNT, 6700);
|
||||
} // namespace Service::DMNT
|
||||
55
src/core/hle/service/dmnt/dmnt_types.h
Normal file
55
src/core/hle/service/dmnt/dmnt_types.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::DMNT {
|
||||
struct MemoryRegionExtents {
|
||||
u64 base{};
|
||||
u64 size{};
|
||||
};
|
||||
static_assert(sizeof(MemoryRegionExtents) == 0x10, "MemoryRegionExtents is an invalid size");
|
||||
|
||||
struct CheatProcessMetadata {
|
||||
u64 process_id{};
|
||||
u64 program_id{};
|
||||
MemoryRegionExtents main_nso_extents{};
|
||||
MemoryRegionExtents heap_extents{};
|
||||
MemoryRegionExtents alias_extents{};
|
||||
MemoryRegionExtents aslr_extents{};
|
||||
std::array<u8, 0x20> main_nso_build_id{};
|
||||
};
|
||||
static_assert(sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata is an invalid size");
|
||||
|
||||
struct CheatDefinition {
|
||||
std::array<char, 0x40> readable_name;
|
||||
u32 num_opcodes;
|
||||
std::array<u32, 0x100> opcodes;
|
||||
};
|
||||
static_assert(sizeof(CheatDefinition) == 0x444, "CheatDefinition is an invalid size");
|
||||
|
||||
struct CheatEntry {
|
||||
bool enabled;
|
||||
u32 cheat_id;
|
||||
CheatDefinition definition;
|
||||
};
|
||||
static_assert(sizeof(CheatEntry) == 0x44C, "CheatEntry is an invalid size");
|
||||
static_assert(std::is_trivial_v<CheatEntry>, "CheatEntry type must be trivially copyable.");
|
||||
|
||||
struct FrozenAddressValue {
|
||||
u64 value;
|
||||
u8 width;
|
||||
};
|
||||
static_assert(sizeof(FrozenAddressValue) == 0x10, "FrozenAddressValue is an invalid size");
|
||||
|
||||
struct FrozenAddressEntry {
|
||||
u64 address;
|
||||
FrozenAddressValue value;
|
||||
};
|
||||
static_assert(sizeof(FrozenAddressEntry) == 0x18, "FrozenAddressEntry is an invalid size");
|
||||
} // namespace Service::DMNT
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
#include "core/hle/service/btdrv/btdrv.h"
|
||||
#include "core/hle/service/btm/btm.h"
|
||||
#include "core/hle/service/caps/caps.h"
|
||||
#include "core/hle/service/dmnt/dmnt.h"
|
||||
#include "core/hle/service/erpt/erpt.h"
|
||||
#include "core/hle/service/es/es.h"
|
||||
#include "core/hle/service/eupld/eupld.h"
|
||||
|
|
@ -107,6 +108,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
|
|||
{"btdrv", &BtDrv::LoopProcess},
|
||||
{"btm", &BTM::LoopProcess},
|
||||
{"capsrv", &Capture::LoopProcess},
|
||||
{"dmnt", &DMNT::LoopProcess},
|
||||
{"erpt", &ERPT::LoopProcess},
|
||||
{"es", &ES::LoopProcess},
|
||||
{"eupld", &EUPLD::LoopProcess},
|
||||
|
|
|
|||
|
|
@ -1,288 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <locale>
|
||||
#include "common/hex_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/arm/debug.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_page_table.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_process_page_table.h"
|
||||
#include "core/hle/kernel/svc_types.h"
|
||||
#include "core/hle/service/hid/hid_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory/cheat_engine.h"
|
||||
#include "hid_core/resource_manager.h"
|
||||
#include "hid_core/resources/npad/npad.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
namespace {
|
||||
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
|
||||
|
||||
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
|
||||
std::size_t start_index, char match) {
|
||||
auto end_index = start_index;
|
||||
while (data[end_index] != match) {
|
||||
++end_index;
|
||||
if (end_index > data.size()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out_name_size = end_index - start_index;
|
||||
|
||||
// Clamp name if it's too big
|
||||
if (out_name_size > sizeof(CheatDefinition::readable_name)) {
|
||||
end_index = start_index + sizeof(CheatDefinition::readable_name);
|
||||
}
|
||||
|
||||
return data.substr(start_index, end_index - start_index);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_)
|
||||
: metadata{metadata_}, system{system_} {}
|
||||
|
||||
StandardVmCallbacks::~StandardVmCallbacks() = default;
|
||||
|
||||
void StandardVmCallbacks::MemoryReadUnsafe(VAddr address, void* data, u64 size) {
|
||||
// Return zero on invalid address
|
||||
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
|
||||
std::memset(data, 0, size);
|
||||
return;
|
||||
}
|
||||
|
||||
system.ApplicationMemory().ReadBlock(address, data, size);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) {
|
||||
// Skip invalid memory write address
|
||||
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (system.ApplicationMemory().WriteBlock(address, data, size)) {
|
||||
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), address, size);
|
||||
}
|
||||
}
|
||||
|
||||
u64 StandardVmCallbacks::HidKeysDown() {
|
||||
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid");
|
||||
if (hid == nullptr) {
|
||||
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto applet_resource = hid->GetResourceManager();
|
||||
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) {
|
||||
LOG_WARNING(CheatEngine,
|
||||
"Attempted to read input state, but applet resource is not initialized!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState();
|
||||
return static_cast<u64>(press_state & HID::NpadButton::All);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::PauseProcess() {
|
||||
if (system.ApplicationProcess()->IsSuspended()) {
|
||||
return;
|
||||
}
|
||||
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::ResumeProcess() {
|
||||
if (!system.ApplicationProcess()->IsSuspended()) {
|
||||
return;
|
||||
}
|
||||
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
|
||||
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::CommandLog(std::string_view data) {
|
||||
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
|
||||
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
|
||||
}
|
||||
|
||||
bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
|
||||
if ((in < metadata.main_nso_extents.base ||
|
||||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
|
||||
(in < metadata.heap_extents.base ||
|
||||
in >= metadata.heap_extents.base + metadata.heap_extents.size) &&
|
||||
(in < metadata.alias_extents.base ||
|
||||
in >= metadata.alias_extents.base + metadata.alias_extents.size) &&
|
||||
(in < metadata.aslr_extents.base ||
|
||||
in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) {
|
||||
LOG_DEBUG(CheatEngine,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this "
|
||||
"persists, "
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if "
|
||||
"the game has not properly set up yet.",
|
||||
in);
|
||||
return false; ///< Invalid addresses will hard crash
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
TextCheatParser::~TextCheatParser() = default;
|
||||
|
||||
std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
|
||||
std::vector<CheatEntry> out(1);
|
||||
std::optional<u64> current_entry;
|
||||
|
||||
for (std::size_t i = 0; i < data.size(); ++i) {
|
||||
if (::isspace(data[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data[i] == '{') {
|
||||
current_entry = 0;
|
||||
|
||||
if (out[*current_entry].definition.num_opcodes > 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, '}');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (data[i] == '[') {
|
||||
current_entry = out.size();
|
||||
out.emplace_back();
|
||||
|
||||
std::size_t name_size{};
|
||||
const auto name = ExtractName(name_size, data, i + 1, ']');
|
||||
if (name.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
|
||||
name.size()));
|
||||
out[*current_entry]
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
|
||||
'\0';
|
||||
|
||||
i += name_size + 1;
|
||||
} else if (::isxdigit(data[i])) {
|
||||
if (!current_entry || out[*current_entry].definition.num_opcodes >=
|
||||
out[*current_entry].definition.opcodes.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto hex = std::string(data.substr(i, 8));
|
||||
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
|
||||
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
|
||||
value;
|
||||
|
||||
i += 8;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
out[0].enabled = out[0].definition.num_opcodes > 0;
|
||||
out[0].cheat_id = 0;
|
||||
|
||||
for (u32 i = 1; i < out.size(); ++i) {
|
||||
out[i].enabled = out[i].definition.num_opcodes > 0;
|
||||
out[i].cheat_id = i;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
|
||||
const std::array<u8, 0x20>& build_id_)
|
||||
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)},
|
||||
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} {
|
||||
metadata.main_nso_build_id = build_id_;
|
||||
}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
if (event)
|
||||
core_timing.UnscheduleEvent(event);
|
||||
else
|
||||
LOG_ERROR(CheatEngine, "~CheatEngine before event was registered");
|
||||
}
|
||||
|
||||
void CheatEngine::Initialize() {
|
||||
event = Core::Timing::CreateEvent(
|
||||
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
|
||||
[this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
FrameCallback(ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
|
||||
|
||||
metadata.process_id = system.ApplicationProcess()->GetProcessId();
|
||||
metadata.title_id = system.GetApplicationProcessProgramID();
|
||||
|
||||
const auto& page_table = system.ApplicationProcess()->GetPageTable();
|
||||
metadata.heap_extents = {
|
||||
.base = GetInteger(page_table.GetHeapRegionStart()),
|
||||
.size = page_table.GetHeapRegionSize(),
|
||||
};
|
||||
metadata.aslr_extents = {
|
||||
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
|
||||
.size = page_table.GetAliasCodeRegionSize(),
|
||||
};
|
||||
metadata.alias_extents = {
|
||||
.base = GetInteger(page_table.GetAliasRegionStart()),
|
||||
.size = page_table.GetAliasRegionSize(),
|
||||
};
|
||||
|
||||
is_pending_reload.exchange(true);
|
||||
}
|
||||
|
||||
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
|
||||
metadata.main_nso_extents = {
|
||||
.base = main_region_begin,
|
||||
.size = main_region_size,
|
||||
};
|
||||
}
|
||||
|
||||
void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
|
||||
cheats = std::move(reload_cheats);
|
||||
is_pending_reload.exchange(true);
|
||||
}
|
||||
|
||||
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
|
||||
if (is_pending_reload.exchange(false)) {
|
||||
vm.LoadProgram(cheats);
|
||||
}
|
||||
|
||||
if (vm.GetProgramSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm.Execute(metadata);
|
||||
}
|
||||
|
||||
} // namespace Core::Memory
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
#include "core/memory/dmnt_cheat_vm.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace Core::Memory {
|
||||
|
||||
class StandardVmCallbacks : public DmntCheatVm::Callbacks {
|
||||
public:
|
||||
StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_);
|
||||
~StandardVmCallbacks() override;
|
||||
|
||||
void MemoryReadUnsafe(VAddr address, void* data, u64 size) override;
|
||||
void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) override;
|
||||
u64 HidKeysDown() override;
|
||||
void PauseProcess() override;
|
||||
void ResumeProcess() override;
|
||||
void DebugLog(u8 id, u64 value) override;
|
||||
void CommandLog(std::string_view data) override;
|
||||
|
||||
private:
|
||||
bool IsAddressInRange(VAddr address) const;
|
||||
|
||||
const CheatProcessMetadata& metadata;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
|
||||
// CheatList object, that can be used for execution.
|
||||
class CheatParser {
|
||||
public:
|
||||
virtual ~CheatParser();
|
||||
|
||||
[[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0;
|
||||
};
|
||||
|
||||
// CheatParser implementation that parses text files
|
||||
class TextCheatParser final : public CheatParser {
|
||||
public:
|
||||
~TextCheatParser() override;
|
||||
|
||||
[[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override;
|
||||
};
|
||||
|
||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
|
||||
class CheatEngine final {
|
||||
public:
|
||||
CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
|
||||
const std::array<u8, 0x20>& build_id_);
|
||||
~CheatEngine();
|
||||
|
||||
void Initialize();
|
||||
void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
|
||||
|
||||
void Reload(std::vector<CheatEntry> reload_cheats);
|
||||
|
||||
private:
|
||||
void FrameCallback(std::chrono::nanoseconds ns_late);
|
||||
|
||||
DmntCheatVm vm;
|
||||
CheatProcessMetadata metadata;
|
||||
|
||||
std::vector<CheatEntry> cheats;
|
||||
std::atomic_bool is_pending_reload{false};
|
||||
|
||||
std::shared_ptr<Core::Timing::EventType> event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace Core::Memory
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
|
||||
struct MemoryRegionExtents {
|
||||
u64 base{};
|
||||
u64 size{};
|
||||
};
|
||||
|
||||
struct CheatProcessMetadata {
|
||||
u64 process_id{};
|
||||
u64 title_id{};
|
||||
MemoryRegionExtents main_nso_extents{};
|
||||
MemoryRegionExtents heap_extents{};
|
||||
MemoryRegionExtents alias_extents{};
|
||||
MemoryRegionExtents aslr_extents{};
|
||||
std::array<u8, 0x20> main_nso_build_id{};
|
||||
};
|
||||
|
||||
struct CheatDefinition {
|
||||
std::array<char, 0x40> readable_name{};
|
||||
u32 num_opcodes{};
|
||||
std::array<u32, 0x100> opcodes{};
|
||||
};
|
||||
|
||||
struct CheatEntry {
|
||||
bool enabled{};
|
||||
u32 cheat_id{};
|
||||
CheatDefinition definition{};
|
||||
};
|
||||
|
||||
} // namespace Core::Memory
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,330 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <fmt/printf.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory/dmnt_cheat_types.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
|
||||
enum class CheatVmOpcodeType : u32 {
|
||||
StoreStatic = 0,
|
||||
BeginConditionalBlock = 1,
|
||||
EndConditionalBlock = 2,
|
||||
ControlLoop = 3,
|
||||
LoadRegisterStatic = 4,
|
||||
LoadRegisterMemory = 5,
|
||||
StoreStaticToAddress = 6,
|
||||
PerformArithmeticStatic = 7,
|
||||
BeginKeypressConditionalBlock = 8,
|
||||
|
||||
// These are not implemented by Gateway's VM.
|
||||
PerformArithmeticRegister = 9,
|
||||
StoreRegisterToAddress = 10,
|
||||
Reserved11 = 11,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
ExtendedWidth = 12,
|
||||
|
||||
// Extended width opcodes.
|
||||
BeginRegisterConditionalBlock = 0xC0,
|
||||
SaveRestoreRegister = 0xC1,
|
||||
SaveRestoreRegisterMask = 0xC2,
|
||||
ReadWriteStaticRegister = 0xC3,
|
||||
|
||||
// This is a meta entry, and not a real opcode.
|
||||
// This is to facilitate multi-nybble instruction decoding.
|
||||
DoubleExtendedWidth = 0xF0,
|
||||
|
||||
// Double-extended width opcodes.
|
||||
PauseProcess = 0xFF0,
|
||||
ResumeProcess = 0xFF1,
|
||||
DebugLog = 0xFFF,
|
||||
};
|
||||
|
||||
enum class MemoryAccessType : u32 {
|
||||
MainNso = 0,
|
||||
Heap = 1,
|
||||
Alias = 2,
|
||||
Aslr = 3,
|
||||
};
|
||||
|
||||
enum class ConditionalComparisonType : u32 {
|
||||
GT = 1,
|
||||
GE = 2,
|
||||
LT = 3,
|
||||
LE = 4,
|
||||
EQ = 5,
|
||||
NE = 6,
|
||||
};
|
||||
|
||||
enum class RegisterArithmeticType : u32 {
|
||||
Addition = 0,
|
||||
Subtraction = 1,
|
||||
Multiplication = 2,
|
||||
LeftShift = 3,
|
||||
RightShift = 4,
|
||||
|
||||
// These are not supported by Gateway's VM.
|
||||
LogicalAnd = 5,
|
||||
LogicalOr = 6,
|
||||
LogicalNot = 7,
|
||||
LogicalXor = 8,
|
||||
|
||||
None = 9,
|
||||
};
|
||||
|
||||
enum class StoreRegisterOffsetType : u32 {
|
||||
None = 0,
|
||||
Reg = 1,
|
||||
Imm = 2,
|
||||
MemReg = 3,
|
||||
MemImm = 4,
|
||||
MemImmReg = 5,
|
||||
};
|
||||
|
||||
enum class CompareRegisterValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
StaticValue = 4,
|
||||
OtherRegister = 5,
|
||||
};
|
||||
|
||||
enum class SaveRestoreRegisterOpType : u32 {
|
||||
Restore = 0,
|
||||
Save = 1,
|
||||
ClearSaved = 2,
|
||||
ClearRegs = 3,
|
||||
};
|
||||
|
||||
enum class DebugLogValueType : u32 {
|
||||
MemoryRelAddr = 0,
|
||||
MemoryOfsReg = 1,
|
||||
RegisterRelAddr = 2,
|
||||
RegisterOfsReg = 3,
|
||||
RegisterValue = 4,
|
||||
};
|
||||
|
||||
union VmInt {
|
||||
u8 bit8;
|
||||
u16 bit16;
|
||||
u32 bit32;
|
||||
u64 bit64;
|
||||
};
|
||||
|
||||
struct StoreStaticOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 offset_register{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct BeginConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct EndConditionalOpcode {
|
||||
bool is_else;
|
||||
};
|
||||
|
||||
struct ControlLoopOpcode {
|
||||
bool start_loop{};
|
||||
u32 reg_index{};
|
||||
u32 num_iters{};
|
||||
};
|
||||
|
||||
struct LoadRegisterStaticOpcode {
|
||||
u32 reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct LoadRegisterMemoryOpcode {
|
||||
u32 bit_width{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 reg_index{};
|
||||
bool load_from_reg{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct StoreStaticToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
bool increment_reg{};
|
||||
bool add_offset_reg{};
|
||||
u32 offset_reg_index{};
|
||||
u64 value{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticStaticOpcode {
|
||||
u32 bit_width{};
|
||||
u32 reg_index{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 value{};
|
||||
};
|
||||
|
||||
struct BeginKeypressConditionalOpcode {
|
||||
u32 key_mask{};
|
||||
};
|
||||
|
||||
struct PerformArithmeticRegisterOpcode {
|
||||
u32 bit_width{};
|
||||
RegisterArithmeticType math_type{};
|
||||
u32 dst_reg_index{};
|
||||
u32 src_reg_1_index{};
|
||||
u32 src_reg_2_index{};
|
||||
bool has_immediate{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct StoreRegisterToAddressOpcode {
|
||||
u32 bit_width{};
|
||||
u32 str_reg_index{};
|
||||
u32 addr_reg_index{};
|
||||
bool increment_reg{};
|
||||
StoreRegisterOffsetType ofs_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct BeginRegisterConditionalOpcode {
|
||||
u32 bit_width{};
|
||||
ConditionalComparisonType cond_type{};
|
||||
u32 val_reg_index{};
|
||||
CompareRegisterValueType comp_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 other_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterOpcode {
|
||||
u32 dst_index{};
|
||||
u32 src_index{};
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterMaskOpcode {
|
||||
SaveRestoreRegisterOpType op_type{};
|
||||
std::array<bool, 0x10> should_operate{};
|
||||
};
|
||||
|
||||
struct ReadWriteStaticRegisterOpcode {
|
||||
u32 static_idx{};
|
||||
u32 idx{};
|
||||
};
|
||||
|
||||
struct PauseProcessOpcode {};
|
||||
|
||||
struct ResumeProcessOpcode {};
|
||||
|
||||
struct DebugLogOpcode {
|
||||
u32 bit_width{};
|
||||
u32 log_id{};
|
||||
DebugLogValueType val_type{};
|
||||
MemoryAccessType mem_type{};
|
||||
u32 addr_reg_index{};
|
||||
u32 val_reg_index{};
|
||||
u32 ofs_reg_index{};
|
||||
u64 rel_address{};
|
||||
};
|
||||
|
||||
struct UnrecognizedInstruction {
|
||||
CheatVmOpcodeType opcode{};
|
||||
};
|
||||
|
||||
struct CheatVmOpcode {
|
||||
bool begin_conditional_block{};
|
||||
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
|
||||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
|
||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
|
||||
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
|
||||
opcode{};
|
||||
};
|
||||
|
||||
class DmntCheatVm {
|
||||
public:
|
||||
/// Helper Type for DmntCheatVm <=> yuzu Interface
|
||||
class Callbacks {
|
||||
public:
|
||||
virtual ~Callbacks();
|
||||
|
||||
virtual void MemoryReadUnsafe(VAddr address, void* data, u64 size) = 0;
|
||||
virtual void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) = 0;
|
||||
|
||||
virtual u64 HidKeysDown() = 0;
|
||||
|
||||
virtual void PauseProcess() = 0;
|
||||
virtual void ResumeProcess() = 0;
|
||||
|
||||
virtual void DebugLog(u8 id, u64 value) = 0;
|
||||
virtual void CommandLog(std::string_view data) = 0;
|
||||
};
|
||||
|
||||
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
|
||||
static constexpr std::size_t NumRegisters = 0x10;
|
||||
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumWritableStaticRegisters = 0x80;
|
||||
static constexpr std::size_t NumStaticRegisters =
|
||||
NumReadableStaticRegisters + NumWritableStaticRegisters;
|
||||
|
||||
explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks_);
|
||||
~DmntCheatVm();
|
||||
|
||||
std::size_t GetProgramSize() const {
|
||||
return this->num_opcodes;
|
||||
}
|
||||
|
||||
bool LoadProgram(const std::vector<CheatEntry>& cheats);
|
||||
void Execute(const CheatProcessMetadata& metadata);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Callbacks> callbacks;
|
||||
|
||||
std::size_t num_opcodes = 0;
|
||||
std::size_t instruction_ptr = 0;
|
||||
std::size_t condition_depth = 0;
|
||||
bool decode_success = false;
|
||||
std::array<u32, MaximumProgramOpcodeCount> program{};
|
||||
std::array<u64, NumRegisters> registers{};
|
||||
std::array<u64, NumRegisters> saved_values{};
|
||||
std::array<u64, NumStaticRegisters> static_registers{};
|
||||
std::array<std::size_t, NumRegisters> loop_tops{};
|
||||
|
||||
bool DecodeNextOpcode(CheatVmOpcode& out);
|
||||
void SkipConditionalBlock(bool is_if);
|
||||
void ResetState();
|
||||
|
||||
// For implementing the DebugLog opcode.
|
||||
void DebugLog(u32 log_id, u64 value);
|
||||
|
||||
void LogOpcode(const CheatVmOpcode& opcode);
|
||||
|
||||
static u64 GetVmInt(VmInt value, u32 bit_width);
|
||||
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
|
||||
MemoryAccessType mem_type, u64 rel_address);
|
||||
};
|
||||
|
||||
}; // namespace Core::Memory
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
add_library(qt_common STATIC
|
||||
qt_common.h
|
||||
qt_common.cpp
|
||||
|
|
@ -26,7 +28,7 @@ add_library(qt_common STATIC
|
|||
util/mod.h util/mod.cpp
|
||||
|
||||
abstract/frontend.h abstract/frontend.cpp
|
||||
abstract/qt_progress_dialog.h abstract/qt_progress_dialog.cpp
|
||||
abstract/progress.h abstract/progress.cpp
|
||||
|
||||
qt_string_lookup.h
|
||||
qt_compat.h
|
||||
|
|
@ -93,3 +95,5 @@ if (UNIX AND NOT APPLE)
|
|||
target_link_libraries(qt_common PRIVATE Qt6::GuiPrivate)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(qt_common)
|
||||
|
|
|
|||
|
|
@ -1,75 +1,27 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QLineEdit>
|
||||
#include "frontend.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
#include <QFileDialog>
|
||||
#endif
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QInputDialog>
|
||||
|
||||
namespace QtCommon::Frontend {
|
||||
|
||||
StandardButton ShowMessage(Icon icon, const QString& title, const QString& text,
|
||||
StandardButtons buttons, QObject* parent) {
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
QMessageBox* box = new QMessageBox(icon, title, text, buttons, (QWidget*)parent);
|
||||
return static_cast<QMessageBox::StandardButton>(box->exec());
|
||||
#endif
|
||||
// TODO(crueter): If Qt Widgets is disabled...
|
||||
// need a way to reference icon/buttons too
|
||||
}
|
||||
|
||||
const QString GetOpenFileName(const QString& title, const QString& dir, const QString& filter,
|
||||
QString* selectedFilter, Options options) {
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
return QFileDialog::getOpenFileName(rootObject, title, dir, filter, selectedFilter, options);
|
||||
#endif
|
||||
QString* selectedFilter) {
|
||||
return QFileDialog::getOpenFileName(rootObject, title, dir, filter, selectedFilter);
|
||||
}
|
||||
|
||||
const QStringList GetOpenFileNames(const QString& title, const QString& dir, const QString& filter,
|
||||
QString* selectedFilter, Options options) {
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
return QFileDialog::getOpenFileNames(rootObject, title, dir, filter, selectedFilter, options);
|
||||
#endif
|
||||
QString* selectedFilter) {
|
||||
return QFileDialog::getOpenFileNames(rootObject, title, dir, filter, selectedFilter);
|
||||
}
|
||||
|
||||
const QString GetSaveFileName(const QString& title, const QString& dir, const QString& filter,
|
||||
QString* selectedFilter, Options options) {
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
return QFileDialog::getSaveFileName(rootObject, title, dir, filter, selectedFilter, options);
|
||||
#endif
|
||||
QString* selectedFilter) {
|
||||
return QFileDialog::getSaveFileName(rootObject, title, dir, filter, selectedFilter);
|
||||
}
|
||||
|
||||
const QString GetExistingDirectory(const QString& caption, const QString& dir, Options options) {
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
return QFileDialog::getExistingDirectory(rootObject, caption, dir, options);
|
||||
#endif
|
||||
}
|
||||
|
||||
int Choice(const QString& title, const QString& caption, const QStringList& options) {
|
||||
QMessageBox box(rootObject);
|
||||
box.setText(caption);
|
||||
box.setWindowTitle(title);
|
||||
|
||||
for (const QString& opt : options) {
|
||||
box.addButton(opt, QMessageBox::AcceptRole);
|
||||
}
|
||||
|
||||
box.addButton(QMessageBox::Cancel);
|
||||
|
||||
box.exec();
|
||||
auto button = box.clickedButton();
|
||||
return options.indexOf(button->text());
|
||||
}
|
||||
|
||||
const QString GetTextInput(const QString& title, const QString& caption,
|
||||
const QString& defaultText) {
|
||||
return QInputDialog::getText(rootObject, title, caption, QLineEdit::Normal, defaultText);
|
||||
const QString GetExistingDirectory(const QString& caption, const QString& dir) {
|
||||
return QFileDialog::getExistingDirectory(rootObject, caption, dir);
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
|
|
|
|||
|
|
@ -7,11 +7,7 @@
|
|||
#include <QGuiApplication>
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
#include <QFileDialog>
|
||||
#include <QWidget>
|
||||
#include <QMessageBox>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* manages common functionality e.g. message boxes and such for Qt/QML
|
||||
|
|
@ -20,60 +16,39 @@ namespace QtCommon::Frontend {
|
|||
|
||||
Q_NAMESPACE
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
using Options = QFileDialog::Options;
|
||||
using Option = QFileDialog::Option;
|
||||
|
||||
using StandardButton = QMessageBox::StandardButton;
|
||||
using StandardButtons = QMessageBox::StandardButtons;
|
||||
|
||||
using Icon = QMessageBox::Icon;
|
||||
#else
|
||||
enum Option {
|
||||
ShowDirsOnly = 0x00000001,
|
||||
DontResolveSymlinks = 0x00000002,
|
||||
DontConfirmOverwrite = 0x00000004,
|
||||
DontUseNativeDialog = 0x00000008,
|
||||
ReadOnly = 0x00000010,
|
||||
HideNameFilterDetails = 0x00000020,
|
||||
DontUseCustomDirectoryIcons = 0x00000040
|
||||
};
|
||||
Q_ENUM_NS(Option)
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
Q_FLAG_NS(Options)
|
||||
|
||||
enum StandardButton {
|
||||
// keep this in sync with QDialogButtonBox::StandardButton and QPlatformDialogHelper::StandardButton
|
||||
NoButton = 0x00000000,
|
||||
Ok = 0x00000400,
|
||||
Save = 0x00000800,
|
||||
SaveAll = 0x00001000,
|
||||
Open = 0x00002000,
|
||||
Yes = 0x00004000,
|
||||
YesToAll = 0x00008000,
|
||||
No = 0x00010000,
|
||||
NoToAll = 0x00020000,
|
||||
Abort = 0x00040000,
|
||||
Retry = 0x00080000,
|
||||
Ignore = 0x00100000,
|
||||
Close = 0x00200000,
|
||||
Cancel = 0x00400000,
|
||||
Discard = 0x00800000,
|
||||
Help = 0x01000000,
|
||||
Apply = 0x02000000,
|
||||
Reset = 0x04000000,
|
||||
RestoreDefaults = 0x08000000,
|
||||
// keep this in sync with QDialogButtonBox::StandardButton and
|
||||
// QPlatformDialogHelper::StandardButton
|
||||
NoButton = 0x00000000,
|
||||
Ok = 0x00000400,
|
||||
Save = 0x00000800,
|
||||
SaveAll = 0x00001000,
|
||||
Open = 0x00002000,
|
||||
Yes = 0x00004000,
|
||||
YesToAll = 0x00008000,
|
||||
No = 0x00010000,
|
||||
NoToAll = 0x00020000,
|
||||
Abort = 0x00040000,
|
||||
Retry = 0x00080000,
|
||||
Ignore = 0x00100000,
|
||||
Close = 0x00200000,
|
||||
Cancel = 0x00400000,
|
||||
Discard = 0x00800000,
|
||||
Help = 0x01000000,
|
||||
Apply = 0x02000000,
|
||||
Reset = 0x04000000,
|
||||
RestoreDefaults = 0x08000000,
|
||||
|
||||
FirstButton = Ok, // internal
|
||||
LastButton = RestoreDefaults, // internal
|
||||
FirstButton = Ok, // internal
|
||||
LastButton = RestoreDefaults, // internal
|
||||
|
||||
YesAll = YesToAll, // obsolete
|
||||
NoAll = NoToAll, // obsolete
|
||||
YesAll = YesToAll, // obsolete
|
||||
NoAll = NoToAll, // obsolete
|
||||
|
||||
Default = 0x00000100, // obsolete
|
||||
Escape = 0x00000200, // obsolete
|
||||
FlagMask = 0x00000300, // obsolete
|
||||
ButtonMask = ~FlagMask // obsolete
|
||||
Default = 0x00000100, // obsolete
|
||||
Escape = 0x00000200, // obsolete
|
||||
FlagMask = 0x00000300, // obsolete
|
||||
ButtonMask = ~FlagMask // obsolete
|
||||
};
|
||||
Q_ENUM_NS(StandardButton)
|
||||
|
||||
|
|
@ -83,7 +58,7 @@ typedef StandardButton Button;
|
|||
Q_DECLARE_FLAGS(StandardButtons, StandardButton)
|
||||
Q_FLAG_NS(StandardButtons)
|
||||
|
||||
enum Icon {
|
||||
enum class Icon {
|
||||
// keep this in sync with QMessageDialogOptions::StandardIcon
|
||||
NoIcon = 0,
|
||||
Information = 1,
|
||||
|
|
@ -93,29 +68,26 @@ enum Icon {
|
|||
};
|
||||
Q_ENUM_NS(Icon)
|
||||
|
||||
#endif
|
||||
|
||||
// TODO(crueter) widgets-less impl, choices et al.
|
||||
StandardButton ShowMessage(Icon icon,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
StandardButton ShowMessage(Icon icon, const QString& title, const QString& text,
|
||||
StandardButtons buttons = StandardButton::NoButton,
|
||||
QObject *parent = nullptr);
|
||||
QObject* parent = nullptr);
|
||||
|
||||
#define UTIL_OVERRIDES(level) \
|
||||
inline StandardButton level(QObject *parent, \
|
||||
const QString &title, \
|
||||
const QString &text, \
|
||||
StandardButtons buttons = StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, title, text, buttons, parent); \
|
||||
} \
|
||||
inline StandardButton level(const QString title, \
|
||||
const QString &text, \
|
||||
StandardButtons buttons \
|
||||
= StandardButton::Ok) \
|
||||
{ \
|
||||
return ShowMessage(Icon::level, title, text, buttons, rootObject); \
|
||||
#define UTIL_OVERRIDES(level) \
|
||||
inline StandardButton level(QObject* parent, const QString& title, const QString& text, \
|
||||
StandardButtons buttons) { \
|
||||
return ShowMessage(Icon::level, title, text, buttons, parent); \
|
||||
} \
|
||||
inline StandardButton level(QObject* parent, const QString& title, const QString& text, \
|
||||
int buttons = StandardButton::Ok) { \
|
||||
return ShowMessage(Icon::level, title, text, StandardButtons(buttons), parent); \
|
||||
} \
|
||||
inline StandardButton level(const QString title, const QString& text, \
|
||||
StandardButtons buttons) { \
|
||||
return ShowMessage(Icon::level, title, text, buttons, rootObject); \
|
||||
} \
|
||||
inline StandardButton level(const QString& title, const QString& text, \
|
||||
int buttons = StandardButton::Ok) { \
|
||||
return ShowMessage(Icon::level, title, text, StandardButtons(buttons), rootObject); \
|
||||
}
|
||||
|
||||
UTIL_OVERRIDES(Information)
|
||||
|
|
@ -123,27 +95,17 @@ UTIL_OVERRIDES(Warning)
|
|||
UTIL_OVERRIDES(Critical)
|
||||
UTIL_OVERRIDES(Question)
|
||||
|
||||
const QString GetOpenFileName(const QString &title,
|
||||
const QString &dir,
|
||||
const QString &filter,
|
||||
QString *selectedFilter = nullptr,
|
||||
Options options = Options());
|
||||
const QString GetOpenFileName(const QString& title, const QString& dir, const QString& filter,
|
||||
QString* selectedFilter = nullptr);
|
||||
|
||||
const QStringList GetOpenFileNames(const QString &title,
|
||||
const QString &dir,
|
||||
const QString &filter,
|
||||
QString *selectedFilter = nullptr,
|
||||
Options options = Options());
|
||||
const QStringList GetOpenFileNames(const QString& title, const QString& dir, const QString& filter,
|
||||
QString* selectedFilter = nullptr);
|
||||
|
||||
const QString GetSaveFileName(const QString &title,
|
||||
const QString &dir,
|
||||
const QString &filter,
|
||||
QString *selectedFilter = nullptr,
|
||||
Options options = Options());
|
||||
const QString GetSaveFileName(const QString& title, const QString& dir, const QString& filter,
|
||||
QString* selectedFilter = nullptr);
|
||||
|
||||
const QString GetExistingDirectory(const QString &caption = QString(),
|
||||
const QString &dir = QString(),
|
||||
Options options = Option::ShowDirsOnly);
|
||||
const QString GetExistingDirectory(const QString& caption = QString(),
|
||||
const QString& dir = QString());
|
||||
|
||||
int Choice(const QString& title = QString(), const QString& caption = QString(),
|
||||
const QStringList& options = {});
|
||||
|
|
|
|||
12
src/qt_common/abstract/progress.cpp
Normal file
12
src/qt_common/abstract/progress.cpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "progress.h"
|
||||
|
||||
namespace QtCommon::Frontend {
|
||||
|
||||
QtProgressDialog::QtProgressDialog(const QString&, const QString&, int, int, QObject* parent,
|
||||
Qt::WindowFlags)
|
||||
: QObject(parent) {}
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
45
src/qt_common/abstract/progress.h
Normal file
45
src/qt_common/abstract/progress.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QObject>
|
||||
|
||||
namespace QtCommon::Frontend {
|
||||
|
||||
class QtProgressDialog : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QtProgressDialog(const QString& labelText, const QString& cancelButtonText, int minimum,
|
||||
int maximum, QObject* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
|
||||
|
||||
virtual ~QtProgressDialog() = default;
|
||||
|
||||
virtual bool wasCanceled() const = 0;
|
||||
virtual void setWindowModality(Qt::WindowModality modality) = 0;
|
||||
virtual void setMinimumDuration(int durationMs) = 0;
|
||||
virtual void setAutoClose(bool autoClose) = 0;
|
||||
virtual void setAutoReset(bool autoReset) = 0;
|
||||
|
||||
public slots:
|
||||
virtual void setTitle(QString title) = 0;
|
||||
virtual void setLabelText(QString text) = 0;
|
||||
virtual void setMinimum(int min) = 0;
|
||||
virtual void setMaximum(int max) = 0;
|
||||
virtual void setValue(int value) = 0;
|
||||
|
||||
virtual bool close() = 0;
|
||||
virtual void show() = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<QtProgressDialog> newProgressDialog(const QString& labelText,
|
||||
const QString& cancelButtonText, int minimum,
|
||||
int maximum,
|
||||
Qt::WindowFlags f = Qt::WindowFlags());
|
||||
|
||||
QtProgressDialog* newProgressDialogPtr(const QString& labelText, const QString& cancelButtonText,
|
||||
int minimum, int maximum,
|
||||
Qt::WindowFlags f = Qt::WindowFlags());
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_progress_dialog.h"
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_PROGRESS_DIALOG_H
|
||||
#define QT_PROGRESS_DIALOG_H
|
||||
|
||||
#include <QWindow>
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
#include <QProgressDialog>
|
||||
#endif
|
||||
|
||||
namespace QtCommon::Frontend {
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
|
||||
using QtProgressDialog = QProgressDialog;
|
||||
|
||||
// TODO(crueter): QML impl
|
||||
#else
|
||||
class QtProgressDialog
|
||||
{
|
||||
public:
|
||||
QtProgressDialog(const QString &labelText,
|
||||
const QString &cancelButtonText,
|
||||
int minimum,
|
||||
int maximum,
|
||||
QObject *parent = nullptr,
|
||||
Qt::WindowFlags f = Qt::WindowFlags());
|
||||
|
||||
bool wasCanceled() const;
|
||||
void setWindowModality(Qt::WindowModality modality);
|
||||
void setMinimumDuration(int durationMs);
|
||||
void setAutoClose(bool autoClose);
|
||||
void setAutoReset(bool autoReset);
|
||||
|
||||
public slots:
|
||||
void setLabelText(QString &text);
|
||||
void setRange(int min, int max);
|
||||
void setValue(int progress);
|
||||
bool close();
|
||||
|
||||
void show();
|
||||
};
|
||||
#endif // YUZU_QT_WIDGETS
|
||||
|
||||
}
|
||||
#endif // QT_PROGRESS_DIALOG_H
|
||||
|
|
@ -319,7 +319,7 @@ void QtConfig::ReadUIGamelistValues() {
|
|||
}
|
||||
|
||||
void QtConfig::ReadUILayoutValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::UiLayout));
|
||||
|
||||
ReadCategory(Settings::Category::UiLayout);
|
||||
|
||||
|
|
@ -578,10 +578,10 @@ void QtConfig::SaveMultiplayerValues() {
|
|||
}
|
||||
|
||||
std::vector<Settings::BasicSetting*>& QtConfig::FindRelevantList(Settings::Category category) {
|
||||
auto& map = Settings::values.linkage.by_category;
|
||||
if (map.contains(category)) {
|
||||
return Settings::values.linkage.by_category[category];
|
||||
}
|
||||
auto& list = Settings::values.linkage.by_category[category];
|
||||
if (!list.empty())
|
||||
return list;
|
||||
|
||||
return UISettings::values.linkage.by_category[category];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,24 +9,23 @@
|
|||
|
||||
#include "shared_translation.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <QCoreApplication>
|
||||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "common/settings_setting.h"
|
||||
#include "common/time_zone.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace ConfigurationShared {
|
||||
|
||||
std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
{
|
||||
std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
|
||||
std::unique_ptr<TranslationMap> translations = std::make_unique<TranslationMap>();
|
||||
const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); };
|
||||
|
||||
#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \
|
||||
#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \
|
||||
translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}})
|
||||
|
||||
// A setting can be ignored by giving it a blank name
|
||||
|
|
@ -47,10 +46,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||
INSERT(Settings, login_share_applet_mode, tr("Login share"), QString());
|
||||
INSERT(Settings, wifi_web_auth_applet_mode, tr("Wifi web auth"), QString());
|
||||
INSERT(Settings, my_page_applet_mode, tr("My page"), QString());
|
||||
INSERT(Settings,
|
||||
enable_overlay,
|
||||
tr("Enable Overlay Applet"),
|
||||
tr("Enables Horizon\'s built-in overlay applet. Press and hold the home button for 1 second to show it."));
|
||||
INSERT(Settings, enable_overlay, tr("Enable Overlay Applet"),
|
||||
tr("Enables Horizon\'s built-in overlay applet. Press and hold the home button for 1 "
|
||||
"second to show it."));
|
||||
|
||||
// Audio
|
||||
INSERT(Settings, sink_id, tr("Output Engine:"), QString());
|
||||
|
|
@ -62,23 +60,16 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||
INSERT(UISettings, mute_when_in_background, tr("Mute audio when in background"), QString());
|
||||
|
||||
// Core
|
||||
INSERT(
|
||||
Settings,
|
||||
use_multi_core,
|
||||
tr("Multicore CPU Emulation"),
|
||||
tr("This option increases CPU emulation thread use from 1 to the maximum of 4.\n"
|
||||
"This is mainly a debug option and shouldn't be disabled."));
|
||||
INSERT(
|
||||
Settings,
|
||||
memory_layout_mode,
|
||||
tr("Memory Layout"),
|
||||
tr("Increases the amount of emulated RAM.\nDoesn't affect performance/stability but may allow HD texture "
|
||||
"mods to load."));
|
||||
INSERT(Settings, use_multi_core, tr("Multicore CPU Emulation"),
|
||||
tr("This option increases CPU emulation thread use from 1 to the maximum of 4.\n"
|
||||
"This is mainly a debug option and shouldn't be disabled."));
|
||||
INSERT(Settings, memory_layout_mode, tr("Memory Layout"),
|
||||
tr("Increases the amount of emulated RAM.\nDoesn't affect performance/stability but may "
|
||||
"allow HD texture "
|
||||
"mods to load."));
|
||||
INSERT(Settings, use_speed_limit, QString(), QString());
|
||||
INSERT(Settings, current_speed_mode, QString(), QString());
|
||||
INSERT(Settings,
|
||||
speed_limit,
|
||||
tr("Limit Speed Percent"),
|
||||
INSERT(Settings, speed_limit, tr("Limit Speed Percent"),
|
||||
tr("Controls the game's maximum rendering speed, but it's up to each game if it runs "
|
||||
"faster or not.\n200% for a 30 FPS game is 60 FPS, and for a "
|
||||
"60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the "
|
||||
|
|
@ -91,171 +82,128 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||
tr("When the Slow Speed hotkey is pressed, the speed will be limited to this "
|
||||
"percentage."));
|
||||
|
||||
INSERT(Settings,
|
||||
sync_core_speed,
|
||||
tr("Synchronize Core Speed"),
|
||||
INSERT(Settings, sync_core_speed, tr("Synchronize Core Speed"),
|
||||
tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS "
|
||||
"without affecting game speed (animations, physics, etc.).\n"
|
||||
"Can help reduce stuttering at lower framerates."));
|
||||
|
||||
// Cpu
|
||||
INSERT(Settings,
|
||||
cpu_accuracy,
|
||||
tr("Accuracy:"),
|
||||
INSERT(Settings, cpu_accuracy, tr("Accuracy:"),
|
||||
tr("Change the accuracy of the emulated CPU (for debugging only)."));
|
||||
INSERT(Settings, cpu_backend, tr("Backend:"), QString());
|
||||
|
||||
INSERT(Settings,
|
||||
fast_cpu_time,
|
||||
tr("CPU Overclock"),
|
||||
tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, "
|
||||
"and certain games may behave improperly.\nUse Boost (1700MHz) to run at the Switch's highest native "
|
||||
INSERT(Settings, fast_cpu_time, tr("CPU Overclock"),
|
||||
tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see "
|
||||
"reduced performance, "
|
||||
"and certain games may behave improperly.\nUse Boost (1700MHz) to run at the "
|
||||
"Switch's highest native "
|
||||
"clock, or Fast (2000MHz) to run at 2x clock."));
|
||||
|
||||
INSERT(Settings, use_custom_cpu_ticks, QString(), QString());
|
||||
INSERT(Settings,
|
||||
cpu_ticks,
|
||||
tr("Custom CPU Ticks"),
|
||||
INSERT(Settings, cpu_ticks, tr("Custom CPU Ticks"),
|
||||
tr("Set a custom value of CPU ticks. Higher values can increase performance, but may "
|
||||
"cause deadlocks. A range of 77-21000 is recommended."));
|
||||
INSERT(Settings, cpu_backend, tr("Backend:"), QString());
|
||||
|
||||
INSERT(Settings, vtable_bouncing,
|
||||
tr("Virtual Table Bouncing"),
|
||||
tr("Bounces (by emulating a 0-valued return) any functions that triggers a prefetch abort"));
|
||||
INSERT(Settings, vtable_bouncing, tr("Virtual Table Bouncing"),
|
||||
tr("Bounces (by emulating a 0-valued return) any functions that triggers a prefetch "
|
||||
"abort"));
|
||||
|
||||
// Cpu Debug
|
||||
|
||||
// Cpu Unsafe
|
||||
INSERT(Settings, cpuopt_unsafe_host_mmu, tr("Enable Host MMU Emulation (fastmem)"),
|
||||
tr("This optimization speeds up memory accesses by the guest program.\nEnabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.\nDisabling this forces all memory accesses to use Software MMU Emulation."));
|
||||
INSERT(
|
||||
Settings,
|
||||
cpuopt_unsafe_unfuse_fma,
|
||||
Settings, cpuopt_unsafe_host_mmu, tr("Enable Host MMU Emulation (fastmem)"),
|
||||
tr("This optimization speeds up memory accesses by the guest program.\nEnabling it causes "
|
||||
"guest memory reads/writes to be done directly into memory and make use of Host's "
|
||||
"MMU.\nDisabling this forces all memory accesses to use Software MMU Emulation."));
|
||||
INSERT(
|
||||
Settings, cpuopt_unsafe_unfuse_fma,
|
||||
tr("Unfuse FMA (improve performance on CPUs without FMA)"),
|
||||
tr("This option improves speed by reducing accuracy of fused-multiply-add instructions on "
|
||||
"CPUs without native FMA support."));
|
||||
INSERT(
|
||||
Settings,
|
||||
cpuopt_unsafe_reduce_fp_error,
|
||||
tr("Faster FRSQRTE and FRECPE"),
|
||||
Settings, cpuopt_unsafe_reduce_fp_error, tr("Faster FRSQRTE and FRECPE"),
|
||||
tr("This option improves the speed of some approximate floating-point functions by using "
|
||||
"less accurate native approximations."));
|
||||
INSERT(Settings,
|
||||
cpuopt_unsafe_ignore_standard_fpcr,
|
||||
INSERT(Settings, cpuopt_unsafe_ignore_standard_fpcr,
|
||||
tr("Faster ASIMD instructions (32 bits only)"),
|
||||
tr("This option improves the speed of 32 bits ASIMD floating-point functions by running "
|
||||
"with incorrect rounding modes."));
|
||||
INSERT(Settings,
|
||||
cpuopt_unsafe_inaccurate_nan,
|
||||
tr("Inaccurate NaN handling"),
|
||||
INSERT(Settings, cpuopt_unsafe_inaccurate_nan, tr("Inaccurate NaN handling"),
|
||||
tr("This option improves speed by removing NaN checking.\nPlease note this also reduces "
|
||||
"accuracy of certain floating-point instructions."));
|
||||
INSERT(Settings,
|
||||
cpuopt_unsafe_fastmem_check,
|
||||
tr("Disable address space checks"),
|
||||
INSERT(Settings, cpuopt_unsafe_fastmem_check, tr("Disable address space checks"),
|
||||
tr("This option improves speed by eliminating a safety check before every memory "
|
||||
"operation.\nDisabling it may allow arbitrary code execution."));
|
||||
INSERT(
|
||||
Settings,
|
||||
cpuopt_unsafe_ignore_global_monitor,
|
||||
tr("Ignore global monitor"),
|
||||
Settings, cpuopt_unsafe_ignore_global_monitor, tr("Ignore global monitor"),
|
||||
tr("This option improves speed by relying only on the semantics of cmpxchg to ensure "
|
||||
"safety of exclusive access instructions.\nPlease note this may result in deadlocks and "
|
||||
"other race conditions."));
|
||||
|
||||
// Renderer
|
||||
INSERT(
|
||||
Settings,
|
||||
renderer_backend,
|
||||
tr("API:"),
|
||||
tr("Changes the output graphics API.\nVulkan is recommended."));
|
||||
INSERT(Settings,
|
||||
vulkan_device,
|
||||
tr("Device:"),
|
||||
INSERT(Settings, renderer_backend, tr("API:"),
|
||||
tr("Changes the output graphics API.\nVulkan is recommended."));
|
||||
INSERT(Settings, vulkan_device, tr("Device:"),
|
||||
tr("This setting selects the GPU to use (Vulkan only)."));
|
||||
INSERT(Settings,
|
||||
resolution_setup,
|
||||
tr("Resolution:"),
|
||||
INSERT(Settings, resolution_setup, tr("Resolution:"),
|
||||
tr("Forces to render at a different resolution.\n"
|
||||
"Higher resolutions require more VRAM and bandwidth.\n"
|
||||
"Options lower than 1X can cause artifacts."));
|
||||
INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString());
|
||||
INSERT(Settings,
|
||||
fsr_sharpening_slider,
|
||||
tr("FSR Sharpness:"),
|
||||
INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"),
|
||||
tr("Determines how sharpened the image will look using FSR's dynamic contrast."));
|
||||
INSERT(Settings,
|
||||
anti_aliasing,
|
||||
tr("Anti-Aliasing Method:"),
|
||||
INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"),
|
||||
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
|
||||
"can produce a more stable picture in lower resolutions."));
|
||||
INSERT(Settings,
|
||||
fullscreen_mode,
|
||||
tr("Fullscreen Mode:"),
|
||||
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
|
||||
tr("The method used to render the window in fullscreen.\nBorderless offers the best "
|
||||
"compatibility with the on-screen keyboard that some games request for "
|
||||
"input.\nExclusive "
|
||||
"fullscreen may offer better performance and better Freesync/Gsync support."));
|
||||
INSERT(Settings,
|
||||
aspect_ratio,
|
||||
tr("Aspect Ratio:"),
|
||||
INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"),
|
||||
tr("Stretches the renderer to fit the specified aspect ratio.\nMost games only support "
|
||||
"16:9, so modifications are required to get other ratios.\nAlso controls the "
|
||||
"aspect ratio of captured screenshots."));
|
||||
INSERT(Settings,
|
||||
use_disk_shader_cache,
|
||||
tr("Use persistent pipeline cache"),
|
||||
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,
|
||||
optimize_spirv_output,
|
||||
tr("Optimize SPIRV output"),
|
||||
INSERT(Settings, optimize_spirv_output, tr("Optimize SPIRV output"),
|
||||
tr("Runs an additional optimization pass over generated SPIRV shaders.\n"
|
||||
"Will increase time required for shader compilation.\nMay slightly improve "
|
||||
"performance.\nThis feature is experimental."));
|
||||
INSERT(Settings,
|
||||
nvdec_emulation,
|
||||
tr("NVDEC emulation:"),
|
||||
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"
|
||||
"In most cases, GPU decoding provides the best performance."));
|
||||
INSERT(Settings,
|
||||
accelerate_astc,
|
||||
tr("ASTC Decoding Method:"),
|
||||
INSERT(Settings, accelerate_astc, tr("ASTC Decoding Method:"),
|
||||
tr("This option controls how ASTC textures should be decoded.\n"
|
||||
"CPU: Use the CPU for decoding.\n"
|
||||
"GPU: Use the GPU's compute shaders to decode ASTC textures (recommended).\n"
|
||||
"CPU Asynchronously: Use the CPU to decode ASTC textures on demand. Eliminates"
|
||||
"ASTC decoding\nstuttering but may present artifacts."));
|
||||
INSERT(
|
||||
Settings,
|
||||
astc_recompression,
|
||||
tr("ASTC Recompression Method:"),
|
||||
tr("Most GPUs lack support for ASTC textures and must decompress to an"
|
||||
"intermediate format: RGBA8.\n"
|
||||
"BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format,\n"
|
||||
" saving VRAM but degrading image quality."));
|
||||
INSERT(Settings, astc_recompression, tr("ASTC Recompression Method:"),
|
||||
tr("Most GPUs lack support for ASTC textures and must decompress to an"
|
||||
"intermediate format: RGBA8.\n"
|
||||
"BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format,\n"
|
||||
" saving VRAM but degrading image quality."));
|
||||
INSERT(Settings, frame_pacing_mode, tr("Frame Pacing Mode (Vulkan only)"),
|
||||
tr("Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent."));
|
||||
INSERT(Settings,
|
||||
vram_usage_mode,
|
||||
tr("VRAM Usage Mode:"),
|
||||
tr("Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance.\nAggressive mode may impact performance of other applications such as recording software."));
|
||||
INSERT(Settings,
|
||||
skip_cpu_inner_invalidation,
|
||||
tr("Skip CPU Inner Invalidation"),
|
||||
tr("Controls how the emulator manages frame pacing to reduce stuttering and make the "
|
||||
"frame rate smoother and more consistent."));
|
||||
INSERT(Settings, vram_usage_mode, tr("VRAM Usage Mode:"),
|
||||
tr("Selects whether the emulator should prefer to conserve memory or make maximum usage "
|
||||
"of available video memory for performance.\nAggressive mode may impact performance "
|
||||
"of other applications such as recording software."));
|
||||
INSERT(Settings, skip_cpu_inner_invalidation, tr("Skip CPU Inner Invalidation"),
|
||||
tr("Skips certain cache invalidations during memory updates, reducing CPU usage and "
|
||||
"improving latency. This may cause soft-crashes."));
|
||||
INSERT(
|
||||
Settings,
|
||||
vsync_mode,
|
||||
tr("VSync Mode:"),
|
||||
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
|
||||
"refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n"
|
||||
"Mailbox can have lower latency than FIFO and does not tear but may drop "
|
||||
"frames.\nImmediate (no synchronization) presents whatever is available and can "
|
||||
"exhibit tearing."));
|
||||
INSERT(Settings, vsync_mode, tr("VSync Mode:"),
|
||||
tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen "
|
||||
"refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n"
|
||||
"Mailbox can have lower latency than FIFO and does not tear but may drop "
|
||||
"frames.\nImmediate (no synchronization) presents whatever is available and can "
|
||||
"exhibit tearing."));
|
||||
INSERT(Settings, bg_red, QString(), QString());
|
||||
INSERT(Settings, bg_green, QString(), QString());
|
||||
INSERT(Settings, bg_blue, QString(), QString());
|
||||
|
|
@ -264,103 +212,74 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||
INSERT(Settings, use_asynchronous_gpu_emulation, QString(), QString());
|
||||
|
||||
INSERT(Settings, sync_memory_operations, tr("Sync Memory Operations"),
|
||||
tr("Ensures data consistency between compute and memory operations.\nThis option fixes issues in games, but may degrade performance.\nUnreal Engine 4 games often see the most significant changes thereof."));
|
||||
INSERT(Settings,
|
||||
async_presentation,
|
||||
tr("Enable asynchronous presentation (Vulkan only)"),
|
||||
tr("Ensures data consistency between compute and memory operations.\nThis option fixes "
|
||||
"issues in games, but may degrade performance.\nUnreal Engine 4 games often see the "
|
||||
"most significant changes thereof."));
|
||||
INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"),
|
||||
tr("Slightly improves performance by moving presentation to a separate CPU thread."));
|
||||
INSERT(
|
||||
Settings,
|
||||
renderer_force_max_clock,
|
||||
tr("Force maximum clocks (Vulkan only)"),
|
||||
Settings, renderer_force_max_clock, tr("Force maximum clocks (Vulkan only)"),
|
||||
tr("Runs work in the background while waiting for graphics commands to keep the GPU from "
|
||||
"lowering its clock speed."));
|
||||
INSERT(Settings,
|
||||
max_anisotropy,
|
||||
tr("Anisotropic Filtering:"),
|
||||
tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on most GPUs."));
|
||||
INSERT(Settings,
|
||||
gpu_accuracy,
|
||||
tr("GPU Mode:"),
|
||||
tr("Controls the GPU emulation mode.\nMost games render fine with Fast or Balanced modes, but Accurate is still "
|
||||
INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"),
|
||||
tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on "
|
||||
"most GPUs."));
|
||||
INSERT(Settings, gpu_accuracy, tr("GPU Mode:"),
|
||||
tr("Controls the GPU emulation mode.\nMost games render fine with Fast or Balanced "
|
||||
"modes, but Accurate is still "
|
||||
"required for some.\nParticles tend to only render correctly with Accurate mode."));
|
||||
INSERT(Settings,
|
||||
dma_accuracy,
|
||||
tr("DMA Accuracy:"),
|
||||
tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance."));
|
||||
INSERT(Settings,
|
||||
use_asynchronous_shaders,
|
||||
tr("Enable asynchronous shader compilation"),
|
||||
INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"),
|
||||
tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but "
|
||||
"may degrade performance."));
|
||||
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
|
||||
tr("May reduce shader stutter."));
|
||||
INSERT(Settings,
|
||||
fast_gpu_time,
|
||||
tr("Fast GPU Time"),
|
||||
INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"),
|
||||
tr("Overclocks the emulated GPU to increase dynamic resolution and render "
|
||||
"distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity."));
|
||||
INSERT(Settings,
|
||||
gpu_unswizzle_enabled,
|
||||
tr("GPU Unswizzle"),
|
||||
INSERT(Settings, gpu_unswizzle_enabled, tr("GPU Unswizzle"),
|
||||
tr("Accelerates BCn 3D texture decoding using GPU compute.\n"
|
||||
"Disable if experiencing crashes or graphical glitches."));
|
||||
INSERT(Settings,
|
||||
gpu_unswizzle_texture_size,
|
||||
tr("GPU Unswizzle Max Texture Size"),
|
||||
INSERT(Settings, gpu_unswizzle_texture_size, tr("GPU Unswizzle Max Texture Size"),
|
||||
tr("Sets the maximum size (MiB) for GPU-based texture unswizzling.\n"
|
||||
"While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones.\n"
|
||||
"While the GPU is faster for medium and large textures, the CPU may be more "
|
||||
"efficient for very small ones.\n"
|
||||
"Adjust this to find the balance between GPU acceleration and CPU overhead."));
|
||||
INSERT(Settings,
|
||||
gpu_unswizzle_stream_size,
|
||||
tr("GPU Unswizzle Stream Size"),
|
||||
INSERT(Settings, gpu_unswizzle_stream_size, tr("GPU Unswizzle Stream Size"),
|
||||
tr("Sets the maximum amount of texture data (in MiB) processed per frame.\n"
|
||||
"Higher values can reduce stutter during texture loading but may impact frame consistency."));
|
||||
INSERT(Settings,
|
||||
gpu_unswizzle_chunk_size,
|
||||
tr("GPU Unswizzle Chunk Size"),
|
||||
"Higher values can reduce stutter during texture loading but may impact frame "
|
||||
"consistency."));
|
||||
INSERT(Settings, gpu_unswizzle_chunk_size, tr("GPU Unswizzle Chunk Size"),
|
||||
tr("Determines the number of depth slices processed in a single dispatch.\n"
|
||||
"Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware."));
|
||||
"Increasing this can improve throughput on high-end GPUs but may cause TDR or driver "
|
||||
"timeouts on weaker hardware."));
|
||||
|
||||
INSERT(Settings,
|
||||
use_vulkan_driver_pipeline_cache,
|
||||
tr("Use Vulkan pipeline cache"),
|
||||
INSERT(Settings, use_vulkan_driver_pipeline_cache, tr("Use Vulkan pipeline cache"),
|
||||
tr("Enables GPU vendor-specific pipeline cache.\nThis option can improve shader loading "
|
||||
"time significantly in cases where the Vulkan driver does not store pipeline cache "
|
||||
"files internally."));
|
||||
INSERT(Settings, enable_compute_pipelines, tr("Enable Compute Pipelines (Intel Vulkan Only)"),
|
||||
tr("Required by some games.\nThis setting only exists for Intel "
|
||||
"proprietary drivers and may crash if enabled.\nCompute pipelines are always enabled "
|
||||
"on all other drivers."));
|
||||
INSERT(
|
||||
Settings,
|
||||
enable_compute_pipelines,
|
||||
tr("Enable Compute Pipelines (Intel Vulkan Only)"),
|
||||
tr("Required by some games.\nThis setting only exists for Intel "
|
||||
"proprietary drivers and may crash if enabled.\nCompute pipelines are always enabled "
|
||||
"on all other drivers."));
|
||||
INSERT(
|
||||
Settings,
|
||||
use_reactive_flushing,
|
||||
tr("Enable Reactive Flushing"),
|
||||
Settings, use_reactive_flushing, tr("Enable Reactive Flushing"),
|
||||
tr("Uses reactive flushing instead of predictive flushing, allowing more accurate memory "
|
||||
"syncing."));
|
||||
INSERT(Settings,
|
||||
use_video_framerate,
|
||||
tr("Sync to framerate of video playback"),
|
||||
INSERT(Settings, use_video_framerate, tr("Sync to framerate of video playback"),
|
||||
tr("Run the game at normal speed during video playback, even when the framerate is "
|
||||
"unlocked."));
|
||||
INSERT(Settings,
|
||||
barrier_feedback_loops,
|
||||
tr("Barrier feedback loops"),
|
||||
INSERT(Settings, barrier_feedback_loops, tr("Barrier feedback loops"),
|
||||
tr("Improves rendering of transparency effects in specific games."));
|
||||
INSERT(Settings,
|
||||
enable_buffer_history,
|
||||
tr("Enable buffer history"),
|
||||
tr("Enables access to previous buffer states.\nThis option may improve rendering quality and performance consistency in some games."));
|
||||
INSERT(Settings,
|
||||
fix_bloom_effects,
|
||||
tr("Fix bloom effects"),
|
||||
tr("Removes bloom in Burnout."));
|
||||
INSERT(Settings, enable_buffer_history, tr("Enable buffer history"),
|
||||
tr("Enables access to previous buffer states.\nThis option may improve rendering "
|
||||
"quality and performance consistency in some games."));
|
||||
INSERT(Settings, fix_bloom_effects, tr("Fix bloom effects"), tr("Removes bloom in Burnout."));
|
||||
|
||||
INSERT(Settings,
|
||||
rescale_hack,
|
||||
tr("Enable Legacy Rescale Pass"),
|
||||
tr("May fix rescale issues in some games by relying on behavior from the previous implementation.\n"
|
||||
"Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3."));
|
||||
INSERT(Settings, rescale_hack, tr("Enable Legacy Rescale Pass"),
|
||||
tr("May fix rescale issues in some games by relying on behavior from the previous "
|
||||
"implementation.\n"
|
||||
"Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and "
|
||||
"grey texture flicker on Nvidia GPUs in Luigis Mansion 3."));
|
||||
|
||||
// Renderer (Extensions)
|
||||
INSERT(Settings, dyna_state, tr("Extended Dynamic State"),
|
||||
|
|
@ -368,59 +287,42 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||
"Higher states allow for more features and can increase performance, but may cause "
|
||||
"additional graphical issues."));
|
||||
|
||||
INSERT(Settings,
|
||||
vertex_input_dynamic_state,
|
||||
tr("Vertex Input Dynamic State"),
|
||||
INSERT(Settings, vertex_input_dynamic_state, tr("Vertex Input Dynamic State"),
|
||||
tr("Enables vertex input dynamic state feature for better quality and performance."));
|
||||
|
||||
INSERT(Settings,
|
||||
provoking_vertex,
|
||||
tr("Provoking Vertex"),
|
||||
INSERT(Settings, provoking_vertex, tr("Provoking Vertex"),
|
||||
tr("Improves lighting and vertex handling in some games.\n"
|
||||
"Only Vulkan 1.0+ devices support this extension."));
|
||||
|
||||
INSERT(Settings,
|
||||
descriptor_indexing,
|
||||
tr("Descriptor Indexing"),
|
||||
INSERT(Settings, descriptor_indexing, tr("Descriptor Indexing"),
|
||||
tr("Improves texture & buffer handling and the Maxwell translation layer.\n"
|
||||
"Some Vulkan 1.1+ and all 1.2+ devices support this extension."));
|
||||
|
||||
INSERT(Settings,
|
||||
sample_shading,
|
||||
tr("Sample Shading"),
|
||||
tr("Allows the fragment shader to execute per sample in a multi-sampled fragment "
|
||||
"instead of once per fragment. Improves graphics quality at the cost of performance.\n"
|
||||
"Higher values improve quality but degrade performance."));
|
||||
INSERT(
|
||||
Settings, sample_shading, tr("Sample Shading"),
|
||||
tr("Allows the fragment shader to execute per sample in a multi-sampled fragment "
|
||||
"instead of once per fragment. Improves graphics quality at the cost of performance.\n"
|
||||
"Higher values improve quality but degrade performance."));
|
||||
|
||||
// Renderer (Debug)
|
||||
|
||||
// System
|
||||
INSERT(Settings,
|
||||
rng_seed,
|
||||
tr("RNG Seed"),
|
||||
INSERT(Settings, rng_seed, tr("RNG Seed"),
|
||||
tr("Controls the seed of the random number generator.\nMainly used for speedrunning."));
|
||||
INSERT(Settings, rng_seed_enabled, QString(), QString());
|
||||
INSERT(Settings, device_name, tr("Device Name"), tr("The name of the console."));
|
||||
INSERT(Settings,
|
||||
custom_rtc,
|
||||
tr("Custom RTC Date:"),
|
||||
INSERT(Settings, custom_rtc, tr("Custom RTC Date:"),
|
||||
tr("This option allows to change the clock of the console.\n"
|
||||
"Can be used to manipulate time in games."));
|
||||
INSERT(Settings, custom_rtc_enabled, QString(), QString());
|
||||
INSERT(Settings,
|
||||
custom_rtc_offset,
|
||||
QStringLiteral(" "),
|
||||
INSERT(Settings, custom_rtc_offset, QStringLiteral(" "),
|
||||
tr("The number of seconds from the current unix time"));
|
||||
INSERT(Settings,
|
||||
language_index,
|
||||
tr("Language:"),
|
||||
INSERT(Settings, language_index, tr("Language:"),
|
||||
tr("This option can be overridden when region setting is auto-select"));
|
||||
INSERT(Settings, region_index, tr("Region:"), tr("The region of the console."));
|
||||
INSERT(Settings, time_zone_index, tr("Time Zone:"), tr("The time zone of the console."));
|
||||
INSERT(Settings, sound_index, tr("Sound Output Mode:"), QString());
|
||||
INSERT(Settings,
|
||||
use_docked_mode,
|
||||
tr("Console Mode:"),
|
||||
INSERT(Settings, use_docked_mode, tr("Console Mode:"),
|
||||
tr("Selects if the console is in Docked or Handheld mode.\nGames will change "
|
||||
"their resolution, details and supported controllers and depending on this setting.\n"
|
||||
"Setting to Handheld can help improve performance for low end systems."));
|
||||
|
|
@ -444,31 +346,19 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||
// Ui
|
||||
|
||||
// Ui General
|
||||
INSERT(UISettings,
|
||||
select_user_on_boot,
|
||||
tr("Prompt for user profile on boot"),
|
||||
INSERT(UISettings, select_user_on_boot, tr("Prompt for user profile on boot"),
|
||||
tr("Useful if multiple people use the same PC."));
|
||||
INSERT(UISettings,
|
||||
pause_when_in_background,
|
||||
tr("Pause when not in focus"),
|
||||
INSERT(UISettings, pause_when_in_background, tr("Pause when not in focus"),
|
||||
tr("Pauses emulation when focusing on other windows."));
|
||||
INSERT(UISettings,
|
||||
confirm_before_stopping,
|
||||
tr("Confirm before stopping emulation"),
|
||||
INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"),
|
||||
tr("Overrides prompts asking to confirm stopping the emulation.\nEnabling "
|
||||
"it bypasses such prompts and directly exits the emulation."));
|
||||
INSERT(UISettings,
|
||||
hide_mouse,
|
||||
tr("Hide mouse on inactivity"),
|
||||
INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"),
|
||||
tr("Hides the mouse after 2.5s of inactivity."));
|
||||
INSERT(UISettings,
|
||||
controller_applet_disabled,
|
||||
tr("Disable controller applet"),
|
||||
INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"),
|
||||
tr("Forcibly disables the use of the controller applet in emulated programs.\n"
|
||||
"When a program attempts to open the controller applet, it is immediately closed."));
|
||||
INSERT(UISettings,
|
||||
check_for_updates,
|
||||
tr("Check for updates"),
|
||||
"When a program attempts to open the controller applet, it is immediately closed."));
|
||||
INSERT(UISettings, check_for_updates, tr("Check for updates"),
|
||||
tr("Whether or not to check for updates upon startup."));
|
||||
|
||||
// Linux
|
||||
|
|
@ -489,9 +379,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||
return translations;
|
||||
}
|
||||
|
||||
std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
|
||||
{
|
||||
std::unique_ptr<ComboboxTranslationMap> translations = std::make_unique<ComboboxTranslationMap>();
|
||||
std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
|
||||
std::unique_ptr<ComboboxTranslationMap> translations =
|
||||
std::make_unique<ComboboxTranslationMap>();
|
||||
const auto& tr = [&](const char* text, const char* context = "") {
|
||||
return parent->tr(text, context);
|
||||
};
|
||||
|
|
@ -537,15 +427,15 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
|
|||
PAIR(VramUsageMode, Conservative, tr("Conservative")),
|
||||
PAIR(VramUsageMode, Aggressive, tr("Aggressive")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::RendererBackend>::Index(), {
|
||||
PAIR(RendererBackend, Vulkan, tr("Vulkan")),
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::RendererBackend>::Index(),
|
||||
{PAIR(RendererBackend, Vulkan, tr("Vulkan")),
|
||||
#ifdef HAS_OPENGL
|
||||
PAIR(RendererBackend, OpenGL_GLSL, tr("OpenGL GLSL")),
|
||||
PAIR(RendererBackend, OpenGL_GLASM, tr("OpenGL GLASM (Assembly Shaders, NVIDIA Only)")),
|
||||
PAIR(RendererBackend, OpenGL_SPIRV, tr("OpenGL SPIR-V (Experimental, AMD/Mesa Only)")),
|
||||
PAIR(RendererBackend, OpenGL_GLSL, tr("OpenGL GLSL")),
|
||||
PAIR(RendererBackend, OpenGL_GLASM, tr("OpenGL GLASM (Assembly Shaders, NVIDIA Only)")),
|
||||
PAIR(RendererBackend, OpenGL_SPIRV, tr("OpenGL SPIR-V (Experimental, AMD/Mesa Only)")),
|
||||
#endif
|
||||
PAIR(RendererBackend, Null, tr("Null"))
|
||||
}});
|
||||
PAIR(RendererBackend, Null, tr("Null"))}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::GpuAccuracy>::Index(),
|
||||
{
|
||||
PAIR(GpuAccuracy, Low, tr("Fast")),
|
||||
|
|
@ -679,58 +569,58 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
|
|||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::TimeZone>::Index(),
|
||||
{
|
||||
{static_cast<u32>(Settings::TimeZone::Auto),
|
||||
tr("Auto (%1)", "Auto select time zone")
|
||||
.arg(QString::fromStdString(
|
||||
Settings::GetTimeZoneString(Settings::TimeZone::Auto)))},
|
||||
{static_cast<u32>(Settings::TimeZone::Default),
|
||||
tr("Default (%1)", "Default time zone")
|
||||
.arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))},
|
||||
PAIR(TimeZone, Cet, tr("CET")),
|
||||
PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")),
|
||||
PAIR(TimeZone, Cuba, tr("Cuba")),
|
||||
PAIR(TimeZone, Eet, tr("EET")),
|
||||
PAIR(TimeZone, Egypt, tr("Egypt")),
|
||||
PAIR(TimeZone, Eire, tr("Eire")),
|
||||
PAIR(TimeZone, Est, tr("EST")),
|
||||
PAIR(TimeZone, Est5Edt, tr("EST5EDT")),
|
||||
PAIR(TimeZone, Gb, tr("GB")),
|
||||
PAIR(TimeZone, GbEire, tr("GB-Eire")),
|
||||
PAIR(TimeZone, Gmt, tr("GMT")),
|
||||
PAIR(TimeZone, GmtPlusZero, tr("GMT+0")),
|
||||
PAIR(TimeZone, GmtMinusZero, tr("GMT-0")),
|
||||
PAIR(TimeZone, GmtZero, tr("GMT0")),
|
||||
PAIR(TimeZone, Greenwich, tr("Greenwich")),
|
||||
PAIR(TimeZone, Hongkong, tr("Hongkong")),
|
||||
PAIR(TimeZone, Hst, tr("HST")),
|
||||
PAIR(TimeZone, Iceland, tr("Iceland")),
|
||||
PAIR(TimeZone, Iran, tr("Iran")),
|
||||
PAIR(TimeZone, Israel, tr("Israel")),
|
||||
PAIR(TimeZone, Jamaica, tr("Jamaica")),
|
||||
PAIR(TimeZone, Japan, tr("Japan")),
|
||||
PAIR(TimeZone, Kwajalein, tr("Kwajalein")),
|
||||
PAIR(TimeZone, Libya, tr("Libya")),
|
||||
PAIR(TimeZone, Met, tr("MET")),
|
||||
PAIR(TimeZone, Mst, tr("MST")),
|
||||
PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")),
|
||||
PAIR(TimeZone, Navajo, tr("Navajo")),
|
||||
PAIR(TimeZone, Nz, tr("NZ")),
|
||||
PAIR(TimeZone, NzChat, tr("NZ-CHAT")),
|
||||
PAIR(TimeZone, Poland, tr("Poland")),
|
||||
PAIR(TimeZone, Portugal, tr("Portugal")),
|
||||
PAIR(TimeZone, Prc, tr("PRC")),
|
||||
PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")),
|
||||
PAIR(TimeZone, Roc, tr("ROC")),
|
||||
PAIR(TimeZone, Rok, tr("ROK")),
|
||||
PAIR(TimeZone, Singapore, tr("Singapore")),
|
||||
PAIR(TimeZone, Turkey, tr("Turkey")),
|
||||
PAIR(TimeZone, Uct, tr("UCT")),
|
||||
PAIR(TimeZone, Universal, tr("Universal")),
|
||||
PAIR(TimeZone, Utc, tr("UTC")),
|
||||
PAIR(TimeZone, WSu, tr("W-SU")),
|
||||
PAIR(TimeZone, Wet, tr("WET")),
|
||||
PAIR(TimeZone, Zulu, tr("Zulu")),
|
||||
}});
|
||||
{static_cast<u32>(Settings::TimeZone::Auto),
|
||||
tr("Auto (%1)", "Auto select time zone")
|
||||
.arg(QString::fromStdString(
|
||||
Settings::GetTimeZoneString(Settings::TimeZone::Auto)))},
|
||||
{static_cast<u32>(Settings::TimeZone::Default),
|
||||
tr("Default (%1)", "Default time zone")
|
||||
.arg(QString::fromStdString(Common::TimeZone::GetDefaultTimeZone()))},
|
||||
PAIR(TimeZone, Cet, tr("CET")),
|
||||
PAIR(TimeZone, Cst6Cdt, tr("CST6CDT")),
|
||||
PAIR(TimeZone, Cuba, tr("Cuba")),
|
||||
PAIR(TimeZone, Eet, tr("EET")),
|
||||
PAIR(TimeZone, Egypt, tr("Egypt")),
|
||||
PAIR(TimeZone, Eire, tr("Eire")),
|
||||
PAIR(TimeZone, Est, tr("EST")),
|
||||
PAIR(TimeZone, Est5Edt, tr("EST5EDT")),
|
||||
PAIR(TimeZone, Gb, tr("GB")),
|
||||
PAIR(TimeZone, GbEire, tr("GB-Eire")),
|
||||
PAIR(TimeZone, Gmt, tr("GMT")),
|
||||
PAIR(TimeZone, GmtPlusZero, tr("GMT+0")),
|
||||
PAIR(TimeZone, GmtMinusZero, tr("GMT-0")),
|
||||
PAIR(TimeZone, GmtZero, tr("GMT0")),
|
||||
PAIR(TimeZone, Greenwich, tr("Greenwich")),
|
||||
PAIR(TimeZone, Hongkong, tr("Hongkong")),
|
||||
PAIR(TimeZone, Hst, tr("HST")),
|
||||
PAIR(TimeZone, Iceland, tr("Iceland")),
|
||||
PAIR(TimeZone, Iran, tr("Iran")),
|
||||
PAIR(TimeZone, Israel, tr("Israel")),
|
||||
PAIR(TimeZone, Jamaica, tr("Jamaica")),
|
||||
PAIR(TimeZone, Japan, tr("Japan")),
|
||||
PAIR(TimeZone, Kwajalein, tr("Kwajalein")),
|
||||
PAIR(TimeZone, Libya, tr("Libya")),
|
||||
PAIR(TimeZone, Met, tr("MET")),
|
||||
PAIR(TimeZone, Mst, tr("MST")),
|
||||
PAIR(TimeZone, Mst7Mdt, tr("MST7MDT")),
|
||||
PAIR(TimeZone, Navajo, tr("Navajo")),
|
||||
PAIR(TimeZone, Nz, tr("NZ")),
|
||||
PAIR(TimeZone, NzChat, tr("NZ-CHAT")),
|
||||
PAIR(TimeZone, Poland, tr("Poland")),
|
||||
PAIR(TimeZone, Portugal, tr("Portugal")),
|
||||
PAIR(TimeZone, Prc, tr("PRC")),
|
||||
PAIR(TimeZone, Pst8Pdt, tr("PST8PDT")),
|
||||
PAIR(TimeZone, Roc, tr("ROC")),
|
||||
PAIR(TimeZone, Rok, tr("ROK")),
|
||||
PAIR(TimeZone, Singapore, tr("Singapore")),
|
||||
PAIR(TimeZone, Turkey, tr("Turkey")),
|
||||
PAIR(TimeZone, Uct, tr("UCT")),
|
||||
PAIR(TimeZone, Universal, tr("Universal")),
|
||||
PAIR(TimeZone, Utc, tr("UTC")),
|
||||
PAIR(TimeZone, WSu, tr("W-SU")),
|
||||
PAIR(TimeZone, Wet, tr("WET")),
|
||||
PAIR(TimeZone, Zulu, tr("Zulu")),
|
||||
}});
|
||||
translations->insert({Settings::EnumMetadata<Settings::AudioMode>::Index(),
|
||||
{
|
||||
PAIR(AudioMode, Mono, tr("Mono")),
|
||||
|
|
@ -803,9 +693,9 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
|
|||
}});
|
||||
|
||||
translations->insert({Settings::EnumMetadata<Settings::GameListMode>::Index(),
|
||||
{
|
||||
PAIR(GameListMode, TreeView, tr("Tree View")),
|
||||
PAIR(GameListMode, GridView, tr("Grid View")),
|
||||
{
|
||||
PAIR(GameListMode, TreeView, tr("Tree View")),
|
||||
PAIR(GameListMode, GridView, tr("Grid View")),
|
||||
}});
|
||||
|
||||
#undef PAIR
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings_enums.h"
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ using TranslationMap = std::map<u32, std::pair<QString, QString>>;
|
|||
using ComboboxTranslations = std::vector<std::pair<u32, QString>>;
|
||||
using ComboboxTranslationMap = std::map<u32, ComboboxTranslations>;
|
||||
|
||||
std::unique_ptr<TranslationMap> InitializeTranslations(QObject *parent);
|
||||
std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent);
|
||||
|
||||
std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent);
|
||||
|
||||
|
|
@ -39,15 +39,15 @@ static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map
|
|||
{Settings::ScalingFilter::Bilinear,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Bilinear"))},
|
||||
{Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Bicubic"))},
|
||||
{Settings::ScalingFilter::ZeroTangent, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Zero-Tangent"))},
|
||||
{Settings::ScalingFilter::ZeroTangent,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Zero-Tangent"))},
|
||||
{Settings::ScalingFilter::BSpline, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "B-Spline"))},
|
||||
{Settings::ScalingFilter::Mitchell, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Mitchell"))},
|
||||
{Settings::ScalingFilter::Spline1,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Spline-1"))},
|
||||
{Settings::ScalingFilter::Mitchell,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Mitchell"))},
|
||||
{Settings::ScalingFilter::Spline1, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Spline-1"))},
|
||||
{Settings::ScalingFilter::Gaussian,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Gaussian"))},
|
||||
{Settings::ScalingFilter::Lanczos,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Lanczos"))},
|
||||
{Settings::ScalingFilter::Lanczos, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Lanczos"))},
|
||||
{Settings::ScalingFilter::ScaleForce,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "ScaleForce"))},
|
||||
{Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR"))},
|
||||
|
|
@ -68,9 +68,12 @@ static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map = {
|
|||
|
||||
static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map = {
|
||||
{Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Vulkan"))},
|
||||
{Settings::RendererBackend::OpenGL_GLSL, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLSL"))},
|
||||
{Settings::RendererBackend::OpenGL_SPIRV, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL SPIRV"))},
|
||||
{Settings::RendererBackend::OpenGL_GLASM, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLASM"))},
|
||||
{Settings::RendererBackend::OpenGL_GLSL,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLSL"))},
|
||||
{Settings::RendererBackend::OpenGL_SPIRV,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL SPIRV"))},
|
||||
{Settings::RendererBackend::OpenGL_GLASM,
|
||||
QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "OpenGL GLASM"))},
|
||||
{Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Null"))},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -145,14 +145,15 @@ struct Values {
|
|||
// Linux/MinGW may support (requires libdl support)
|
||||
SwitchableSetting<bool> enable_gamemode{linkage,
|
||||
#ifndef _MSC_VER
|
||||
true,
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
false,
|
||||
#endif
|
||||
"enable_gamemode", Category::UiGeneral};
|
||||
"enable_gamemode", Category::UiGeneral};
|
||||
#ifdef __unix__
|
||||
SwitchableSetting<bool> gui_force_x11{linkage, false, "gui_force_x11", Category::UiGeneral};
|
||||
Setting<bool> gui_hide_backend_warning{linkage, false, "gui_hide_backend_warning", Category::UiGeneral};
|
||||
Setting<bool> gui_hide_backend_warning{linkage, false, "gui_hide_backend_warning",
|
||||
Category::UiGeneral};
|
||||
#endif
|
||||
|
||||
// Discord RPC
|
||||
|
|
@ -210,7 +211,8 @@ struct Values {
|
|||
Setting<u32> folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList};
|
||||
Setting<u8> row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList};
|
||||
Setting<u8> row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList};
|
||||
Setting<Settings::GameListMode> game_list_mode{linkage, Settings::GameListMode::TreeView, "game_list_mode", Category::UiGameList};
|
||||
Setting<Settings::GameListMode> game_list_mode{linkage, Settings::GameListMode::TreeView,
|
||||
"game_list_mode", Category::UiGameList};
|
||||
Setting<bool> show_game_name{linkage, true, "show_game_name", Category::UiGameList};
|
||||
|
||||
std::atomic_bool is_game_list_reload_pending{false};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
|
||||
|
|
@ -70,7 +70,9 @@ std::string DiscordImpl::GetGameString(const std::string& title) {
|
|||
}
|
||||
|
||||
static constexpr char DEFAULT_DISCORD_TEXT[] = "Eden is an emulator for the Nintendo Switch";
|
||||
static constexpr char DEFAULT_DISCORD_IMAGE[] = "https://git.eden-emu.dev/eden-emu/eden/raw/branch/master/dist/qt_themes/default/icons/256x256/eden.png";
|
||||
static constexpr char DEFAULT_DISCORD_IMAGE[] =
|
||||
"https://git.eden-emu.dev/eden-emu/eden/raw/branch/master/dist/qt_themes/default/icons/256x256/"
|
||||
"eden.png";
|
||||
|
||||
void DiscordImpl::UpdateGameStatus(bool use_default) {
|
||||
const std::string url = use_default ? std::string{DEFAULT_DISCORD_IMAGE} : game_url;
|
||||
|
|
@ -120,7 +122,9 @@ void DiscordImpl::Update() {
|
|||
return;
|
||||
}
|
||||
|
||||
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
DiscordRichPresence presence{};
|
||||
presence.largeImageKey = DEFAULT_DISCORD_IMAGE;
|
||||
presence.largeImageText = DEFAULT_DISCORD_TEXT;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
|
|
@ -9,9 +9,9 @@
|
|||
#ifdef __unix__
|
||||
#include <gamemode_client.h>
|
||||
#endif
|
||||
#include "qt_common/gamemode.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/gamemode.h"
|
||||
|
||||
namespace Common::FeralGamemode {
|
||||
|
||||
|
|
@ -49,4 +49,4 @@ void Stop() noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
} // namespace Common::Linux
|
||||
} // namespace Common::FeralGamemode
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "gui_settings.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "gui_settings.h"
|
||||
|
||||
namespace FS = Common::FS;
|
||||
|
||||
namespace GraphicsBackend {
|
||||
|
||||
QString GuiConfigPath() {
|
||||
return QString::fromStdString(FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "gui_config.ini"));
|
||||
return QString::fromStdString(
|
||||
FS::PathToUTF8String(FS::GetEdenPath(FS::EdenPath::ConfigDir) / "gui_config.ini"));
|
||||
}
|
||||
|
||||
void SetForceX11(bool state) {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_common.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/ryujinx_compat.h"
|
||||
#include "qt_common.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QStringLiteral>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "qt_common/abstract/frontend.h"
|
||||
#include "qt_common/qt_string_lookup.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <JlCompress.h>
|
||||
|
||||
#if !defined(WIN32) && !defined(__APPLE__)
|
||||
|
|
@ -27,18 +22,13 @@
|
|||
|
||||
namespace QtCommon {
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
QWidget* rootObject = nullptr;
|
||||
#else
|
||||
QObject* rootObject = nullptr;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<Core::System> system = nullptr;
|
||||
std::shared_ptr<FileSys::RealVfsFilesystem> vfs = nullptr;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> provider = nullptr;
|
||||
|
||||
Core::Frontend::WindowSystemType GetWindowSystemType()
|
||||
{
|
||||
Core::Frontend::WindowSystemType GetWindowSystemType() {
|
||||
// Determine WSI type based on Qt platform.
|
||||
QString platform_name = QGuiApplication::platformName();
|
||||
if (platform_name == QStringLiteral("windows"))
|
||||
|
|
@ -60,8 +50,7 @@ Core::Frontend::WindowSystemType GetWindowSystemType()
|
|||
return Core::Frontend::WindowSystemType::Windows;
|
||||
} // namespace Core::Frontend::WindowSystemType
|
||||
|
||||
Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
||||
{
|
||||
Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
|
||||
Core::Frontend::EmuWindow::WindowSystemInfo wsi;
|
||||
wsi.type = GetWindowSystemType();
|
||||
|
||||
|
|
@ -69,8 +58,8 @@ 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__)
|
||||
id layer = reinterpret_cast<id (*) (id, SEL)>(
|
||||
objc_msgSend)(reinterpret_cast<id>(window->winId()), sel_registerName("layer"));
|
||||
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.
|
||||
|
|
@ -78,15 +67,20 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
|||
id metal_layer = nullptr;
|
||||
|
||||
if (layer) {
|
||||
if (reinterpret_cast<bool (*) (id, SEL, Class)>(objc_msgSend)(layer, sel_registerName("isKindOfClass:"), metal_layer_class)) {
|
||||
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"));
|
||||
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"));
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
|
|
@ -108,30 +102,22 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window)
|
|||
return wsi;
|
||||
}
|
||||
|
||||
const QString tr(const char* str)
|
||||
{
|
||||
const QString tr(const char* str) {
|
||||
return QGuiApplication::tr(str);
|
||||
}
|
||||
|
||||
const QString tr(const std::string& str)
|
||||
{
|
||||
const QString tr(const std::string& str) {
|
||||
return QGuiApplication::tr(str.c_str());
|
||||
}
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
void Init(QWidget* root)
|
||||
#else
|
||||
void Init(QObject* root)
|
||||
#endif
|
||||
{
|
||||
void Init(QWidget* root) {
|
||||
system = std::make_unique<Core::System>();
|
||||
rootObject = root;
|
||||
vfs = std::make_unique<FileSys::RealVfsFilesystem>();
|
||||
provider = std::make_unique<FileSys::ManualContentProvider>();
|
||||
}
|
||||
|
||||
std::filesystem::path GetEdenCommand()
|
||||
{
|
||||
std::filesystem::path GetEdenCommand() {
|
||||
std::filesystem::path command;
|
||||
|
||||
// TODO: flatpak?
|
||||
|
|
|
|||
|
|
@ -1,24 +1,20 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_COMMON_H
|
||||
#define QT_COMMON_H
|
||||
|
||||
#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 <memory>
|
||||
|
||||
#include <core/file_sys/vfs/vfs_real.h>
|
||||
|
||||
namespace QtCommon {
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
extern QWidget *rootObject;
|
||||
#else
|
||||
extern QObject *rootObject;
|
||||
#endif
|
||||
extern QWidget* rootObject;
|
||||
|
||||
extern std::unique_ptr<Core::System> system;
|
||||
extern std::shared_ptr<FileSys::RealVfsFilesystem> vfs;
|
||||
|
|
@ -28,16 +24,12 @@ typedef std::function<bool(std::size_t, std::size_t)> QtProgressCallback;
|
|||
|
||||
Core::Frontend::WindowSystemType GetWindowSystemType();
|
||||
|
||||
Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow *window);
|
||||
Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window);
|
||||
|
||||
#ifdef YUZU_QT_WIDGETS
|
||||
void Init(QWidget *root);
|
||||
#else
|
||||
void Init(QObject *root);
|
||||
#endif
|
||||
void Init(QWidget* root);
|
||||
|
||||
const QString tr(const char *str);
|
||||
const QString tr(const std::string &str);
|
||||
const QString tr(const char* str);
|
||||
const QString tr(const std::string& str);
|
||||
|
||||
std::filesystem::path GetEdenCommand();
|
||||
} // namespace QtCommon
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@
|
|||
/// Small helper to look up enums.
|
||||
/// res = the result code
|
||||
/// base = the base matching value in the StringKey table
|
||||
#define LOOKUP_ENUM(res, base) QtCommon::StringLookup::Lookup( \
|
||||
QtCommon::StringLookup::StringKey((int) res + (int) QtCommon::StringLookup::base))
|
||||
#define LOOKUP_ENUM(res, base) \
|
||||
QtCommon::StringLookup::Lookup( \
|
||||
QtCommon::StringLookup::StringKey((int)res + (int)QtCommon::StringLookup::base))
|
||||
|
||||
namespace QtCommon::StringLookup {
|
||||
|
||||
|
|
@ -111,16 +112,14 @@ static const constexpr frozen::map<StringKey, frozen::string, 29> strings = {
|
|||
QT_TR_NOOP("Would you like to migrate your data for use in Eden?\n"
|
||||
"Select the corresponding button to migrate data from that emulator.\n"
|
||||
"This may take a while.")},
|
||||
{MigrationTooltipClearShader,
|
||||
QT_TR_NOOP("Clearing shader cache is recommended for all "
|
||||
"users.\nDo not uncheck unless you know what "
|
||||
"you're doing.")},
|
||||
{MigrationTooltipClearShader, QT_TR_NOOP("Clearing shader cache is recommended for all "
|
||||
"users.\nDo not uncheck unless you know what "
|
||||
"you're doing.")},
|
||||
{MigrationTooltipKeepOld,
|
||||
QT_TR_NOOP("Keeps the old data directory. This is recommended if you aren't\n"
|
||||
"space-constrained and want to keep separate data for the old emulator.")},
|
||||
{MigrationTooltipClearOld,
|
||||
QT_TR_NOOP("Deletes the old data directory.\nThis is recommended on "
|
||||
"devices with space constraints.")},
|
||||
{MigrationTooltipClearOld, QT_TR_NOOP("Deletes the old data directory.\nThis is recommended on "
|
||||
"devices with space constraints.")},
|
||||
{MigrationTooltipLinkOld,
|
||||
QT_TR_NOOP("Creates a filesystem link between the old directory and Eden directory.\n"
|
||||
"This is recommended if you want to share data between emulators.")},
|
||||
|
|
@ -135,8 +134,7 @@ static const constexpr frozen::map<StringKey, frozen::string, 29> strings = {
|
|||
{RyujinxNoSaveId, QT_TR_NOOP("Title %1 not found in Ryujinx title database.")},
|
||||
};
|
||||
|
||||
static inline const QString Lookup(StringKey key)
|
||||
{
|
||||
static inline const QString Lookup(StringKey key) {
|
||||
return QObject::tr(strings.at(key).data());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_APPLET_UTIL_H
|
||||
#define QT_APPLET_UTIL_H
|
||||
|
||||
// TODO
|
||||
namespace QtCommon::Applets {
|
||||
|
||||
}
|
||||
namespace QtCommon::Applets {}
|
||||
#endif // QT_APPLET_UTIL_H
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "compress.h"
|
||||
|
|
@ -10,11 +10,8 @@
|
|||
/** This is a modified version of JlCompress **/
|
||||
namespace QtCommon::Compress {
|
||||
|
||||
bool compressDir(QString fileCompressed,
|
||||
QString dir,
|
||||
const Options &options,
|
||||
QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
bool compressDir(QString fileCompressed, QString dir, const Options& options,
|
||||
QtCommon::QtProgressCallback callback) {
|
||||
// Create zip
|
||||
QuaZip zip(fileCompressed);
|
||||
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
|
||||
|
|
@ -26,8 +23,7 @@ bool compressDir(QString fileCompressed,
|
|||
// See how big the overall fs structure is
|
||||
// good approx. of total progress
|
||||
// TODO(crueter): QDirListing impl... or fs::recursive_dir_iterator
|
||||
QDirIterator iter(dir,
|
||||
QDir::NoDotAndDotDot | QDir::Hidden | QDir::Files,
|
||||
QDirIterator iter(dir, QDir::NoDotAndDotDot | QDir::Hidden | QDir::Files,
|
||||
QDirIterator::Subdirectories);
|
||||
|
||||
std::size_t total = 0;
|
||||
|
|
@ -54,14 +50,8 @@ bool compressDir(QString fileCompressed,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool compressSubDir(QuaZip *zip,
|
||||
QString dir,
|
||||
QString origDir,
|
||||
const Options &options,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtProgressCallback callback)
|
||||
{
|
||||
bool compressSubDir(QuaZip* zip, QString dir, QString origDir, const Options& options,
|
||||
std::size_t total, std::size_t& progress, QtProgressCallback callback) {
|
||||
// zip: object where to add the file
|
||||
// dir: current real directory
|
||||
// origDir: original real directory
|
||||
|
|
@ -69,22 +59,20 @@ bool compressSubDir(QuaZip *zip,
|
|||
|
||||
if (!zip)
|
||||
return false;
|
||||
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend
|
||||
&& zip->getMode() != QuaZip::mdAdd)
|
||||
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend &&
|
||||
zip->getMode() != QuaZip::mdAdd)
|
||||
return false;
|
||||
|
||||
QDir directory(dir);
|
||||
if (!directory.exists())
|
||||
return false;
|
||||
|
||||
|
||||
QDir origDirectory(origDir);
|
||||
if (dir != origDir) {
|
||||
QuaZipFile dirZipFile(zip);
|
||||
std::unique_ptr<QuaZipNewInfo> qzni;
|
||||
qzni = std::make_unique<QuaZipNewInfo>(origDirectory.relativeFilePath(dir)
|
||||
+ QLatin1String("/"),
|
||||
dir);
|
||||
qzni = std::make_unique<QuaZipNewInfo>(
|
||||
origDirectory.relativeFilePath(dir) + QLatin1String("/"), dir);
|
||||
if (!dirZipFile.open(QIODevice::WriteOnly, *qzni, nullptr, 0, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -92,18 +80,18 @@ bool compressSubDir(QuaZip *zip,
|
|||
}
|
||||
|
||||
// For each subfolder
|
||||
QFileInfoList subfiles = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot
|
||||
| QDir::Hidden | QDir::Dirs);
|
||||
for (const auto &file : std::as_const(subfiles)) {
|
||||
if (!compressSubDir(
|
||||
zip, file.absoluteFilePath(), origDir, options, total, progress, callback)) {
|
||||
QFileInfoList subfiles =
|
||||
directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::Dirs);
|
||||
for (const auto& file : std::as_const(subfiles)) {
|
||||
if (!compressSubDir(zip, file.absoluteFilePath(), origDir, options, total, progress,
|
||||
callback)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// For each file in directory
|
||||
QFileInfoList files = directory.entryInfoList(QDir::Hidden | QDir::Files);
|
||||
for (const auto &file : std::as_const(files)) {
|
||||
for (const auto& file : std::as_const(files)) {
|
||||
// If it's not a file or it's the compressed file being created
|
||||
if (!file.isFile() || file.absoluteFilePath() == zip->getZipName())
|
||||
continue;
|
||||
|
|
@ -112,7 +100,8 @@ bool compressSubDir(QuaZip *zip,
|
|||
QString filename = origDirectory.relativeFilePath(file.absoluteFilePath());
|
||||
|
||||
// Compress the file
|
||||
if (!compressFile(zip, file.absoluteFilePath(), filename, options, total, progress, callback)) {
|
||||
if (!compressFile(zip, file.absoluteFilePath(), filename, options, total, progress,
|
||||
callback)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -120,40 +109,26 @@ bool compressSubDir(QuaZip *zip,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool compressFile(QuaZip *zip,
|
||||
QString fileName,
|
||||
QString fileDest,
|
||||
const Options &options,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
bool compressFile(QuaZip* zip, QString fileName, QString fileDest, const Options& options,
|
||||
std::size_t total, std::size_t& progress, QtCommon::QtProgressCallback callback) {
|
||||
// zip: object where to add the file
|
||||
// fileName: real file name
|
||||
// fileDest: file name inside the zip object
|
||||
|
||||
if (!zip)
|
||||
return false;
|
||||
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend
|
||||
&& zip->getMode() != QuaZip::mdAdd)
|
||||
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend &&
|
||||
zip->getMode() != QuaZip::mdAdd)
|
||||
return false;
|
||||
|
||||
QuaZipFile outFile(zip);
|
||||
if (options.getDateTime().isNull()) {
|
||||
if (!outFile.open(QIODevice::WriteOnly,
|
||||
QuaZipNewInfo(fileDest, fileName),
|
||||
nullptr,
|
||||
0,
|
||||
options.getCompressionMethod(),
|
||||
options.getCompressionLevel()))
|
||||
if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, fileName), nullptr, 0,
|
||||
options.getCompressionMethod(), options.getCompressionLevel()))
|
||||
return false;
|
||||
} else {
|
||||
if (!outFile.open(QIODevice::WriteOnly,
|
||||
QuaZipNewInfo(fileDest, fileName),
|
||||
nullptr,
|
||||
0,
|
||||
options.getCompressionMethod(),
|
||||
options.getCompressionLevel()))
|
||||
if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, fileName), nullptr, 0,
|
||||
options.getCompressionMethod(), options.getCompressionLevel()))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +146,8 @@ bool compressFile(QuaZip *zip,
|
|||
if (!inFile.open(QIODevice::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
if (!copyData(inFile, outFile, total, progress, callback) || outFile.getZipError() != UNZ_OK) {
|
||||
if (!copyData(inFile, outFile, total, progress, callback) ||
|
||||
outFile.getZipError() != UNZ_OK) {
|
||||
return false;
|
||||
}
|
||||
inFile.close();
|
||||
|
|
@ -181,12 +157,8 @@ bool compressFile(QuaZip *zip,
|
|||
return outFile.getZipError() == UNZ_OK;
|
||||
}
|
||||
|
||||
bool copyData(QIODevice &inFile,
|
||||
QIODevice &outFile,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtProgressCallback callback)
|
||||
{
|
||||
bool copyData(QIODevice& inFile, QIODevice& outFile, std::size_t total, std::size_t& progress,
|
||||
QtProgressCallback callback) {
|
||||
while (!inFile.atEnd()) {
|
||||
char buf[4096];
|
||||
qint64 readLen = inFile.read(buf, 4096);
|
||||
|
|
@ -203,15 +175,13 @@ bool copyData(QIODevice &inFile,
|
|||
return true;
|
||||
}
|
||||
|
||||
QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback) {
|
||||
// Open zip
|
||||
QuaZip zip(fileCompressed);
|
||||
return extractDir(zip, dir, callback);
|
||||
}
|
||||
|
||||
QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
QStringList extractDir(QuaZip& zip, const QString& dir, QtCommon::QtProgressCallback callback) {
|
||||
if (!zip.open(QuaZip::mdUnzip)) {
|
||||
return QStringList();
|
||||
}
|
||||
|
|
@ -226,7 +196,7 @@ QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCall
|
|||
}
|
||||
|
||||
std::size_t total = 0;
|
||||
for (const QuaZipFileInfo64 &info : zip.getFileInfoList64()) {
|
||||
for (const QuaZipFileInfo64& info : zip.getFileInfoList64()) {
|
||||
total += info.uncompressedSize;
|
||||
}
|
||||
|
||||
|
|
@ -256,13 +226,8 @@ QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCall
|
|||
return extracted;
|
||||
}
|
||||
|
||||
bool extractFile(QuaZip *zip,
|
||||
QString fileName,
|
||||
QString fileDest,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback)
|
||||
{
|
||||
bool extractFile(QuaZip* zip, QString fileName, QString fileDest, std::size_t total,
|
||||
std::size_t& progress, QtCommon::QtProgressCallback callback) {
|
||||
// zip: object where to add the file
|
||||
// filename: real file name
|
||||
// fileincompress: file name of the compressed file
|
||||
|
|
@ -334,8 +299,7 @@ bool extractFile(QuaZip *zip,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool removeFile(QStringList listFile)
|
||||
{
|
||||
bool removeFile(QStringList listFile) {
|
||||
bool ret = true;
|
||||
// For each file
|
||||
for (int i = 0; i < listFile.count(); i++) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include "qt_common/qt_common.h"
|
||||
#include <quazip.h>
|
||||
#include <zlib.h>
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
/** This is a modified version of JlCompress **/
|
||||
namespace QtCommon::Compress {
|
||||
|
|
@ -15,49 +15,49 @@ namespace QtCommon::Compress {
|
|||
class Options {
|
||||
public:
|
||||
/**
|
||||
* The enum values refer to the comments in the open function of the quazipfile.h file.
|
||||
*
|
||||
* The value is represented by two hexadecimal characters,
|
||||
* the left character indicating the compression method,
|
||||
* and the right character indicating the compression level.
|
||||
*
|
||||
* method == 0 indicates that the file is not compressed but rather stored as is.
|
||||
* method == 8(Z_DEFLATED) indicates that zlib compression is used.
|
||||
*
|
||||
* A higher value of level indicates a smaller size of the compressed file,
|
||||
* although it also implies more time consumed during the compression process.
|
||||
*/
|
||||
enum CompressionStrategy
|
||||
{
|
||||
* The enum values refer to the comments in the open function of the quazipfile.h file.
|
||||
*
|
||||
* The value is represented by two hexadecimal characters,
|
||||
* the left character indicating the compression method,
|
||||
* and the right character indicating the compression level.
|
||||
*
|
||||
* method == 0 indicates that the file is not compressed but rather stored as is.
|
||||
* method == 8(Z_DEFLATED) indicates that zlib compression is used.
|
||||
*
|
||||
* A higher value of level indicates a smaller size of the compressed file,
|
||||
* although it also implies more time consumed during the compression process.
|
||||
*/
|
||||
enum CompressionStrategy {
|
||||
/// Storage without compression
|
||||
Storage = 0x00, // Z_NO_COMPRESSION 0
|
||||
/// The fastest compression speed
|
||||
Fastest = 0x81, // Z_BEST_SPEED 1
|
||||
/// Relatively fast compression speed
|
||||
Faster = 0x83,
|
||||
Storage = 0x00, // Z_NO_COMPRESSION 0
|
||||
/// The fastest compression speed
|
||||
Fastest = 0x81, // Z_BEST_SPEED 1
|
||||
/// Relatively fast compression speed
|
||||
Faster = 0x83,
|
||||
/// Standard compression speed and ratio
|
||||
Standard = 0x86,
|
||||
/// Better compression ratio
|
||||
Better = 0x87,
|
||||
Better = 0x87,
|
||||
/// The best compression ratio
|
||||
Best = 0x89, // Z_BEST_COMPRESSION 9
|
||||
/// The default compression strategy, according to the open function of quazipfile.h,
|
||||
/// the value of method is Z_DEFLATED, and the value of level is Z_DEFAULT_COMPRESSION -1 (equals lvl 6)
|
||||
Default = 0xff
|
||||
Best = 0x89, // Z_BEST_COMPRESSION 9
|
||||
/// The default compression strategy, according to the open function of
|
||||
/// quazipfile.h, the value of method is Z_DEFLATED, and the value of level is
|
||||
/// Z_DEFAULT_COMPRESSION -1 (equals lvl 6)
|
||||
Default = 0xff
|
||||
};
|
||||
|
||||
public:
|
||||
explicit Options(const CompressionStrategy& strategy)
|
||||
: m_compressionStrategy(strategy) {}
|
||||
explicit Options(const CompressionStrategy& strategy) : m_compressionStrategy(strategy) {}
|
||||
|
||||
explicit Options(const QDateTime& dateTime = QDateTime(), const CompressionStrategy& strategy = Default)
|
||||
explicit Options(const QDateTime& dateTime = QDateTime(),
|
||||
const CompressionStrategy& strategy = Default)
|
||||
: m_dateTime(dateTime), m_compressionStrategy(strategy) {}
|
||||
|
||||
QDateTime getDateTime() const {
|
||||
return m_dateTime;
|
||||
}
|
||||
|
||||
void setDateTime(const QDateTime &dateTime) {
|
||||
void setDateTime(const QDateTime& dateTime) {
|
||||
m_dateTime = dateTime;
|
||||
}
|
||||
|
||||
|
|
@ -70,10 +70,11 @@ public:
|
|||
}
|
||||
|
||||
int getCompressionLevel() const {
|
||||
return m_compressionStrategy != Default ? m_compressionStrategy & 0x0f : Z_DEFAULT_COMPRESSION;
|
||||
return m_compressionStrategy != Default ? m_compressionStrategy & 0x0f
|
||||
: Z_DEFAULT_COMPRESSION;
|
||||
}
|
||||
|
||||
void setCompressionStrategy(const CompressionStrategy &strategy) {
|
||||
void setCompressionStrategy(const CompressionStrategy& strategy) {
|
||||
m_compressionStrategy = strategy;
|
||||
}
|
||||
|
||||
|
|
@ -89,34 +90,21 @@ private:
|
|||
* @param fileCompressed Destination file
|
||||
* @param dir The directory to compress
|
||||
* @param options Compression level, etc
|
||||
* @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled.
|
||||
* @param callback Callback that takes in two std::size_t (total, progress) and returns false
|
||||
* if the current operation should be cancelled.
|
||||
*/
|
||||
bool compressDir(QString fileCompressed,
|
||||
QString dir,
|
||||
const Options& options = Options(),
|
||||
bool compressDir(QString fileCompressed, QString dir, const Options& options = Options(),
|
||||
QtCommon::QtProgressCallback callback = {});
|
||||
|
||||
// Internal //
|
||||
bool compressSubDir(QuaZip *zip,
|
||||
QString dir,
|
||||
QString origDir,
|
||||
const Options &options,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
bool compressSubDir(QuaZip* zip, QString dir, QString origDir, const Options& options,
|
||||
std::size_t total, std::size_t& progress,
|
||||
QtCommon::QtProgressCallback callback);
|
||||
|
||||
bool compressFile(QuaZip *zip,
|
||||
QString fileName,
|
||||
QString fileDest,
|
||||
const Options &options,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback);
|
||||
bool compressFile(QuaZip* zip, QString fileName, QString fileDest, const Options& options,
|
||||
std::size_t total, std::size_t& progress, QtCommon::QtProgressCallback callback);
|
||||
|
||||
bool copyData(QIODevice &inFile,
|
||||
QIODevice &outFile,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
bool copyData(QIODevice& inFile, QIODevice& outFile, std::size_t total, std::size_t& progress,
|
||||
QtCommon::QtProgressCallback callback);
|
||||
|
||||
// Extract //
|
||||
|
|
@ -125,20 +113,18 @@ bool copyData(QIODevice &inFile,
|
|||
* @brief Extract a zip file and report its progress.
|
||||
* @param fileCompressed Compressed file
|
||||
* @param dir The directory to push the results to
|
||||
* @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled.
|
||||
* @param callback Callback that takes in two std::size_t (total, progress) and returns false
|
||||
* if the current operation should be cancelled.
|
||||
*/
|
||||
QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback = {});
|
||||
QStringList extractDir(QString fileCompressed, QString dir,
|
||||
QtCommon::QtProgressCallback callback = {});
|
||||
|
||||
// Internal //
|
||||
QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback);
|
||||
QStringList extractDir(QuaZip& zip, const QString& dir, QtCommon::QtProgressCallback callback);
|
||||
|
||||
bool extractFile(QuaZip *zip,
|
||||
QString fileName,
|
||||
QString fileDest,
|
||||
std::size_t total,
|
||||
std::size_t &progress,
|
||||
QtCommon::QtProgressCallback callback);
|
||||
bool extractFile(QuaZip* zip, QString fileName, QString fileDest, std::size_t total,
|
||||
std::size_t& progress, QtCommon::QtProgressCallback callback);
|
||||
|
||||
bool removeFile(QStringList listFile);
|
||||
|
||||
}
|
||||
} // namespace QtCommon::Compress
|
||||
|
|
|
|||
|
|
@ -12,63 +12,54 @@
|
|||
|
||||
#include "compress.h"
|
||||
#include "qt_common/abstract/frontend.h"
|
||||
#include "qt_common/abstract/qt_progress_dialog.h"
|
||||
#include "qt_common/abstract/progress.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
#include <JlCompress.h>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrentRun>
|
||||
#include <JlCompress.h>
|
||||
|
||||
namespace QtCommon::Content {
|
||||
|
||||
bool CheckGameFirmware(u64 program_id, QObject* parent)
|
||||
{
|
||||
if (FirmwareManager::GameRequiresFirmware(program_id)
|
||||
&& !FirmwareManager::CheckFirmwarePresence(*system)) {
|
||||
auto result = QtCommon::Frontend::ShowMessage(
|
||||
QMessageBox::Warning,
|
||||
bool CheckGameFirmware(u64 program_id) {
|
||||
if (FirmwareManager::GameRequiresFirmware(program_id) &&
|
||||
!FirmwareManager::CheckFirmwarePresence(*system)) {
|
||||
auto result = QtCommon::Frontend::Warning(
|
||||
tr("Game Requires Firmware"),
|
||||
tr("The game you are trying to launch requires firmware to boot or to get past the "
|
||||
"opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>"
|
||||
"dump and install firmware</a>, or press \"OK\" to launch anyways."),
|
||||
QMessageBox::Ok | QMessageBox::Cancel,
|
||||
parent);
|
||||
QtCommon::Frontend::Ok | QtCommon::Frontend::Cancel);
|
||||
|
||||
return result == QMessageBox::Ok;
|
||||
return result == QtCommon::Frontend::Ok;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallFirmware(const QString& location, bool recursive)
|
||||
{
|
||||
QtCommon::Frontend::QtProgressDialog progress(tr("Installing Firmware..."),
|
||||
tr("Cancel"),
|
||||
0,
|
||||
100,
|
||||
rootObject);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
progress.setAutoReset(false);
|
||||
progress.show();
|
||||
void InstallFirmware(const QString& location, bool recursive) {
|
||||
// Initialize a progress dialog.
|
||||
auto progress =
|
||||
QtCommon::Frontend::newProgressDialog(tr("Installing Firmware..."), tr("Cancel"), 0, 100);
|
||||
progress->show();
|
||||
|
||||
QGuiApplication::processEvents();
|
||||
|
||||
// Declare progress callback.
|
||||
auto callback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress.wasCanceled();
|
||||
QGuiApplication::processEvents();
|
||||
progress->setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress->wasCanceled();
|
||||
};
|
||||
|
||||
QString failedTitle = tr("Firmware Install Failed");
|
||||
QString successTitle = tr("Firmware Install Succeeded");
|
||||
QMessageBox::Icon icon;
|
||||
QtCommon::Frontend::Icon icon;
|
||||
FirmwareInstallResult result;
|
||||
|
||||
const auto ShowMessage = [&]() {
|
||||
QtCommon::Frontend::ShowMessage(icon,
|
||||
failedTitle,
|
||||
GetFirmwareInstallResultString(result));
|
||||
QtCommon::Frontend::ShowMessage(icon, failedTitle, GetFirmwareInstallResultString(result));
|
||||
};
|
||||
|
||||
LOG_INFO(Frontend, "Installing firmware from {}", location.toStdString());
|
||||
|
|
@ -93,28 +84,26 @@ void InstallFirmware(const QString& location, bool recursive)
|
|||
callback(100, 10);
|
||||
|
||||
if (recursive) {
|
||||
Common::FS::IterateDirEntriesRecursively(firmware_source_path,
|
||||
dir_callback,
|
||||
Common::FS::IterateDirEntriesRecursively(firmware_source_path, dir_callback,
|
||||
Common::FS::DirEntryFilter::File);
|
||||
} else {
|
||||
Common::FS::IterateDirEntries(firmware_source_path,
|
||||
dir_callback,
|
||||
Common::FS::IterateDirEntries(firmware_source_path, dir_callback,
|
||||
Common::FS::DirEntryFilter::File);
|
||||
}
|
||||
|
||||
if (out.size() <= 0) {
|
||||
result = FirmwareInstallResult::NoNCAs;
|
||||
icon = QMessageBox::Warning;
|
||||
icon = QtCommon::Frontend::Icon::Warning;
|
||||
ShowMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
// Locate and erase the content of nand/system/Content/registered/*.nca, if any.
|
||||
auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory();
|
||||
if (sysnand_content_vdir->IsWritable()
|
||||
&& !sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
|
||||
if (sysnand_content_vdir->IsWritable() &&
|
||||
!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
|
||||
result = FirmwareInstallResult::FailedDelete;
|
||||
icon = QMessageBox::Critical;
|
||||
icon = QtCommon::Frontend::Icon::Critical;
|
||||
ShowMessage();
|
||||
return;
|
||||
}
|
||||
|
|
@ -130,22 +119,20 @@ void InstallFirmware(const QString& location, bool recursive)
|
|||
int i = 0;
|
||||
for (const auto& firmware_src_path : out) {
|
||||
i++;
|
||||
auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(),
|
||||
FileSys::OpenMode::Read);
|
||||
auto firmware_dst_vfile = firmware_vdir->CreateFileRelative(
|
||||
firmware_src_path.filename().string());
|
||||
auto firmware_src_vfile =
|
||||
vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read);
|
||||
auto firmware_dst_vfile =
|
||||
firmware_vdir->CreateFileRelative(firmware_src_path.filename().string());
|
||||
|
||||
if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) {
|
||||
LOG_ERROR(Frontend,
|
||||
"Failed to copy firmware file {} to {} in registered folder!",
|
||||
firmware_src_path.generic_string(),
|
||||
firmware_src_path.filename().string());
|
||||
LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!",
|
||||
firmware_src_path.generic_string(), firmware_src_path.filename().string());
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (callback(100, 20 + static_cast<int>(((i) / static_cast<float>(out.size())) * 70.0))) {
|
||||
result = FirmwareInstallResult::FailedCorrupted;
|
||||
icon = QMessageBox::Warning;
|
||||
icon = QtCommon::Frontend::Icon::Warning;
|
||||
ShowMessage();
|
||||
return;
|
||||
}
|
||||
|
|
@ -153,7 +140,7 @@ void InstallFirmware(const QString& location, bool recursive)
|
|||
|
||||
if (!success) {
|
||||
result = FirmwareInstallResult::FailedCopy;
|
||||
icon = QMessageBox::Critical;
|
||||
icon = QtCommon::Frontend::Icon::Critical;
|
||||
ShowMessage();
|
||||
return;
|
||||
}
|
||||
|
|
@ -162,39 +149,37 @@ void InstallFirmware(const QString& location, bool recursive)
|
|||
system->GetFileSystemController().CreateFactories(*vfs);
|
||||
|
||||
auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(90 + static_cast<int>((processed_size * 10) / total_size));
|
||||
return progress.wasCanceled();
|
||||
QGuiApplication::processEvents();
|
||||
progress->setValue(90 + static_cast<int>((processed_size * 10) / total_size));
|
||||
return progress->wasCanceled();
|
||||
};
|
||||
|
||||
auto results = ContentManager::VerifyInstalledContents(*QtCommon::system,
|
||||
*QtCommon::provider,
|
||||
VerifyFirmwareCallback,
|
||||
true);
|
||||
auto results = ContentManager::VerifyInstalledContents(*QtCommon::system, *QtCommon::provider,
|
||||
VerifyFirmwareCallback, true);
|
||||
|
||||
if (results.size() > 0) {
|
||||
const auto failed_names = QString::fromStdString(
|
||||
fmt::format("{}", fmt::join(results, "\n")));
|
||||
progress.close();
|
||||
const auto failed_names =
|
||||
QString::fromStdString(fmt::format("{}", fmt::join(results, "\n")));
|
||||
progress->close();
|
||||
QtCommon::Frontend::Critical(
|
||||
tr("Firmware integrity verification failed!"),
|
||||
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
|
||||
return;
|
||||
}
|
||||
|
||||
progress.close();
|
||||
progress->close();
|
||||
QGuiApplication::processEvents();
|
||||
|
||||
const auto pair = FirmwareManager::GetFirmwareVersion(*system);
|
||||
const auto firmware_data = pair.first;
|
||||
const std::string display_version(firmware_data.display_version.data());
|
||||
|
||||
result = FirmwareInstallResult::Success;
|
||||
QtCommon::Frontend::Information(successTitle,
|
||||
GetFirmwareInstallResultString(result).arg(
|
||||
QString::fromStdString(display_version)));
|
||||
QtCommon::Frontend::Information(successTitle, GetFirmwareInstallResultString(result).arg(
|
||||
QString::fromStdString(display_version)));
|
||||
}
|
||||
|
||||
QString UnzipFirmwareToTmp(const QString& location)
|
||||
{
|
||||
QString UnzipFirmwareToTmp(const QString& location) {
|
||||
namespace fs = std::filesystem;
|
||||
fs::path tmp{fs::temp_directory_path()};
|
||||
|
||||
|
|
@ -219,59 +204,47 @@ QString UnzipFirmwareToTmp(const QString& location)
|
|||
}
|
||||
|
||||
// Content //
|
||||
void VerifyGameContents(const std::string& game_path)
|
||||
{
|
||||
QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."),
|
||||
tr("Cancel"),
|
||||
0,
|
||||
100,
|
||||
rootObject);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
progress.setAutoReset(false);
|
||||
void VerifyGameContents(const std::string& game_path) {
|
||||
auto progress =
|
||||
QtCommon::Frontend::newProgressDialog(tr("Verifying integrity..."), tr("Cancel"), 0, 100);
|
||||
progress->show();
|
||||
|
||||
QGuiApplication::processEvents();
|
||||
|
||||
const auto callback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress.wasCanceled();
|
||||
QGuiApplication::processEvents();
|
||||
progress->setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress->wasCanceled();
|
||||
};
|
||||
|
||||
const auto result = ContentManager::VerifyGameContents(*system, game_path, callback);
|
||||
|
||||
switch (result) {
|
||||
case ContentManager::GameVerificationResult::Success:
|
||||
QtCommon::Frontend::Information(rootObject,
|
||||
tr("Integrity verification succeeded!"),
|
||||
QtCommon::Frontend::Information(rootObject, tr("Integrity verification succeeded!"),
|
||||
tr("The operation completed successfully."));
|
||||
break;
|
||||
case ContentManager::GameVerificationResult::Failed:
|
||||
QtCommon::Frontend::Critical(rootObject,
|
||||
tr("Integrity verification failed!"),
|
||||
QtCommon::Frontend::Critical(rootObject, tr("Integrity verification failed!"),
|
||||
tr("File contents may be corrupt or missing."));
|
||||
break;
|
||||
case ContentManager::GameVerificationResult::NotImplemented:
|
||||
QtCommon::Frontend::Warning(
|
||||
rootObject,
|
||||
tr("Integrity verification couldn't be performed"),
|
||||
rootObject, tr("Integrity verification couldn't be performed"),
|
||||
tr("Firmware installation cancelled, firmware may be in a bad state or corrupted. "
|
||||
"File contents could not be checked for validity."));
|
||||
}
|
||||
}
|
||||
|
||||
void InstallKeys()
|
||||
{
|
||||
const QString key_source_location
|
||||
= QtCommon::Frontend::GetOpenFileName(tr("Select Dumped Keys Location"),
|
||||
{},
|
||||
QStringLiteral("Decryption Keys (*.keys)"),
|
||||
{},
|
||||
QtCommon::Frontend::Option::ReadOnly);
|
||||
void InstallKeys() {
|
||||
const QString key_source_location = QtCommon::Frontend::GetOpenFileName(
|
||||
tr("Select Dumped Keys Location"), {}, QStringLiteral("Decryption Keys (*.keys)"), {});
|
||||
|
||||
if (key_source_location.isEmpty())
|
||||
return;
|
||||
|
||||
FirmwareManager::KeyInstallResult result
|
||||
= FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys");
|
||||
FirmwareManager::KeyInstallResult result =
|
||||
FirmwareManager::InstallKeys(key_source_location.toStdString(), "keys");
|
||||
|
||||
system->GetFileSystemController().CreateFactories(*QtCommon::vfs);
|
||||
|
||||
|
|
@ -286,46 +259,42 @@ void InstallKeys()
|
|||
}
|
||||
}
|
||||
|
||||
void VerifyInstalledContents()
|
||||
{
|
||||
void VerifyInstalledContents() {
|
||||
// Initialize a progress dialog.
|
||||
QtCommon::Frontend::QtProgressDialog progress(tr("Verifying integrity..."),
|
||||
tr("Cancel"),
|
||||
0,
|
||||
100,
|
||||
rootObject);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(100);
|
||||
progress.setAutoClose(false);
|
||||
progress.setAutoReset(false);
|
||||
auto progress =
|
||||
QtCommon::Frontend::newProgressDialog(tr("Verifying integrity..."), tr("Cancel"), 0, 100);
|
||||
progress->show();
|
||||
|
||||
QGuiApplication::processEvents();
|
||||
|
||||
// Declare progress callback.
|
||||
auto QtProgressCallback = [&](size_t total_size, size_t processed_size) {
|
||||
progress.setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress.wasCanceled();
|
||||
QGuiApplication::processEvents();
|
||||
progress->setValue(static_cast<int>((processed_size * 100) / total_size));
|
||||
return progress->wasCanceled();
|
||||
};
|
||||
|
||||
const std::vector<std::string> result
|
||||
= ContentManager::VerifyInstalledContents(*QtCommon::system,
|
||||
*QtCommon::provider,
|
||||
QtProgressCallback);
|
||||
progress.close();
|
||||
const std::vector<std::string> result = ContentManager::VerifyInstalledContents(
|
||||
*QtCommon::system, *QtCommon::provider, QtProgressCallback);
|
||||
|
||||
progress->close();
|
||||
|
||||
if (result.empty()) {
|
||||
QtCommon::Frontend::Information(tr("Integrity verification succeeded!"),
|
||||
tr("The operation completed successfully."));
|
||||
} else {
|
||||
const auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
|
||||
const auto failed_names =
|
||||
QString::fromStdString(fmt::format("{}", fmt::join(result, "\n")));
|
||||
QtCommon::Frontend::Critical(
|
||||
tr("Integrity verification failed!"),
|
||||
tr("Verification failed for the following files:\n\n%1").arg(failed_names));
|
||||
}
|
||||
}
|
||||
|
||||
void FixProfiles()
|
||||
{
|
||||
void FixProfiles() {
|
||||
// Reset user save files after config is initialized and migration is done.
|
||||
// Doing it at init time causes profiles to read from the wrong place entirely if NAND dir is not default
|
||||
// Doing it at init time causes profiles to read from the wrong place entirely if NAND dir is
|
||||
// not default
|
||||
// TODO: better solution
|
||||
system->GetProfileManager().ResetUserSaveFile();
|
||||
std::vector<std::string> orphaned = system->GetProfileManager().FindOrphanedProfiles();
|
||||
|
|
@ -365,79 +334,66 @@ void FixProfiles()
|
|||
"%2<br><br>"
|
||||
"Click \"OK\" to open your save folder and fix up your profiles.<br>"
|
||||
"Hint: copy the contents of the largest or last-modified folder elsewhere, "
|
||||
"delete all orphaned profiles, and move your copied contents to the good profile.<br><br>"
|
||||
"Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br>")
|
||||
"delete all orphaned profiles, and move your copied contents to the good "
|
||||
"profile.<br><br>"
|
||||
"Still confused? See the <a "
|
||||
"href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/"
|
||||
"Orphaned.md'>help page</a>.<br>")
|
||||
.arg(qorphaned, qgood));
|
||||
|
||||
QtCommon::Game::OpenSaveFolder();
|
||||
}
|
||||
|
||||
void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id)
|
||||
{
|
||||
auto result = QtCommon::Frontend::Warning(tr("Really clear data?"),
|
||||
tr("Important data may be lost!"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id) {
|
||||
using namespace QtCommon::Frontend;
|
||||
auto result = Warning(tr("Really clear data?"), tr("Important data may be lost!"), Yes | No);
|
||||
|
||||
if (result != QMessageBox::Yes)
|
||||
if (result != Yes)
|
||||
return;
|
||||
|
||||
result = QtCommon::Frontend::Warning(
|
||||
tr("Are you REALLY sure?"),
|
||||
tr("Once deleted, your data will NOT come back!\n"
|
||||
"Only do this if you're 100% sure you want to delete this data."),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
result = Warning(tr("Are you REALLY sure?"),
|
||||
tr("Once deleted, your data will NOT come back!\n"
|
||||
"Only do this if you're 100% sure you want to delete this data."),
|
||||
Yes | No);
|
||||
|
||||
if (result != QMessageBox::Yes)
|
||||
if (result != Yes)
|
||||
return;
|
||||
|
||||
QtCommon::Frontend::QtProgressDialog dialog(tr("Clearing..."), QString(), 0, 0);
|
||||
dialog.show();
|
||||
auto dialog = newProgressDialog(tr("Clearing..."), QString(), 0, 0);
|
||||
dialog->show();
|
||||
|
||||
FrontendCommon::DataManager::ClearDir(dir, user_id);
|
||||
|
||||
dialog.close();
|
||||
dialog->close();
|
||||
}
|
||||
|
||||
void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir,
|
||||
const std::string& user_id,
|
||||
const QString& name,
|
||||
std::function<void()> callback)
|
||||
{
|
||||
void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::string& user_id,
|
||||
const QString& name, std::function<void()> callback) {
|
||||
using namespace QtCommon::Frontend;
|
||||
const std::string dir = FrontendCommon::DataManager::GetDataDirString(data_dir, user_id);
|
||||
|
||||
const QString zip_dump_location = GetSaveFileName(tr("Select Export Location"),
|
||||
tr("%1.zip").arg(name),
|
||||
tr("Zipped Archives (*.zip)"));
|
||||
const QString zip_dump_location = GetSaveFileName(
|
||||
tr("Select Export Location"), tr("%1.zip").arg(name), tr("Zipped Archives (*.zip)"));
|
||||
|
||||
if (zip_dump_location.isEmpty())
|
||||
return;
|
||||
|
||||
QtProgressDialog* progress = new QtProgressDialog(
|
||||
tr("Exporting data. This may take a while..."), tr("Cancel"), 0, 100, rootObject);
|
||||
auto progress = QtCommon::Frontend::newProgressDialogPtr(
|
||||
tr("Exporting data. This may take a while..."), tr("Cancel"), 0, 100);
|
||||
|
||||
progress->setWindowTitle(tr("Exporting"));
|
||||
progress->setWindowModality(Qt::WindowModal);
|
||||
progress->setMinimumDuration(100);
|
||||
progress->setAutoClose(false);
|
||||
progress->setAutoReset(false);
|
||||
progress->setTitle(tr("Exporting"));
|
||||
progress->show();
|
||||
|
||||
QGuiApplication::processEvents();
|
||||
|
||||
auto progress_callback = [=](size_t total_size, size_t processed_size) {
|
||||
QMetaObject::invokeMethod(progress,
|
||||
"setValue",
|
||||
Qt::DirectConnection,
|
||||
Q_ARG(int, static_cast<int>((processed_size * 100) / total_size)));
|
||||
QMetaObject::invokeMethod(
|
||||
progress, "setValue", Qt::DirectConnection,
|
||||
Q_ARG(int, static_cast<int>((processed_size * 100) / total_size)));
|
||||
return !progress->wasCanceled();
|
||||
};
|
||||
|
||||
QFuture<bool> future = QtConcurrent::run([=]() {
|
||||
return QtCommon::Compress::compressDir(zip_dump_location,
|
||||
QString::fromStdString(dir),
|
||||
QtCommon::Compress::Options(),
|
||||
progress_callback);
|
||||
return QtCommon::Compress::compressDir(zip_dump_location, QString::fromStdString(dir),
|
||||
QtCommon::Compress::Options(), progress_callback);
|
||||
});
|
||||
|
||||
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>(rootObject);
|
||||
|
|
@ -464,42 +420,32 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir data_dir,
|
|||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir,
|
||||
const std::string& user_id,
|
||||
std::function<void()> callback)
|
||||
{
|
||||
void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::string& user_id,
|
||||
std::function<void()> callback) {
|
||||
const std::string dir = FrontendCommon::DataManager::GetDataDirString(data_dir, user_id);
|
||||
|
||||
using namespace QtCommon::Frontend;
|
||||
|
||||
const QString zip_dump_location = GetOpenFileName(tr("Select Import Location"),
|
||||
{},
|
||||
tr("Zipped Archives (*.zip)"));
|
||||
const QString zip_dump_location =
|
||||
GetOpenFileName(tr("Select Import Location"), {}, tr("Zipped Archives (*.zip)"));
|
||||
|
||||
if (zip_dump_location.isEmpty())
|
||||
return;
|
||||
|
||||
StandardButton button = Warning(
|
||||
tr("Import Warning"),
|
||||
tr("All previous data in this directory will be deleted. Are you sure you wish to "
|
||||
"proceed?"),
|
||||
StandardButton::Yes | StandardButton::No);
|
||||
StandardButton button =
|
||||
Warning(tr("Import Warning"),
|
||||
tr("All previous data in this directory will be deleted. Are you sure you wish to "
|
||||
"proceed?"),
|
||||
StandardButton::Yes | StandardButton::No);
|
||||
|
||||
if (button != QMessageBox::Yes)
|
||||
if (button != QtCommon::Frontend::Yes)
|
||||
return;
|
||||
|
||||
QtProgressDialog* progress = new QtProgressDialog(
|
||||
tr("Importing data. This may take a while..."), tr("Cancel"), 0, 100, rootObject);
|
||||
QtProgressDialog* progress =
|
||||
newProgressDialogPtr(tr("Importing data. This may take a while..."), tr("Cancel"), 0, 100);
|
||||
|
||||
progress->setWindowTitle(tr("Importing"));
|
||||
progress->setWindowModality(Qt::WindowModal);
|
||||
progress->setMinimumDuration(100);
|
||||
progress->setAutoClose(false);
|
||||
progress->setAutoReset(false);
|
||||
progress->setTitle(tr("Importing"));
|
||||
progress->show();
|
||||
progress->setValue(0);
|
||||
|
||||
QGuiApplication::processEvents();
|
||||
|
||||
// to prevent GUI mangling we have to run this in a thread as well
|
||||
QFuture<bool> delete_future = QtConcurrent::run([=]() {
|
||||
|
|
@ -512,17 +458,14 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir,
|
|||
|
||||
QObject::connect(delete_watcher, &QFutureWatcher<bool>::finished, rootObject, [=]() {
|
||||
auto progress_callback = [=](size_t total_size, size_t processed_size) {
|
||||
QMetaObject::invokeMethod(progress,
|
||||
"setValue",
|
||||
Qt::DirectConnection,
|
||||
Q_ARG(int,
|
||||
static_cast<int>((processed_size * 100) / total_size)));
|
||||
QMetaObject::invokeMethod(
|
||||
progress, "setValue", Qt::DirectConnection,
|
||||
Q_ARG(int, static_cast<int>((processed_size * 100) / total_size)));
|
||||
return !progress->wasCanceled();
|
||||
};
|
||||
|
||||
QFuture<bool> future = QtConcurrent::run([=]() {
|
||||
return !QtCommon::Compress::extractDir(zip_dump_location,
|
||||
QString::fromStdString(dir),
|
||||
return !QtCommon::Compress::extractDir(zip_dump_location, QString::fromStdString(dir),
|
||||
progress_callback)
|
||||
.empty();
|
||||
});
|
||||
|
|
@ -553,4 +496,5 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir,
|
|||
});
|
||||
}
|
||||
|
||||
// TODO(crueter): Port InstallFirmware et al. from QML Branch
|
||||
} // namespace QtCommon::Content
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_CONTENT_UTIL_H
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
namespace QtCommon::Content {
|
||||
|
||||
//
|
||||
bool CheckGameFirmware(u64 program_id, QObject *parent);
|
||||
bool CheckGameFirmware(u64 program_id);
|
||||
|
||||
enum class FirmwareInstallResult {
|
||||
Success,
|
||||
|
|
@ -23,8 +23,7 @@ enum class FirmwareInstallResult {
|
|||
FailedCorrupted,
|
||||
};
|
||||
|
||||
inline const QString GetFirmwareInstallResultString(FirmwareInstallResult result)
|
||||
{
|
||||
inline const QString GetFirmwareInstallResultString(FirmwareInstallResult result) {
|
||||
return LOOKUP_ENUM(result, FwInstallSuccess);
|
||||
}
|
||||
|
||||
|
|
@ -33,30 +32,29 @@ inline const QString GetFirmwareInstallResultString(FirmwareInstallResult result
|
|||
* \param result The result code.
|
||||
* \return A string representation of the passed result code.
|
||||
*/
|
||||
inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult result)
|
||||
{
|
||||
inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult result) {
|
||||
return LOOKUP_ENUM(result, KeyInstallSuccess);
|
||||
}
|
||||
|
||||
void InstallFirmware(const QString &location, bool recursive);
|
||||
void InstallFirmware(const QString& location, bool recursive);
|
||||
|
||||
QString UnzipFirmwareToTmp(const QString &location);
|
||||
QString UnzipFirmwareToTmp(const QString& location);
|
||||
|
||||
// Keys //
|
||||
void InstallKeys();
|
||||
|
||||
// Content //
|
||||
void VerifyGameContents(const std::string &game_path);
|
||||
void VerifyGameContents(const std::string& game_path);
|
||||
void VerifyInstalledContents();
|
||||
|
||||
void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = "");
|
||||
void ExportDataDir(FrontendCommon::DataManager::DataDir dir,
|
||||
const std::string &user_id = "",
|
||||
const QString &name = QStringLiteral("export"),
|
||||
void ClearDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "");
|
||||
void ExportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "",
|
||||
const QString& name = QStringLiteral("export"),
|
||||
std::function<void()> callback = {});
|
||||
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "",
|
||||
std::function<void()> callback = {});
|
||||
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string &user_id = "", std::function<void()> callback = {});
|
||||
|
||||
// Profiles //
|
||||
void FixProfiles();
|
||||
}
|
||||
} // namespace QtCommon::Content
|
||||
#endif // QT_CONTENT_UTIL_H
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
|
|
@ -13,8 +13,7 @@ namespace fs = std::filesystem;
|
|||
|
||||
namespace QtCommon::FS {
|
||||
|
||||
void LinkRyujinx(std::filesystem::path &from, std::filesystem::path &to)
|
||||
{
|
||||
void LinkRyujinx(std::filesystem::path& from, std::filesystem::path& to) {
|
||||
std::error_code ec;
|
||||
|
||||
// "ignore" errors--if the dir fails to be deleted, error handling later will handle it
|
||||
|
|
@ -25,12 +24,12 @@ void LinkRyujinx(std::filesystem::path &from, std::filesystem::path &to)
|
|||
} else {
|
||||
QtCommon::Frontend::Critical(
|
||||
tr("Failed to link save data"),
|
||||
tr("Could not link directory:\n\t%1\nTo:\n\t%2").arg(QString::fromStdString(from.string()), QString::fromStdString(to.string())));
|
||||
tr("Could not link directory:\n\t%1\nTo:\n\t%2")
|
||||
.arg(QString::fromStdString(from.string()), QString::fromStdString(to.string())));
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir)
|
||||
{
|
||||
bool CheckUnlink(const fs::path& eden_dir, const fs::path& ryu_dir) {
|
||||
bool eden_link = Common::FS::IsSymlink(eden_dir);
|
||||
bool ryu_link = Common::FS::IsSymlink(ryu_dir);
|
||||
|
||||
|
|
@ -64,7 +63,7 @@ bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir)
|
|||
// NB: do NOT use remove_all, as Windows treats this as a remove_all to the target,
|
||||
// NOT the junction
|
||||
fs::remove(linked);
|
||||
} catch (std::exception &e) {
|
||||
} catch (std::exception& e) {
|
||||
QtCommon::Frontend::Critical(
|
||||
tr("Failed to unlink old directory"),
|
||||
tr("OS returned error: %1").arg(QString::fromStdString(e.what())));
|
||||
|
|
@ -74,7 +73,7 @@ bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir)
|
|||
// then COPY the other dir
|
||||
try {
|
||||
fs::copy(orig, linked, fs::copy_options::recursive);
|
||||
} catch (std::exception &e) {
|
||||
} catch (std::exception& e) {
|
||||
QtCommon::Frontend::Critical(
|
||||
tr("Failed to copy save data"),
|
||||
tr("OS returned error: %1").arg(QString::fromStdString(e.what())));
|
||||
|
|
@ -87,8 +86,7 @@ bool CheckUnlink(const fs::path &eden_dir, const fs::path &ryu_dir)
|
|||
return true;
|
||||
}
|
||||
|
||||
const fs::path GetRyujinxSavePath(const fs::path &path_hint, const u64 &program_id)
|
||||
{
|
||||
const fs::path GetRyujinxSavePath(const fs::path& path_hint, const u64& program_id) {
|
||||
auto ryu_path = path_hint;
|
||||
|
||||
auto kvdb_path = Common::FS::GetKvdbPath(ryu_path);
|
||||
|
|
@ -99,10 +97,13 @@ const fs::path GetRyujinxSavePath(const fs::path &path_hint, const u64 &program_
|
|||
tr("Could not find Ryujinx installation"),
|
||||
tr("Could not find a valid Ryujinx installation. This may typically occur if you are "
|
||||
"using Ryujinx in portable mode.\n\nWould you like to manually select a portable "
|
||||
"folder to use?"), StandardButton::Yes | StandardButton::No);
|
||||
"folder to use?"),
|
||||
StandardButton::Yes | StandardButton::No);
|
||||
|
||||
if (res == StandardButton::Yes) {
|
||||
auto selected_path = GetExistingDirectory(tr("Ryujinx Portable Location"), QDir::homePath()).toStdString();
|
||||
auto selected_path =
|
||||
GetExistingDirectory(tr("Ryujinx Portable Location"), QDir::homePath())
|
||||
.toStdString();
|
||||
if (selected_path.empty())
|
||||
return fs::path{};
|
||||
|
||||
|
|
@ -131,7 +132,7 @@ const fs::path GetRyujinxSavePath(const fs::path &path_hint, const u64 &program_
|
|||
Common::FS::IMENReadResult res = Common::FS::ReadKvdb(kvdb_path, imens);
|
||||
|
||||
if (res == Common::FS::IMENReadResult::Success) {
|
||||
for (const Common::FS::IMEN &imen : imens) {
|
||||
for (const Common::FS::IMEN& imen : imens) {
|
||||
if (imen.title_id == program_id)
|
||||
return Common::FS::GetRyuSavePath(ryu_path, imen.save_id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include "common/common_types.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace QtCommon::FS {
|
||||
|
||||
void LinkRyujinx(std::filesystem::path &from, std::filesystem::path &to);
|
||||
const std::filesystem::path GetRyujinxSavePath(const std::filesystem::path &path_hint, const u64 &program_id);
|
||||
void LinkRyujinx(std::filesystem::path& from, std::filesystem::path& to);
|
||||
const std::filesystem::path GetRyujinxSavePath(const std::filesystem::path& path_hint,
|
||||
const u64& program_id);
|
||||
|
||||
/// returns FALSE if the dirs are NOT linked
|
||||
bool CheckUnlink(const std::filesystem::path& eden_dir,
|
||||
const std::filesystem::path& ryu_dir);
|
||||
bool CheckUnlink(const std::filesystem::path& eden_dir, const std::filesystem::path& ryu_dir);
|
||||
|
||||
} // namespace QtCommon::FS
|
||||
|
|
|
|||
|
|
@ -18,40 +18,34 @@
|
|||
#include <QUrl>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/string_util.h"
|
||||
#include <shlobj.h>
|
||||
#include <windows.h>
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/string_util.h"
|
||||
#else
|
||||
#include "fmt/ostream.h"
|
||||
#include <fstream>
|
||||
#include "fmt/ostream.h"
|
||||
#endif
|
||||
|
||||
namespace QtCommon::Game {
|
||||
|
||||
bool CreateShortcutLink(const std::filesystem::path& shortcut_path,
|
||||
const std::string& comment,
|
||||
bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment,
|
||||
const std::filesystem::path& icon_path,
|
||||
const std::filesystem::path& command,
|
||||
const std::string& arguments,
|
||||
const std::string& categories,
|
||||
const std::string& keywords,
|
||||
const std::string& name)
|
||||
try {
|
||||
const std::filesystem::path& command, const std::string& arguments,
|
||||
const std::string& categories, const std::string& keywords,
|
||||
const std::string& name) try {
|
||||
#ifdef _WIN32 // Windows
|
||||
HRESULT hr = CoInitialize(nullptr);
|
||||
if (FAILED(hr)) {
|
||||
LOG_ERROR(Frontend, "CoInitialize failed");
|
||||
return false;
|
||||
}
|
||||
SCOPE_EXIT
|
||||
{
|
||||
SCOPE_EXIT {
|
||||
CoUninitialize();
|
||||
};
|
||||
IShellLinkW* ps1 = nullptr;
|
||||
IPersistFile* persist_file = nullptr;
|
||||
SCOPE_EXIT
|
||||
{
|
||||
SCOPE_EXIT {
|
||||
if (persist_file != nullptr) {
|
||||
persist_file->Release();
|
||||
}
|
||||
|
|
@ -59,10 +53,7 @@ try {
|
|||
ps1->Release();
|
||||
}
|
||||
};
|
||||
HRESULT hres = CoCreateInstance(CLSID_ShellLink,
|
||||
nullptr,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_IShellLinkW,
|
||||
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
|
||||
reinterpret_cast<void**>(&ps1));
|
||||
if (FAILED(hres)) {
|
||||
LOG_ERROR(Frontend, "Failed to create IShellLinkW instance");
|
||||
|
|
@ -142,10 +133,8 @@ try {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MakeShortcutIcoPath(const u64 program_id,
|
||||
const std::string_view game_file_name,
|
||||
std::filesystem::path& out_icon_path)
|
||||
{
|
||||
bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
|
||||
std::filesystem::path& out_icon_path) {
|
||||
// Get path to Yuzu icons directory & icon extension
|
||||
std::string ico_extension = "png";
|
||||
#if defined(_WIN32)
|
||||
|
|
@ -166,46 +155,38 @@ bool MakeShortcutIcoPath(const u64 program_id,
|
|||
return true;
|
||||
}
|
||||
|
||||
void OpenEdenFolder(const Common::FS::EdenPath& path)
|
||||
{
|
||||
void OpenEdenFolder(const Common::FS::EdenPath& path) {
|
||||
QDesktopServices::openUrl(
|
||||
QUrl::fromLocalFile(QString::fromStdString(Common::FS::GetEdenPathString(path))));
|
||||
}
|
||||
|
||||
void OpenRootDataFolder()
|
||||
{
|
||||
void OpenRootDataFolder() {
|
||||
OpenEdenFolder(Common::FS::EdenPath::EdenDir);
|
||||
}
|
||||
|
||||
void OpenNANDFolder()
|
||||
{
|
||||
void OpenNANDFolder() {
|
||||
OpenEdenFolder(Common::FS::EdenPath::NANDDir);
|
||||
}
|
||||
|
||||
void OpenSaveFolder()
|
||||
{
|
||||
const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir)
|
||||
/ "user/save/0000000000000000";
|
||||
void OpenSaveFolder() {
|
||||
const auto path =
|
||||
Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "user/save/0000000000000000";
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path.string())));
|
||||
}
|
||||
|
||||
void OpenSDMCFolder()
|
||||
{
|
||||
void OpenSDMCFolder() {
|
||||
OpenEdenFolder(Common::FS::EdenPath::SDMCDir);
|
||||
}
|
||||
|
||||
void OpenModFolder()
|
||||
{
|
||||
void OpenModFolder() {
|
||||
OpenEdenFolder(Common::FS::EdenPath::LoadDir);
|
||||
}
|
||||
|
||||
void OpenLogFolder()
|
||||
{
|
||||
void OpenLogFolder() {
|
||||
OpenEdenFolder(Common::FS::EdenPath::LogDir);
|
||||
}
|
||||
|
||||
static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type)
|
||||
{
|
||||
static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type) {
|
||||
switch (type) {
|
||||
case QtCommon::Game::InstalledEntryType::Game:
|
||||
return tr("Error Removing Contents");
|
||||
|
|
@ -219,10 +200,9 @@ static QString GetGameListErrorRemoving(QtCommon::Game::InstalledEntryType type)
|
|||
}
|
||||
|
||||
// Game Content //
|
||||
void RemoveBaseContent(u64 program_id, InstalledEntryType type)
|
||||
{
|
||||
const auto res = ContentManager::RemoveBaseContent(system->GetFileSystemController(),
|
||||
program_id);
|
||||
void RemoveBaseContent(u64 program_id, InstalledEntryType type) {
|
||||
const auto res =
|
||||
ContentManager::RemoveBaseContent(system->GetFileSystemController(), program_id);
|
||||
if (res) {
|
||||
QtCommon::Frontend::Information(tr("Successfully Removed"),
|
||||
tr("Successfully removed the installed base game."));
|
||||
|
|
@ -234,8 +214,7 @@ void RemoveBaseContent(u64 program_id, InstalledEntryType type)
|
|||
}
|
||||
}
|
||||
|
||||
void RemoveUpdateContent(u64 program_id, InstalledEntryType type)
|
||||
{
|
||||
void RemoveUpdateContent(u64 program_id, InstalledEntryType type) {
|
||||
const auto res = ContentManager::RemoveUpdate(system->GetFileSystemController(), program_id);
|
||||
if (res) {
|
||||
QtCommon::Frontend::Information(tr("Successfully Removed"),
|
||||
|
|
@ -246,8 +225,7 @@ void RemoveUpdateContent(u64 program_id, InstalledEntryType type)
|
|||
}
|
||||
}
|
||||
|
||||
void RemoveAddOnContent(u64 program_id, InstalledEntryType type)
|
||||
{
|
||||
void RemoveAddOnContent(u64 program_id, InstalledEntryType type) {
|
||||
const size_t count = ContentManager::RemoveAllDLC(*system, program_id);
|
||||
if (count == 0) {
|
||||
QtCommon::Frontend::Warning(GetGameListErrorRemoving(type),
|
||||
|
|
@ -261,8 +239,7 @@ void RemoveAddOnContent(u64 program_id, InstalledEntryType type)
|
|||
|
||||
// Global Content //
|
||||
|
||||
void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target)
|
||||
{
|
||||
void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
|
||||
const auto target_file_name = [target] {
|
||||
switch (target) {
|
||||
case GameListRemoveTarget::GlShaderCache:
|
||||
|
|
@ -291,8 +268,7 @@ void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target)
|
|||
}
|
||||
}
|
||||
|
||||
void RemoveVulkanDriverPipelineCache(u64 program_id)
|
||||
{
|
||||
void RemoveVulkanDriverPipelineCache(u64 program_id) {
|
||||
static constexpr std::string_view target_file_name = "vulkan_pipelines.bin";
|
||||
|
||||
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
|
|
@ -308,8 +284,7 @@ void RemoveVulkanDriverPipelineCache(u64 program_id)
|
|||
}
|
||||
}
|
||||
|
||||
void RemoveAllTransferableShaderCaches(u64 program_id)
|
||||
{
|
||||
void RemoveAllTransferableShaderCaches(u64 program_id) {
|
||||
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
const auto program_shader_cache_dir = shader_cache_dir / fmt::format("{:016x}", program_id);
|
||||
|
||||
|
|
@ -329,14 +304,13 @@ void RemoveAllTransferableShaderCaches(u64 program_id)
|
|||
}
|
||||
}
|
||||
|
||||
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path)
|
||||
{
|
||||
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) {
|
||||
const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path));
|
||||
const auto config_file_name
|
||||
= program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini")
|
||||
: fmt::format("{:016X}.ini", program_id);
|
||||
const auto custom_config_file_path = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir)
|
||||
/ "custom" / config_file_name;
|
||||
const auto config_file_name =
|
||||
program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini")
|
||||
: fmt::format("{:016X}.ini", program_id);
|
||||
const auto custom_config_file_path =
|
||||
Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir) / "custom" / config_file_name;
|
||||
|
||||
if (!Common::FS::Exists(custom_config_file_path)) {
|
||||
QtCommon::Frontend::Warning(tr("Error Removing Custom Configuration"),
|
||||
|
|
@ -353,20 +327,14 @@ void RemoveCustomConfiguration(u64 program_id, const std::string& game_path)
|
|||
}
|
||||
}
|
||||
|
||||
void RemoveCacheStorage(u64 program_id)
|
||||
{
|
||||
void RemoveCacheStorage(u64 program_id) {
|
||||
const auto nand_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir);
|
||||
auto vfs_nand_dir = vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir),
|
||||
FileSys::OpenMode::Read);
|
||||
auto vfs_nand_dir =
|
||||
vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::OpenMode::Read);
|
||||
|
||||
const auto cache_storage_path
|
||||
= FileSys::SaveDataFactory::GetFullPath({},
|
||||
vfs_nand_dir,
|
||||
FileSys::SaveDataSpaceId::User,
|
||||
FileSys::SaveDataType::Cache,
|
||||
0 /* program_id */,
|
||||
{},
|
||||
0);
|
||||
const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath(
|
||||
{}, vfs_nand_dir, FileSys::SaveDataSpaceId::User, FileSys::SaveDataType::Cache,
|
||||
0 /* program_id */, {}, 0);
|
||||
|
||||
const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path);
|
||||
|
||||
|
|
@ -400,32 +368,31 @@ void ResetMetadata(bool show_message) {
|
|||
// Uhhh //
|
||||
|
||||
// Messages in pre-defined message boxes for less code spaghetti
|
||||
inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title)
|
||||
{
|
||||
inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) {
|
||||
int result = 0;
|
||||
QMessageBox::StandardButtons buttons;
|
||||
using namespace QtCommon::Frontend;
|
||||
int buttons;
|
||||
|
||||
switch (imsg) {
|
||||
case ShortcutMessages::Fullscreen:
|
||||
buttons = QMessageBox::Yes | QMessageBox::No;
|
||||
result
|
||||
= QtCommon::Frontend::Information(tr("Create Shortcut"),
|
||||
tr("Do you want to launch the game in fullscreen?"),
|
||||
buttons);
|
||||
return result == QMessageBox::Yes;
|
||||
buttons = Yes | No;
|
||||
result = QtCommon::Frontend::Information(
|
||||
tr("Create Shortcut"), tr("Do you want to launch the game in fullscreen?"), buttons);
|
||||
return result == Yes;
|
||||
case ShortcutMessages::Success:
|
||||
QtCommon::Frontend::Information(tr("Shortcut Created"),
|
||||
tr("Successfully created a shortcut to %1").arg(game_title));
|
||||
QtCommon::Frontend::Information(
|
||||
tr("Shortcut Created"), tr("Successfully created a shortcut to %1").arg(game_title));
|
||||
return false;
|
||||
case ShortcutMessages::Volatile:
|
||||
buttons = QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel;
|
||||
buttons = Ok | Cancel;
|
||||
result = QtCommon::Frontend::Warning(
|
||||
tr("Shortcut may be Volatile!"),
|
||||
tr("This will create a shortcut to the current AppImage. This may "
|
||||
"not work well if you update. Continue?"),
|
||||
buttons);
|
||||
return result == QMessageBox::Ok;
|
||||
return result == Ok;
|
||||
default:
|
||||
buttons = QMessageBox::Ok;
|
||||
buttons = Ok;
|
||||
QtCommon::Frontend::Critical(tr("Failed to Create Shortcut"),
|
||||
tr("Failed to create a shortcut to %1").arg(game_title),
|
||||
buttons);
|
||||
|
|
@ -433,13 +400,9 @@ inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QSt
|
|||
}
|
||||
}
|
||||
|
||||
void CreateShortcut(const std::string& game_path,
|
||||
const u64 program_id,
|
||||
const std::string& game_title_,
|
||||
const ShortcutTarget& target,
|
||||
std::string arguments_,
|
||||
const bool needs_title)
|
||||
{
|
||||
void CreateShortcut(const std::string& game_path, const u64 program_id,
|
||||
const std::string& game_title_, const ShortcutTarget& target,
|
||||
std::string arguments_, const bool needs_title) {
|
||||
// Get path to Eden executable
|
||||
std::filesystem::path command = GetEdenCommand();
|
||||
|
||||
|
|
@ -453,13 +416,11 @@ void CreateShortcut(const std::string& game_path,
|
|||
return;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm{program_id,
|
||||
QtCommon::system->GetFileSystemController(),
|
||||
const FileSys::PatchManager pm{program_id, QtCommon::system->GetFileSystemController(),
|
||||
QtCommon::system->GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
const auto loader = Loader::GetLoader(*QtCommon::system,
|
||||
QtCommon::vfs->OpenFile(game_path,
|
||||
FileSys::OpenMode::Read));
|
||||
const auto loader = Loader::GetLoader(
|
||||
*QtCommon::system, QtCommon::vfs->OpenFile(game_path, FileSys::OpenMode::Read));
|
||||
|
||||
std::string game_title{game_title_};
|
||||
|
||||
|
|
@ -490,8 +451,8 @@ void CreateShortcut(const std::string& game_path,
|
|||
LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
|
||||
}
|
||||
|
||||
QImage icon_data = QImage::fromData(icon_image_file.data(),
|
||||
static_cast<int>(icon_image_file.size()));
|
||||
QImage icon_data =
|
||||
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
|
||||
std::filesystem::path out_icon_path;
|
||||
if (QtCommon::Game::MakeShortcutIcoPath(program_id, game_title, out_icon_path)) {
|
||||
if (!SaveIconToFile(out_icon_path, icon_data)) {
|
||||
|
|
@ -524,39 +485,32 @@ void CreateShortcut(const std::string& game_path,
|
|||
const std::string categories = "Game;Emulator;Qt;";
|
||||
const std::string keywords = "Switch;Nintendo;";
|
||||
|
||||
if (QtCommon::Game::CreateShortcutLink(shortcut_path,
|
||||
comment,
|
||||
out_icon_path,
|
||||
command,
|
||||
arguments,
|
||||
categories,
|
||||
keywords,
|
||||
game_title)) {
|
||||
if (QtCommon::Game::CreateShortcutLink(shortcut_path, comment, out_icon_path, command,
|
||||
arguments, categories, keywords, game_title)) {
|
||||
CreateShortcutMessagesGUI(ShortcutMessages::Success, qgame_title);
|
||||
return;
|
||||
}
|
||||
CreateShortcutMessagesGUI(ShortcutMessages::Failed, qgame_title);
|
||||
}
|
||||
|
||||
// TODO: You want this to be constexpr? Well too bad, clang19 doesn't believe this is a string literal
|
||||
std::string GetShortcutPath(ShortcutTarget target)
|
||||
{
|
||||
// TODO: You want this to be constexpr? Well too bad, clang19 doesn't believe this is a string
|
||||
// literal
|
||||
std::string GetShortcutPath(ShortcutTarget target) {
|
||||
{
|
||||
std::string shortcut_path{};
|
||||
if (target == ShortcutTarget::Desktop) {
|
||||
shortcut_path
|
||||
= QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString();
|
||||
shortcut_path =
|
||||
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString();
|
||||
} else if (target == ShortcutTarget::Applications) {
|
||||
shortcut_path
|
||||
= QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString();
|
||||
shortcut_path = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)
|
||||
.toStdString();
|
||||
}
|
||||
|
||||
return shortcut_path;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateHomeMenuShortcut(ShortcutTarget target)
|
||||
{
|
||||
void CreateHomeMenuShortcut(ShortcutTarget target) {
|
||||
constexpr u64 QLaunchId = static_cast<u64>(Service::AM::AppletProgramId::QLaunch);
|
||||
auto bis_system = QtCommon::system->GetFileSystemController().GetSystemNANDContents();
|
||||
if (!bis_system) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_GAME_UTIL_H
|
||||
|
|
@ -29,27 +29,18 @@ enum class ShortcutTarget {
|
|||
Applications,
|
||||
};
|
||||
|
||||
enum class ShortcutMessages{
|
||||
Fullscreen = 0,
|
||||
Success = 1,
|
||||
Volatile = 2,
|
||||
Failed = 3
|
||||
};
|
||||
enum class ShortcutMessages { Fullscreen = 0, Success = 1, Volatile = 2, Failed = 3 };
|
||||
|
||||
bool CreateShortcutLink(const std::filesystem::path& shortcut_path,
|
||||
const std::string& comment,
|
||||
bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment,
|
||||
const std::filesystem::path& icon_path,
|
||||
const std::filesystem::path& command,
|
||||
const std::string& arguments,
|
||||
const std::string& categories,
|
||||
const std::string& keywords,
|
||||
const std::filesystem::path& command, const std::string& arguments,
|
||||
const std::string& categories, const std::string& keywords,
|
||||
const std::string& name);
|
||||
|
||||
bool MakeShortcutIcoPath(const u64 program_id,
|
||||
const std::string_view game_file_name,
|
||||
bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
|
||||
std::filesystem::path& out_icon_path);
|
||||
|
||||
void OpenEdenFolder(const Common::FS::EdenPath &path);
|
||||
void OpenEdenFolder(const Common::FS::EdenPath& path);
|
||||
void OpenRootDataFolder();
|
||||
void OpenNANDFolder();
|
||||
void OpenSaveFolder();
|
||||
|
|
@ -71,16 +62,13 @@ void RemoveCacheStorage(u64 program_id);
|
|||
void ResetMetadata(bool show_message = true);
|
||||
|
||||
// Shortcuts //
|
||||
void CreateShortcut(const std::string& game_path,
|
||||
const u64 program_id,
|
||||
const std::string& game_title_,
|
||||
const ShortcutTarget& target,
|
||||
std::string arguments_,
|
||||
const bool needs_title);
|
||||
void CreateShortcut(const std::string& game_path, const u64 program_id,
|
||||
const std::string& game_title_, const ShortcutTarget& target,
|
||||
std::string arguments_, const bool needs_title);
|
||||
|
||||
std::string GetShortcutPath(ShortcutTarget target);
|
||||
void CreateHomeMenuShortcut(ShortcutTarget target);
|
||||
|
||||
}
|
||||
} // namespace QtCommon::Game
|
||||
|
||||
#endif // QT_GAME_UTIL_H
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_common/util/meta.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/applets/cabinet.h"
|
||||
|
|
@ -9,11 +8,11 @@
|
|||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/frontend/applets/software_keyboard.h"
|
||||
#include "core/hle/service/am/frontend/applet_web_browser_types.h"
|
||||
#include "qt_common/util/meta.h"
|
||||
|
||||
namespace QtCommon::Meta {
|
||||
|
||||
void RegisterMetaTypes()
|
||||
{
|
||||
void RegisterMetaTypes() {
|
||||
// Register integral and floating point types
|
||||
qRegisterMetaType<u8>("u8");
|
||||
qRegisterMetaType<u16>("u16");
|
||||
|
|
@ -72,4 +71,4 @@ void RegisterMetaTypes()
|
|||
qRegisterMetaType<Core::SystemResultStatus>("Core::SystemResultStatus");
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace QtCommon::Meta
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_META_H
|
||||
|
|
@ -11,5 +11,5 @@ namespace QtCommon::Meta {
|
|||
//
|
||||
void RegisterMetaTypes();
|
||||
|
||||
}
|
||||
} // namespace QtCommon::Meta
|
||||
#endif // QT_META_H
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ QStringList GetModFolders(const QString& root, const QString& fallbackName) {
|
|||
QString name = QtCommon::Frontend::GetTextInput(
|
||||
tr("Mod Name"), tr("What should this mod be called?"), default_name);
|
||||
|
||||
if (name.isEmpty()) return {};
|
||||
if (name.isEmpty())
|
||||
return {};
|
||||
|
||||
// if std_path is empty, frontend_common could not determine mod type and/or name.
|
||||
// so we have to prompt the user and set up the structure ourselves
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
namespace QtCommon::Mod {
|
||||
|
||||
QStringList GetModFolders(const QString &root, const QString &fallbackName);
|
||||
QStringList GetModFolders(const QString& root, const QString& fallbackName);
|
||||
|
||||
const QString ExtractMod(const QString &path);
|
||||
const QString ExtractMod(const QString& path);
|
||||
|
||||
}
|
||||
} // namespace QtCommon::Mod
|
||||
|
|
|
|||
|
|
@ -1,28 +1,24 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_common/util/path.h"
|
||||
#include <QDesktopServices>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <fmt/format.h>
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "qt_common/abstract/frontend.h"
|
||||
#include <fmt/format.h>
|
||||
#include "qt_common/util/path.h"
|
||||
|
||||
namespace QtCommon::Path {
|
||||
|
||||
bool OpenShaderCache(u64 program_id, QObject *parent)
|
||||
{
|
||||
bool OpenShaderCache(u64 program_id, QObject* parent) {
|
||||
const auto shader_cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir);
|
||||
const auto shader_cache_folder_path{shader_cache_dir / fmt::format("{:016x}", program_id)};
|
||||
if (!Common::FS::CreateDirs(shader_cache_folder_path)) {
|
||||
QtCommon::Frontend::ShowMessage(QMessageBox::Warning,
|
||||
tr("Error Opening Shader Cache"),
|
||||
tr("Failed to create or open shader cache for this title, "
|
||||
"ensure your app data directory has write permissions."),
|
||||
QMessageBox::Ok,
|
||||
parent);
|
||||
QtCommon::Frontend::Warning(tr("Error Opening Shader Cache"),
|
||||
tr("Failed to create or open shader cache for this title, "
|
||||
"ensure your app data directory has write permissions."));
|
||||
}
|
||||
|
||||
const auto shader_path_string{Common::FS::PathToUTF8String(shader_cache_folder_path)};
|
||||
|
|
@ -30,4 +26,4 @@ bool OpenShaderCache(u64 program_id, QObject *parent)
|
|||
return QDesktopServices::openUrl(QUrl::fromLocalFile(qt_shader_cache_path));
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace QtCommon::Path
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_PATH_UTIL_H
|
||||
#define QT_PATH_UTIL_H
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include <QObject>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace QtCommon::Path { bool OpenShaderCache(u64 program_id, QObject *parent); }
|
||||
namespace QtCommon::Path {
|
||||
bool OpenShaderCache(u64 program_id, QObject* parent);
|
||||
}
|
||||
|
||||
#endif // QT_PATH_UTIL_H
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "qt_common/util/rom.h"
|
||||
|
|
@ -7,13 +7,8 @@
|
|||
|
||||
namespace QtCommon::ROM {
|
||||
|
||||
bool RomFSRawCopy(size_t total_size,
|
||||
size_t& read_size,
|
||||
QtProgressCallback callback,
|
||||
const FileSys::VirtualDir& src,
|
||||
const FileSys::VirtualDir& dest,
|
||||
bool full)
|
||||
{
|
||||
bool RomFSRawCopy(size_t total_size, size_t& read_size, QtProgressCallback callback,
|
||||
const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, bool full) {
|
||||
// TODO(crueter)
|
||||
// if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
|
||||
// return false;
|
||||
|
|
@ -75,4 +70,4 @@ bool RomFSRawCopy(size_t total_size,
|
|||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace QtCommon::ROM
|
||||
|
|
|
|||
|
|
@ -1,20 +1,16 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_ROM_UTIL_H
|
||||
#define QT_ROM_UTIL_H
|
||||
|
||||
#include "qt_common/qt_common.h"
|
||||
#include <cstddef>
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
namespace QtCommon::ROM {
|
||||
|
||||
bool RomFSRawCopy(size_t total_size,
|
||||
size_t& read_size,
|
||||
QtProgressCallback callback,
|
||||
const FileSys::VirtualDir& src,
|
||||
const FileSys::VirtualDir& dest,
|
||||
bool full);
|
||||
bool RomFSRawCopy(size_t total_size, size_t& read_size, QtProgressCallback callback,
|
||||
const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, bool full);
|
||||
|
||||
}
|
||||
#endif // QT_ROM_UTIL_H
|
||||
|
|
|
|||
|
|
@ -70,10 +70,14 @@ TextureCache<P>::TextureCache(Runtime& runtime_, Tegra::MaxwellDeviceMemoryManag
|
|||
(std::max)((std::min)(device_local_memory - min_vacancy_critical, min_spacing_critical),
|
||||
DEFAULT_CRITICAL_MEMORY));
|
||||
minimum_memory = static_cast<u64>((device_local_memory - mem_threshold) / 2);
|
||||
|
||||
lowmemorydevice = false;
|
||||
} else {
|
||||
expected_memory = DEFAULT_EXPECTED_MEMORY + 512_MiB;
|
||||
critical_memory = DEFAULT_CRITICAL_MEMORY + 1_GiB;
|
||||
minimum_memory = 0;
|
||||
|
||||
lowmemorydevice = true;
|
||||
}
|
||||
|
||||
const bool gpu_unswizzle_enabled = Settings::values.gpu_unswizzle_enabled.GetValue();
|
||||
|
|
@ -118,46 +122,102 @@ void TextureCache<P>::RunGarbageCollector() {
|
|||
bool aggressive_mode = false;
|
||||
u64 ticks_to_destroy = 0;
|
||||
size_t num_iterations = 0;
|
||||
|
||||
const auto Configure = [&](bool allow_aggressive) {
|
||||
high_priority_mode = total_used_memory >= expected_memory;
|
||||
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
|
||||
ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
|
||||
num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
|
||||
};
|
||||
const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode](ImageId image_id) {
|
||||
|
||||
const auto Cleanup = [this, &num_iterations, &high_priority_mode,
|
||||
&aggressive_mode](ImageId image_id) {
|
||||
if (num_iterations == 0) {
|
||||
return true;
|
||||
}
|
||||
--num_iterations;
|
||||
auto& image = slot_images[image_id];
|
||||
if (True(image.flags & ImageFlagBits::IsDecoding)) {
|
||||
|
||||
// Never delete recently allocated sparse textures (within 3 frames)
|
||||
const bool is_recently_allocated = image.allocation_tick >= frame_tick - 3;
|
||||
if (is_recently_allocated && image.info.is_sparse) {
|
||||
return false;
|
||||
}
|
||||
const bool must_download = image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
|
||||
if (must_download && !image.info.is_sparse) {
|
||||
|
||||
if (True(image.flags & ImageFlagBits::IsDecoding)) {
|
||||
// This image is still being decoded, deleting it will invalidate the slot
|
||||
// used by the async decoder thread.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prioritize large sparse textures for cleanup
|
||||
const bool is_large_sparse = lowmemorydevice &&
|
||||
image.info.is_sparse &&
|
||||
image.guest_size_bytes >= 256_MiB;
|
||||
|
||||
if (!aggressive_mode && !is_large_sparse &&
|
||||
True(image.flags & ImageFlagBits::CostlyLoad)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool must_download =
|
||||
image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap);
|
||||
if (!high_priority_mode && !is_large_sparse && must_download) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (must_download && !is_large_sparse) {
|
||||
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
|
||||
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
|
||||
image.DownloadMemory(map, copies);
|
||||
runtime.Finish();
|
||||
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, swizzle_data_buffer);
|
||||
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
|
||||
swizzle_data_buffer);
|
||||
}
|
||||
|
||||
if (True(image.flags & ImageFlagBits::Tracked)) {
|
||||
UntrackImage(image, image_id);
|
||||
}
|
||||
UnregisterImage(image_id);
|
||||
DeleteImage(image_id, (frame_tick - image.scale_tick) > 5 || aggressive_mode);
|
||||
if (aggressive_mode && total_used_memory < critical_memory) {
|
||||
num_iterations >>= 2;
|
||||
aggressive_mode = false;
|
||||
}
|
||||
if (high_priority_mode && total_used_memory < expected_memory) {
|
||||
num_iterations >>= 1;
|
||||
high_priority_mode = false;
|
||||
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
|
||||
|
||||
if (total_used_memory < critical_memory) {
|
||||
if (aggressive_mode) {
|
||||
// Sink the aggresiveness.
|
||||
num_iterations >>= 2;
|
||||
aggressive_mode = false;
|
||||
return false;
|
||||
}
|
||||
if (high_priority_mode && total_used_memory < expected_memory) {
|
||||
num_iterations >>= 1;
|
||||
high_priority_mode = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Aggressively clear massive sparse textures
|
||||
if (total_used_memory >= expected_memory) {
|
||||
lru_cache.ForEachItemBelow(frame_tick, [&](ImageId image_id) {
|
||||
auto& image = slot_images[image_id];
|
||||
// Only target sparse textures that are old enough
|
||||
if (lowmemorydevice &&
|
||||
image.info.is_sparse &&
|
||||
image.guest_size_bytes >= 256_MiB &&
|
||||
image.allocation_tick < frame_tick - 3) {
|
||||
LOG_DEBUG(HW_GPU, "GC targeting old sparse texture at 0x{:X} ({} MiB, age: {} frames)",
|
||||
image.gpu_addr, image.guest_size_bytes / (1024 * 1024),
|
||||
frame_tick - image.allocation_tick);
|
||||
return Cleanup(image_id);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
Configure(false);
|
||||
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
|
||||
|
||||
// If pressure is still too high, prune aggressively.
|
||||
if (total_used_memory >= critical_memory) {
|
||||
Configure(true);
|
||||
lru_cache.ForEachItemBelow(frame_tick - ticks_to_destroy, Cleanup);
|
||||
|
|
@ -1136,6 +1196,9 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
|
|||
}
|
||||
|
||||
image.flags &= ~ImageFlagBits::CpuModified;
|
||||
if( lowmemorydevice && image.info.format == PixelFormat::BC1_RGBA_UNORM && MapSizeBytes(image) >= 256_MiB ) {
|
||||
return;
|
||||
}
|
||||
|
||||
TrackImage(image, image_id);
|
||||
|
||||
|
|
@ -1556,6 +1619,39 @@ ImageId TextureCache<P>::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr,
|
|||
}
|
||||
}
|
||||
ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr);
|
||||
|
||||
// For large sparse textures, aggressively clean up old allocations at same address
|
||||
if (lowmemorydevice && info.is_sparse && CalculateGuestSizeInBytes(info) >= 256_MiB) {
|
||||
const auto alloc_it = image_allocs_table.find(gpu_addr);
|
||||
if (alloc_it != image_allocs_table.end()) {
|
||||
const ImageAllocId alloc_id = alloc_it->second;
|
||||
auto& alloc_images = slot_image_allocs[alloc_id].images;
|
||||
|
||||
// Collect old images at this address that were created more than 2 frames ago
|
||||
boost::container::small_vector<ImageId, 4> to_delete;
|
||||
for (ImageId old_image_id : alloc_images) {
|
||||
Image& old_image = slot_images[old_image_id];
|
||||
if (old_image.info.is_sparse &&
|
||||
old_image.gpu_addr == gpu_addr &&
|
||||
old_image.allocation_tick < frame_tick - 2) { // Try not to delete fresh textures
|
||||
to_delete.push_back(old_image_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete old images immediately
|
||||
for (ImageId old_id : to_delete) {
|
||||
Image& old_image = slot_images[old_id];
|
||||
LOG_DEBUG(HW_GPU, "Immediately deleting old sparse texture at 0x{:X} ({} MiB)",
|
||||
gpu_addr, old_image.guest_size_bytes / (1024 * 1024));
|
||||
if (True(old_image.flags & ImageFlagBits::Tracked)) {
|
||||
UntrackImage(old_image, old_id);
|
||||
}
|
||||
UnregisterImage(old_id);
|
||||
DeleteImage(old_id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr);
|
||||
const Image& image = slot_images[image_id];
|
||||
// Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different
|
||||
|
|
@ -1571,6 +1667,27 @@ template <class P>
|
|||
ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DAddr cpu_addr) {
|
||||
ImageInfo new_info = info;
|
||||
const size_t size_bytes = CalculateGuestSizeInBytes(new_info);
|
||||
|
||||
// Proactive cleanup for large sparse texture allocations
|
||||
if (lowmemorydevice && new_info.is_sparse && size_bytes >= 256_MiB) {
|
||||
const u64 estimated_alloc_size = size_bytes;
|
||||
|
||||
if (total_used_memory + estimated_alloc_size >= critical_memory) {
|
||||
LOG_DEBUG(HW_GPU, "Large sparse texture allocation ({} MiB) - running aggressive GC. "
|
||||
"Current memory: {} MiB, Critical: {} MiB",
|
||||
size_bytes / (1024 * 1024),
|
||||
total_used_memory / (1024 * 1024),
|
||||
critical_memory / (1024 * 1024));
|
||||
RunGarbageCollector();
|
||||
|
||||
// If still over threshold after GC, try one more aggressive pass
|
||||
if (total_used_memory + estimated_alloc_size >= critical_memory) {
|
||||
LOG_DEBUG(HW_GPU, "Still critically low on memory, running second GC pass");
|
||||
RunGarbageCollector();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool broken_views = runtime.HasBrokenTextureViewFormats();
|
||||
const bool native_bgr = runtime.HasNativeBgr();
|
||||
join_overlap_ids.clear();
|
||||
|
|
|
|||
|
|
@ -478,6 +478,7 @@ private:
|
|||
u64 minimum_memory;
|
||||
u64 expected_memory;
|
||||
u64 critical_memory;
|
||||
bool lowmemorydevice = false;
|
||||
size_t gpu_unswizzle_maxsize = 0;
|
||||
size_t swizzle_chunk_size = 0;
|
||||
u32 swizzle_slices_per_batch = 0;
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@ add_executable(yuzu
|
|||
configuration/addon/mod_select_dialog.h configuration/addon/mod_select_dialog.cpp configuration/addon/mod_select_dialog.ui
|
||||
|
||||
render/performance_overlay.h render/performance_overlay.cpp render/performance_overlay.ui
|
||||
libqt_common.h libqt_common.cpp
|
||||
)
|
||||
|
||||
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
|
||||
|
|
|
|||
|
|
@ -1,29 +1,24 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// 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
|
||||
|
||||
#include "yuzu/about_dialog.h"
|
||||
#include <QIcon>
|
||||
#include <fmt/ranges.h>
|
||||
#include "common/scm_rev.h"
|
||||
#include "ui_aboutdialog.h"
|
||||
#include <fmt/ranges.h>
|
||||
#include "yuzu/about_dialog.h"
|
||||
|
||||
AboutDialog::AboutDialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, ui{std::make_unique<Ui::AboutDialog>()}
|
||||
{
|
||||
: QDialog(parent), ui{std::make_unique<Ui::AboutDialog>()} {
|
||||
static const std::string build_id = std::string{Common::g_build_id};
|
||||
static const std::string yuzu_build = fmt::format("{} | {} | {}",
|
||||
std::string{Common::g_build_name},
|
||||
std::string{Common::g_build_version},
|
||||
std::string{Common::g_compiler_id}
|
||||
);
|
||||
static const std::string yuzu_build =
|
||||
fmt::format("{} | {} | {}", std::string{Common::g_build_name},
|
||||
std::string{Common::g_build_version}, std::string{Common::g_compiler_id});
|
||||
|
||||
const auto override_build = fmt::format(fmt::runtime(
|
||||
std::string(Common::g_title_bar_format_idle)),
|
||||
build_id);
|
||||
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;
|
||||
|
||||
ui->setupUi(this);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
|
|
@ -17,8 +17,8 @@
|
|||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/hid_types.h"
|
||||
#include "hid_core/resources/npad/npad.h"
|
||||
#include "ui_qt_controller.h"
|
||||
#include "qt_common/qt_compat.h"
|
||||
#include "ui_qt_controller.h"
|
||||
#include "yuzu/applets/qt_controller.h"
|
||||
#include "yuzu/configuration/configure_input.h"
|
||||
#include "yuzu/configuration/configure_input_profile_dialog.h"
|
||||
|
|
@ -188,14 +188,15 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
|||
CheckIfParametersMet();
|
||||
});
|
||||
|
||||
connect(connected_controller_checkboxes[i], &QCheckBox::STATE_CHANGED, [this, i](int state) {
|
||||
player_groupboxes[i]->setChecked(state == Qt::Checked);
|
||||
UpdateControllerIcon(i);
|
||||
UpdateControllerState(i);
|
||||
UpdateLEDPattern(i);
|
||||
UpdateBorderColor(i);
|
||||
CheckIfParametersMet();
|
||||
});
|
||||
connect(connected_controller_checkboxes[i], &QCheckBox::STATE_CHANGED,
|
||||
[this, i](int state) {
|
||||
player_groupboxes[i]->setChecked(state == Qt::Checked);
|
||||
UpdateControllerIcon(i);
|
||||
UpdateControllerState(i);
|
||||
UpdateLEDPattern(i);
|
||||
UpdateBorderColor(i);
|
||||
CheckIfParametersMet();
|
||||
});
|
||||
|
||||
if (i == 0) {
|
||||
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
|
|
|
|||
|
|
@ -1499,22 +1499,22 @@ void QtSoftwareKeyboardDialog::StartInputThread() {
|
|||
input_interpreter->PollInput();
|
||||
HandleButtonPressedOnce<
|
||||
Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X,
|
||||
Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL, Core::HID::NpadButton::StickR,
|
||||
Core::HID::NpadButton::L, Core::HID::NpadButton::R, Core::HID::NpadButton::Plus,
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
|
||||
Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
|
||||
Core::HID::NpadButton::StickRDown>();
|
||||
Core::HID::NpadButton::Y, Core::HID::NpadButton::StickL,
|
||||
Core::HID::NpadButton::StickR, Core::HID::NpadButton::L, Core::HID::NpadButton::R,
|
||||
Core::HID::NpadButton::Plus, Core::HID::NpadButton::Left, Core::HID::NpadButton::Up,
|
||||
Core::HID::NpadButton::Right, Core::HID::NpadButton::Down,
|
||||
Core::HID::NpadButton::StickLLeft, Core::HID::NpadButton::StickLUp,
|
||||
Core::HID::NpadButton::StickLRight, Core::HID::NpadButton::StickLDown,
|
||||
Core::HID::NpadButton::StickRLeft, Core::HID::NpadButton::StickRUp,
|
||||
Core::HID::NpadButton::StickRRight, Core::HID::NpadButton::StickRDown>();
|
||||
HandleButtonHold<Core::HID::NpadButton::B, Core::HID::NpadButton::L,
|
||||
Core::HID::NpadButton::R, Core::HID::NpadButton::Left,
|
||||
Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
|
||||
Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
|
||||
Core::HID::NpadButton::StickRDown>();
|
||||
Core::HID::NpadButton::R, Core::HID::NpadButton::Left,
|
||||
Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown, Core::HID::NpadButton::StickRLeft,
|
||||
Core::HID::NpadButton::StickRUp, Core::HID::NpadButton::StickRRight,
|
||||
Core::HID::NpadButton::StickRDown>();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
|
|||
: QWebEngineView(parent), input_subsystem{input_subsystem_},
|
||||
url_interceptor(std::make_unique<UrlRequestInterceptor>()),
|
||||
input_interpreter(std::make_unique<InputInterpreter>(system)),
|
||||
default_profile{QWebEngineProfile::defaultProfile()}, global_settings{
|
||||
default_profile->settings()} {
|
||||
default_profile{QWebEngineProfile::defaultProfile()},
|
||||
global_settings{default_profile->settings()} {
|
||||
default_profile->setPersistentStoragePath(QString::fromStdString(Common::FS::PathToUTF8String(
|
||||
Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir) / "qtwebengine")));
|
||||
|
||||
|
|
@ -299,21 +299,21 @@ void QtNXWebEngineView::StartInputThread() {
|
|||
while (!stoken.stop_requested()) {
|
||||
input_interpreter->PollInput();
|
||||
|
||||
HandleWindowFooterButtonPressedOnce<Core::HID::NpadButton::A, Core::HID::NpadButton::B,
|
||||
Core::HID::NpadButton::X, Core::HID::NpadButton::Y,
|
||||
Core::HID::NpadButton::L, Core::HID::NpadButton::R>();
|
||||
HandleWindowFooterButtonPressedOnce<
|
||||
Core::HID::NpadButton::A, Core::HID::NpadButton::B, Core::HID::NpadButton::X,
|
||||
Core::HID::NpadButton::Y, Core::HID::NpadButton::L, Core::HID::NpadButton::R>();
|
||||
|
||||
HandleWindowKeyButtonPressedOnce<
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown>();
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up,
|
||||
Core::HID::NpadButton::Right, Core::HID::NpadButton::Down,
|
||||
Core::HID::NpadButton::StickLLeft, Core::HID::NpadButton::StickLUp,
|
||||
Core::HID::NpadButton::StickLRight, Core::HID::NpadButton::StickLDown>();
|
||||
|
||||
HandleWindowKeyButtonHold<
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up, Core::HID::NpadButton::Right,
|
||||
Core::HID::NpadButton::Down, Core::HID::NpadButton::StickLLeft,
|
||||
Core::HID::NpadButton::StickLUp, Core::HID::NpadButton::StickLRight,
|
||||
Core::HID::NpadButton::StickLDown>();
|
||||
Core::HID::NpadButton::Left, Core::HID::NpadButton::Up,
|
||||
Core::HID::NpadButton::Right, Core::HID::NpadButton::Down,
|
||||
Core::HID::NpadButton::StickLLeft, Core::HID::NpadButton::StickLUp,
|
||||
Core::HID::NpadButton::StickLRight, Core::HID::NpadButton::StickLDown>();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,8 @@ private:
|
|||
std::unique_ptr<InputInterpreter> input_interpreter;
|
||||
std::jthread input_thread;
|
||||
std::atomic<bool> finished{};
|
||||
Service::AM::Frontend::WebExitReason exit_reason{Service::AM::Frontend::WebExitReason::EndButtonPressed};
|
||||
Service::AM::Frontend::WebExitReason exit_reason{
|
||||
Service::AM::Frontend::WebExitReason::EndButtonPressed};
|
||||
std::string last_url{"http://localhost/"};
|
||||
bool is_local{};
|
||||
QWebEngineProfile* default_profile;
|
||||
|
|
|
|||
|
|
@ -54,12 +54,12 @@
|
|||
#include "input_common/drivers/tas_input.h"
|
||||
#include "input_common/drivers/touch_screen.h"
|
||||
#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/qt_common.h"
|
||||
|
||||
class QObject;
|
||||
class QPaintEngine;
|
||||
|
|
@ -282,8 +282,8 @@ struct NullRenderWidget : public RenderWidget {
|
|||
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_} {
|
||||
: 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),
|
||||
|
|
@ -887,13 +887,14 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
|||
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
|
||||
#ifdef HAS_OPENGL
|
||||
if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL
|
||||
|| Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM
|
||||
|| Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV) {
|
||||
if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL ||
|
||||
Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM ||
|
||||
Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV) {
|
||||
auto c = static_cast<OpenGLSharedContext*>(main_context.get());
|
||||
// Bind the shared contexts to the main surface in case the backend wants to take over
|
||||
// presentation
|
||||
return std::make_unique<OpenGLSharedContext>(c->GetShareContext(), child_widget->windowHandle());
|
||||
return std::make_unique<OpenGLSharedContext>(c->GetShareContext(),
|
||||
child_widget->windowHandle());
|
||||
}
|
||||
#endif
|
||||
return std::make_unique<DummyContext>();
|
||||
|
|
@ -940,9 +941,9 @@ bool GRenderWindow::InitRenderTarget() {
|
|||
OnFramebufferSizeChanged();
|
||||
BackupGeometry();
|
||||
|
||||
if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL
|
||||
|| Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM
|
||||
|| Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV)
|
||||
if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLSL ||
|
||||
Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_GLASM ||
|
||||
Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL_SPIRV)
|
||||
return LoadOpenGL();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1050,21 +1051,23 @@ bool GRenderWindow::LoadOpenGL() {
|
|||
}
|
||||
// Display various warnings (but not fatal errors) for missing OpenGL extensions or lack of
|
||||
// OpenGL 4.6 support
|
||||
const QString renderer = QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
|
||||
const QString renderer =
|
||||
QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
|
||||
if (!GLAD_GL_VERSION_4_6) {
|
||||
QMessageBox::warning(this, tr("Error while initializing OpenGL 4.6!"),
|
||||
tr("Your GPU may not support OpenGL 4.6, or you do not have the "
|
||||
"latest graphics driver.<br><br>GL Renderer:<br>%1")
|
||||
.arg(renderer));
|
||||
tr("Your GPU may not support OpenGL 4.6, or you do not have the "
|
||||
"latest graphics driver.<br><br>GL Renderer:<br>%1")
|
||||
.arg(renderer));
|
||||
return false;
|
||||
}
|
||||
if (QStringList missing_ext = GetUnsupportedGLExtensions(); !missing_ext.empty()) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Error while initializing OpenGL!"),
|
||||
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).arg(missing_ext.join(QStringLiteral("<br>"))));
|
||||
"have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported "
|
||||
"extensions:<br>%2")
|
||||
.arg(renderer)
|
||||
.arg(missing_ext.join(QStringLiteral("<br>"))));
|
||||
// Non fatal
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ ModSelectDialog::ModSelectDialog(const QStringList& mods, QWidget* parent)
|
|||
width = qMax(width, item_model->item(i)->sizeHint().width());
|
||||
}
|
||||
|
||||
width += ui->treeView->contentsMargins().left() * 4 + ui->treeView->contentsMargins().right() * 4;
|
||||
width +=
|
||||
ui->treeView->contentsMargins().left() * 4 + ui->treeView->contentsMargins().right() * 4;
|
||||
ui->treeView->setMinimumHeight(qMin(height, 600));
|
||||
ui->treeView->setMinimumWidth(qMin(width, 700));
|
||||
adjustSize();
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@ class ModSelectDialog : public QDialog {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ModSelectDialog(const QStringList &mods, QWidget* parent = nullptr);
|
||||
explicit ModSelectDialog(const QStringList& mods, QWidget* parent = nullptr);
|
||||
~ModSelectDialog();
|
||||
|
||||
signals:
|
||||
void modsSelected(const QStringList &mods);
|
||||
void modsSelected(const QStringList& mods);
|
||||
|
||||
private:
|
||||
Ui::ModSelectDialog* ui;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
|
|
@ -16,12 +16,12 @@
|
|||
#include "common/settings.h"
|
||||
#include "common/settings_common.h"
|
||||
#include "core/core.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "ui_configure_audio.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/configuration/configure_audio.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "yuzu/configuration/shared_widget.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
|
||||
ConfigureAudio::ConfigureAudio(const Core::System& system_,
|
||||
std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
|
||||
|
|
@ -188,8 +188,8 @@ void ConfigureAudio::SetOutputSinkFromSinkID() {
|
|||
|
||||
const std::string new_sink_id = []() -> const std::string {
|
||||
const Settings::AudioEngine sink_id = Settings::values.sink_id.GetValue();
|
||||
const auto canonicalizations
|
||||
= Settings::EnumMetadata<Settings::AudioEngine>::Canonicalizations();
|
||||
const auto canonicalizations =
|
||||
Settings::EnumMetadata<Settings::AudioEngine>::Canonicalizations();
|
||||
|
||||
for (u32 i = 0; i < canonicalizations.size(); ++i) {
|
||||
const Settings::AudioEngine value = canonicalizations[i].second;
|
||||
|
|
@ -242,8 +242,8 @@ void ConfigureAudio::ApplyConfiguration() {
|
|||
|
||||
const u32 new_sink_id = [this]() {
|
||||
const std::string sink_id = sink_combo_box->currentText().toStdString();
|
||||
const auto canonicalizations
|
||||
= Settings::EnumMetadata<Settings::AudioEngine>::Canonicalizations();
|
||||
const auto canonicalizations =
|
||||
Settings::EnumMetadata<Settings::AudioEngine>::Canonicalizations();
|
||||
|
||||
for (u32 i = 0; i < canonicalizations.size(); ++i) {
|
||||
if (sink_id == canonicalizations[i].first)
|
||||
|
|
@ -291,7 +291,8 @@ void ConfigureAudio::InitializeAudioSinkComboBox() {
|
|||
sink_combo_box->clear();
|
||||
sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
|
||||
for (const auto& id : AudioCore::Sink::GetSinkIDs())
|
||||
sink_combo_box->addItem(QString::fromStdString(std::string{Settings::CanonicalizeEnum(id)}));
|
||||
sink_combo_box->addItem(
|
||||
QString::fromStdString(std::string{Settings::CanonicalizeEnum(id)}));
|
||||
}
|
||||
|
||||
void ConfigureAudio::RetranslateUI() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
|
|
@ -76,9 +76,9 @@ 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.vtable_bouncing.Id()
|
||||
|| setting->Id() == Settings::values.cpu_ticks.Id()) {
|
||||
} else if (setting->Id() == Settings::values.fast_cpu_time.Id() ||
|
||||
setting->Id() == Settings::values.vtable_bouncing.Id() ||
|
||||
setting->Id() == Settings::values.cpu_ticks.Id()) {
|
||||
ui->general_layout->addWidget(widget);
|
||||
} else {
|
||||
// Presently, all other settings here are unsafe checkboxes
|
||||
|
|
@ -93,12 +93,12 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
|
|||
UpdateGroup();
|
||||
}
|
||||
|
||||
void ConfigureCpu::UpdateGroup()
|
||||
{
|
||||
void ConfigureCpu::UpdateGroup() {
|
||||
const u32 accuracy = accuracy_combobox->currentIndex();
|
||||
const u32 backend = backend_combobox->currentIndex();
|
||||
// TODO(crueter): see if this works on NCE
|
||||
ui->unsafe_group->setVisible(accuracy == (u32) Settings::CpuAccuracy::Unsafe && backend == (u32) Settings::CpuBackend::Dynarmic);
|
||||
ui->unsafe_group->setVisible(accuracy == (u32)Settings::CpuAccuracy::Unsafe &&
|
||||
backend == (u32)Settings::CpuBackend::Dynarmic);
|
||||
}
|
||||
|
||||
void ConfigureCpu::ApplyConfiguration() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
|
|
@ -9,8 +9,8 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
#include <QWidget>
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
|
||||
class QComboBox;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@
|
|||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "ui_configure_debug.h"
|
||||
#include "yuzu/configuration/configure_debug.h"
|
||||
#include "yuzu/debugger/console.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
|
||||
ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
|
||||
: QScrollArea(parent), ui{std::make_unique<Ui::ConfigureDebug>()}, system{system_} {
|
||||
|
|
@ -60,7 +60,8 @@ void ConfigureDebug::SetConfiguration() {
|
|||
|
||||
// Immutable after starting
|
||||
ui->homebrew_args_edit->setEnabled(runtime_lock);
|
||||
ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args.GetValue()));
|
||||
ui->homebrew_args_edit->setText(
|
||||
QString::fromStdString(Settings::values.program_args.GetValue()));
|
||||
ui->toggle_console->setEnabled(runtime_lock);
|
||||
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
|
||||
ui->fs_access_log->setEnabled(runtime_lock);
|
||||
|
|
@ -84,7 +85,8 @@ void ConfigureDebug::SetConfiguration() {
|
|||
ui->disable_macro_hle->setEnabled(runtime_lock);
|
||||
ui->disable_macro_hle->setChecked(Settings::values.disable_macro_hle.GetValue());
|
||||
ui->disable_loop_safety_checks->setEnabled(runtime_lock);
|
||||
ui->disable_loop_safety_checks->setChecked(Settings::values.disable_shader_loop_safety_checks.GetValue());
|
||||
ui->disable_loop_safety_checks->setChecked(
|
||||
Settings::values.disable_shader_loop_safety_checks.GetValue());
|
||||
ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue());
|
||||
ui->debug_knobs_spinbox->setValue(Settings::values.debug_knobs.GetValue());
|
||||
#ifdef YUZU_USE_QT_WEB_ENGINE
|
||||
|
|
@ -118,7 +120,8 @@ void ConfigureDebug::ApplyConfiguration() {
|
|||
Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
|
||||
Settings::values.dump_shaders = ui->dump_shaders->isChecked();
|
||||
Settings::values.dump_macros = ui->dump_macros->isChecked();
|
||||
Settings::values.disable_shader_loop_safety_checks = ui->disable_loop_safety_checks->isChecked();
|
||||
Settings::values.disable_shader_loop_safety_checks =
|
||||
ui->disable_loop_safety_checks->isChecked();
|
||||
Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
|
||||
Settings::values.disable_macro_hle = ui->disable_macro_hle->isChecked();
|
||||
Settings::values.extended_logging = ui->extended_logging->isChecked();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "core/core.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "ui_configure.h"
|
||||
#include "vk_device_info.h"
|
||||
#include "yuzu/configuration/configure_applets.h"
|
||||
|
|
@ -30,15 +31,14 @@
|
|||
#include "yuzu/configuration/configure_ui.h"
|
||||
#include "yuzu/configuration/configure_web.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
|
||||
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
|
||||
InputCommon::InputSubsystem* input_subsystem,
|
||||
std::vector<VkDeviceInfo::Record>& vk_device_records,
|
||||
Core::System& system_, bool enable_web_config)
|
||||
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()},
|
||||
registry(registry_), system{system_}, builder{std::make_unique<ConfigurationShared::Builder>(
|
||||
this, !system_.IsPoweredOn())},
|
||||
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry(registry_),
|
||||
system{system_},
|
||||
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
|
||||
applets_tab{std::make_unique<ConfigureApplets>(system_, nullptr, *builder, this)},
|
||||
audio_tab{std::make_unique<ConfigureAudio>(system_, nullptr, *builder, this)},
|
||||
cpu_tab{std::make_unique<ConfigureCpu>(system_, nullptr, *builder, this)},
|
||||
|
|
@ -46,9 +46,9 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
|
|||
filesystem_tab{std::make_unique<ConfigureFilesystem>(this)},
|
||||
general_tab{std::make_unique<ConfigureGeneral>(system_, nullptr, *builder, this)},
|
||||
graphics_advanced_tab{
|
||||
std::make_unique<ConfigureGraphicsAdvanced>(system_, nullptr, *builder, this)},
|
||||
std::make_unique<ConfigureGraphicsAdvanced>(system_, nullptr, *builder, this)},
|
||||
graphics_extensions_tab{
|
||||
std::make_unique<ConfigureGraphicsExtensions>(system_, nullptr, *builder, this)},
|
||||
std::make_unique<ConfigureGraphicsExtensions>(system_, nullptr, *builder, this)},
|
||||
ui_tab{std::make_unique<ConfigureUi>(system_, this)},
|
||||
graphics_tab{std::make_unique<ConfigureGraphics>(
|
||||
system_, vk_device_records, [&]() { graphics_advanced_tab->ExposeComputeOption(); },
|
||||
|
|
@ -113,7 +113,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
|
|||
adjustSize();
|
||||
ui->selectorList->setCurrentRow(0);
|
||||
|
||||
// Selects the leftmost button on the bottom bar (Cancel as of writing)
|
||||
// Selects the leftmost button on the bottom bar (Cancel as of writing)
|
||||
ui->buttonBox->setFocus();
|
||||
}
|
||||
|
||||
|
|
@ -172,16 +172,17 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
|
|||
|
||||
void ConfigureDialog::PopulateSelectionList() {
|
||||
const std::array<std::pair<QString, QList<QWidget*>>, 6> items{
|
||||
{{tr("General"),
|
||||
{general_tab.get(), hotkeys_tab.get(), ui_tab.get(), web_tab.get(), debug_tab_tab.get()}},
|
||||
{tr("System"),
|
||||
{system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get(),
|
||||
applets_tab.get()}},
|
||||
{tr("CPU"), {cpu_tab.get()}},
|
||||
{tr("Graphics"), {graphics_tab.get(), graphics_advanced_tab.get(), graphics_extensions_tab.get()}},
|
||||
{tr("Audio"), {audio_tab.get()}},
|
||||
{tr("Controls"), input_tab->GetSubTabs()}},
|
||||
};
|
||||
{{tr("General"),
|
||||
{general_tab.get(), hotkeys_tab.get(), ui_tab.get(), web_tab.get(), debug_tab_tab.get()}},
|
||||
{tr("System"),
|
||||
{system_tab.get(), profile_tab.get(), network_tab.get(), filesystem_tab.get(),
|
||||
applets_tab.get()}},
|
||||
{tr("CPU"), {cpu_tab.get()}},
|
||||
{tr("Graphics"),
|
||||
{graphics_tab.get(), graphics_advanced_tab.get(), graphics_extensions_tab.get()}},
|
||||
{tr("Audio"), {audio_tab.get()}},
|
||||
{tr("Controls"), input_tab->GetSubTabs()}},
|
||||
};
|
||||
|
||||
[[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
#include <vector>
|
||||
#include <QDialog>
|
||||
#include "configuration/shared_widget.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/vk_device_info.h"
|
||||
|
||||
namespace Core {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "yuzu/configuration/configure_filesystem.h"
|
||||
#include <filesystem>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
|
|
@ -12,10 +11,11 @@
|
|||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/settings.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "qt_common/qt_compat.h"
|
||||
#include "qt_common/util/game.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "ui_configure_filesystem.h"
|
||||
#include "yuzu/configuration/configure_filesystem.h"
|
||||
|
||||
ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureFilesystem>()) {
|
||||
|
|
@ -26,8 +26,7 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
|
|||
[this] { SetDirectory(DirectoryTarget::NAND, ui->nand_directory_edit); });
|
||||
connect(ui->sdmc_directory_button, &QToolButton::pressed, this,
|
||||
[this] { SetDirectory(DirectoryTarget::SD, ui->sdmc_directory_edit); });
|
||||
connect(ui->save_directory_button, &QToolButton::pressed, this,
|
||||
[this] { SetSaveDirectory(); });
|
||||
connect(ui->save_directory_button, &QToolButton::pressed, this, [this] { SetSaveDirectory(); });
|
||||
connect(ui->gamecard_path_button, &QToolButton::pressed, this,
|
||||
[this] { SetDirectory(DirectoryTarget::Gamecard, ui->gamecard_path_edit); });
|
||||
connect(ui->dump_path_button, &QToolButton::pressed, this,
|
||||
|
|
@ -221,9 +220,9 @@ void ConfigureFilesystem::PromptSaveMigration(const QString& from_path, const QS
|
|||
.arg(QString::fromStdString(dest_save_dir.string()));
|
||||
}
|
||||
|
||||
QMessageBox::StandardButton reply = QMessageBox::question(
|
||||
this, tr("Migrate Save Data"), message,
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
QMessageBox::StandardButton reply =
|
||||
QMessageBox::question(this, tr("Migrate Save Data"), message,
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
|
||||
if (reply != QMessageBox::Yes) {
|
||||
return;
|
||||
|
|
@ -249,17 +248,17 @@ void ConfigureFilesystem::PromptSaveMigration(const QString& from_path, const QS
|
|||
progress.close();
|
||||
|
||||
if (ec) {
|
||||
QMessageBox::warning(this, tr("Migration Failed"),
|
||||
tr("Failed to migrate save data:\n%1")
|
||||
.arg(QString::fromStdString(ec.message())));
|
||||
QMessageBox::warning(
|
||||
this, tr("Migration Failed"),
|
||||
tr("Failed to migrate save data:\n%1").arg(QString::fromStdString(ec.message())));
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox::StandardButton deleteReply = QMessageBox::question(
|
||||
this, tr("Migration Complete"),
|
||||
tr("Save data has been migrated successfully.\n\n"
|
||||
"Would you like to delete the old save data?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
QMessageBox::StandardButton deleteReply =
|
||||
QMessageBox::question(this, tr("Migration Complete"),
|
||||
tr("Save data has been migrated successfully.\n\n"
|
||||
"Would you like to delete the old save data?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
|
||||
if (deleteReply == QMessageBox::Yes) {
|
||||
Common::FS::RemoveDirRecursively(source_save_dir);
|
||||
|
|
@ -278,7 +277,6 @@ void ConfigureFilesystem::UpdateEnabledControls() {
|
|||
!ui->gamecard_current_game->isChecked());
|
||||
}
|
||||
|
||||
|
||||
void ConfigureFilesystem::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@
|
|||
#include <QMessageBox>
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "ui_configure_general.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/configuration/configure_general.h"
|
||||
#include "yuzu/configuration/shared_widget.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
|
||||
ConfigureGeneral::ConfigureGeneral(const Core::System& system_,
|
||||
std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
|
||||
|
|
|
|||
|
|
@ -39,12 +39,12 @@
|
|||
#include "common/settings.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "core/core.h"
|
||||
#include "qt_common/config/uisettings.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 "qt_common/qt_common.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "yuzu/vk_device_info.h"
|
||||
|
||||
static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR,
|
||||
|
|
@ -91,8 +91,7 @@ ConfigureGraphics::ConfigureGraphics(
|
|||
: ConfigurationShared::Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphics>()},
|
||||
records{records_}, expose_compute_option{expose_compute_option_},
|
||||
update_aspect_ratio{update_aspect_ratio_}, system{system_},
|
||||
combobox_translations{builder.ComboboxTranslations()}
|
||||
{
|
||||
combobox_translations{builder.ComboboxTranslations()} {
|
||||
vulkan_device = Settings::values.vulkan_device.GetValue();
|
||||
RetrieveVulkanDevices();
|
||||
|
||||
|
|
@ -215,9 +214,9 @@ void ConfigureGraphics::PopulateVSyncModeSelection(bool use_setting) {
|
|||
|
||||
const Settings::VSyncMode global_vsync_mode = Settings::values.vsync_mode.GetValue(true);
|
||||
vsync_restore_global_button->setEnabled(
|
||||
((backend == Settings::RendererBackend::OpenGL_GLSL
|
||||
|| backend == Settings::RendererBackend::OpenGL_GLASM
|
||||
|| backend == Settings::RendererBackend::OpenGL_SPIRV) &&
|
||||
((backend == Settings::RendererBackend::OpenGL_GLSL ||
|
||||
backend == Settings::RendererBackend::OpenGL_GLASM ||
|
||||
backend == Settings::RendererBackend::OpenGL_SPIRV) &&
|
||||
(global_vsync_mode == Settings::VSyncMode::Immediate ||
|
||||
global_vsync_mode == Settings::VSyncMode::Fifo)) ||
|
||||
backend == Settings::RendererBackend::Vulkan);
|
||||
|
|
@ -286,7 +285,9 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
|
|||
api_combobox = widget->combobox;
|
||||
api_restore_global_button = widget->restore_button;
|
||||
if (!Settings::IsConfiguringGlobal()) {
|
||||
api_restore_global_button->connect(api_restore_global_button, &QAbstractButton::clicked, [this](bool) { UpdateAPILayout(); });
|
||||
api_restore_global_button->connect(api_restore_global_button,
|
||||
&QAbstractButton::clicked,
|
||||
[this](bool) { UpdateAPILayout(); });
|
||||
// Detach API's restore button and place it where we want
|
||||
// Lets us put it on the side, and it will automatically scale if there's a
|
||||
// second combobox (vulkan_device)
|
||||
|
|
@ -312,20 +313,21 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
|
|||
widget->layout()->addWidget(restore_button);
|
||||
|
||||
restore_button->connect(restore_button, &QAbstractButton::clicked,
|
||||
[restore_button, this](bool) {
|
||||
Settings::values.vsync_mode.SetGlobal(true);
|
||||
PopulateVSyncModeSelection(true);
|
||||
[restore_button, this](bool) {
|
||||
Settings::values.vsync_mode.SetGlobal(true);
|
||||
PopulateVSyncModeSelection(true);
|
||||
|
||||
restore_button->setVisible(false);
|
||||
});
|
||||
restore_button->setVisible(false);
|
||||
});
|
||||
|
||||
std::function<void()> set_non_global = [restore_button, this]() {
|
||||
Settings::values.vsync_mode.SetGlobal(false);
|
||||
UpdateVsyncSetting();
|
||||
restore_button->setVisible(true);
|
||||
};
|
||||
widget->combobox->connect(widget->combobox, QOverload<int>::of(&QComboBox::activated),
|
||||
[set_non_global]() { set_non_global(); });
|
||||
widget->combobox->connect(widget->combobox,
|
||||
QOverload<int>::of(&QComboBox::activated),
|
||||
[set_non_global]() { set_non_global(); });
|
||||
vsync_restore_global_button = restore_button;
|
||||
}
|
||||
hold_graphics.emplace(setting->Id(), widget);
|
||||
|
|
@ -364,15 +366,15 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
|
|||
ui->bg_widget->layout()->addWidget(bg_restore_button);
|
||||
|
||||
bg_restore_button->connect(bg_restore_button, &QAbstractButton::clicked,
|
||||
[bg_restore_button, this](bool) {
|
||||
const int r = Settings::values.bg_red.GetValue(true);
|
||||
const int g = Settings::values.bg_green.GetValue(true);
|
||||
const int b = Settings::values.bg_blue.GetValue(true);
|
||||
UpdateBackgroundColorButton(QColor::fromRgb(r, g, b));
|
||||
[bg_restore_button, this](bool) {
|
||||
const int r = Settings::values.bg_red.GetValue(true);
|
||||
const int g = Settings::values.bg_green.GetValue(true);
|
||||
const int b = Settings::values.bg_blue.GetValue(true);
|
||||
UpdateBackgroundColorButton(QColor::fromRgb(r, g, b));
|
||||
|
||||
bg_restore_button->setVisible(false);
|
||||
bg_restore_button->setEnabled(false);
|
||||
});
|
||||
bg_restore_button->setVisible(false);
|
||||
bg_restore_button->setEnabled(false);
|
||||
});
|
||||
|
||||
ui->bg_button->connect(ui->bg_button, &QAbstractButton::clicked, [bg_restore_button](bool) {
|
||||
bg_restore_button->setVisible(true);
|
||||
|
|
@ -397,17 +399,19 @@ const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode,
|
|||
Settings::RendererBackend backend) const {
|
||||
switch (mode) {
|
||||
case VK_PRESENT_MODE_IMMEDIATE_KHR:
|
||||
return (backend == Settings::RendererBackend::OpenGL_GLSL
|
||||
|| backend == Settings::RendererBackend::OpenGL_GLASM
|
||||
|| backend == Settings::RendererBackend::OpenGL_SPIRV)
|
||||
? tr("Off") : QStringLiteral("Immediate (%1)").arg(tr("VSync Off"));
|
||||
return (backend == Settings::RendererBackend::OpenGL_GLSL ||
|
||||
backend == Settings::RendererBackend::OpenGL_GLASM ||
|
||||
backend == Settings::RendererBackend::OpenGL_SPIRV)
|
||||
? tr("Off")
|
||||
: QStringLiteral("Immediate (%1)").arg(tr("VSync Off"));
|
||||
case VK_PRESENT_MODE_MAILBOX_KHR:
|
||||
return QStringLiteral("Mailbox (%1)").arg(tr("Recommended"));
|
||||
case VK_PRESENT_MODE_FIFO_KHR:
|
||||
return (backend == Settings::RendererBackend::OpenGL_GLSL
|
||||
|| backend == Settings::RendererBackend::OpenGL_GLASM
|
||||
|| backend == Settings::RendererBackend::OpenGL_SPIRV)
|
||||
? tr("On") : QStringLiteral("FIFO (%1)").arg(tr("VSync On"));
|
||||
return (backend == Settings::RendererBackend::OpenGL_GLSL ||
|
||||
backend == Settings::RendererBackend::OpenGL_GLASM ||
|
||||
backend == Settings::RendererBackend::OpenGL_SPIRV)
|
||||
? tr("On")
|
||||
: QStringLiteral("FIFO (%1)").arg(tr("VSync On"));
|
||||
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
|
||||
return QStringLiteral("FIFO Relaxed");
|
||||
default:
|
||||
|
|
@ -416,7 +420,9 @@ const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode,
|
|||
}
|
||||
|
||||
int ConfigureGraphics::FindIndex(u32 enumeration, int value) const {
|
||||
for (u32 i = 0; enumeration < combobox_translations.size() && i < combobox_translations.at(enumeration).size(); i++)
|
||||
for (u32 i = 0; enumeration < combobox_translations.size() &&
|
||||
i < combobox_translations.at(enumeration).size();
|
||||
i++)
|
||||
if (combobox_translations.at(enumeration)[i].first == u32(value))
|
||||
return i;
|
||||
return -1;
|
||||
|
|
@ -432,10 +438,14 @@ void ConfigureGraphics::ApplyConfiguration() {
|
|||
|
||||
Settings::values.vulkan_device.SetGlobal(true);
|
||||
auto const index = Settings::EnumMetadata<Settings::RendererBackend>::Index();
|
||||
if (Settings::IsConfiguringGlobal() || (!Settings::IsConfiguringGlobal() && api_restore_global_button->isEnabled())) {
|
||||
auto backend = index >= combobox_translations.size() || size_t(api_combobox->currentIndex()) >= combobox_translations.at(index).size()
|
||||
? Settings::values.renderer_backend.GetValue()
|
||||
: Settings::RendererBackend(combobox_translations.at(index)[api_combobox->currentIndex()].first);
|
||||
if (Settings::IsConfiguringGlobal() ||
|
||||
(!Settings::IsConfiguringGlobal() && api_restore_global_button->isEnabled())) {
|
||||
auto backend =
|
||||
index >= combobox_translations.size() ||
|
||||
size_t(api_combobox->currentIndex()) >= combobox_translations.at(index).size()
|
||||
? Settings::values.renderer_backend.GetValue()
|
||||
: Settings::RendererBackend(
|
||||
combobox_translations.at(index)[api_combobox->currentIndex()].first);
|
||||
switch (backend) {
|
||||
case Settings::RendererBackend::Vulkan:
|
||||
Settings::values.vulkan_device.SetGlobal(Settings::IsConfiguringGlobal());
|
||||
|
|
@ -506,12 +516,15 @@ Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
|
|||
auto const index = Settings::EnumMetadata<Settings::RendererBackend>::Index();
|
||||
if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled())
|
||||
return Settings::values.renderer_backend.GetValue(true);
|
||||
return index >= combobox_translations.size() || size_t(api_combobox->currentIndex()) >= combobox_translations.at(index).size()
|
||||
? Settings::values.renderer_backend.GetValue()
|
||||
: Settings::RendererBackend(combobox_translations.at(index).at(api_combobox->currentIndex()).first);
|
||||
return index >= combobox_translations.size() || size_t(api_combobox->currentIndex()) >=
|
||||
combobox_translations.at(index).size()
|
||||
? Settings::values.renderer_backend.GetValue()
|
||||
: Settings::RendererBackend(
|
||||
combobox_translations.at(index).at(api_combobox->currentIndex()).first);
|
||||
}();
|
||||
|
||||
if (selected_backend == Settings::RendererBackend::Vulkan && UISettings::values.has_broken_vulkan)
|
||||
if (selected_backend == Settings::RendererBackend::Vulkan &&
|
||||
UISettings::values.has_broken_vulkan)
|
||||
return Settings::RendererBackend::OpenGL_GLSL;
|
||||
return selected_backend;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
#include <qnamespace.h>
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "ui_configure_graphics_advanced.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/configuration/configure_graphics_advanced.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "yuzu/configuration/shared_widget.h"
|
||||
|
||||
ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <vector>
|
||||
#include <QLabel>
|
||||
#include <qnamespace.h>
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QSlider>
|
||||
#include <qnamespace.h>
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "ui_configure_graphics_extensions.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/configuration/configure_graphics_extensions.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "yuzu/configuration/shared_widget.h"
|
||||
|
||||
ConfigureGraphicsExtensions::ConfigureGraphicsExtensions(
|
||||
const Core::System& system_, std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
|
||||
const ConfigurationShared::Builder& builder, QWidget* parent)
|
||||
: Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphicsExtensions>()}, system{system_} {
|
||||
: Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphicsExtensions>()},
|
||||
system{system_} {
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
|
|
@ -44,8 +45,8 @@ void ConfigureGraphicsExtensions::Setup(const ConfigurationShared::Builder& buil
|
|||
if (setting->Id() == Settings::values.sample_shading.Id()) {
|
||||
// TODO(crueter): should support this natively perhaps?
|
||||
return builder.BuildWidget(
|
||||
setting, apply_funcs, ConfigurationShared::RequestType::Slider, true,
|
||||
1.0f, nullptr, tr("%", "Sample Shading percentage (e.g. 50%)"));
|
||||
setting, apply_funcs, ConfigurationShared::RequestType::Slider, true, 1.0f,
|
||||
nullptr, tr("%", "Sample Shading percentage (e.g. 50%)"));
|
||||
} else {
|
||||
return builder.BuildWidget(setting, apply_funcs);
|
||||
}
|
||||
|
|
@ -64,7 +65,8 @@ void ConfigureGraphicsExtensions::Setup(const ConfigurationShared::Builder& buil
|
|||
#ifdef __APPLE__
|
||||
if (setting->Id() == Settings::values.dyna_state.Id()) {
|
||||
widget->setEnabled(false);
|
||||
widget->setToolTip(tr("Extended Dynamic State is disabled on macOS due to MoltenVK compatibility issues that cause black screens."));
|
||||
widget->setToolTip(tr("Extended Dynamic State is disabled on macOS due to MoltenVK "
|
||||
"compatibility issues that cause black screens."));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
|
|
@ -13,10 +13,10 @@
|
|||
#include "hid_core/hid_core.h"
|
||||
|
||||
#include "frontend_common/config.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "ui_configure_hotkeys.h"
|
||||
#include "yuzu/configuration/configure_hotkeys.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
|
||||
|
||||
constexpr int name_column = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
|
|
@ -15,10 +15,10 @@
|
|||
#include "core/hle/service/sm/sm.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "qt_common/qt_compat.h"
|
||||
#include "ui_configure_input.h"
|
||||
#include "ui_configure_input_advanced.h"
|
||||
#include "ui_configure_input_player.h"
|
||||
#include "qt_common/qt_compat.h"
|
||||
#include "yuzu/configuration/configure_camera.h"
|
||||
#include "yuzu/configuration/configure_debug_controller.h"
|
||||
#include "yuzu/configuration/configure_input.h"
|
||||
|
|
@ -102,7 +102,7 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
|
|||
};
|
||||
|
||||
for (std::size_t i = 0; i < player_tabs.size(); ++i) {
|
||||
QHBoxLayout *tab_layout = new QHBoxLayout(player_tabs[i]);
|
||||
QHBoxLayout* tab_layout = new QHBoxLayout(player_tabs[i]);
|
||||
tab_layout->addWidget(player_controllers[i]);
|
||||
connect(player_controllers[i], &ConfigureInputPlayer::Connected, [this, i](bool checked) {
|
||||
// Ensures that connecting a controller changes the number of players
|
||||
|
|
@ -125,10 +125,11 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
|
|||
&ConfigureInput::UpdateAllInputDevices);
|
||||
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
|
||||
&ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
|
||||
connect(connected_controller_checkboxes[i], &QCheckBox::STATE_CHANGED, [this, i](int state) {
|
||||
// Keep activated controllers synced with the "Connected Controllers" checkboxes
|
||||
player_controllers[i]->ConnectPlayer(state == Qt::Checked);
|
||||
});
|
||||
connect(connected_controller_checkboxes[i], &QCheckBox::STATE_CHANGED,
|
||||
[this, i](int state) {
|
||||
// Keep activated controllers synced with the "Connected Controllers" checkboxes
|
||||
player_controllers[i]->ConnectPlayer(state == Qt::Checked);
|
||||
});
|
||||
|
||||
// Remove/hide all the elements that exceed max_players, if applicable.
|
||||
if (i >= max_players) {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
#include "core/core.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "ui_configure_input_advanced.h"
|
||||
#include "qt_common/qt_compat.h"
|
||||
#include "ui_configure_input_advanced.h"
|
||||
#include "yuzu/configuration/configure_input_advanced.h"
|
||||
|
||||
ConfigureInputAdvanced::ConfigureInputAdvanced(Core::HID::HIDCore& hid_core_, QWidget* parent)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||
|
|
@ -10,9 +10,9 @@
|
|||
|
||||
#include <QWidget>
|
||||
|
||||
#include "qt_common/config/qt_config.h"
|
||||
#include "ui_configure_input_per_game.h"
|
||||
#include "yuzu/configuration/input_profiles.h"
|
||||
#include "qt_common/config/qt_config.h"
|
||||
|
||||
class QComboBox;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
|
|
@ -14,13 +14,13 @@
|
|||
#include <QTimer>
|
||||
#include "common/assert.h"
|
||||
#include "common/param_package.h"
|
||||
#include "qt_common/config/qt_config.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/hid_types.h"
|
||||
#include "input_common/drivers/keyboard.h"
|
||||
#include "input_common/drivers/mouse.h"
|
||||
#include "input_common/main.h"
|
||||
#include "qt_common/config/qt_config.h"
|
||||
#include "ui_configure_input_player.h"
|
||||
#include "yuzu/bootmanager.h"
|
||||
#include "yuzu/configuration/configure_input_player.h"
|
||||
|
|
@ -294,11 +294,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
|
|||
InputCommon::InputSubsystem* input_subsystem_,
|
||||
InputProfiles* profiles_, Core::HID::HIDCore& hid_core_,
|
||||
bool is_powered_on_, bool debug_)
|
||||
: QWidget(parent),
|
||||
ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index{player_index_}, debug{debug_},
|
||||
is_powered_on{is_powered_on_}, input_subsystem{input_subsystem_}, profiles(profiles_),
|
||||
timeout_timer(std::make_unique<QTimer>()),
|
||||
poll_timer(std::make_unique<QTimer>()), bottom_row{bottom_row_}, hid_core{hid_core_} {
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()),
|
||||
player_index{player_index_}, debug{debug_}, is_powered_on{is_powered_on_},
|
||||
input_subsystem{input_subsystem_}, profiles(profiles_),
|
||||
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
|
||||
bottom_row{bottom_row_}, hid_core{hid_core_} {
|
||||
if (player_index == 0) {
|
||||
auto* emulated_controller_p1 =
|
||||
hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
|
|
@ -1215,10 +1215,8 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
|
|||
case Core::HID::NpadStyleIndex::Fullkey:
|
||||
case Core::HID::NpadStyleIndex::Handheld:
|
||||
layout_hidden = {
|
||||
ui->buttonShoulderButtonsSLSRLeft,
|
||||
ui->buttonShoulderButtonsSLSRRight,
|
||||
ui->horizontalSpacerShoulderButtonsWidget2,
|
||||
ui->horizontalSpacerShoulderButtonsWidget4,
|
||||
ui->buttonShoulderButtonsSLSRLeft, ui->buttonShoulderButtonsSLSRRight,
|
||||
ui->horizontalSpacerShoulderButtonsWidget2, ui->horizontalSpacerShoulderButtonsWidget4,
|
||||
ui->buttonMiscButtonsScreenshotGroup,
|
||||
};
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
|
|
@ -2432,7 +2432,8 @@ void PlayerControlPreview::DrawProJoystick(QPainter& p, const QPointF center, co
|
|||
1.0 - std::sqrt((offset.x() * offset.x()) + (offset.y() * offset.y())) * 0.1f);
|
||||
|
||||
const float rotation =
|
||||
((offset.x() == 0.f) ? std::atan(1.f) * 2.f : std::atan(offset.y() / offset.x())) * (180.f / (std::atan(1.f) * 4.f));
|
||||
((offset.x() == 0.f) ? std::atan(1.f) * 2.f : std::atan(offset.y() / offset.x())) *
|
||||
(180.f / (std::atan(1.f) * 4.f));
|
||||
|
||||
p.save();
|
||||
p.translate(offset_center);
|
||||
|
|
|
|||
|
|
@ -12,10 +12,7 @@
|
|||
#include "yuzu/configuration/configure_network.h"
|
||||
|
||||
ConfigureNetwork::ConfigureNetwork(const Core::System& system_, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, ui(std::make_unique<Ui::ConfigureNetwork>())
|
||||
, system{system_}
|
||||
{
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureNetwork>()), system{system_} {
|
||||
ui->setupUi(this);
|
||||
for (const auto& iface : Network::GetAvailableNetworkInterfaces())
|
||||
ui->network_interface->addItem(QString::fromStdString(iface.name));
|
||||
|
|
|
|||
|
|
@ -29,28 +29,28 @@
|
|||
#include "core/file_sys/xts_archive.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "qt_common/config/uisettings.h"
|
||||
#include "ui_configure_per_game.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "yuzu/configuration/configure_applets.h"
|
||||
#include "yuzu/configuration/configure_audio.h"
|
||||
#include "yuzu/configuration/configure_cpu.h"
|
||||
#include "yuzu/configuration/configure_graphics.h"
|
||||
#include "yuzu/configuration/configure_graphics_advanced.h"
|
||||
#include "yuzu/configuration/configure_graphics_extensions.h"
|
||||
#include "yuzu/configuration/configure_input_per_game.h"
|
||||
#include "yuzu/configuration/configure_network.h"
|
||||
#include "yuzu/configuration/configure_per_game.h"
|
||||
#include "yuzu/configuration/configure_per_game_addons.h"
|
||||
#include "yuzu/configuration/configure_system.h"
|
||||
#include "yuzu/configuration/configure_network.h"
|
||||
#include "yuzu/configuration/configure_applets.h"
|
||||
#include "qt_common/config/uisettings.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,
|
||||
Core::System& system_)
|
||||
: QDialog(parent),
|
||||
ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_}, system{system_},
|
||||
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id{title_id_},
|
||||
system{system_},
|
||||
builder{std::make_unique<ConfigurationShared::Builder>(this, !system_.IsPoweredOn())},
|
||||
tab_group{std::make_shared<std::vector<ConfigurationShared::Tab*>>()} {
|
||||
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@
|
|||
#include "configuration/shared_widget.h"
|
||||
#include "core/file_sys/vfs/vfs_types.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "vk_device_info.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
#include "qt_common/config/qt_config.h"
|
||||
#include "qt_common/config/shared_translation.h"
|
||||
#include "vk_device_info.h"
|
||||
#include "yuzu/configuration/configuration_shared.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -80,7 +82,8 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p
|
|||
connect(ui->folder, &QAbstractButton::clicked, this, &ConfigurePerGameAddons::InstallModFolder);
|
||||
connect(ui->zip, &QAbstractButton::clicked, this, &ConfigurePerGameAddons::InstallModZip);
|
||||
|
||||
connect(tree_view, &QTreeView::customContextMenuRequested, this, &ConfigurePerGameAddons::showContextMenu);
|
||||
connect(tree_view, &QTreeView::customContextMenuRequested, this,
|
||||
&ConfigurePerGameAddons::showContextMenu);
|
||||
}
|
||||
|
||||
ConfigurePerGameAddons::~ConfigurePerGameAddons() = default;
|
||||
|
|
@ -92,10 +95,10 @@ void ConfigurePerGameAddons::OnItemChanged(QStandardItem* item) {
|
|||
for (auto* update_item : update_items) {
|
||||
if (update_item != item && update_item->checkState() == Qt::Checked) {
|
||||
disconnect(item_model, &QStandardItemModel::itemChanged, this,
|
||||
&ConfigurePerGameAddons::OnItemChanged);
|
||||
&ConfigurePerGameAddons::OnItemChanged);
|
||||
update_item->setCheckState(Qt::Unchecked);
|
||||
connect(item_model, &QStandardItemModel::itemChanged, this,
|
||||
&ConfigurePerGameAddons::OnItemChanged);
|
||||
&ConfigurePerGameAddons::OnItemChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -105,17 +108,39 @@ void ConfigurePerGameAddons::OnItemChanged(QStandardItem* item) {
|
|||
void ConfigurePerGameAddons::ApplyConfiguration() {
|
||||
std::vector<std::string> disabled_addons;
|
||||
|
||||
for (const auto& item : list_items) {
|
||||
const auto disabled = item.front()->checkState() == Qt::Unchecked;
|
||||
if (disabled) {
|
||||
QVariant userData = item.front()->data(Qt::UserRole);
|
||||
if (userData.isValid() && userData.canConvert<quint32>() && item.front()->text() == QStringLiteral("Update")) {
|
||||
quint32 numeric_version = userData.toUInt();
|
||||
// Helper function to recursively collect disabled items
|
||||
std::function<void(QStandardItem*)> collect_disabled = [&](QStandardItem* item) {
|
||||
if (item == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this item is disabled
|
||||
if (item->isCheckable() && item->checkState() == Qt::Unchecked) {
|
||||
QVariant userData = item->data(Qt::UserRole);
|
||||
if (userData.isValid() && userData.canConvert<quint32>() &&
|
||||
item->text() == QStringLiteral("Update")) {
|
||||
const quint32 numeric_version = userData.toUInt();
|
||||
disabled_addons.push_back(fmt::format("Update@{}", numeric_version));
|
||||
} else {
|
||||
disabled_addons.push_back(item.front()->text().toStdString());
|
||||
// Use the stored key from UserRole, falling back to text
|
||||
const auto key = userData.toString();
|
||||
if (!key.isEmpty()) {
|
||||
disabled_addons.push_back(key.toStdString());
|
||||
} else {
|
||||
disabled_addons.push_back(item->text().toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process children (for cheats under mods)
|
||||
for (int row = 0; row < item->rowCount(); ++row) {
|
||||
collect_disabled(item->child(row, 0));
|
||||
}
|
||||
};
|
||||
|
||||
// Process all root items
|
||||
for (int row = 0; row < item_model->rowCount(); ++row) {
|
||||
collect_disabled(item_model->item(row, 0));
|
||||
}
|
||||
|
||||
auto current = Settings::values.disabled_addons[title_id];
|
||||
|
|
@ -164,7 +189,7 @@ void ConfigurePerGameAddons::InstallMods(const QStringList& mods) {
|
|||
}
|
||||
}
|
||||
|
||||
void ConfigurePerGameAddons::InstallModPath(const QString& path, const QString &fallbackName) {
|
||||
void ConfigurePerGameAddons::InstallModPath(const QString& path, const QString& fallbackName) {
|
||||
const auto mods = QtCommon::Mod::GetModFolders(path, fallbackName);
|
||||
|
||||
if (mods.size() > 1) {
|
||||
|
|
@ -203,8 +228,9 @@ void ConfigurePerGameAddons::InstallModZip() {
|
|||
|
||||
void ConfigurePerGameAddons::AddonDeleteRequested(QList<QModelIndex> selected) {
|
||||
QList<QModelIndex> filtered;
|
||||
for (const QModelIndex &index : selected) {
|
||||
if (!index.data(PATCH_LOCATION).toString().isEmpty()) filtered << index;
|
||||
for (const QModelIndex& index : selected) {
|
||||
if (!index.data(PATCH_LOCATION).toString().isEmpty())
|
||||
filtered << index;
|
||||
}
|
||||
|
||||
if (filtered.empty()) {
|
||||
|
|
@ -215,10 +241,9 @@ void ConfigurePerGameAddons::AddonDeleteRequested(QList<QModelIndex> selected) {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
const auto header = tr("You are about to delete the following installed mods:\n");
|
||||
QString selected_str;
|
||||
for (const QModelIndex &index : filtered) {
|
||||
for (const QModelIndex& index : filtered) {
|
||||
selected_str = selected_str % index.data().toString() % QStringLiteral("\n");
|
||||
}
|
||||
|
||||
|
|
@ -231,9 +256,10 @@ void ConfigurePerGameAddons::AddonDeleteRequested(QList<QModelIndex> selected) {
|
|||
QtCommon::Frontend::StandardButton::Yes |
|
||||
QtCommon::Frontend::StandardButton::No);
|
||||
|
||||
if (choice == QtCommon::Frontend::StandardButton::No) return;
|
||||
if (choice == QtCommon::Frontend::StandardButton::No)
|
||||
return;
|
||||
|
||||
for (const QModelIndex &index : filtered) {
|
||||
for (const QModelIndex& index : filtered) {
|
||||
std::filesystem::remove_all(index.data(PATCH_LOCATION).toString().toStdString());
|
||||
}
|
||||
|
||||
|
|
@ -252,17 +278,18 @@ void ConfigurePerGameAddons::showContextMenu(const QPoint& pos) {
|
|||
auto selected = tree_view->selectionModel()->selectedRows();
|
||||
if (index.isValid() && selected.empty()) {
|
||||
QModelIndex idx = item_model->index(index.row(), 0);
|
||||
if (idx.isValid()) selected << idx;
|
||||
if (idx.isValid())
|
||||
selected << idx;
|
||||
}
|
||||
|
||||
if (selected.empty()) return;
|
||||
if (selected.empty())
|
||||
return;
|
||||
|
||||
QMenu menu(this);
|
||||
|
||||
QAction *remove = menu.addAction(tr("&Delete"));
|
||||
connect(remove, &QAction::triggered, this, [this, selected]() {
|
||||
AddonDeleteRequested(selected);
|
||||
});
|
||||
QAction* remove = menu.addAction(tr("&Delete"));
|
||||
connect(remove, &QAction::triggered, this,
|
||||
[this, selected]() { AddonDeleteRequested(selected); });
|
||||
|
||||
if (selected.length() == 1) {
|
||||
auto loc = selected.at(0).data(PATCH_LOCATION).toString();
|
||||
|
|
@ -300,23 +327,40 @@ void ConfigurePerGameAddons::LoadConfiguration() {
|
|||
FileSys::VirtualFile update_raw;
|
||||
loader->ReadUpdateRaw(update_raw);
|
||||
|
||||
// Get the build ID from the main executable for cheat enumeration
|
||||
const auto build_id = pm.GetBuildID(update_raw);
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
update_items.clear();
|
||||
list_items.clear();
|
||||
item_model->removeRows(0, item_model->rowCount());
|
||||
|
||||
std::vector<FileSys::Patch> patches = pm.GetPatches(update_raw);
|
||||
std::vector<FileSys::Patch> patches = pm.GetPatches(update_raw, build_id);
|
||||
|
||||
bool has_enabled_update = false;
|
||||
|
||||
// Map to store parent items for mods (for adding cheat children)
|
||||
std::map<std::string, QStandardItem*> mod_items;
|
||||
|
||||
for (const auto& patch : patches) {
|
||||
const auto name = QString::fromStdString(patch.name);
|
||||
|
||||
// For cheats, we need to use the full key (parent::name) for storage
|
||||
std::string storage_key;
|
||||
if (patch.type == FileSys::PatchType::Cheat && !patch.parent_name.empty()) {
|
||||
storage_key = patch.parent_name + "::" + patch.name;
|
||||
} else {
|
||||
storage_key = patch.name;
|
||||
}
|
||||
|
||||
auto* const first_item = new QStandardItem;
|
||||
first_item->setText(name);
|
||||
first_item->setCheckable(true);
|
||||
|
||||
// Store the storage key as user data for later retrieval
|
||||
first_item->setData(QString::fromStdString(storage_key), Qt::UserRole);
|
||||
|
||||
const bool is_external_update = patch.type == FileSys::PatchType::Update &&
|
||||
patch.source == FileSys::PatchSource::External &&
|
||||
patch.numeric_version != 0;
|
||||
|
|
@ -333,9 +377,11 @@ void ConfigurePerGameAddons::LoadConfiguration() {
|
|||
bool patch_disabled = false;
|
||||
if (is_external_update) {
|
||||
std::string disabled_key = fmt::format("Update@{}", patch.numeric_version);
|
||||
patch_disabled = std::find(disabled.begin(), disabled.end(), disabled_key) != disabled.end();
|
||||
patch_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), disabled_key) != disabled.end();
|
||||
} else {
|
||||
patch_disabled = std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
|
||||
patch_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), storage_key) != disabled.end();
|
||||
}
|
||||
|
||||
bool should_enable = !patch_disabled;
|
||||
|
|
@ -353,10 +399,30 @@ void ConfigurePerGameAddons::LoadConfiguration() {
|
|||
|
||||
first_item->setCheckState(should_enable ? Qt::Checked : Qt::Unchecked);
|
||||
|
||||
list_items.push_back(QList<QStandardItem*>{
|
||||
first_item, new QStandardItem{QString::fromStdString(patch.version)}});
|
||||
item_model->appendRow(list_items.back());
|
||||
auto* const version_item = new QStandardItem{QString::fromStdString(patch.version)};
|
||||
|
||||
if (patch.type == FileSys::PatchType::Cheat && !patch.parent_name.empty()) {
|
||||
// This is a cheat - add as child of its parent mod
|
||||
auto parent_it = mod_items.find(patch.parent_name);
|
||||
if (parent_it != mod_items.end()) {
|
||||
parent_it->second->appendRow(QList<QStandardItem*>{first_item, version_item});
|
||||
} else {
|
||||
// Parent not found (shouldn't happen), add to root
|
||||
list_items.push_back(QList<QStandardItem*>{first_item, version_item});
|
||||
item_model->appendRow(list_items.back());
|
||||
}
|
||||
} else {
|
||||
// This is a top-level item (Update, Mod, DLC)
|
||||
list_items.push_back(QList<QStandardItem*>{first_item, version_item});
|
||||
item_model->appendRow(list_items.back());
|
||||
|
||||
// Store mod items for later cheat attachment
|
||||
if (patch.type == FileSys::PatchType::Mod) {
|
||||
mod_items[patch.name] = first_item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tree_view->expandAll();
|
||||
tree_view->resizeColumnToContents(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,10 +32,7 @@ class ConfigurePerGameAddons : public QWidget {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum PatchData {
|
||||
NUMERIC_VERSION = Qt::UserRole,
|
||||
PATCH_LOCATION
|
||||
};
|
||||
enum PatchData { NUMERIC_VERSION = Qt::UserRole, PATCH_LOCATION };
|
||||
|
||||
explicit ConfigurePerGameAddons(Core::System& system_, QWidget* parent = nullptr);
|
||||
~ConfigurePerGameAddons() override;
|
||||
|
|
@ -48,7 +45,7 @@ public:
|
|||
void SetTitleId(u64 id);
|
||||
|
||||
public slots:
|
||||
void InstallMods(const QStringList &mods);
|
||||
void InstallMods(const QStringList& mods);
|
||||
void InstallModPath(const QString& path, const QString& fallbackName = {});
|
||||
|
||||
void InstallModFolder();
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue