Compare commits

...

2 Commits

Author SHA1 Message Date
PavelBARABANOV 44cd950747 LRU-cache NCZBLOCK blocks for faster update boots 2026-10-27 07:30:28 +03:00
PavelBARABANOV 7cef75b23d [core] Fix the display of the xcz format 2026-10-27 02:13:42 +03:00
2 changed files with 40 additions and 12 deletions
+38 -11
View File
@@ -8,6 +8,7 @@
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <span> #include <span>
#include <list>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -120,9 +121,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. Keeps a small LRU cache of recently
// decoded blocks so random reads (e.g. update NCA with thousands of sections) don't re-decompress
// the same block over and over.
class BlockReader { class BlockReader {
public: public:
static constexpr std::size_t BlockCacheSize = 8;
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} {
@@ -139,11 +144,13 @@ public:
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 +158,44 @@ public:
} }
private: private:
bool Load(u64 id) { const std::vector<u8>* GetBlock(u64 id) {
// LRU hit: move to front.
for (auto it = cache.begin(); it != cache.end(); ++it) {
if (it->first == id) {
cache.splice(cache.begin(), cache, it);
return &cache.front().second;
}
}
// Miss: decode the block.
std::vector<u8> buffer;
if (cache.size() >= BlockCacheSize) {
buffer = std::move(cache.back().second);
cache.pop_back();
}
if (!DecodeBlock(id, buffer)) {
return nullptr;
}
cache.emplace_front(id, std::move(buffer));
return &cache.front().second;
}
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 ||
Common::Compression::DecompressDataZSTD(comp, cache) != want) return false; Common::Compression::DecompressDataZSTD(comp, buffer) != want) return false;
} else if (source->Read(cache.data(), cache.size(), offsets[id]) != cache.size()) { } 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 +204,7 @@ 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; std::list<std::pair<u64, std::vector<u8>>> cache; // LRU: front = most recent
std::vector<u8> cache;
}; };
class BlockNczFile final : public FileSys::VfsFile { class BlockNczFile final : public FileSys::VfsFile {
+2 -1
View File
@@ -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;