[fs/core] Load external content without NAND install (#2862)

Adds the capability to add DLC and Updates without installing them to NAND. This was tested on Windows only and needs Android integration.

Co-authored-by: crueter <crueter@eden-emu.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-authored-by: nekle <nekle@protonmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2862
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: Maufeat <sahyno1996@gmail.com>
Co-committed-by: Maufeat <sahyno1996@gmail.com>
This commit is contained in:
Maufeat 2026-02-06 14:05:44 +01:00 committed by crueter
parent e07e269bd7
commit 69aff83ef4
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
40 changed files with 1790 additions and 126 deletions

View file

@ -8,6 +8,8 @@
#include <memory>
#include <utility>
#include <fmt/format.h>
#include <QHeaderView>
#include <QMenu>
#include <QStandardItemModel>
@ -16,6 +18,7 @@
#include <QTreeView>
#include <qstandardpaths.h>
#include "common/common_types.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "configuration/addon/mod_select_dialog.h"
@ -68,6 +71,8 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p
ui->scrollArea->setEnabled(!system.IsPoweredOn());
connect(item_model, &QStandardItemModel::itemChanged, this,
&ConfigurePerGameAddons::OnItemChanged);
connect(item_model, &QStandardItemModel::itemChanged,
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
@ -77,13 +82,37 @@ ConfigurePerGameAddons::ConfigurePerGameAddons(Core::System& system_, QWidget* p
ConfigurePerGameAddons::~ConfigurePerGameAddons() = default;
void ConfigurePerGameAddons::OnItemChanged(QStandardItem* item) {
if (update_items.size() > 1 && item->checkState() == Qt::Checked) {
auto it = std::find(update_items.begin(), update_items.end(), item);
if (it != update_items.end()) {
for (auto* update_item : update_items) {
if (update_item != item && update_item->checkState() == Qt::Checked) {
disconnect(item_model, &QStandardItemModel::itemChanged, this,
&ConfigurePerGameAddons::OnItemChanged);
update_item->setCheckState(Qt::Unchecked);
connect(item_model, &QStandardItemModel::itemChanged, this,
&ConfigurePerGameAddons::OnItemChanged);
}
}
}
}
}
void ConfigurePerGameAddons::ApplyConfiguration() {
std::vector<std::string> disabled_addons;
for (const auto& item : list_items) {
const auto disabled = item.front()->checkState() == Qt::Unchecked;
if (disabled)
disabled_addons.push_back(item.front()->text().toStdString());
if (disabled) {
QVariant userData = item.front()->data(Qt::UserRole);
if (userData.isValid() && userData.canConvert<quint32>() && item.front()->text() == QStringLiteral("Update")) {
quint32 numeric_version = userData.toUInt();
disabled_addons.push_back(fmt::format("Update@{}", numeric_version));
} else {
disabled_addons.push_back(item.front()->text().toStdString());
}
}
}
auto current = Settings::values.disabled_addons[title_id];
@ -194,17 +223,51 @@ void ConfigurePerGameAddons::LoadConfiguration() {
const auto& disabled = Settings::values.disabled_addons[title_id];
for (const auto& patch : pm.GetPatches(update_raw)) {
update_items.clear();
list_items.clear();
item_model->removeRows(0, item_model->rowCount());
std::vector<FileSys::Patch> patches = pm.GetPatches(update_raw);
bool has_enabled_update = false;
for (const auto& patch : patches) {
const auto name = QString::fromStdString(patch.name);
auto* const first_item = new QStandardItem;
first_item->setText(name);
first_item->setCheckable(true);
const auto patch_disabled =
std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
const bool is_external_update = patch.type == FileSys::PatchType::Update &&
patch.source == FileSys::PatchSource::External &&
patch.numeric_version != 0;
first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
if (is_external_update) {
first_item->setData(static_cast<quint32>(patch.numeric_version), Qt::UserRole);
}
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(), name.toStdString()) != disabled.end();
}
bool should_enable = !patch_disabled;
if (patch.type == FileSys::PatchType::Update) {
if (should_enable) {
if (has_enabled_update) {
should_enable = false;
} else {
has_enabled_update = true;
}
}
update_items.push_back(first_item);
}
first_item->setCheckState(should_enable ? Qt::Checked : Qt::Unchecked);
list_items.push_back(QList<QStandardItem*>{
first_item, new QStandardItem{QString::fromStdString(patch.version)}});