mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-10-28 02:03:50 +00:00
Compare commits
3 Commits
master
...
xcz-display
| Author | SHA1 | Date | |
|---|---|---|---|
| 465b95f353 | |||
| 44cd950747 | |||
| 7cef75b23d |
@@ -120,9 +120,13 @@ std::optional<std::pair<std::vector<Section>, std::optional<BlockHeader>>> Parse
|
|||||||
return std::make_pair(std::move(sections), std::move(bh));
|
return std::make_pair(std::move(sections), std::move(bh));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sequential reader over independently-compressed blocks, one block cached (like nsz_rs).
|
// Sequential reader over independently-compressed blocks. Uses a direct-mapped cache (like
|
||||||
|
// NCZVirtualFile) instead of an LRU list: block_index % CacheSlots picks the slot, so a hit is
|
||||||
|
// O(1). Reuses one ZSTD_DCtx for the lifetime of the reader.
|
||||||
class BlockReader {
|
class BlockReader {
|
||||||
public:
|
public:
|
||||||
|
static constexpr std::size_t CacheSlots = 32;
|
||||||
|
|
||||||
BlockReader(FileSys::VirtualFile f, const BlockHeader& bh, u64 payload_start)
|
BlockReader(FileSys::VirtualFile f, const BlockHeader& bh, u64 payload_start)
|
||||||
: source{std::move(f)}, block_size{1ULL << bh.exponent}, sizes{bh.sizes},
|
: source{std::move(f)}, block_size{1ULL << bh.exponent}, sizes{bh.sizes},
|
||||||
decompressed_size{bh.decompressed_size} {
|
decompressed_size{bh.decompressed_size} {
|
||||||
@@ -131,19 +135,31 @@ public:
|
|||||||
u64 off = payload_start;
|
u64 off = payload_start;
|
||||||
offsets.reserve(sizes.size());
|
offsets.reserve(sizes.size());
|
||||||
for (u32 s : sizes) { offsets.push_back(off); off += s; }
|
for (u32 s : sizes) { offsets.push_back(off); off += s; }
|
||||||
|
dctx = ZSTD_createDCtx();
|
||||||
|
if (dctx == nullptr) throw std::runtime_error("ZSTD_createDCtx failed");
|
||||||
|
cache.resize(CacheSlots);
|
||||||
|
for (auto& slot : cache) slot.id = ~0ULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fills `out` from `position`; returns bytes written.
|
~BlockReader() {
|
||||||
|
if (dctx != nullptr) ZSTD_freeDCtx(dctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockReader(const BlockReader&) = delete;
|
||||||
|
BlockReader& operator=(const BlockReader&) = delete;
|
||||||
|
|
||||||
std::size_t ReadAt(u64 position, std::span<u8> out) {
|
std::size_t ReadAt(u64 position, std::span<u8> out) {
|
||||||
std::size_t done = 0;
|
std::size_t done = 0;
|
||||||
while (done < out.size() && position < decompressed_size) {
|
while (done < out.size() && position < decompressed_size) {
|
||||||
const u64 id = position / block_size;
|
const u64 id = position / block_size;
|
||||||
if (id >= sizes.size()) break;
|
if (id >= sizes.size()) break;
|
||||||
if (cache_id != id && !Load(id)) break;
|
const std::vector<u8>* block = GetBlock(id);
|
||||||
|
if (block == nullptr) break;
|
||||||
const u64 in = position - id * block_size;
|
const u64 in = position - id * block_size;
|
||||||
|
if (in >= block->size()) break;
|
||||||
const std::size_t n = static_cast<std::size_t>(
|
const std::size_t n = static_cast<std::size_t>(
|
||||||
std::min<u64>(out.size() - done, cache.size() - in));
|
std::min<u64>(out.size() - done, block->size() - in));
|
||||||
std::memcpy(out.data() + done, cache.data() + in, n);
|
std::memcpy(out.data() + done, block->data() + in, n);
|
||||||
done += n;
|
done += n;
|
||||||
position += n;
|
position += n;
|
||||||
}
|
}
|
||||||
@@ -151,23 +167,48 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool Load(u64 id) {
|
struct Slot {
|
||||||
|
u64 id = ~0ULL;
|
||||||
|
std::vector<u8> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<u8>* GetBlock(u64 id) {
|
||||||
|
Slot& slot = cache[id % CacheSlots];
|
||||||
|
if (slot.id == id) {
|
||||||
|
return &slot.data; // O(1) hit
|
||||||
|
}
|
||||||
|
std::vector<u8> buffer;
|
||||||
|
buffer.swap(slot.data); // reuse existing allocation
|
||||||
|
if (!DecodeBlock(id, buffer)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
slot.data = std::move(buffer);
|
||||||
|
slot.id = id;
|
||||||
|
return &slot.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DecodeBlock(u64 id, std::vector<u8>& buffer) {
|
||||||
u64 want = block_size;
|
u64 want = block_size;
|
||||||
if (id + 1 == sizes.size()) {
|
if (id + 1 == sizes.size()) {
|
||||||
const u64 rem = decompressed_size % block_size;
|
const u64 rem = decompressed_size % block_size;
|
||||||
if (rem > 0) want = rem; // avoid the 0-size bug from the original nsz
|
if (rem > 0) want = rem; // avoid the 0-size bug from the original nsz
|
||||||
}
|
}
|
||||||
try { cache.resize(static_cast<std::size_t>(want)); }
|
try { buffer.resize(static_cast<std::size_t>(want)); }
|
||||||
catch (const std::bad_alloc&) { return false; }
|
catch (const std::bad_alloc&) { return false; }
|
||||||
const u32 csize = sizes[id];
|
const u32 csize = sizes[id];
|
||||||
if (csize < want) {
|
if (csize < want) {
|
||||||
const auto comp = source->ReadBytes(csize, offsets[id]);
|
const auto comp = source->ReadBytes(csize, offsets[id]);
|
||||||
if (comp.size() != csize ||
|
if (comp.size() != csize) return false;
|
||||||
Common::Compression::DecompressDataZSTD(comp, cache) != want) return false;
|
const std::size_t res = ZSTD_decompressDCtx(
|
||||||
} else if (source->Read(cache.data(), cache.size(), offsets[id]) != cache.size()) {
|
dctx, buffer.data(), buffer.size(), comp.data(), comp.size());
|
||||||
|
if (ZSTD_isError(res) || res != want) {
|
||||||
|
LOG_ERROR(Crypto, "Failed to decompress NCZBLOCK block {}: {}", id,
|
||||||
|
ZSTD_isError(res) ? ZSTD_getErrorName(res) : "size mismatch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (source->Read(buffer.data(), buffer.size(), offsets[id]) != buffer.size()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
cache_id = id;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,8 +217,8 @@ private:
|
|||||||
std::vector<u64> offsets;
|
std::vector<u64> offsets;
|
||||||
std::vector<u32> sizes;
|
std::vector<u32> sizes;
|
||||||
u64 decompressed_size;
|
u64 decompressed_size;
|
||||||
u64 cache_id = ~0ULL;
|
ZSTD_DCtx* dctx = nullptr;
|
||||||
std::vector<u8> cache;
|
std::vector<Slot> cache; // direct-mapped, CacheSlots entries
|
||||||
};
|
};
|
||||||
|
|
||||||
class BlockNczFile final : public FileSys::VfsFile {
|
class BlockNczFile final : public FileSys::VfsFile {
|
||||||
@@ -189,10 +230,6 @@ public:
|
|||||||
nca_size = HeaderSize;
|
nca_size = HeaderSize;
|
||||||
for (const auto& s : sections)
|
for (const auto& s : sections)
|
||||||
nca_size = std::max<u64>(nca_size, static_cast<u64>(s.offset + s.size));
|
nca_size = std::max<u64>(nca_size, static_cast<u64>(s.offset + s.size));
|
||||||
ciphers.reserve(sections.size());
|
|
||||||
for (const auto& s : sections)
|
|
||||||
ciphers.push_back(s.Ctr() ? std::make_unique<AESCipher<Key128>>(s.key, Mode::CTR)
|
|
||||||
: nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GetName() const override { return name; }
|
std::string GetName() const override { return name; }
|
||||||
@@ -202,6 +239,10 @@ public:
|
|||||||
bool IsWritable() const override { return false; }
|
bool IsWritable() const override { return false; }
|
||||||
bool IsReadable() const override { return true; }
|
bool IsReadable() const override { return true; }
|
||||||
|
|
||||||
|
// NCZ stores CTR sections decrypted; this file serves them as-is, and NcaFileSystemDriver
|
||||||
|
// skips its AES layers because of this flag.
|
||||||
|
bool HasDecryptedSections() const override { return true; }
|
||||||
|
|
||||||
std::size_t Read(u8* data, std::size_t len, std::size_t off) const override {
|
std::size_t Read(u8* data, std::size_t len, std::size_t off) const override {
|
||||||
if (off >= nca_size) return 0;
|
if (off >= nca_size) return 0;
|
||||||
len = static_cast<std::size_t>(std::min<u64>(len, nca_size - off));
|
len = static_cast<std::size_t>(std::min<u64>(len, nca_size - off));
|
||||||
@@ -213,8 +254,15 @@ public:
|
|||||||
pos += n;
|
pos += n;
|
||||||
}
|
}
|
||||||
std::scoped_lock lock{mutex};
|
std::scoped_lock lock{mutex};
|
||||||
for (std::size_t i = 0; i < sections.size() && pos < end; ++i) {
|
// Sections are sorted by offset; find the first one whose stop > pos.
|
||||||
const auto& s = sections[i];
|
auto it = std::lower_bound(
|
||||||
|
sections.begin(), sections.end(), pos, [](const Section& s, u64 p) {
|
||||||
|
const u64 b = std::max<u64>(static_cast<u64>(s.offset), HeaderSize);
|
||||||
|
const u64 e = std::max<u64>(static_cast<u64>(s.offset) + static_cast<u64>(s.size), b);
|
||||||
|
return e <= p;
|
||||||
|
});
|
||||||
|
for (; it != sections.end() && pos < end; ++it) {
|
||||||
|
const auto& s = *it;
|
||||||
const u64 begin = std::max<u64>(static_cast<u64>(s.offset), HeaderSize);
|
const u64 begin = std::max<u64>(static_cast<u64>(s.offset), HeaderSize);
|
||||||
const u64 stop = std::max<u64>(static_cast<u64>(s.offset + s.size), begin);
|
const u64 stop = std::max<u64>(static_cast<u64>(s.offset + s.size), begin);
|
||||||
if (stop <= pos) continue;
|
if (stop <= pos) continue;
|
||||||
@@ -228,12 +276,7 @@ public:
|
|||||||
const std::span<u8> piece(data + (pos - off), static_cast<std::size_t>(pe - pos));
|
const std::span<u8> piece(data + (pos - off), static_cast<std::size_t>(pe - pos));
|
||||||
if (body->ReadAt(pos - HeaderSize, piece) != piece.size())
|
if (body->ReadAt(pos - HeaderSize, piece) != piece.size())
|
||||||
return static_cast<std::size_t>(pos - off);
|
return static_cast<std::size_t>(pos - off);
|
||||||
if (ciphers[i]) {
|
// No CTR here: NCZ body is already decrypted, and NcaFileSystemDriver skips AES.
|
||||||
Key128 iv{};
|
|
||||||
SetIv(iv, s.counter, pos);
|
|
||||||
ciphers[i]->SetIV(iv);
|
|
||||||
ciphers[i]->Transcode(piece.data(), piece.size(), piece.data(), Op::Encrypt);
|
|
||||||
}
|
|
||||||
pos = pe;
|
pos = pe;
|
||||||
}
|
}
|
||||||
return len;
|
return len;
|
||||||
@@ -247,7 +290,6 @@ private:
|
|||||||
const std::vector<u8> hdr;
|
const std::vector<u8> hdr;
|
||||||
const std::vector<Section> sections;
|
const std::vector<Section> sections;
|
||||||
const std::unique_ptr<BlockReader> body;
|
const std::unique_ptr<BlockReader> body;
|
||||||
std::vector<std::unique_ptr<AESCipher<Key128>>> ciphers;
|
|
||||||
mutable std::mutex mutex;
|
mutable std::mutex mutex;
|
||||||
u64 nca_size{};
|
u64 nca_size{};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -93,6 +93,11 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
|
|||||||
// Initialize our header reader for the fs index.
|
// Initialize our header reader for the fs index.
|
||||||
R_TRY(out_header_reader->Initialize(*m_reader, fs_index));
|
R_TRY(out_header_reader->Initialize(*m_reader, fs_index));
|
||||||
|
|
||||||
|
// NCZ files serve already-decrypted body data (CTR sections are stored decrypted).
|
||||||
|
// Skip the AES layers so we don't decrypt plaintext twice.
|
||||||
|
const bool is_pre_decrypted =
|
||||||
|
m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections();
|
||||||
|
|
||||||
// Declare the storage we're opening.
|
// Declare the storage we're opening.
|
||||||
VirtualFile storage;
|
VirtualFile storage;
|
||||||
|
|
||||||
@@ -158,7 +163,7 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
|
|||||||
out_header_reader->GetPatchMetaDataHashDataInfo()));
|
out_header_reader->GetPatchMetaDataHashDataInfo()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (patch_info.HasAesCtrExTable()) {
|
if (patch_info.HasAesCtrExTable() && !is_pre_decrypted) {
|
||||||
// Check the encryption type.
|
// Check the encryption type.
|
||||||
ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None ||
|
ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None ||
|
||||||
out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx ||
|
out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx ||
|
||||||
@@ -196,6 +201,7 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create the appropriate storage for the encryption type.
|
// Create the appropriate storage for the encryption type.
|
||||||
|
if (!is_pre_decrypted) {
|
||||||
switch (out_header_reader->GetEncryptionType()) {
|
switch (out_header_reader->GetEncryptionType()) {
|
||||||
case NcaFsHeader::EncryptionType::None:
|
case NcaFsHeader::EncryptionType::None:
|
||||||
// If there's no encryption, use the base storage we made previously.
|
// If there's no encryption, use the base storage we made previously.
|
||||||
@@ -223,6 +229,7 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
|
|||||||
default:
|
default:
|
||||||
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
|
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Potentially save storages to our context.
|
// Potentially save storages to our context.
|
||||||
if (ctx != nullptr) {
|
if (ctx != nullptr) {
|
||||||
@@ -382,22 +389,26 @@ Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the appropriate storage for the encryption type.
|
// Create the appropriate storage for the encryption type.
|
||||||
|
const bool is_pre_decrypted =
|
||||||
|
m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections();
|
||||||
|
if (!is_pre_decrypted) {
|
||||||
switch (header_reader->GetEncryptionType()) {
|
switch (header_reader->GetEncryptionType()) {
|
||||||
case NcaFsHeader::EncryptionType::None:
|
case NcaFsHeader::EncryptionType::None:
|
||||||
// If there's no encryption, use the base storage we made previously.
|
// If there's no encryption, use the base storage we made previously.
|
||||||
break;
|
break;
|
||||||
case NcaFsHeader::EncryptionType::AesXts:
|
case NcaFsHeader::EncryptionType::AesXts:
|
||||||
R_TRY(
|
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
|
||||||
this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset));
|
fs_data_offset));
|
||||||
break;
|
break;
|
||||||
case NcaFsHeader::EncryptionType::AesCtr:
|
case NcaFsHeader::EncryptionType::AesCtr:
|
||||||
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset,
|
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
|
||||||
header_reader->GetAesCtrUpperIv(),
|
fs_data_offset, header_reader->GetAesCtrUpperIv(),
|
||||||
AlignmentStorageRequirement::CacheBlockSize));
|
AlignmentStorageRequirement::CacheBlockSize));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
|
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set output storage.
|
// Set output storage.
|
||||||
*out = std::move(storage);
|
*out = std::move(storage);
|
||||||
@@ -432,6 +443,11 @@ Result NcaFileSystemDriver::CreateAesCtrStorage(
|
|||||||
ASSERT(out != nullptr);
|
ASSERT(out != nullptr);
|
||||||
ASSERT(base_storage != nullptr);
|
ASSERT(base_storage != nullptr);
|
||||||
|
|
||||||
|
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
|
||||||
|
*out = std::move(base_storage);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
// Create the iv.
|
// Create the iv.
|
||||||
std::array<u8, AesCtrStorage::IvSize> iv{};
|
std::array<u8, AesCtrStorage::IvSize> iv{};
|
||||||
AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset);
|
AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset);
|
||||||
@@ -469,6 +485,11 @@ Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile ba
|
|||||||
ASSERT(out != nullptr);
|
ASSERT(out != nullptr);
|
||||||
ASSERT(base_storage != nullptr);
|
ASSERT(base_storage != nullptr);
|
||||||
|
|
||||||
|
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
|
||||||
|
*out = std::move(base_storage);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
// Create the iv.
|
// Create the iv.
|
||||||
std::array<u8, AesXtsStorage::IvSize> iv{};
|
std::array<u8, AesXtsStorage::IvSize> iv{};
|
||||||
AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize);
|
AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize);
|
||||||
@@ -809,6 +830,12 @@ Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(
|
|||||||
// Validate preconditions.
|
// Validate preconditions.
|
||||||
ASSERT(out != nullptr);
|
ASSERT(out != nullptr);
|
||||||
ASSERT(base_storage != nullptr);
|
ASSERT(base_storage != nullptr);
|
||||||
|
|
||||||
|
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
|
||||||
|
*out = std::move(base_storage);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT(encryption_type == NcaFsHeader::EncryptionType::None ||
|
ASSERT(encryption_type == NcaFsHeader::EncryptionType::None ||
|
||||||
encryption_type == NcaFsHeader::EncryptionType::AesCtrEx ||
|
encryption_type == NcaFsHeader::EncryptionType::AesCtrEx ||
|
||||||
encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
|
encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
|
||||||
@@ -871,6 +898,11 @@ Result NcaFileSystemDriver::CreateAesCtrExStorage(
|
|||||||
ASSERT(meta_storage != nullptr);
|
ASSERT(meta_storage != nullptr);
|
||||||
ASSERT(patch_info.HasAesCtrExTable());
|
ASSERT(patch_info.HasAesCtrExTable());
|
||||||
|
|
||||||
|
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
|
||||||
|
*out = std::move(base_storage);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
// Read the bucket tree header.
|
// Read the bucket tree header.
|
||||||
BucketTree::Header header;
|
BucketTree::Header header;
|
||||||
std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header));
|
std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header));
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ public:
|
|||||||
const NcaCompressionConfiguration& compression_cfg);
|
const NcaCompressionConfiguration& compression_cfg);
|
||||||
|
|
||||||
VirtualFile GetSharedBodyStorage();
|
VirtualFile GetSharedBodyStorage();
|
||||||
|
VirtualFile GetStorage() const { return m_body_storage; }
|
||||||
u32 GetMagic() const;
|
u32 GetMagic() const;
|
||||||
NcaHeader::DistributionType GetDistributionType() const;
|
NcaHeader::DistributionType GetDistributionType() const;
|
||||||
NcaHeader::ContentType GetContentType() const;
|
NcaHeader::ContentType GetContentType() const;
|
||||||
|
|||||||
@@ -112,6 +112,9 @@ public:
|
|||||||
// Returns whether or not the file can be read from.
|
// Returns whether or not the file can be read from.
|
||||||
virtual bool IsReadable() const = 0;
|
virtual bool IsReadable() const = 0;
|
||||||
|
|
||||||
|
// Returns true if this file serves NCA body data that is already decrypted (e.g. NCZ).
|
||||||
|
virtual bool HasDecryptedSections() const { return false; }
|
||||||
|
|
||||||
// The primary method of reading from the file. Reads length bytes into data starting at offset
|
// The primary method of reading from the file. Reads length bytes into data starting at offset
|
||||||
// into file. Returns number of bytes successfully read.
|
// into file. Returns number of bytes successfully read.
|
||||||
virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0;
|
virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0;
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ public:
|
|||||||
|
|
||||||
std::size_t GetOffset() const;
|
std::size_t GetOffset() const;
|
||||||
|
|
||||||
|
bool HasDecryptedSections() const override {
|
||||||
|
return file && file->HasDecryptedSections();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
|
std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& xci_file) {
|
|||||||
// Identify XCI as a valid container even when it does not include a bootable Program NCA.
|
// Identify XCI as a valid container even when it does not include a bootable Program NCA.
|
||||||
// Bootability is handled by AppLoader_XCI::Load().
|
// Bootability is handled by AppLoader_XCI::Load().
|
||||||
if (xci.GetSecurePartitionNSP() != nullptr) {
|
if (xci.GetSecurePartitionNSP() != nullptr) {
|
||||||
return FileType::XCI;
|
return xci_file->GetName().ends_with(".xcz") ? FileType::XCZ
|
||||||
|
: FileType::XCI;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FileType::Error;
|
return FileType::Error;
|
||||||
|
|||||||
Reference in New Issue
Block a user