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..627646ee84 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -123,6 +123,39 @@ bool IsVersionedExternalUpdateDisabled(const std::vector& disabled, return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() || std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); } + +std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64 update_tid) { + if (provider == nullptr) { + return {}; + } + + auto control_nca = provider->GetEntry(update_tid, ContentRecordType::Control); + if (control_nca == nullptr || + control_nca->GetStatus() != Loader::ResultStatus::Success) { + return {}; + } + + const auto romfs = control_nca->GetRomFS(); + if (romfs == nullptr) { + return {}; + } + + const auto extracted = ExtractRomFS(romfs); + if (extracted == nullptr) { + return {}; + } + + auto nacp_file = extracted->GetFile("control.nacp"); + if (nacp_file == nullptr) { + nacp_file = extracted->GetFile("Control.nacp"); + } + if (nacp_file == nullptr) { + return {}; + } + + NACP nacp{nacp_file}; + return nacp.GetVersionString(); +} } // Anonymous namespace PatchManager::PatchManager(u64 title_id_, @@ -771,6 +804,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 +820,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: @@ -795,19 +829,16 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { std::string version_str; u32 numeric_ver = 0; - PatchManager update{update_tid, fs_controller, content_provider}; - const auto metadata = update.GetControlMetadata(); - const auto& nacp = metadata.first; - - if (nacp != nullptr) { - version_str = nacp->GetVersionString(); - } - - const auto meta_ver = content_provider.GetEntryVersion(update_tid); - if (meta_ver.has_value()) { - numeric_ver = *meta_ver; - if (version_str.empty() && numeric_ver != 0) { - version_str = FormatTitleVersion(numeric_ver); + const auto* slot_provider = content_union->GetSlotProvider(slot); + version_str = GetUpdateVersionStringFromSlot(slot_provider, update_tid); + + if (slot_provider != nullptr) { + const auto slot_ver = slot_provider->GetEntryVersion(update_tid); + if (slot_ver.has_value()) { + numeric_ver = *slot_ver; + if (version_str.empty() && numeric_ver != 0) { + version_str = FormatTitleVersion(numeric_ver); + } } } @@ -956,37 +987,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 +1054,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, };