aboutsummaryrefslogtreecommitdiff
path: root/src/core/loader
diff options
context:
space:
mode:
authorxbzk <xbzk@eden-emu.dev>2026-03-04 22:51:35 +0100
committercrueter <crueter@eden-emu.dev>2026-03-04 22:51:35 +0100
commit7f5de6bcd651f8145e4746c76d0531b6560fd5e4 (patch)
treecaefce3d338f65309db1725ac84257c99c14784e /src/core/loader
parentc682306788055c57953aea38fdded95a0c6dfc09 (diff)
[android/fs] external content loader + nca/xci patches (#3596)
Foreword: WHY DON'T EVERYBODY USE ONE FOLDER FOR EACH GAME+CONTENTS? AIN'T THIS THE FORMAT GAMES COME WHEN YOU BUE THEM? DO YOU LIVE WITH ALL YOUR FRIENDS AND HAVE A 2ND HOUSE FOR ALL THE CHILDREN? Nice, i feel better now. This feat extends Maufeat's work on external content loading. It harmonically additions: "...also, if in each game folder X, you find a folder Y, and in this folder Y you detect ONLY a single game, then mount all external content for that game found in that folder Y and its subfolders." Permanent (not toggleable). External Content folders are supported equally. Also: -Reworked several routines for preserving single source of truth between android and other systems; -Fixed the annoying unknown format error for content files, by providing proper format detection. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3596 Reviewed-by: MaranBr <maranbr@eden-emu.dev> Reviewed-by: Lizzie <lizzie@eden-emu.dev> Co-authored-by: xbzk <xbzk@eden-emu.dev> Co-committed-by: xbzk <xbzk@eden-emu.dev>
Diffstat (limited to 'src/core/loader')
-rw-r--r--src/core/loader/loader.cpp70
-rw-r--r--src/core/loader/loader.h22
-rw-r--r--src/core/loader/nsp.cpp36
-rw-r--r--src/core/loader/xci.cpp14
4 files changed, 125 insertions, 17 deletions
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 4379634d03..b4d50227d3 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -9,11 +9,15 @@
#include <ostream>
#include <string>
#include <concepts>
+#include <algorithm>
#include "common/concepts.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
+#include "core/file_sys/card_image.h"
+#include "core/file_sys/common_funcs.h"
+#include "core/file_sys/submission_package.h"
#include "core/hle/kernel/k_process.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/kip.h"
@@ -37,6 +41,49 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
return std::nullopt;
}
+std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, FileType type,
+ u64 program_id = 0,
+ std::size_t program_index = 0) {
+ if (!file) {
+ return nullptr;
+ }
+
+ if (type == FileType::NSP) {
+ auto nsp = std::make_shared<FileSys::NSP>(file, program_id, program_index);
+ return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr;
+ }
+
+ if (type == FileType::XCI) {
+ FileSys::XCI xci{file, program_id, program_index};
+ if (xci.GetStatus() != ResultStatus::Success) {
+ return nullptr;
+ }
+
+ auto secure_nsp = xci.GetSecurePartitionNSP();
+ if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) {
+ return nullptr;
+ }
+
+ return secure_nsp;
+ }
+
+ return nullptr;
+}
+
+bool HasApplicationProgramContent(const std::shared_ptr<FileSys::NSP>& nsp) {
+ if (!nsp) {
+ return false;
+ }
+
+ const auto& ncas = nsp->GetNCAs();
+ return std::any_of(ncas.cbegin(), ncas.cend(), [](const auto& title_entry) {
+ const auto& nca_map = title_entry.second;
+ return nca_map.find(
+ {FileSys::TitleType::Application, FileSys::ContentRecordType::Program}) !=
+ nca_map.end();
+ });
+}
+
} // namespace
FileType IdentifyFile(FileSys::VirtualFile file) {
@@ -62,6 +109,27 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
}
}
+bool IsContainerType(FileType type) {
+ return type == FileType::NSP || type == FileType::XCI;
+}
+
+bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id,
+ std::size_t program_index) {
+ if (!file) {
+ return false;
+ }
+
+ if (type == FileType::Unknown) {
+ type = IdentifyFile(file);
+ }
+
+ if (!IsContainerType(type)) {
+ return false;
+ }
+
+ return HasApplicationProgramContent(OpenContainerAsNsp(file, type, program_id, program_index));
+}
+
FileType GuessFromFilename(const std::string& name) {
if (name == "main")
return FileType::DeconstructedRomDirectory;
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index f4e932cec9..95ce638da0 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -46,13 +49,30 @@ enum class FileType {
};
/**
- * Identifies the type of a bootable file based on the magic value in its header.
+ * Identifies the type of a supported file/container based on its structure.
* @param file open file
* @return FileType of file
*/
FileType IdentifyFile(FileSys::VirtualFile file);
/**
+ * Returns whether the file type represents a container format that can bundle multiple titles
+ * (currently NSP/XCI).
+ */
+bool IsContainerType(FileType type);
+
+/**
+ * Returns whether a container file is bootable as a game (has Application/Program content).
+ *
+ * @param file open file
+ * @param type optional file type; if Unknown it is auto-detected.
+ * @param program_id optional program id hint for multi-program containers.
+ * @param program_index optional program index hint for multi-program containers.
+ */
+bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type = FileType::Unknown,
+ u64 program_id = 0, std::size_t program_index = 0);
+
+/**
* Guess the type of a bootable file from its name
* @param name String name of bootable file
* @return FileType of file. Note: this will return FileType::Unknown if it is unable to determine
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 3016d5f25f..4333acb70c 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -55,19 +58,30 @@ AppLoader_NSP::~AppLoader_NSP() = default;
FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) {
const FileSys::NSP nsp(nsp_file);
- if (nsp.GetStatus() == ResultStatus::Success) {
- // Extracted Type case
- if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
- FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
- return FileType::NSP;
+ if (nsp.GetStatus() != ResultStatus::Success) {
+ return FileType::Error;
+ }
+
+ // Extracted Type case
+ if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
+ FileSys::IsDirectoryExeFS(nsp.GetExeFS())) {
+ return FileType::NSP;
+ }
+
+ // Non-extracted NSPs can legitimately contain only update/DLC content.
+ // Identify the container format itself; bootability is validated by Load().
+ if (!nsp.GetNCAs().empty()) {
+ return FileType::NSP;
+ }
+
+ // Fallback when NCAs couldn't be parsed (e.g. missing keys) but the PFS still contains NCAs.
+ for (const auto& entry : nsp.GetFiles()) {
+ if (entry == nullptr) {
+ continue;
}
- // Non-Extracted Type case
- const auto program_id = nsp.GetProgramTitleID();
- if (!nsp.IsExtractedType() &&
- nsp.GetNCA(program_id, FileSys::ContentRecordType::Program) != nullptr &&
- AppLoader_NCA::IdentifyType(
- nsp.GetNCAFile(program_id, FileSys::ContentRecordType::Program)) == FileType::NCA) {
+ const auto& name = entry->GetName();
+ if (name.size() >= 4 && name.substr(name.size() - 4) == ".nca") {
return FileType::NSP;
}
}
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index e9abb199a1..983184a226 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -44,10 +47,13 @@ AppLoader_XCI::~AppLoader_XCI() = default;
FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
const FileSys::XCI xci(xci_file);
- if (xci.GetStatus() == ResultStatus::Success &&
- xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
- AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
- FileType::NCA) {
+ if (xci.GetStatus() != ResultStatus::Success) {
+ return FileType::Error;
+ }
+
+ // Identify XCI as a valid container even when it does not include a bootable Program NCA.
+ // Bootability is handled by AppLoader_XCI::Load().
+ if (xci.GetSecurePartitionNSP() != nullptr) {
return FileType::XCI;
}