Compare commits

..

1 Commits

Author SHA1 Message Date
xbzk ec16a6142e [video_core] Revert 4490 and 4531 2026-10-28 18:33:56 -03:00
12 changed files with 75 additions and 192 deletions
+27 -69
View File
@@ -120,13 +120,9 @@ std::optional<std::pair<std::vector<Section>, std::optional<BlockHeader>>> Parse
return std::make_pair(std::move(sections), std::move(bh));
}
// 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.
// Sequential reader over independently-compressed blocks, one block cached (like nsz_rs).
class BlockReader {
public:
static constexpr std::size_t CacheSlots = 32;
BlockReader(FileSys::VirtualFile f, const BlockHeader& bh, u64 payload_start)
: source{std::move(f)}, block_size{1ULL << bh.exponent}, sizes{bh.sizes},
decompressed_size{bh.decompressed_size} {
@@ -135,31 +131,19 @@ public:
u64 off = payload_start;
offsets.reserve(sizes.size());
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;
}
~BlockReader() {
if (dctx != nullptr) ZSTD_freeDCtx(dctx);
}
BlockReader(const BlockReader&) = delete;
BlockReader& operator=(const BlockReader&) = delete;
// Fills `out` from `position`; returns bytes written.
std::size_t ReadAt(u64 position, std::span<u8> out) {
std::size_t done = 0;
while (done < out.size() && position < decompressed_size) {
const u64 id = position / block_size;
if (id >= sizes.size()) break;
const std::vector<u8>* block = GetBlock(id);
if (block == nullptr) break;
if (cache_id != id && !Load(id)) break;
const u64 in = position - id * block_size;
if (in >= block->size()) break;
const std::size_t n = static_cast<std::size_t>(
std::min<u64>(out.size() - done, block->size() - in));
std::memcpy(out.data() + done, block->data() + in, n);
std::min<u64>(out.size() - done, cache.size() - in));
std::memcpy(out.data() + done, cache.data() + in, n);
done += n;
position += n;
}
@@ -167,48 +151,23 @@ public:
}
private:
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) {
bool Load(u64 id) {
u64 want = block_size;
if (id + 1 == sizes.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 { buffer.resize(static_cast<std::size_t>(want)); }
try { cache.resize(static_cast<std::size_t>(want)); }
catch (const std::bad_alloc&) { return false; }
const u32 csize = sizes[id];
if (csize < want) {
const auto comp = source->ReadBytes(csize, offsets[id]);
if (comp.size() != csize) return false;
const std::size_t res = ZSTD_decompressDCtx(
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()) {
if (comp.size() != csize ||
Common::Compression::DecompressDataZSTD(comp, cache) != want) return false;
} else if (source->Read(cache.data(), cache.size(), offsets[id]) != cache.size()) {
return false;
}
cache_id = id;
return true;
}
@@ -217,8 +176,8 @@ private:
std::vector<u64> offsets;
std::vector<u32> sizes;
u64 decompressed_size;
ZSTD_DCtx* dctx = nullptr;
std::vector<Slot> cache; // direct-mapped, CacheSlots entries
u64 cache_id = ~0ULL;
std::vector<u8> cache;
};
class BlockNczFile final : public FileSys::VfsFile {
@@ -230,6 +189,10 @@ public:
nca_size = HeaderSize;
for (const auto& s : sections)
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; }
@@ -239,10 +202,6 @@ public:
bool IsWritable() const override { return false; }
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 {
if (off >= nca_size) return 0;
len = static_cast<std::size_t>(std::min<u64>(len, nca_size - off));
@@ -254,15 +213,8 @@ public:
pos += n;
}
std::scoped_lock lock{mutex};
// Sections are sorted by offset; find the first one whose stop > pos.
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;
for (std::size_t i = 0; i < sections.size() && pos < end; ++i) {
const auto& s = sections[i];
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);
if (stop <= pos) continue;
@@ -276,7 +228,12 @@ public:
const std::span<u8> piece(data + (pos - off), static_cast<std::size_t>(pe - pos));
if (body->ReadAt(pos - HeaderSize, piece) != piece.size())
return static_cast<std::size_t>(pos - off);
// No CTR here: NCZ body is already decrypted, and NcaFileSystemDriver skips AES.
if (ciphers[i]) {
Key128 iv{};
SetIv(iv, s.counter, pos);
ciphers[i]->SetIV(iv);
ciphers[i]->Transcode(piece.data(), piece.size(), piece.data(), Op::Encrypt);
}
pos = pe;
}
return len;
@@ -290,6 +247,7 @@ private:
const std::vector<u8> hdr;
const std::vector<Section> sections;
const std::unique_ptr<BlockReader> body;
std::vector<std::unique_ptr<AESCipher<Key128>>> ciphers;
mutable std::mutex mutex;
u64 nca_size{};
};
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -93,11 +93,6 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
// Initialize our header reader for the 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.
VirtualFile storage;
@@ -163,7 +158,7 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
out_header_reader->GetPatchMetaDataHashDataInfo()));
}
if (patch_info.HasAesCtrExTable() && !is_pre_decrypted) {
if (patch_info.HasAesCtrExTable()) {
// Check the encryption type.
ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None ||
out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx ||
@@ -201,34 +196,32 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
}
} else {
// Create the appropriate storage for the encryption type.
if (!is_pre_decrypted) {
switch (out_header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
break;
case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: {
// Create the aes ctr storage.
VirtualFile aes_ctr_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage,
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
switch (out_header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
break;
case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: {
// Create the aes ctr storage.
VirtualFile aes_ctr_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage,
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
// Create region switch storage.
R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader,
std::move(storage), std::move(aes_ctr_storage)));
} break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
}
// Create region switch storage.
R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader,
std::move(storage), std::move(aes_ctr_storage)));
} break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
}
// Potentially save storages to our context.
@@ -389,25 +382,21 @@ Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(
}
// 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()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::CacheBlockSize));
break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
}
switch (header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(
this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset,
header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::CacheBlockSize));
break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
}
// Set output storage.
@@ -443,11 +432,6 @@ Result NcaFileSystemDriver::CreateAesCtrStorage(
ASSERT(out != nullptr);
ASSERT(base_storage != nullptr);
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
// Create the iv.
std::array<u8, AesCtrStorage::IvSize> iv{};
AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset);
@@ -485,11 +469,6 @@ Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile ba
ASSERT(out != nullptr);
ASSERT(base_storage != nullptr);
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
// Create the iv.
std::array<u8, AesXtsStorage::IvSize> iv{};
AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize);
@@ -830,12 +809,6 @@ Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(
// Validate preconditions.
ASSERT(out != 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 ||
encryption_type == NcaFsHeader::EncryptionType::AesCtrEx ||
encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
@@ -898,11 +871,6 @@ Result NcaFileSystemDriver::CreateAesCtrExStorage(
ASSERT(meta_storage != nullptr);
ASSERT(patch_info.HasAesCtrExTable());
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
// Read the bucket tree header.
BucketTree::Header header;
std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header));
@@ -104,7 +104,6 @@ public:
const NcaCompressionConfiguration& compression_cfg);
VirtualFile GetSharedBodyStorage();
VirtualFile GetStorage() const { return m_body_storage; }
u32 GetMagic() const;
NcaHeader::DistributionType GetDistributionType() const;
NcaHeader::ContentType GetContentType() const;
-3
View File
@@ -112,9 +112,6 @@ public:
// Returns whether or not the file can be read from.
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
// 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;
-7
View File
@@ -1,6 +1,3 @@
// 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
@@ -40,10 +37,6 @@ public:
std::size_t GetOffset() const;
bool HasDecryptedSections() const override {
return file && file->HasDecryptedSections();
}
private:
std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
+1 -2
View File
@@ -54,8 +54,7 @@ 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.
// Bootability is handled by AppLoader_XCI::Load().
if (xci.GetSecurePartitionNSP() != nullptr) {
return xci_file->GetName().ends_with(".xcz") ? FileType::XCZ
: FileType::XCI;
return FileType::XCI;
}
return FileType::Error;
+1 -1
View File
@@ -757,7 +757,7 @@ struct Memory::Impl {
};
auto& gpu = system.GPU();
gpu_device_memory->ApplyOpOnPointer(
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size, true); });
p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); });
}
Core::System& system;
+3 -15
View File
@@ -226,19 +226,7 @@ struct GPU::Impl {
}
/// Notify rasterizer that any caches of the specified region should be invalidated
void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) {
if (preserve_gpu_writes) {
VideoCore::RasterizerInterface* rasterizer = renderer->ReadRasterizer();
if (!rasterizer->TryCacheInvalidation(addr, size)) {
const u64 fence = RequestSyncOperation([rasterizer, addr, size] {
rasterizer->FlushRegion(addr, size, VideoCommon::CacheType::BufferCache);
rasterizer->OnCacheInvalidation(addr, size);
});
gpu_thread.TickGPU(is_async);
WaitForSyncOperation(fence);
}
return;
}
void InvalidateRegion(DAddr addr, u64 size) {
gpu_thread.InvalidateRegion(addr, size);
}
@@ -530,8 +518,8 @@ void GPU::FlushRegion(DAddr addr, u64 size) {
impl->FlushRegion(addr, size);
}
void GPU::InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) {
impl->InvalidateRegion(addr, size, preserve_gpu_writes);
void GPU::InvalidateRegion(DAddr addr, u64 size) {
impl->InvalidateRegion(addr, size);
}
bool GPU::OnCPUWrite(DAddr addr, u64 size) {
+1 -1
View File
@@ -248,7 +248,7 @@ public:
void FlushRegion(DAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be invalidated
void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes = false);
void InvalidateRegion(DAddr addr, u64 size);
/// Notify rasterizer that CPU is trying to write this area. It returns true if the area is
/// sensible, false otherwise, addr and size must be a valid combination
-6
View File
@@ -114,12 +114,6 @@ public:
/// Notify rasterizer that any caches of the specified region are desync with guest
virtual void OnCacheInvalidation(PAddr addr, u64 size) = 0;
virtual bool TryCacheInvalidation(DAddr addr, u64 size) {
if (MustFlushRegion(addr, size, VideoCommon::CacheType::BufferCache)) return false;
OnCacheInvalidation(addr, size);
return true;
}
virtual bool OnCPUWrite(PAddr addr, u64 size) = 0;
/// Sync memory between guest and host.
@@ -798,18 +798,6 @@ void RasterizerVulkan::OnCacheInvalidation(DAddr addr, u64 size) {
pipeline_cache.InvalidateRegion(addr, size);
}
bool RasterizerVulkan::TryCacheInvalidation(DAddr addr, u64 size) {
if (addr == 0 || size == 0) return true;
{
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
if (buffer_cache.IsRegionGpuModified(addr, size)) return false;
texture_cache.WriteMemory(addr, size);
buffer_cache.WriteMemory(addr, size);
}
pipeline_cache.InvalidateRegion(addr, size);
return true;
}
void RasterizerVulkan::InvalidateGPUCache() {
gpu.InvalidateGPUCache();
}
@@ -106,7 +106,6 @@ public:
VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
void InnerInvalidation(std::span<const std::pair<DAddr, std::size_t>> sequences) override;
void OnCacheInvalidation(DAddr addr, u64 size) override;
bool TryCacheInvalidation(DAddr addr, u64 size) override;
bool OnCPUWrite(DAddr addr, u64 size) override;
void InvalidateGPUCache() override;
void UnmapMemory(DAddr addr, u64 size) override;