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 371fef0025..cbca66e13a 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 @@ -40,11 +40,21 @@ class AddonAdapter(val addonViewModel: AddonViewModel) : } } - val deleteAction = { - addonViewModel.setAddonToDelete(model) + val canDelete = model.isRemovable + binding.deleteCard.isEnabled = canDelete + binding.buttonDelete.isEnabled = canDelete + binding.deleteCard.alpha = if (canDelete) 1f else 0.38f + + if (canDelete) { + val deleteAction = { + addonViewModel.setAddonToDelete(model) + } + binding.deleteCard.setOnClickListener { deleteAction() } + binding.buttonDelete.setOnClickListener { deleteAction() } + } else { + binding.deleteCard.setOnClickListener(null) + binding.buttonDelete.setOnClickListener(null) } - binding.deleteCard.setOnClickListener { deleteAction() } - binding.buttonDelete.setOnClickListener { deleteAction() } } } } 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 8a367116c1..a3785dd3ac 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 @@ -16,5 +16,17 @@ data class Patch( val type: Int, val programId: String, val titleId: String, - val numericVersion: Long = 0 -) + val numericVersion: Long = 0, + val source: Int = 0 +) { + companion object { + const val SOURCE_UNKNOWN = 0 + const val SOURCE_NAND = 1 + const val SOURCE_SDMC = 2 + const val SOURCE_EXTERNAL = 3 + const val SOURCE_PACKED = 4 + } + + val isRemovable: Boolean + get() = source != SOURCE_EXTERNAL && source != SOURCE_PACKED +} diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 3f0029c78a..2108e05911 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -1407,7 +1407,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env Common::Android::ToJString(env, patch.version), static_cast(patch.type), 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.numeric_version), static_cast(patch.source)); env->SetObjectArrayElement(jpatchArray, i, jpatch); ++i; } diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index c7f5332a68..76af1e0fb2 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -516,7 +516,7 @@ namespace Common::Android { s_patch_class = reinterpret_cast(env->NewGlobalRef(patch_class)); s_patch_constructor = env->GetMethodID( patch_class, "", - "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;J)V"); + "(ZLjava/lang/String;Ljava/lang/String;ILjava/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;"); diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index e9c3bb75c2..e2f2eed32a 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -771,6 +771,7 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { std::nullopt, std::nullopt, ContentRecordType::Program, update_tid); for (const auto& [slot, entry] : all_updates) { + (void)entry; if (slot == ContentProviderUnionSlot::External || slot == ContentProviderUnionSlot::FrontendManual) { continue; @@ -786,7 +787,7 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { source_suffix = " (NAND)"; break; case ContentProviderUnionSlot::SDMC: - source_type = PatchSource::NAND; + source_type = PatchSource::SDMC; source_suffix = " (SDMC)"; break; default: @@ -956,37 +957,60 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { } // DLC - const auto dlc_entries = - content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); - std::vector dlc_match; - dlc_match.reserve(dlc_entries.size()); - std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), - [this](const ContentProviderEntry& entry) { - const auto base_tid = GetBaseTitleID(entry.title_id); - const bool matches_base = base_tid == title_id; - - if (!matches_base) { - LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}", - entry.title_id, base_tid, title_id); - return false; - } - - auto nca = content_provider.GetEntry(entry); - if (!nca) { - LOG_DEBUG(Loader, "Failed to get NCA for DLC {:016X}", entry.title_id); - return false; - } - - const auto status = nca->GetStatus(); - if (status != Loader::ResultStatus::Success) { - LOG_DEBUG(Loader, "DLC {:016X} NCA has status {}", - entry.title_id, static_cast(status)); - return false; - } - - return true; - }); + bool has_external_dlc = false; + bool has_nand_dlc = false; + bool has_sdmc_dlc = false; + bool has_other_dlc = false; + const auto dlc_entries_with_origin = + content_union->ListEntriesFilterOrigin(std::nullopt, TitleType::AOC, ContentRecordType::Data); + + dlc_match.reserve(dlc_entries_with_origin.size()); + for (const auto& [slot, entry] : dlc_entries_with_origin) { + const auto base_tid = GetBaseTitleID(entry.title_id); + const bool matches_base = base_tid == title_id; + if (!matches_base) { + LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}", + entry.title_id, base_tid, title_id); + continue; + } + + const auto* slot_provider = content_union->GetSlotProvider(slot); + if (slot_provider == nullptr) { + continue; + } + + auto nca = slot_provider->GetEntry(entry); + if (!nca) { + LOG_DEBUG(Loader, "Failed to get NCA for DLC {:016X}", entry.title_id); + continue; + } + + const auto status = nca->GetStatus(); + if (status != Loader::ResultStatus::Success) { + LOG_DEBUG(Loader, "DLC {:016X} NCA has status {}", entry.title_id, + static_cast(status)); + continue; + } + + switch (slot) { + case ContentProviderUnionSlot::External: + case ContentProviderUnionSlot::FrontendManual: + has_external_dlc = true; + break; + case ContentProviderUnionSlot::UserNAND: + case ContentProviderUnionSlot::SysNAND: + has_nand_dlc = true; + break; + case ContentProviderUnionSlot::SDMC: + has_sdmc_dlc = true; + break; + default: + has_other_dlc = true; + break; + } + dlc_match.push_back(entry); + } if (!dlc_match.empty()) { // Ensure sorted so DLC IDs show in order. @@ -1000,13 +1024,22 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { const auto dlc_disabled = std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end(); + PatchSource dlc_source = PatchSource::Unknown; + if (has_external_dlc && !has_nand_dlc && !has_sdmc_dlc && !has_other_dlc) { + dlc_source = PatchSource::External; + } else if (has_nand_dlc && !has_external_dlc && !has_sdmc_dlc && !has_other_dlc) { + dlc_source = PatchSource::NAND; + } else if (has_sdmc_dlc && !has_external_dlc && !has_nand_dlc && !has_other_dlc) { + dlc_source = PatchSource::SDMC; + } + out.push_back({.enabled = !dlc_disabled, .name = "DLC", .version = std::move(list), .type = PatchType::DLC, .program_id = title_id, .title_id = dlc_match.back().title_id, - .source = PatchSource::Unknown}); + .source = dlc_source}); } return out; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 2be963078d..755d924fd4 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -34,6 +34,7 @@ enum class PatchType { Update, DLC, Mod }; enum class PatchSource { Unknown, NAND, + SDMC, External, Packed, };