Compare commits

..

1 Commits

Author SHA1 Message Date
lizzie b807013aac 2026-09-25 13:00:48
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-25 13:00:48 +00:00
9 changed files with 273 additions and 283 deletions
-10
View File
@@ -5,11 +5,6 @@
"repo": "lioncash/biscuit", "repo": "lioncash/biscuit",
"version": "v0.19.0" "version": "v0.19.0"
}, },
"atmosphere_zstd_bic": {
"hash": "204f7573ea40de2878759c7ff5b2c016111fc29ae8574df50a3a0db9018c3b012f9040ea7466559130424d4335f2dfd6d7abfdc4d4d67e8a34531b8284f0cc6a",
"repo": "Atmosphere-NX/Atmosphere",
"version": "3ace80cc7b87e1837fccca1522801bbf6fddc6aa"
},
"boost": { "boost": {
"artifact": "boost-%VERSION%.tar.zst", "artifact": "boost-%VERSION%.tar.zst",
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem", "find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
@@ -338,11 +333,6 @@
"repo": "herumi/xbyak", "repo": "herumi/xbyak",
"version": "v7.40.1" "version": "v7.40.1"
}, },
"zbic": {
"hash": "fbe2f37986377d7f0d96ae3224c80b5971df6e8b6961f68061f1975ebb2e8cb78f07b90f014b007be4023dbf5513581504e7f7d61a5237cb2a8a7a61ae11e482",
"repo": "kinnay/zbic",
"version": "11b08f2712264bbed731545085cbd9702096ceb7"
},
"zlib": { "zlib": {
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4", "hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
"min_version": "1.2", "min_version": "1.2",
-5
View File
@@ -48,11 +48,6 @@ if (NOT TARGET stb::headers)
add_library(stb::headers ALIAS stb) add_library(stb::headers ALIAS stb)
endif() endif()
AddJsonPackage(NAME zbic DOWNLOAD_ONLY)
set(ZBIC_INCLUDE_DIR
"${zbic_SOURCE_DIR}/src"
PARENT_SCOPE)
# ItaniumDemangle (Windows only) # ItaniumDemangle (Windows only)
if (WIN32 AND NOT TARGET LLVM::Demangle) if (WIN32 AND NOT TARGET LLVM::Demangle)
add_library(demangle demangle/ItaniumDemangle.cpp) add_library(demangle demangle/ItaniumDemangle.cpp)
-6
View File
@@ -137,8 +137,6 @@ add_library(
uuid.cpp uuid.cpp
uuid.h uuid.h
vector_math.h vector_math.h
zbic_compression.cpp
zbic_compression.h
zstd_compression.cpp zstd_compression.cpp
zstd_compression.h zstd_compression.h
fs/ryujinx_compat.h fs/ryujinx_compat.cpp fs/ryujinx_compat.h fs/ryujinx_compat.cpp
@@ -149,10 +147,6 @@ add_library(
net/net.h net/net.cpp net/net.h net/net.cpp
container/unordered_map.h container/unordered_set.h) container/unordered_map.h container/unordered_set.h)
set_source_files_properties(zbic_compression.cpp PROPERTIES
INCLUDE_DIRECTORIES "${ZBIC_INCLUDE_DIR}"
COMPILE_OPTIONS "$<$<CXX_COMPILER_ID:Clang,GNU>:-Wno-unused-function;-Wno-missing-declarations;-Wno-shadow>")
if(WIN32) if(WIN32)
target_sources(common PRIVATE windows/timer_resolution.cpp target_sources(common PRIVATE windows/timer_resolution.cpp
windows/timer_resolution.h) windows/timer_resolution.h)
+119 -52
View File
@@ -8,7 +8,14 @@
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <mutex> #include <mutex>
#else #include <algorithm>
#include <vector>
#endif
#include <cerrno>
#include <cstring>
#ifndef _WIN32
#include <sys/mman.h> #include <sys/mman.h>
#endif #endif
@@ -19,78 +26,92 @@
namespace Common { namespace Common {
#ifdef _WIN32 #ifdef _WIN32
static std::vector<std::pair<u64, u64>> vector_regions {};
// Workaround for handling non-commited memory accessed by Dynarmic; usually result of an error struct VectorRegion {
u64 start_page;
u64 end_page;
};
static std::mutex& GetVectorRegionsMutex() {
static std::mutex* m = new std::mutex();
return *m;
}
static std::vector<VectorRegion>& GetVectorRegions() {
static std::vector<VectorRegion>* v = new std::vector<VectorRegion>();
return *v;
}
static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) { static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) {
DWORD code = info->ExceptionRecord->ExceptionCode; if (info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) {
u64 exception_addr = reinterpret_cast<u64>(info->ExceptionRecord->ExceptionAddress);
if (code != EXCEPTION_ACCESS_VIOLATION) {
// Not our problem
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
u64 addr = 0, addr2 = 0; const u64 fault_addr = info->ExceptionRecord->ExceptionInformation[1];
const u64 access_type = info->ExceptionRecord->ExceptionInformation[0];
const bool is_write = (access_type == 1);
const u64 fault_page = fault_addr >> HostPageBits;
for (auto region: vector_regions) { u64 addr = 0;
auto addr_shifted = exception_addr >> HostPageBits; u64 addr2 = 0;
if (region.first <= addr_shifted && addr_shifted <= region.second) {
addr = addr_shifted; {
std::lock_guard lock(GetVectorRegionsMutex());
for (const auto& region : GetVectorRegions()) {
if (fault_page >= region.start_page && fault_page < region.end_page) {
addr = fault_page;
} }
const u64 page2 = (fault_addr + 0x3F) >> HostPageBits;
// Page-boundary accesses if (page2 != fault_page && page2 >= region.start_page && page2 < region.end_page) {
if (auto addr_ = (exception_addr + 0x40) >> HostPageBits; addr_ != addr_shifted && region.first <= addr_ && addr_ <= region.second) { addr2 = page2;
addr2 = addr_;
} }
if (addr != 0 || addr2 != 0) break;
if (addr != 0 || addr2 != 0) {
break;
} }
} }
if (addr == 0 && addr2 == 0) { if (addr == 0 && addr2 == 0) {
// Not our problem
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
LOG_ERROR(HW_Memory, "Accessing an unallocated region of a SparseLargeVector at {:#x}; this shouldn't happen and is likely a Dynarmic error!", exception_addr); LOG_ERROR(HW_Memory, "Accessing an unallocated region of a SparseLargeVector at {:#x}; this shouldn't happen and is likely a Dynarmic error!", fault_addr);
// Commit this region if (addr != 0 && !CommitVectorPage(addr << HostPageBits, is_write)) {
if (addr != 0) {
if (!CommitVectorPage(addr << HostPageBits, false)) {
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
} if (addr2 != 0 && !CommitVectorPage(addr2 << HostPageBits, is_write)) {
// Commit next region if needed
if (addr2 != 0) {
if (!CommitVectorPage(addr2 << HostPageBits, false)) {
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
}
return EXCEPTION_CONTINUE_EXECUTION; return EXCEPTION_CONTINUE_EXECUTION;
} }
bool CommitVectorPage(uintptr_t addr, bool write) noexcept { bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
MEMORY_BASIC_INFORMATION info {}; MEMORY_BASIC_INFORMATION info {};
auto res = VirtualQuery(reinterpret_cast<void*>(addr), &info, sizeof(info)); const auto res = VirtualQuery(reinterpret_cast<void*>(addr), &info, sizeof(info));
const DWORD perm = write ? PAGE_READWRITE : PAGE_READONLY;
if (res == 0) { if (res == 0) {
LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError()); LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError());
} else if (info.State == MEM_COMMIT) {
DWORD old_protect {};
if (!VirtualProtect(reinterpret_cast<void*>(addr), HostPageSize, perm, &old_protect)) {
LOG_ERROR(HW_Memory, "VirtualProtect failed at {:#x}, error {}", addr, GetLastError());
return false;
}
return true;
} else if (info.State != MEM_RESERVE) { } else if (info.State != MEM_RESERVE) {
LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} that is not mapped or is already committed (state {:#x})", addr, info.State); LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} (state {:#x})", addr, info.State);
return false; return false;
} }
auto perm = write ? PAGE_READWRITE : PAGE_READONLY; if (VirtualAlloc(reinterpret_cast<LPVOID>(addr), HostPageSize, MEM_COMMIT, perm) == nullptr) {
void* res2 = VirtualAlloc(reinterpret_cast<LPVOID>(addr), HostPageSize, MEM_COMMIT, perm);
if (res2 == nullptr) {
LOG_ERROR(HW_Memory, "Failed to commit large buffer region at {:#x}, error {}", addr, GetLastError()); LOG_ERROR(HW_Memory, "Failed to commit large buffer region at {:#x}, error {}", addr, GetLastError());
return false; return false;
} }
return true; return true;
} }
#endif #endif
#ifndef MAP_NOCORE #ifndef MAP_NOCORE
@@ -102,56 +123,102 @@ bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
void DecommitVectorPage(uintptr_t base) noexcept { void DecommitVectorPage(uintptr_t base) noexcept {
#if defined(_WIN32) #if defined(_WIN32)
VirtualFree(reinterpret_cast<LPVOID>(base), HostPageSize, MEM_DECOMMIT); if (!VirtualFree(reinterpret_cast<LPVOID>(base), HostPageSize, MEM_DECOMMIT)) {
LOG_WARNING(HW_Memory, "VirtualFree(MEM_DECOMMIT) failed at {:#x}, error {}", base, GetLastError());
}
#elif defined(__linux__) #elif defined(__linux__)
// Linux's MADV_DONTNEED zeros out pages for us if (madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_DONTNEED) != 0) {
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_DONTNEED); LOG_WARNING(HW_Memory, "madvise(MADV_DONTNEED) failed at {:#x}: {}", base, std::strerror(errno));
}
#else #else
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_FREE); if (madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_FREE) != 0) {
LOG_WARNING(HW_Memory, "madvise(MADV_FREE) failed at {:#x}: {}", base, std::strerror(errno));
}
std::memset(reinterpret_cast<void*>(base), 0, HostPageSize); std::memset(reinterpret_cast<void*>(base), 0, HostPageSize);
#endif #endif
} }
void* AllocateMemoryPages(std::size_t size) noexcept { void* AllocateMemoryPages(std::size_t size) noexcept {
if (auto page = HostPageSize; size % page != 0) { if (size == 0) {
return nullptr;
}
const auto page = HostPageSize;
if (size % page != 0) {
LOG_WARNING(HW_Memory, "Allocating unaligned large vector with size {:#x}; aligning to {} page size", size, page); LOG_WARNING(HW_Memory, "Allocating unaligned large vector with size {:#x}; aligning to {} page size", size, page);
if (size > SIZE_MAX - (page - 1)) {
LOG_CRITICAL(HW_Memory, "Size {:#x} would overflow page alignment", size);
return nullptr;
}
size = AlignUp(size, page); size = AlignUp(size, page);
} }
#ifdef _WIN32 #ifdef _WIN32
// We will never use this memory entirely so instead of committing it up front let's just reserve it and commit each page individually
void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
if (base != nullptr) { if (base != nullptr) {
vector_regions.emplace_back(reinterpret_cast<u64>(base), reinterpret_cast<u64>(base) + size); {
std::lock_guard lock(GetVectorRegionsMutex());
GetVectorRegions().push_back({
reinterpret_cast<u64>(base) >> HostPageBits,
(reinterpret_cast<u64>(base) + size) >> HostPageBits,
});
}
static std::once_flag flag; static std::once_flag flag;
std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); }); std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); });
} else { } else {
// Try committing everything instead??
LOG_WARNING(HW_Memory, "Failed to reserve large vector region with error {}, trying to commit instead..", GetLastError()); LOG_WARNING(HW_Memory, "Failed to reserve large vector region with error {}, trying to commit instead..", GetLastError());
base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE); base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
} }
ASSERT_MSG(base, "Failed to reserve {:#x} sized region with error {}", size, GetLastError()); ASSERT_MSG(base, "Failed to reserve {:#x} sized region with error {}", size, GetLastError());
#else #else
void* base = mmap(nullptr, size, PROT_READ, MAP_ANON | MAP_PRIVATE | MAP_NOCORE, -1, 0); int flags = MAP_ANON | MAP_PRIVATE;
if (base == MAP_FAILED) #ifdef MAP_NORESERVE
base = nullptr; flags |= MAP_NORESERVE;
ASSERT_MSG(base, "Failed to allocate {:#x} sized region with error {}", size, strerror(errno));
#endif #endif
#if defined(MAP_NOCORE)
flags |= MAP_NOCORE;
#endif
void* base = mmap(nullptr, size, PROT_READ, flags, -1, 0);
if (base == MAP_FAILED) {
base = nullptr;
}
#ifdef MADV_HUGEPAGE
if (base != nullptr) {
madvise(base, size, MADV_HUGEPAGE);
}
#endif
ASSERT_MSG(base, "Failed to allocate {:#x} sized region with error {}", size, std::strerror(errno));
#endif
return base; return base;
} }
void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept { void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
if (auto page = HostPageSize; size % page != 0) { if (base == nullptr) {
return;
}
if (const auto page = HostPageSize; size % page != 0) {
size = AlignUp(size, page); size = AlignUp(size, page);
} }
if (!base)
return;
#ifdef _WIN32 #ifdef _WIN32
ASSERT(VirtualFree(base, 0, MEM_RELEASE)); {
std::lock_guard lock(GetVectorRegionsMutex());
auto& regions = GetVectorRegions();
const u64 base_page = reinterpret_cast<u64>(base) >> HostPageBits;
regions.erase(std::remove_if(regions.begin(), regions.end(),
[base_page](const VectorRegion& r) { return r.start_page == base_page; }), regions.end());
}
if (!VirtualFree(base, 0, MEM_RELEASE)) {
LOG_ERROR(HW_Memory, "VirtualFree failed, error {}", GetLastError());
}
#else #else
ASSERT(munmap(base, size) == 0); if (munmap(base, size) != 0) {
LOG_ERROR(HW_Memory, "munmap failed: {}", std::strerror(errno));
}
#endif #endif
} }
+145 -107
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 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
/* virtual_buffer.h */ /* virtual_buffer.h */
@@ -7,10 +7,14 @@
#pragma once #pragma once
#include <array>
#include <atomic> #include <atomic>
#include <bit> #include <bit>
#include <utility> #include <cerrno>
#include <vector> #include <cstdlib>
#include <cstring>
#include <memory>
#include <type_traits>
#ifndef _WIN32 #ifndef _WIN32
#include <unistd.h> #include <unistd.h>
@@ -28,9 +32,9 @@ constexpr u64 HostPageBits = 12;
constexpr u64 HostPageMask = ~(HostPageSize - 1); constexpr u64 HostPageMask = ~(HostPageSize - 1);
bool CommitVectorPage(uintptr_t addr, bool write) noexcept; bool CommitVectorPage(uintptr_t addr, bool write) noexcept;
#else #else
const u64 HostPageSize = sysconf(_SC_PAGESIZE); inline const u64 HostPageSize = static_cast<u64>(sysconf(_SC_PAGESIZE));
const u64 HostPageBits = std::countr_zero(HostPageSize); inline const u64 HostPageBits = std::countr_zero(HostPageSize);
const u64 HostPageMask = ~(HostPageSize - 1); inline const u64 HostPageMask = ~(HostPageSize - 1);
#endif #endif
void* AllocateMemoryPages(std::size_t size) noexcept; void* AllocateMemoryPages(std::size_t size) noexcept;
@@ -43,20 +47,18 @@ template <typename T>
// requires std::is_trivially_copyable_v<T> // requires std::is_trivially_copyable_v<T>
class SparseLargeVector final { class SparseLargeVector final {
public: public:
constexpr SparseLargeVector() = default; SparseLargeVector() = default;
explicit SparseLargeVector(std::size_t count) noexcept explicit SparseLargeVector(std::size_t count) noexcept {
: alloc_size{count * sizeof(T)} if (count > SIZE_MAX / sizeof(T)) {
{ LOG_CRITICAL(Common_Memory, "SparseLargeVector size overflow: {} elements", count);
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size)); return;
}
// each item in vector holds information for 64 pages Allocate(count * sizeof(T));
auto denom = HostPageSize * 64;
committed_pages = std::vector<std::atomic<u64>>((alloc_size + denom - 1) / denom);
} }
~SparseLargeVector() noexcept { ~SparseLargeVector() noexcept {
FreeMemoryPages(base_ptr, alloc_size); Release();
} }
SparseLargeVector(const SparseLargeVector&) = delete; SparseLargeVector(const SparseLargeVector&) = delete;
@@ -65,145 +67,181 @@ public:
SparseLargeVector& operator=(SparseLargeVector&& other) = delete; SparseLargeVector& operator=(SparseLargeVector&& other) = delete;
void ResizeAndClear(std::size_t count) noexcept { void ResizeAndClear(std::size_t count) noexcept {
if (auto const new_size = count * sizeof(T); new_size != alloc_size) { if (count > SIZE_MAX / sizeof(T)) {
FreeMemoryPages(base_ptr, alloc_size); LOG_CRITICAL(Common_Memory, "SparseLargeVector resize overflow: {} elements", count);
alloc_size = new_size; return;
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size));
auto denom = HostPageSize * 64;
committed_pages = std::vector<std::atomic<u64>>((alloc_size + denom - 1) / denom);
} }
const std::size_t new_size = count * sizeof(T);
if (new_size == alloc_size) {
ZeroRegion(0, alloc_size / sizeof(T));
return;
}
Release();
Allocate(new_size);
} }
/// Returns a reference to the value of the requested index and allocates memory if needed.
T& GetAndFault(std::size_t index) noexcept { T& GetAndFault(std::size_t index) noexcept {
if (index > alloc_size / sizeof(T)) { if (base_ptr == nullptr || index >= size()) [[unlikely]] {
UNREACHABLE_MSG("Out of bounds RW access on SparseLargeVector @ {}", index); LOG_CRITICAL(Common_Memory, "SparseLargeVector RW access out of bounds @ {} (size {})", index, size());
std::abort();
} }
const u64 byte_offset = static_cast<u64>(index) * sizeof(T);
if (!IsCommittedPage(index)) { if (!CommitPage(byte_offset)) [[unlikely]] {
CommitPage(index); LOG_CRITICAL(Common_Memory, "SparseLargeVector commit failed @ {} (offset {:#x})", index, byte_offset);
std::abort();
} }
return base_ptr[index]; return base_ptr[index];
} }
/// Returns a reference to the value of the requested index if initialized, or will otherwise return a zero-initialized object. const T& GetOrDefault(std::size_t index) const noexcept {
const T& GetOrDefault(std::size_t index) const { if (base_ptr == nullptr || index >= size()) [[unlikely]] {
LOG_CRITICAL(Common_Memory, "SparseLargeVector RO access out of bounds @ {}", index);
return DefaultValue();
}
#ifdef _WIN32 #ifdef _WIN32
if (!IsCommittedPage(index)) { if (!IsPageCommitted(static_cast<u64>(index) * sizeof(T))) {
return *reinterpret_cast<const T*>(&default_val); return DefaultValue();
} }
#endif #endif
// On non-Windows, OS page table should optimize this by pointing to a zero page if unallocated.
return base_ptr[index]; return base_ptr[index];
} }
void Set(std::size_t index, const T& value) noexcept { void Set(std::size_t index, const T& value) noexcept {
if (index > alloc_size / sizeof(T)) { if (base_ptr == nullptr || index >= size()) [[unlikely]] {
LOG_CRITICAL(Common_Memory, "Out of bounds write on SparseLargeVector @ {}", index); LOG_CRITICAL(Common_Memory, "SparseLargeVector write out of bounds @ {}", index);
return;
}
const u64 byte_offset = static_cast<u64>(index) * sizeof(T);
if (!CommitPage(byte_offset)) [[unlikely]] {
LOG_CRITICAL(Common_Memory, "SparseLargeVector commit failed for write @ {}", index);
return; return;
} }
if (!IsCommittedPage(index))
CommitPage(index);
base_ptr[index] = value; base_ptr[index] = value;
} }
void ZeroRegion(std::size_t start, std::size_t end_) noexcept { void ZeroRegion(std::size_t start, std::size_t end_) noexcept {
u64 base = reinterpret_cast<u64>(&base_ptr[start]); if (base_ptr == nullptr || start >= end_) return;
const u64 end = reinterpret_cast<u64>(&base_ptr[end_]);
const u64 end_page = AlignUp(base, HostPageSize); const u64 start_off = static_cast<u64>(start) * sizeof(T);
const u64 first_size = (std::min)(end_page, end) - base; const u64 end_off = static_cast<u64>(end_) * sizeof(T);
const u64 first_page_end = (start_off + HostPageSize - 1) & HostPageMask;
if (IsCommittedPage(start)) { if (start_off < first_page_end) {
std::memset(reinterpret_cast<void*>(base), 0, first_size); const u64 chunk_end = (std::min)(first_page_end, end_off);
const u64 chunk_size = chunk_end - start_off;
if (chunk_size != 0 && IsPageCommitted(start_off)) {
std::memset(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(base_ptr) + start_off), 0, chunk_size);
}
if (end_off <= first_page_end) return;
} }
if (end <= end_page) for (u64 off = first_page_end; off < end_off; off += HostPageSize) {
return; if (!IsPageCommitted(off)) continue;
const u64 remaining = end_off - off;
base = end_page; if (remaining >= HostPageSize) {
DecommitPage(off);
for (u64 page = base; page < end; page += HostPageSize) {
auto index = (page - reinterpret_cast<u64>(base_ptr)) / sizeof(T);
if (!IsCommittedPage(index)) {
continue;
}
if (end - page >= HostPageSize) {
DecommitPage(index);
} else { } else {
std::memset(reinterpret_cast<void*>(page), 0, end - page); std::memset(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(base_ptr) + off), 0, remaining);
} }
} }
} }
constexpr void CommitRegion(size_t index, size_t end_) { void CommitRegion(std::size_t index, std::size_t end_) noexcept {
const u64 base = static_cast<u64>(index) * sizeof(T); if (base_ptr == nullptr || index >= end_) return;
const u64 end = static_cast<u64>(end_) * sizeof(T); const u64 start_off = static_cast<u64>(index) * sizeof(T);
const u64 end_off = static_cast<u64>(end_) * sizeof(T);
for (u64 page = AlignDown(base, HostPageSize); page < end; page += HostPageSize) { const u64 start_page = start_off & HostPageMask;
if (!IsCommittedPage(page / sizeof(T))) { for (u64 off = start_page; off < end_off; off += HostPageSize) {
CommitPage(page / sizeof(T)); if (!IsPageCommitted(off)) {
(void)CommitPage(off);
} }
} }
} }
constexpr T& GetUnchecked(size_t index) { T& GetUnchecked(std::size_t index) noexcept { return base_ptr[index]; }
return base_ptr[index];
}
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept { [[nodiscard]] const T& operator[](std::size_t index) const noexcept { return GetOrDefault(index); }
return GetOrDefault(index); [[nodiscard]] const T* data() const noexcept { return base_ptr; }
} [[nodiscard]] std::size_t size() const noexcept { return alloc_size / sizeof(T); }
[[nodiscard]] constexpr const T* data() const noexcept {
return base_ptr;
}
[[nodiscard]] constexpr std::size_t size() const noexcept {
return alloc_size / sizeof(T);
}
private: private:
[[nodiscard]] constexpr bool IsCommittedPage(std::size_t index) const noexcept { void Allocate(std::size_t new_size) noexcept {
if (index > alloc_size / sizeof(T)) { alloc_size = new_size;
LOG_CRITICAL(Common_Memory, "Out of bounds access on large vector @ {}", index); if (alloc_size == 0) {
base_ptr = nullptr;
committed_pages.reset();
return;
}
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size));
const std::size_t num_pages = NumPages();
const std::size_t num_words = (num_pages + 63) / 64;
committed_pages = std::make_unique<std::atomic<u64>[]>(num_words);
}
void Release() noexcept {
if (base_ptr != nullptr) {
FreeMemoryPages(base_ptr, alloc_size);
base_ptr = nullptr;
}
committed_pages.reset();
alloc_size = 0;
}
[[nodiscard]] u64 NumPages() const noexcept {
return (alloc_size + HostPageSize - 1) >> HostPageBits;
}
[[nodiscard]] bool IsPageCommitted(u64 byte_offset) const noexcept {
const u64 page_index = byte_offset >> HostPageBits;
if (committed_pages == nullptr || page_index >= NumPages()) return false;
const auto val = committed_pages[page_index >> 6].load(std::memory_order_acquire);
return (val >> (page_index & 63)) & 1;
}
void SetPageBit(u64 page_index, bool value) noexcept {
if (committed_pages == nullptr) return;
const u64 bit = 1ULL << (page_index & 63);
auto& atom = committed_pages[page_index >> 6];
if (value) {
atom.fetch_or(bit, std::memory_order_release);
} else {
atom.fetch_and(~bit, std::memory_order_release);
}
}
bool CommitPage(u64 byte_offset) noexcept {
const u64 page_index = byte_offset >> HostPageBits;
const uintptr_t page_addr = (reinterpret_cast<uintptr_t>(base_ptr) + byte_offset) & HostPageMask;
if (IsPageCommitted(byte_offset)) return true;
#if defined(_WIN32)
if (!CommitVectorPage(page_addr, true)) return false;
#else
if (mprotect(reinterpret_cast<void*>(page_addr), HostPageSize, PROT_READ | PROT_WRITE) != 0) {
LOG_ERROR(Common_Memory, "mprotect failed at {:#x}: {}", page_addr, std::strerror(errno));
return false; return false;
} }
auto page = (index * sizeof(T)) >> HostPageBits;
auto val = committed_pages[page >> 6].load(std::memory_order_acquire);
return (val >> (page & 63)) & 1;
}
constexpr void CommitPage(std::size_t index) noexcept {
auto page_index = (index * sizeof(T)) >> HostPageBits;
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
#if defined(_WIN32)
CommitVectorPage(page, true);
#else
mprotect(reinterpret_cast<void*>(page), HostPageSize, PROT_READ | PROT_WRITE);
#endif #endif
SetPageBit(page_index, true);
committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release); return true;
} }
constexpr void DecommitPage(std::size_t index) noexcept { void DecommitPage(u64 byte_offset) noexcept {
auto page_index = (index * sizeof(T)) >> HostPageBits; const u64 page_index = byte_offset >> HostPageBits;
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask; const uintptr_t page_addr = (reinterpret_cast<uintptr_t>(base_ptr) + byte_offset) & HostPageMask;
DecommitVectorPage(page_addr);
SetPageBit(page_index, false);
}
committed_pages[page_index >> 6].fetch_and(~(1ULL << (page_index & 63)), std::memory_order_release); [[nodiscard]] const T& DefaultValue() const noexcept {
DecommitVectorPage(page); return *reinterpret_cast<const T*>(&default_val);
} }
std::size_t alloc_size{}; std::size_t alloc_size{};
T* base_ptr{}; T* base_ptr{};
std::unique_ptr<std::atomic<u64>[]> committed_pages{};
std::vector<std::atomic<u64>> committed_pages{}; alignas(T) const std::array<u8, sizeof(T)> default_val{};
#ifdef _WIN32
const std::array<u8, sizeof(T)> default_val{};
#endif
}; };
} // namespace Common } // namespace Common
-46
View File
@@ -1,46 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstring>
#include "common/zbic_compression.h"
#define ZSTD_ZBIC_SUPPORT 1
#define ZSTDLIB_VISIBLE static
#define ZSTDLIB_HIDDEN static
#define ZSTDERRORLIB_VISIBLE static
#define ZSTDERRORLIB_HIDDEN static
#undef ZSTD_MULTITHREAD
#if defined(__ANDROID__)
#undef _GNU_SOURCE
#endif
#include "zstd.h"
#define g_ZSTD_threading_useless_symbol g_ZSTD_zbic_threading_useless_symbol
#include "zstd.c"
#undef g_ZSTD_threading_useless_symbol
namespace Common::Compression {
bool IsZBIC(std::span<const u8> src) {
if (src.size() < sizeof(u32)) {
return false;
}
u32 magic = 0;
std::memcpy(&magic, src.data(), sizeof(u32));
return magic == ZSTD_MAGICNUMBER; // 0x4349425A ("ZBIC")
}
int DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src) {
if (dst.empty() || src.empty()) {
return -1;
}
const size_t res = ZSTD_decompress(dst.data(), dst.size(), src.data(), src.size());
if (ZSTD_isError(res)) {
return -1;
}
return static_cast<int>(res);
}
} // namespace Common::Compression
-15
View File
@@ -1,15 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <span>
#include "common/common_types.h"
namespace Common::Compression {
[[nodiscard]] bool IsZBIC(std::span<const u8> src);
[[nodiscard]] int DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src);
} // namespace Common::Compression
+3 -30
View File
@@ -7,14 +7,12 @@
#include <algorithm> #include <algorithm>
#include <cinttypes> #include <cinttypes>
#include <cstring> #include <cstring>
#include <span>
#include <vector> #include <vector>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/lz4_compression.h" #include "common/lz4_compression.h"
#include "common/zbic_compression.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/swap.h" #include "common/swap.h"
#include "core/core.h" #include "core/core.h"
@@ -106,36 +104,11 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) { for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset); nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
if (nso_header.IsSegmentCompressed(i)) { if (nso_header.IsSegmentCompressed(i)) {
if (nso_header.IsZBICCompressed()) { int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]);
// ZBIC compression
const int r = Common::Compression::DecompressDataZBIC(
std::span<u8>{decompressed_size}.first(nso_header.segments[i].size),
std::span<const u8>{compressed_data}.first(nso_header.segments_compressed_size[i])
);
ASSERT(r > 0);
} else {
// LZ4 compression
int r = Common::Compression::DecompressDataLZ4(
decompressed_size.data(),
nso_header.segments[i].size,
compressed_data.data(),
nso_header.segments_compressed_size[i]
);
ASSERT(r == int(nso_header.segments[i].size)); ASSERT(r == int(nso_header.segments[i].size));
} std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size);
std::memcpy(
codeset.memory.data() + module_start + nso_header.segments[i].location,
decompressed_size.data(),
nso_header.segments[i].size
);
} else { } else {
// Not compressed std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size);
std::memcpy(
codeset.memory.data() + module_start + nso_header.segments[i].location,
compressed_data.data(),
nso_header.segments[i].size
);
} }
codeset.segments[i].addr = module_start + nso_header.segments[i].location; codeset.segments[i].addr = module_start + nso_header.segments[i].location;
codeset.segments[i].offset = module_start + nso_header.segments[i].location; codeset.segments[i].offset = module_start + nso_header.segments[i].location;
-6
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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -61,9 +58,6 @@ struct NSOHeader {
std::array<SHA256Hash, 3> segment_hashes; std::array<SHA256Hash, 3> segment_hashes;
bool IsSegmentCompressed(size_t segment_num) const; bool IsSegmentCompressed(size_t segment_num) const;
bool IsZBICCompressed() const {
return ((flags >> 7) & 1) != 0;
}
}; };
static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable."); static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");