Browse Source

[android] patches bin button + version bug fixes (#3691)

This fixed the delete button enabled for external content (which is auto handled and the proper way to get rid of them is either by removing its folder from ext content list, or removing the file itself) by streaming patch source thru jni.

Along the way stumbled upon another bug: If you have an external content update installed (say latest version for example) and you NAND install a previous update (like in silksong's hard mode update), the newest update version string would leak to the previous one.

Did videos for both. Fixed both. Seems good to go.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3691
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Co-authored-by: xbzk <xbzk@eden-emu.dev>
Co-committed-by: xbzk <xbzk@eden-emu.dev>
xbzk/transition-animation-fix
xbzk 3 days ago
committed by crueter
parent
commit
a1b50e9339
No known key found for this signature in database GPG Key ID: 425ACD2D4830EBC6
  1. 18
      src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt
  2. 16
      src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt
  3. 2
      src/android/app/src/main/jni/native.cpp
  4. 2
      src/common/android/id_cache.cpp
  5. 153
      src/core/file_sys/patch_manager.cpp
  6. 1
      src/core/file_sys/patch_manager.h

18
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() }
} }
} }
} }

16
src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt

@ -16,5 +16,17 @@ data class Patch(
val type: Int, val type: Int,
val programId: String, val programId: String,
val titleId: 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
}

2
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<jint>(patch.type), Common::Android::ToJString(env, patch.version), static_cast<jint>(patch.type),
Common::Android::ToJString(env, std::to_string(patch.program_id)), Common::Android::ToJString(env, std::to_string(patch.program_id)),
Common::Android::ToJString(env, std::to_string(patch.title_id)), Common::Android::ToJString(env, std::to_string(patch.title_id)),
static_cast<jlong>(patch.numeric_version));
static_cast<jlong>(patch.numeric_version), static_cast<jint>(patch.source));
env->SetObjectArrayElement(jpatchArray, i, jpatch); env->SetObjectArrayElement(jpatchArray, i, jpatch);
++i; ++i;
} }

2
src/common/android/id_cache.cpp

@ -516,7 +516,7 @@ namespace Common::Android {
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class)); s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
s_patch_constructor = env->GetMethodID( s_patch_constructor = env->GetMethodID(
patch_class, "<init>", patch_class, "<init>",
"(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_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");

153
src/core/file_sys/patch_manager.cpp

@ -123,6 +123,39 @@ bool IsVersionedExternalUpdateDisabled(const std::vector<std::string>& disabled,
return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() || return std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend() ||
std::find(disabled.cbegin(), disabled.cend(), "Update") != 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 } // Anonymous namespace
PatchManager::PatchManager(u64 title_id_, PatchManager::PatchManager(u64 title_id_,
@ -771,6 +804,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
std::nullopt, std::nullopt, ContentRecordType::Program, update_tid); std::nullopt, std::nullopt, ContentRecordType::Program, update_tid);
for (const auto& [slot, entry] : all_updates) { for (const auto& [slot, entry] : all_updates) {
(void)entry;
if (slot == ContentProviderUnionSlot::External || if (slot == ContentProviderUnionSlot::External ||
slot == ContentProviderUnionSlot::FrontendManual) { slot == ContentProviderUnionSlot::FrontendManual) {
continue; continue;
@ -786,7 +820,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
source_suffix = " (NAND)"; source_suffix = " (NAND)";
break; break;
case ContentProviderUnionSlot::SDMC: case ContentProviderUnionSlot::SDMC:
source_type = PatchSource::NAND;
source_type = PatchSource::SDMC;
source_suffix = " (SDMC)"; source_suffix = " (SDMC)";
break; break;
default: default:
@ -795,19 +829,16 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
std::string version_str; std::string version_str;
u32 numeric_ver = 0; 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<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} }
// DLC // DLC
const auto dlc_entries =
content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
std::vector<ContentProviderEntry> dlc_match; std::vector<ContentProviderEntry> 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<int>(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<int>(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()) { if (!dlc_match.empty()) {
// Ensure sorted so DLC IDs show in order. // Ensure sorted so DLC IDs show in order.
@ -1000,13 +1054,22 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
const auto dlc_disabled = const auto dlc_disabled =
std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end(); 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, out.push_back({.enabled = !dlc_disabled,
.name = "DLC", .name = "DLC",
.version = std::move(list), .version = std::move(list),
.type = PatchType::DLC, .type = PatchType::DLC,
.program_id = title_id, .program_id = title_id,
.title_id = dlc_match.back().title_id, .title_id = dlc_match.back().title_id,
.source = PatchSource::Unknown});
.source = dlc_source});
} }
return out; return out;

1
src/core/file_sys/patch_manager.h

@ -34,6 +34,7 @@ enum class PatchType { Update, DLC, Mod };
enum class PatchSource { enum class PatchSource {
Unknown, Unknown,
NAND, NAND,
SDMC,
External, External,
Packed, Packed,
}; };

Loading…
Cancel
Save