mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-12 20:18:57 +02:00
[dmnt] Fix CheatCompatibility
* now it follows Atmosphere system to only enable cheat if cheat_name.txt = build_id * also deduplicate active update Signed-off-by: DraVee <chimera@dravee.dev>
This commit is contained in:
parent
69c29c651e
commit
80e2a7382b
6 changed files with 173 additions and 219 deletions
|
|
@ -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<Patch>(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<ViewGroup.MarginLayoutParams> {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1397,9 +1397,8 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
|
|||
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);
|
||||
|
||||
auto patches = pm.GetPatches(update_raw, build_id);
|
||||
jobjectArray jpatchArray =
|
||||
env->NewObjectArray(patches.size(), Common::Android::GetPatchClass(), nullptr);
|
||||
int i = 0;
|
||||
|
|
@ -1411,7 +1410,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<jlong>(patch.numeric_version), static_cast<jint>(patch.source),
|
||||
Common::Android::ToJString(env, patch.parent_name));
|
||||
Common::Android::ToJString(env, patch.parent_name),
|
||||
static_cast<jint>(patch.cheat_compat));
|
||||
env->SetObjectArrayElement(jpatchArray, i, jpatch);
|
||||
++i;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<u32> 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,20 @@ 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 +265,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<NCA> 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<NCA>(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Also try ManualContentProvider
|
||||
if (update == nullptr) {
|
||||
const auto* manual_provider = static_cast<const ManualContentProvider*>(
|
||||
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<NCA>(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (update_res.update_active_version.has_value() && update_res.update_active_raw != nullptr) {
|
||||
update = std::make_unique<NCA>(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 +627,21 @@ 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<u32> enabled_version;
|
||||
VirtualFile update_raw = nullptr;
|
||||
bool checked_external = false;
|
||||
bool checked_manual = false;
|
||||
|
||||
const auto* content_union = static_cast<const ContentProviderUnion*>(&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<const ManualContentProvider*>(
|
||||
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<NCA>(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<NCA>(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<NCA>(packed_update_raw, base_nca);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
|
|
@ -777,11 +669,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<NCA>(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<NCA>(update_raw, base_nca.get());
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
|
|
@ -812,7 +705,7 @@ PatchManager::BuildID PatchManager::GetBuildID(VirtualFile update_raw) const {
|
|||
return build_id;
|
||||
}
|
||||
|
||||
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildID& build_id) const {
|
||||
std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
||||
if (title_id == 0) {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -996,6 +889,7 @@ std::vector<Patch> 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; });
|
||||
|
||||
|
|
@ -1068,30 +962,51 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw, const BuildI
|
|||
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 = 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->GetName(),
|
||||
.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<u8> 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<const char*>(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->GetName(),
|
||||
.cheat_compat = CheatCompatibility::Incompatible,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#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 {
|
||||
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<Patch> GetPatches(VirtualFile update_raw = nullptr,
|
||||
const BuildID& build_id = {}) const;
|
||||
[[nodiscard]] std::vector<Patch> GetPatches(VirtualFile update_raw = nullptr) const;
|
||||
|
||||
[[nodiscard]] BuildID GetBuildID(VirtualFile update_raw = nullptr) const;
|
||||
|
||||
|
|
@ -116,6 +122,14 @@ private:
|
|||
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const;
|
||||
|
||||
struct UpdateResolution {
|
||||
bool update_disabled{true};
|
||||
VirtualFile update_active_raw;
|
||||
std::optional<u32> update_active_version;
|
||||
};
|
||||
[[nodiscard]] UpdateResolution GetActiveUpdate(
|
||||
ContentRecordType type = ContentRecordType::Program) const;
|
||||
|
||||
u64 title_id;
|
||||
const Service::FileSystem::FileSystemController& fs_controller;
|
||||
const ContentProvider& content_provider;
|
||||
|
|
|
|||
|
|
@ -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<FileSys::Patch> patches = pm.GetPatches(update_raw, build_id);
|
||||
std::vector<FileSys::Patch> 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<quint32>(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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue