aboutsummaryrefslogtreecommitdiff
path: root/src/core/file_sys
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/file_sys')
-rw-r--r--src/core/file_sys/content_archive.cpp4
-rw-r--r--src/core/file_sys/content_archive.h5
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.cpp13
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.h12
-rw-r--r--src/core/file_sys/nca_metadata.h4
-rw-r--r--src/core/file_sys/registered_cache.cpp35
-rw-r--r--src/core/file_sys/submission_package.cpp14
7 files changed, 71 insertions, 16 deletions
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 2b478d6b99..b47b9437d0 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -29,7 +29,7 @@ static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
return std::max<u8>(key_generation, 1) - 1;
}
-NCA::NCA(VirtualFile file_, const NCA* base_nca)
+NCA::NCA(VirtualFile file_, const NCA* base_nca, bool allow_missing_base)
: file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
@@ -110,7 +110,7 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca)
}
}
- if (is_update && base_nca == nullptr) {
+ if (is_update && base_nca == nullptr && !allow_missing_base) {
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
} else {
status = Loader::ResultStatus::Success;
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 8560617f5b..b88fcdc4ee 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.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
@@ -63,7 +66,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
// After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory {
public:
- explicit NCA(VirtualFile file, const NCA* base_nca = nullptr);
+ explicit NCA(VirtualFile file, const NCA* base_nca = nullptr, bool allow_missing_base = false);
~NCA() override;
Loader::ResultStatus GetStatus() const;
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
index 41f5b75b6e..e66ce49d9a 100644
--- a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
+++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/logging.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
@@ -99,6 +100,18 @@ Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s
R_SUCCEED();
}
+void IndirectStorage::ReportMissingOriginal(s64 offset, s64 size) {
+ if (m_reported_missing_original) {
+ return;
+ }
+ m_reported_missing_original = true;
+
+ LOG_ERROR(Common_Filesystem,
+ "Patch storage requests {:#x} bytes at {:#x} from a base storage that is not "
+ "present; this data will read as garbage",
+ size, offset);
+}
+
size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
// Validate pre-conditions.
ASSERT(this->IsInitialized());
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
index cc7b5cd718..81f94e2e19 100644
--- a/src/core/file_sys/fssystem/fssystem_indirect_storage.h
+++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
@@ -85,6 +85,9 @@ public:
void SetStorage(s32 idx, VirtualFile storage) {
ASSERT(0 <= idx && idx < StorageCount);
m_data_storage[idx] = storage;
+ if (idx == 0) {
+ m_original_missing = storage == nullptr || storage->GetSize() == 0;
+ }
}
template <typename T>
@@ -130,6 +133,9 @@ protected:
template <bool ContinuousCheck, bool RangeCheck, typename F>
Result OperatePerEntry(s64 offset, s64 size, F func);
+ // Launching another game makes the original storage inaccessable.
+ // This is a helper for multi-nca games.
+ void ReportMissingOriginal(s64 offset, s64 size);
private:
struct ContinuousReadingEntry {
@@ -154,6 +160,8 @@ private:
private:
mutable BucketTree m_table;
std::array<VirtualFile, StorageCount> m_data_storage;
+ bool m_original_missing{false};
+ bool m_reported_missing_original{false};
};
template <bool ContinuousCheck, bool RangeCheck, typename F>
@@ -272,6 +280,10 @@ Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
if (needs_operate) {
const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
+ if (cur_entry.storage_index == 0 && m_original_missing) {
+ this->ReportMissingOriginal(cur_offset, cur_size);
+ }
+
if constexpr (RangeCheck) {
// Get the current data storage's size.
s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize();
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 43437d0dd7..07aae1642e 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -48,11 +48,11 @@ struct ContentRecord {
std::array<u8, 0x10> nca_id;
std::array<u8, 0x6> size;
ContentRecordType type;
- INSERT_PADDING_BYTES(1);
+ u8 id_offset;
};
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
-constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}};
+constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, 0};
struct MetaRecord {
u64_le title_id;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 9e9db0ed4d..83f5e265dd 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -567,20 +567,45 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
}
static std::optional<NcaID> CheckMapForContentRecord(const ankerl::unordered_dense::map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
- const auto cmnt_iter = map.find(title_id);
+ auto cmnt_iter = map.find(title_id);
+ u8 id_offset = 0;
+
if (cmnt_iter == map.cend()) {
- return std::nullopt;
+ const auto program_index = title_id & AOC_TITLE_ID_MASK;
+ if (program_index == 0) {
+ return std::nullopt;
+ }
+
+ cmnt_iter = map.find(title_id & ~AOC_TITLE_ID_MASK);
+ if (cmnt_iter == map.cend()) {
+ return std::nullopt;
+ }
+
+ id_offset = static_cast<u8>(program_index);
}
const auto& cnmt = cmnt_iter->second;
const auto& content_records = cnmt.GetContentRecords();
const auto iter = std::find_if(content_records.cbegin(), content_records.cend(),
- [type](const ContentRecord& rec) { return rec.type == type; });
- if (iter == content_records.cend()) {
+ [type, id_offset](const ContentRecord& rec) {
+ return rec.type == type && rec.id_offset == id_offset;
+ });
+ if (iter != content_records.cend()) {
+ return std::make_optional(iter->nca_id);
+ }
+
+ if (id_offset != 0) {
+ return std::nullopt;
+ }
+
+ const auto fallback_iter =
+ std::find_if(content_records.cbegin(), content_records.cend(),
+ [type](const ContentRecord& rec) { return rec.type == type; });
+ if (fallback_iter == content_records.cend()) {
return std::nullopt;
}
- return std::make_optional(iter->nca_id);
+ return std::make_optional(fallback_iter->nca_id);
}
std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 630bb0b079..0ea323492b 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -12,6 +12,7 @@
#include "common/hex_util.h"
#include "common/logging.h"
#include "core/crypto/key_manager.h"
+#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
@@ -65,10 +66,12 @@ u64 NSP::GetProgramTitleID() const {
}
auto program_id = expected_program_id;
- if (program_id == 0) {
- if (!program_status.empty()) {
- program_id = program_status.begin()->first;
- }
+ if (program_id == 0 && !program_status.empty()) {
+ program_id = std::min_element(program_status.cbegin(), program_status.cend(),
+ [](const auto& lhs, const auto& rhs) {
+ return lhs.first < rhs.first;
+ })
+ ->first;
}
program_id = program_id + program_index;
@@ -273,8 +276,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
// If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and
// 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular
// update NCA.
- if ((next_nca->GetTitleId() & 0x7FF) != 0 &&
- (next_nca->GetTitleId() & 0x800) == 0) {
+ if ((next_nca->GetTitleId() & AOC_TITLE_ID_MASK) != 0) {
ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] =
std::move(next_nca);
} else {