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..7bc8a7a388 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -1396,10 +1396,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env FileSys::VirtualFile update_raw; 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, build_id); + auto patches = pm.GetPatches(update_raw); jobjectArray jpatchArray = env->NewObjectArray(patches.size(), Common::Android::GetPatchClass(), nullptr); int i = 0; @@ -1411,7 +1408,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/core.cpp b/src/core/core.cpp index c1219c87f3..94b420f73e 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -470,6 +470,7 @@ struct System::Impl { Core::SpeedLimiter speed_limiter; ExecuteProgramCallback execute_program_callback; ExitCallback exit_callback; + std::optional services; std::optional debugger; std::optional general_channel_context; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index c7aac47b9d..08b2b3b003 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); + 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 (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; - } + 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,22 @@ 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 +267,36 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { } } - // Game Updates + return update_res; +} + +VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { + LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); + + if (exefs == nullptr) + return exefs; + + const auto update_tid = GetUpdateTitleID(title_id); + const auto update_res = GetActiveUpdate(); + std::unique_ptr update = nullptr; - // 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); - } - } - - // 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 (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 +629,22 @@ 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]; + const auto update_res = GetActiveUpdate(type); - 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(); - - 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 +672,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 +708,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,40 +892,51 @@ 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; }); // General Mods (LayeredFS and IPS) const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id); - if (mod_dir != nullptr) { - for (const auto& mod : mod_dir->GetSubdirectories()) { + const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); + + for (const auto& root : std::array{mod_dir, sdmc_mod_dir}) { + if (root == nullptr) + continue; + + const bool is_sdmc = (root == sdmc_mod_dir); + const std::vector mods = + is_sdmc ? std::vector{root} : root->GetSubdirectories(); + + for (const auto& mod : mods) { + std::string mod_name = is_sdmc ? "SDMC" : mod->GetName(); std::string types; bool has_cheats = false; const auto exefs_dir = FindSubdirectoryCaseless(mod, "exefs"); if (IsDirValidAndNonEmpty(exefs_dir)) { - bool ips = false; - bool ipswitch = false; - bool layeredfs = false; - - for (const auto& file : exefs_dir->GetFiles()) { - if (file->GetExtension() == "ips") { - ips = true; - } else if (file->GetExtension() == "pchtxt") { - ipswitch = true; - } else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(), - file->GetName()) != EXEFS_FILE_NAMES.end()) { - layeredfs = true; - } - } - - if (ips) - AppendCommaIfNotEmpty(types, "IPS"); - if (ipswitch) - AppendCommaIfNotEmpty(types, "IPSwitch"); - if (layeredfs) + if (is_sdmc) { AppendCommaIfNotEmpty(types, "LayeredExeFS"); + } else { + bool ips = false, ipswitch = false, layeredfs = false; + for (const auto& file : exefs_dir->GetFiles()) { + if (file->GetExtension() == "ips") + ips = true; + else if (file->GetExtension() == "pchtxt") + ipswitch = true; + else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(), + file->GetName()) != EXEFS_FILE_NAMES.end()) + layeredfs = true; + } + if (ips) + AppendCommaIfNotEmpty(types, "IPS"); + if (ipswitch) + AppendCommaIfNotEmpty(types, "IPSwitch"); + if (layeredfs) + AppendCommaIfNotEmpty(types, "LayeredExeFS"); + } } + if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")) || IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfslite"))) AppendCommaIfNotEmpty(types, "LayeredFS"); @@ -1039,14 +946,16 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw, const BuildI has_cheats = true; AppendCommaIfNotEmpty(types, "Cheats"); } + if (has_cheats && is_sdmc) + mod_name = "Atmosphere"; if (types.empty()) continue; const auto mod_disabled = - std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end(); + std::find(disabled.begin(), disabled.end(), mod_name) != disabled.end(); out.push_back({.enabled = !mod_disabled, - .name = mod->GetName(), + .name = mod_name, .version = types, .type = PatchType::Mod, .program_id = title_id, @@ -1057,72 +966,65 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw, const BuildI // Add individual cheats as sub-entries if (has_cheats) { - // Try to read cheat file with build_id first, fallback to all files std::vector cheat_entries; - if (has_build_id) { - if (auto res = ReadCheatFileFromFolder(title_id, build_id, cheats_dir, true)) { + 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)) { + 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; + 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 = + (is_sdmc ? "SDMC" : 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_name, + .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_name, + .cheat_compat = CheatCompatibility::Incompatible, + }); } } } } - // SDMC mod directory (RomFS LayeredFS) - const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); - if (sdmc_mod_dir != nullptr) { - std::string types; - if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) { - AppendCommaIfNotEmpty(types, "LayeredExeFS"); - } - if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs")) || - IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) { - AppendCommaIfNotEmpty(types, "LayeredFS"); - } - - if (!types.empty()) { - const auto mod_disabled = - std::find(disabled.begin(), disabled.end(), "SDMC") != disabled.end(); - out.push_back({.enabled = !mod_disabled, - .name = "SDMC", - .version = types, - .type = PatchType::Mod, - .program_id = title_id, - .title_id = title_id, - .source = PatchSource::Unknown, - .parent_name = ""}); - } - } - // DLC std::vector dlc_match; bool has_external_dlc = false; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 5cb06b9052..c681b22970 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 : u8 { + 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