diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt index 0bfdc674e7..c3f3a462ce 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt @@ -8,6 +8,8 @@ package org.yuzu.yuzu_emu.adapters import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding import org.yuzu.yuzu_emu.model.Patch import org.yuzu.yuzu_emu.model.PatchType @@ -24,15 +26,25 @@ class AddonAdapter(val addonViewModel: AddonViewModel) : inner class AddonViewHolder(val binding: ListItemAddonBinding) : AbstractViewHolder(binding) { override fun bind(model: Patch) { - binding.addonCard.setOnClickListener { - binding.addonSwitch.performClick() + val isCheat = model.isCheat() + val isIncompatible = isCheat && model.cheatCompat == Patch.CHEAT_COMPAT_INCOMPATIBLE + val indentPx = if (isCheat) { + (32 * binding.root.context.resources.displayMetrics.density).toInt() + } else { + 0 + } + binding.root.updateLayoutParams { + marginStart = indentPx } + binding.addonCard.setOnClickListener( + if (isIncompatible) null else { { binding.addonSwitch.performClick() } } + ) binding.title.text = model.name binding.version.text = model.version - binding.addonSwitch.setOnCheckedChangeListener(null) binding.addonSwitch.isChecked = model.enabled - + binding.addonSwitch.isEnabled = !isIncompatible + binding.addonSwitch.alpha = if (isIncompatible) 0.38f else 1.0f binding.addonSwitch.setOnCheckedChangeListener { _, checked -> if (PatchType.from(model.type) == PatchType.Update && checked) { addonViewModel.enableOnlyThisUpdate(model) @@ -41,13 +53,9 @@ class AddonAdapter(val addonViewModel: AddonViewModel) : model.enabled = checked } } - - val canDelete = model.isRemovable && !model.isCheat() - - binding.deleteCard.isEnabled = canDelete - binding.buttonDelete.isEnabled = canDelete - binding.deleteCard.alpha = if (canDelete) 1f else 0.38f - + val canDelete = model.isRemovable && !isCheat + binding.deleteCard.isVisible = canDelete + binding.buttonDelete.isVisible = canDelete if (canDelete) { val deleteAction = { addonViewModel.setAddonToDelete(model) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt index 91a191b8c7..0acd9aebc3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt @@ -18,7 +18,8 @@ data class Patch( val titleId: String, val numericVersion: Long = 0, val source: Int = 0, - val parentName: String = "" // For cheats: name of the mod folder containing them + val parentName: String = "", // For cheats: name of the mod folder containing them + val cheatCompat: Int = CHEAT_COMPAT_COMPATIBLE ) { companion object { const val SOURCE_UNKNOWN = 0 @@ -26,6 +27,9 @@ data class Patch( const val SOURCE_SDMC = 2 const val SOURCE_EXTERNAL = 3 const val SOURCE_PACKED = 4 + + const val CHEAT_COMPAT_COMPATIBLE = 0 + const val CHEAT_COMPAT_INCOMPATIBLE = 1 } val isRemovable: Boolean @@ -48,4 +52,6 @@ data class Patch( * Individual cheats have type=Cheat and a parent mod name. */ fun isCheat(): Boolean = type == PatchType.Cheat.int && parentName.isNotEmpty() + + fun isIncompatibleCheat(): Boolean = isCheat() && cheatCompat == CHEAT_COMPAT_INCOMPATIBLE } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index d77ccdbf9a..f6d5a7c1de 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -1397,9 +1397,8 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env loader->ReadUpdateRaw(update_raw); // Get build ID for individual cheat enumeration - const auto build_id = pm.GetBuildID(update_raw); + auto patches = pm.GetPatches(update_raw); - auto patches = pm.GetPatches(update_raw, build_id); jobjectArray jpatchArray = env->NewObjectArray(patches.size(), Common::Android::GetPatchClass(), nullptr); int i = 0; @@ -1411,7 +1410,8 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env Common::Android::ToJString(env, std::to_string(patch.program_id)), Common::Android::ToJString(env, std::to_string(patch.title_id)), static_cast(patch.numeric_version), static_cast(patch.source), - Common::Android::ToJString(env, patch.parent_name)); + Common::Android::ToJString(env, patch.parent_name), + static_cast(patch.cheat_compat)); env->SetObjectArrayElement(jpatchArray, i, jpatch); ++i; } diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index c7aac47b9d..eb4cf4d529 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -168,16 +168,11 @@ u64 PatchManager::GetTitleID() const { return title_id; } -VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { - LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); - - if (exefs == nullptr) - return exefs; +PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType type) const { + UpdateResolution update_res{}; const auto& disabled = Settings::values.disabled_addons[title_id]; - bool update_disabled = true; - std::optional enabled_version; bool checked_external = false; bool checked_manual = false; @@ -194,8 +189,10 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { checked_external = true; for (const auto& update_entry : update_versions) { if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { - update_disabled = false; - enabled_version = update_entry.version; + update_res.update_disabled = false; + update_res.update_active_version = update_entry.version; + update_res.update_active_raw = external_provider->GetEntryForVersion( + update_tid, type, update_entry.version); break; } } @@ -213,8 +210,10 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { checked_manual = true; for (const auto& update_entry : manual_update_versions) { if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { - update_disabled = false; - enabled_version = update_entry.version; + update_res.update_disabled = false; + update_res.update_active_version = update_entry.version; + update_res.update_active_raw = manual_provider->GetEntryForVersion( + update_tid, type, update_entry.version); break; } } @@ -226,24 +225,18 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { // check for original NAND style // Check NAND if: no external updates exist, OR all external updates are disabled if (!checked_external && !checked_manual) { - // Only enable NAND update if it exists AND is not disabled - // We need to check if an update actually exists in the content provider - const bool has_nand_update = - content_provider.HasEntry(update_tid, ContentRecordType::Program); - - if (has_nand_update) { - const bool nand_disabled = - std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend(); - const bool sdmc_disabled = - std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend(); - const bool generic_disabled = - std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); - - if (!nand_disabled && !sdmc_disabled && !generic_disabled) { - update_disabled = false; - } + const bool nand_disabled = + std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend(); + const bool sdmc_disabled = + std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend(); + const bool generic_disabled = + std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); + + if (!nand_disabled && !sdmc_disabled && !generic_disabled) { + update_res.update_active_raw = content_provider.GetEntryRaw(update_tid, type); + update_res.update_disabled = update_res.update_active_raw == nullptr; } - } else if (update_disabled && content_union) { + } else if (update_res.update_disabled && content_union) { const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend(); const bool sdmc_disabled = @@ -251,18 +244,20 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (!nand_disabled || !sdmc_disabled) { const auto nand_sdmc_entries = content_union->ListEntriesFilterOrigin( - std::nullopt, TitleType::Update, ContentRecordType::Program, update_tid); + std::nullopt, TitleType::Update, type, update_tid); for (const auto& [slot, entry] : nand_sdmc_entries) { if (slot == ContentProviderUnionSlot::UserNAND || slot == ContentProviderUnionSlot::SysNAND) { if (!nand_disabled) { - update_disabled = false; + update_res.update_active_raw = content_provider.GetEntryRaw(update_tid, type); + update_res.update_disabled = update_res.update_active_raw == nullptr; break; } } else if (slot == ContentProviderUnionSlot::SDMC) { if (!sdmc_disabled) { - update_disabled = false; + update_res.update_active_raw = content_provider.GetEntryRaw(update_tid, type); + update_res.update_disabled = update_res.update_active_raw == nullptr; break; } } @@ -270,46 +265,36 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { } } - // Game Updates - std::unique_ptr update = nullptr; + return update_res; +} - // If we have a specific enabled version from external provider, use it - if (enabled_version.has_value() && content_union) { - const auto* external_provider = content_union->GetExternalProvider(); - if (external_provider) { - auto file = external_provider->GetEntryForVersion( - update_tid, ContentRecordType::Program, *enabled_version); - if (file != nullptr) { - update = std::make_unique(file); - } - } +VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { + LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); - // Also try ManualContentProvider - if (update == nullptr) { - const auto* manual_provider = static_cast( - content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual)); - if (manual_provider) { - auto file = manual_provider->GetEntryForVersion( - update_tid, ContentRecordType::Program, *enabled_version); - if (file != nullptr) { - update = std::make_unique(file); - } - } - } + if (exefs == nullptr) + return exefs; + + const auto update_tid = GetUpdateTitleID(title_id); + const auto update_res = GetActiveUpdate(); + + std::unique_ptr update = nullptr; + + if (update_res.update_active_version.has_value() && update_res.update_active_raw != nullptr) { + update = std::make_unique(update_res.update_active_raw); } - // Fallback to regular content provider if no external update was loaded - if (update == nullptr && !update_disabled) { + if (update == nullptr && !update_res.update_disabled) { update = content_provider.GetEntry(update_tid, ContentRecordType::Program); } - if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) { + if (!update_res.update_disabled && update != nullptr && update->GetExeFS() != nullptr) { LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); exefs = update->GetExeFS(); } // LayeredExeFS + const auto& disabled = Settings::values.disabled_addons[title_id]; const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); @@ -642,114 +627,21 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs auto romfs = base_romfs; - // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto& disabled = Settings::values.disabled_addons[title_id]; - - bool update_disabled = true; - std::optional enabled_version; - VirtualFile update_raw = nullptr; - bool checked_external = false; - bool checked_manual = false; - - const auto* content_union = static_cast(&content_provider); - if (content_union) { - // First, check ExternalContentProvider - const auto* external_provider = content_union->GetExternalProvider(); - if (external_provider) { - const auto update_versions = external_provider->ListUpdateVersions(update_tid); - - if (!update_versions.empty()) { - checked_external = true; - for (const auto& update_entry : update_versions) { - if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { - update_disabled = false; - enabled_version = update_entry.version; - update_raw = external_provider->GetEntryForVersion(update_tid, type, - update_entry.version); - break; - } - } - } - } - - if (!checked_external) { - const auto* manual_provider = static_cast( - content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual)); - if (manual_provider) { - const auto manual_update_versions = manual_provider->ListUpdateVersions(update_tid); - - if (!manual_update_versions.empty()) { - checked_manual = true; - for (const auto& update_entry : manual_update_versions) { - if (!IsVersionedExternalUpdateDisabled(disabled, update_entry.version)) { - update_disabled = false; - enabled_version = update_entry.version; - update_raw = manual_provider->GetEntryForVersion(update_tid, type, - update_entry.version); - break; - } - } - } - } - } - } - - if (!checked_external && !checked_manual) { - const bool nand_disabled = - std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend(); - const bool sdmc_disabled = - std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend(); - const bool generic_disabled = - std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); - - if (!nand_disabled && !sdmc_disabled && !generic_disabled) { - update_disabled = false; - } - if (!update_disabled) { - update_raw = content_provider.GetEntryRaw(update_tid, type); - } - } else if (update_disabled && content_union) { - const bool nand_disabled = - std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend(); - const bool sdmc_disabled = - std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend(); + const auto update_res = GetActiveUpdate(type); - if (!nand_disabled || !sdmc_disabled) { - const auto nand_sdmc_entries = content_union->ListEntriesFilterOrigin( - std::nullopt, TitleType::Update, type, update_tid); - - for (const auto& [slot, entry] : nand_sdmc_entries) { - if (slot == ContentProviderUnionSlot::UserNAND || - slot == ContentProviderUnionSlot::SysNAND) { - if (!nand_disabled) { - update_disabled = false; - update_raw = content_provider.GetEntryRaw(update_tid, type); - break; - } - } else if (slot == ContentProviderUnionSlot::SDMC) { - if (!sdmc_disabled) { - update_disabled = false; - update_raw = content_provider.GetEntryRaw(update_tid, type); - break; - } - } - } - } - } - - if (!update_disabled && update_raw != nullptr && base_nca != nullptr) { - const auto new_nca = std::make_shared(update_raw, base_nca); + if (!update_res.update_disabled && update_res.update_active_raw != nullptr && base_nca != nullptr) { + const auto new_nca = std::make_shared(update_res.update_active_raw, base_nca); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { LOG_INFO( Loader, " RomFS: Update ({}) applied successfully", - enabled_version.has_value() - ? FormatTitleVersion(*enabled_version) + update_res.update_active_version.has_value() + ? FormatTitleVersion(*update_res.update_active_version) : FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); romfs = new_nca->GetRomFS(); } - } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) { + } else if (!update_res.update_disabled && packed_update_raw != nullptr && base_nca != nullptr) { const auto new_nca = std::make_shared(packed_update_raw, base_nca); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { @@ -777,11 +669,12 @@ PatchManager::BuildID PatchManager::GetBuildID(VirtualFile update_raw) const { // 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(); + const auto update_res = GetActiveUpdate(); + if (!update_res.update_disabled && update_res.update_active_raw != nullptr) { + const auto update = std::make_shared(update_res.update_active_raw, base_nca.get()); + if (update->GetStatus() == Loader::ResultStatus::Success && update->GetExeFS() != nullptr) { + exefs = update->GetExeFS(); + } } else if (update_raw != nullptr) { const auto new_nca = std::make_shared(update_raw, base_nca.get()); if (new_nca->GetStatus() == Loader::ResultStatus::Success && @@ -812,7 +705,7 @@ PatchManager::BuildID PatchManager::GetBuildID(VirtualFile update_raw) const { return build_id; } -std::vector PatchManager::GetPatches(VirtualFile update_raw, const BuildID& build_id) const { +std::vector PatchManager::GetPatches(VirtualFile update_raw) const { if (title_id == 0) { return {}; } @@ -996,6 +889,7 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw, const BuildI } // Check if we have a valid build_id for cheat enumeration + const auto build_id = GetBuildID(update_raw); const bool has_build_id = std::any_of(build_id.begin(), build_id.end(), [](u8 b) { return b != 0; }); @@ -1068,30 +962,51 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw, const BuildI 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; + if (!cheat_entries.empty()) { + for (const auto& cheat : cheat_entries) { + if (cheat.cheat_id <= 1 || cheat.definition.readable_name[0] == '\0') { + continue; + } + const std::string cheat_name = cheat.definition.readable_name.data(); + const std::string cheat_key = mod->GetName() + "::" + cheat_name; + out.push_back({ + .enabled = std::find(disabled.begin(), disabled.end(), cheat_key) == + disabled.end(), + .name = cheat_name, + .version = types, + .type = PatchType::Cheat, + .program_id = title_id, + .title_id = title_id, + .parent_name = mod->GetName(), + .cheat_compat = CheatCompatibility::Compatible, + }); } - - const std::string cheat_name = cheat.definition.readable_name.data(); - if (cheat_name.empty()) { - continue; + } else { + std::string title; + for (const auto& cheat_file : cheats_dir->GetFiles()) { + if (!cheat_file->GetName().ends_with(".txt")) + continue; + std::vector data(cheat_file->GetSize()); + if (cheat_file->Read(data.data(), data.size()) == data.size()) { + const auto entries = + Service::DMNT::CheatParser{}.Parse(std::string_view( + reinterpret_cast(data.data()), data.size())); + if (entries.size() > 1) + title = entries[1].definition.readable_name.data(); + } + break; } - - // 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()}); + out.push_back({ + .enabled = false, + .name = title.empty() ? "Incompatible cheat" + : fmt::format("Incompatible cheat: {}", title), + .version = types, + .type = PatchType::Cheat, + .program_id = title_id, + .title_id = title_id, + .parent_name = mod->GetName(), + .cheat_compat = CheatCompatibility::Incompatible, + }); } } } diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 5cb06b9052..7904797862 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -10,6 +10,7 @@ #include #include #include + #include "common/common_types.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs/vfs_types.h" @@ -42,6 +43,11 @@ enum class PatchSource { Packed, }; +enum class CheatCompatibility { + Incompatible, + Compatible, +}; + struct Patch { bool enabled; std::string name; @@ -53,6 +59,7 @@ struct Patch { std::string location; u32 numeric_version{0}; std::string parent_name; + CheatCompatibility cheat_compat{CheatCompatibility::Incompatible}; }; // A centralized class to manage patches to games. @@ -95,8 +102,7 @@ public: bool apply_layeredfs = true) const; // Returns a vector of patches including individual cheats - [[nodiscard]] std::vector GetPatches(VirtualFile update_raw = nullptr, - const BuildID& build_id = {}) const; + [[nodiscard]] std::vector GetPatches(VirtualFile update_raw = nullptr) const; [[nodiscard]] BuildID GetBuildID(VirtualFile update_raw = nullptr) const; @@ -116,6 +122,14 @@ private: [[nodiscard]] std::vector CollectPatches(const std::vector& patch_dirs, const std::string& build_id) const; + struct UpdateResolution { + bool update_disabled{true}; + VirtualFile update_active_raw; + std::optional update_active_version; + }; + [[nodiscard]] UpdateResolution GetActiveUpdate( + ContentRecordType type = ContentRecordType::Program) const; + u64 title_id; const Service::FileSystem::FileSystemController& fs_controller; const ContentProvider& content_provider; diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index 4cdc5a29a5..767d03d8b8 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -327,16 +327,13 @@ 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 patches = pm.GetPatches(update_raw, build_id); + std::vector patches = pm.GetPatches(update_raw); bool has_enabled_update = false; @@ -356,17 +353,24 @@ void ConfigurePerGameAddons::LoadConfiguration() { 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; - const bool is_mod = patch.type == FileSys::PatchType::Mod; + const bool is_incompatible_cheat = + patch.type == FileSys::PatchType::Cheat && + patch.cheat_compat != FileSys::CheatCompatibility::Compatible; + + if (is_incompatible_cheat) { + first_item->setCheckable(false); + first_item->setEnabled(false); + } else { + first_item->setCheckable(true); + first_item->setData(QString::fromStdString(storage_key), Qt::UserRole); + } + if (is_external_update) { first_item->setData(static_cast(patch.numeric_version), NUMERIC_VERSION); } else if (is_mod) { @@ -375,16 +379,18 @@ 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(); - } else { - patch_disabled = - std::find(disabled.begin(), disabled.end(), storage_key) != disabled.end(); + if (!is_incompatible_cheat) { + 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(); + } else { + patch_disabled = + std::find(disabled.begin(), disabled.end(), storage_key) != disabled.end(); + } } - bool should_enable = !patch_disabled; + bool should_enable = !patch_disabled && !is_incompatible_cheat; if (patch.type == FileSys::PatchType::Update) { if (should_enable) { @@ -397,9 +403,14 @@ void ConfigurePerGameAddons::LoadConfiguration() { update_items.push_back(first_item); } - first_item->setCheckState(should_enable ? Qt::Checked : Qt::Unchecked); + if (!is_incompatible_cheat) { + first_item->setCheckState(should_enable ? Qt::Checked : Qt::Unchecked); + } auto* const version_item = new QStandardItem{QString::fromStdString(patch.version)}; + if (is_incompatible_cheat) { + version_item->setEnabled(false); + } if (patch.type == FileSys::PatchType::Cheat && !patch.parent_name.empty()) { // This is a cheat - add as child of its parent mod