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 c3f3a462ce..0bfdc674e7 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,8 +8,6 @@ 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 @@ -26,25 +24,15 @@ class AddonAdapter(val addonViewModel: AddonViewModel) : inner class AddonViewHolder(val binding: ListItemAddonBinding) : AbstractViewHolder(binding) { override fun bind(model: Patch) { - 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.addonCard.setOnClickListener { + binding.addonSwitch.performClick() } - 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) @@ -53,9 +41,13 @@ class AddonAdapter(val addonViewModel: AddonViewModel) : model.enabled = checked } } - val canDelete = model.isRemovable && !isCheat - binding.deleteCard.isVisible = canDelete - binding.buttonDelete.isVisible = canDelete + + val canDelete = model.isRemovable && !model.isCheat() + + binding.deleteCard.isEnabled = canDelete + binding.buttonDelete.isEnabled = canDelete + binding.deleteCard.alpha = if (canDelete) 1f else 0.38f + 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 0acd9aebc3..91a191b8c7 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,8 +18,7 @@ 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 cheatCompat: Int = CHEAT_COMPAT_COMPATIBLE + val parentName: String = "" // For cheats: name of the mod folder containing them ) { companion object { const val SOURCE_UNKNOWN = 0 @@ -27,9 +26,6 @@ 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 @@ -52,6 +48,4 @@ 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 7bc8a7a388..d77ccdbf9a 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -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; @@ -1408,8 +1411,7 @@ 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), - static_cast(patch.cheat_compat)); + Common::Android::ToJString(env, patch.parent_name)); env->SetObjectArrayElement(jpatchArray, i, jpatch); ++i; } diff --git a/src/core/core.cpp b/src/core/core.cpp index 94b420f73e..c1219c87f3 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -470,7 +470,6 @@ 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 08b2b3b003..c7aac47b9d 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -168,11 +168,16 @@ u64 PatchManager::GetTitleID() const { return title_id; } -PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType type) const { - UpdateResolution 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& disabled = Settings::values.disabled_addons[title_id]; + bool update_disabled = true; + std::optional enabled_version; bool checked_external = false; bool checked_manual = false; @@ -189,10 +194,8 @@ PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType t checked_external = true; for (const auto& update_entry : update_versions) { if (!IsVersionedExternalUpdateDisabled(disabled, 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); + update_disabled = false; + enabled_version = update_entry.version; break; } } @@ -210,10 +213,8 @@ PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType t checked_manual = true; for (const auto& update_entry : manual_update_versions) { if (!IsVersionedExternalUpdateDisabled(disabled, 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); + update_disabled = false; + enabled_version = update_entry.version; break; } } @@ -225,18 +226,24 @@ PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType t // check for original NAND style // Check NAND if: no external updates exist, OR all external updates are disabled 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(); + // 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 (!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; + 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; + } } - } else if (update_res.update_disabled && content_union) { + } else if (update_disabled && content_union) { const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend(); const bool sdmc_disabled = @@ -244,22 +251,18 @@ PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType t if (!nand_disabled || !sdmc_disabled) { const auto nand_sdmc_entries = content_union->ListEntriesFilterOrigin( - std::nullopt, TitleType::Update, type, update_tid); + std::nullopt, TitleType::Update, ContentRecordType::Program, update_tid); for (const auto& [slot, entry] : nand_sdmc_entries) { if (slot == ContentProviderUnionSlot::UserNAND || slot == ContentProviderUnionSlot::SysNAND) { if (!nand_disabled) { - update_res.update_active_raw = - content_provider.GetEntryRaw(update_tid, type); - update_res.update_disabled = update_res.update_active_raw == nullptr; + update_disabled = false; break; } } else if (slot == ContentProviderUnionSlot::SDMC) { if (!sdmc_disabled) { - update_res.update_active_raw = - content_provider.GetEntryRaw(update_tid, type); - update_res.update_disabled = update_res.update_active_raw == nullptr; + update_disabled = false; break; } } @@ -267,36 +270,46 @@ PatchManager::UpdateResolution PatchManager::GetActiveUpdate(ContentRecordType t } } - 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(); - + // Game Updates 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); + // 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 == nullptr && !update_res.update_disabled) { + // Fallback to regular content provider if no external update was loaded + if (update == nullptr && !update_disabled) { update = content_provider.GetEntry(update_tid, ContentRecordType::Program); } - if (!update_res.update_disabled && update != nullptr && update->GetExeFS() != nullptr) { + if (!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); @@ -629,22 +642,114 @@ 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 update_res = GetActiveUpdate(type); + const auto& disabled = Settings::values.disabled_addons[title_id]; - 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); + 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 (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { LOG_INFO( Loader, " RomFS: Update ({}) applied successfully", - update_res.update_active_version.has_value() - ? FormatTitleVersion(*update_res.update_active_version) + enabled_version.has_value() + ? FormatTitleVersion(*enabled_version) : FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); romfs = new_nca->GetRomFS(); } - } else if (!update_res.update_disabled && packed_update_raw != nullptr && base_nca != nullptr) { + } else if (!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) { @@ -672,12 +777,11 @@ PatchManager::BuildID PatchManager::GetBuildID(VirtualFile update_raw) const { // Try to get ExeFS from update first, then base VirtualDir exefs; - 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(); - } + 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(update_raw, base_nca.get()); if (new_nca->GetStatus() == Loader::ResultStatus::Success && @@ -708,7 +812,7 @@ PatchManager::BuildID PatchManager::GetBuildID(VirtualFile update_raw) const { return build_id; } -std::vector PatchManager::GetPatches(VirtualFile update_raw) const { +std::vector PatchManager::GetPatches(VirtualFile update_raw, const BuildID& build_id) const { if (title_id == 0) { return {}; } @@ -892,51 +996,40 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { } // 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); - 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(); + if (mod_dir != nullptr) { + 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)) { - 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"); - } - } + 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) + AppendCommaIfNotEmpty(types, "LayeredExeFS"); + } if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")) || IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfslite"))) AppendCommaIfNotEmpty(types, "LayeredFS"); @@ -946,16 +1039,14 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { 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_name) != disabled.end(); + std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end(); out.push_back({.enabled = !mod_disabled, - .name = mod_name, + .name = mod->GetName(), .version = types, .type = PatchType::Mod, .program_id = title_id, @@ -966,65 +1057,72 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { // 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); + } } - 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, - }); + 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; } - } 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; + + const std::string cheat_name = cheat.definition.readable_name.data(); + if (cheat_name.empty()) { + continue; } - 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, - }); + + // 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()}); } } } } + // 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 c681b22970..5cb06b9052 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -10,7 +10,6 @@ #include #include #include - #include "common/common_types.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs/vfs_types.h" @@ -43,11 +42,6 @@ enum class PatchSource { Packed, }; -enum class CheatCompatibility : u8 { - Incompatible, - Compatible, -}; - struct Patch { bool enabled; std::string name; @@ -59,7 +53,6 @@ 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. @@ -102,7 +95,8 @@ public: bool apply_layeredfs = true) const; // Returns a vector of patches including individual cheats - [[nodiscard]] std::vector GetPatches(VirtualFile update_raw = nullptr) const; + [[nodiscard]] std::vector GetPatches(VirtualFile update_raw = nullptr, + const BuildID& build_id = {}) const; [[nodiscard]] BuildID GetBuildID(VirtualFile update_raw = nullptr) const; @@ -122,14 +116,6 @@ 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 767d03d8b8..4cdc5a29a5 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -327,13 +327,16 @@ 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); + std::vector patches = pm.GetPatches(update_raw, build_id); bool has_enabled_update = false; @@ -353,24 +356,17 @@ 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) { @@ -379,18 +375,16 @@ void ConfigurePerGameAddons::LoadConfiguration() { } bool patch_disabled = false; - 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(); - } + 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 && !is_incompatible_cheat; + bool should_enable = !patch_disabled; if (patch.type == FileSys::PatchType::Update) { if (should_enable) { @@ -403,14 +397,9 @@ void ConfigurePerGameAddons::LoadConfiguration() { update_items.push_back(first_item); } - if (!is_incompatible_cheat) { - first_item->setCheckState(should_enable ? Qt::Checked : Qt::Unchecked); - } + 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