Executable loading changes (#393)
Load ZBIC-compressed NSO modules with checked sizes and reads. Keep LZ4 support and ordinary ZSTD separate. Allow the embedded codec to build with AddressSanitizer. Adapted from Eden PR #4482 by PavelBARABANOV and Eden contributors: https://git.eden-emu.dev/eden-emu/eden/pulls/4482 The pinned ZBIC dependency comes from kinnay/zbic and retains its original notices.
This commit is contained in:
parent
4a0bc1497c
commit
57b81f46b5
|
|
@ -67,3 +67,7 @@
|
||||||
[submodule "external/libadrenotools"]
|
[submodule "external/libadrenotools"]
|
||||||
path = external/libadrenotools
|
path = external/libadrenotools
|
||||||
url = https://github.com/bylaws/libadrenotools.git
|
url = https://github.com/bylaws/libadrenotools.git
|
||||||
|
|
||||||
|
[submodule "external/zbic"]
|
||||||
|
path = external/zbic
|
||||||
|
url = https://github.com/kinnay/zbic.git
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 11b08f2712264bbed731545085cbd9702096ceb7
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
|
@ -16,6 +21,7 @@
|
||||||
#include "yuzu_common/hex_util.h"
|
#include "yuzu_common/hex_util.h"
|
||||||
#include "yuzu_common/logging/log.h"
|
#include "yuzu_common/logging/log.h"
|
||||||
#include "yuzu_common/lz4_compression.h"
|
#include "yuzu_common/lz4_compression.h"
|
||||||
|
#include "yuzu_common/zbic_compression.h"
|
||||||
#include "yuzu_common/settings.h"
|
#include "yuzu_common/settings.h"
|
||||||
#include "yuzu_common/swap.h"
|
#include "yuzu_common/swap.h"
|
||||||
|
|
||||||
|
|
@ -35,15 +41,6 @@ struct MODHeader
|
||||||
};
|
};
|
||||||
static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
|
static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
|
||||||
|
|
||||||
std::vector<u8> DecompressSegment(const std::vector<u8> & compressed_data, const NSOSegmentHeader & header)
|
|
||||||
{
|
|
||||||
std::vector<u8> uncompressed_data = Common::Compression::DecompressDataLZ4(compressed_data, header.size);
|
|
||||||
|
|
||||||
ASSERT_MSG(uncompressed_data.size() == header.size, "{} != {}", header.size, uncompressed_data.size());
|
|
||||||
|
|
||||||
return uncompressed_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr u32 PageAlignSize(u32 size)
|
constexpr u32 PageAlignSize(u32 size)
|
||||||
{
|
{
|
||||||
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
|
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
|
||||||
|
|
@ -56,6 +53,7 @@ bool NSOHeader::IsSegmentCompressed(size_t segment_num) const
|
||||||
return ((flags >> segment_num) & 1) != 0;
|
return ((flags >> segment_num) & 1) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file_) :
|
AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file_) :
|
||||||
AppLoader(std::move(file_))
|
AppLoader(std::move(file_))
|
||||||
{
|
{
|
||||||
|
|
@ -102,19 +100,74 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Systemloader & loader, ISystemMod
|
||||||
Kernel::PhysicalMemory program_image;
|
Kernel::PhysicalMemory program_image;
|
||||||
for (std::size_t i = 0; i < nso_header.segments.size(); ++i)
|
for (std::size_t i = 0; i < nso_header.segments.size(); ++i)
|
||||||
{
|
{
|
||||||
std::vector<u8> data = nso_file.ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
const auto & segment = nso_header.segments[i];
|
||||||
if (nso_header.IsSegmentCompressed(i))
|
const bool compressed = nso_header.IsSegmentCompressed(i);
|
||||||
|
const u32 stored_size = compressed ? nso_header.segments_compressed_size[i] : segment.size;
|
||||||
|
const u64 file_size = nso_file.GetSize();
|
||||||
|
// Check in wide arithmetic before allocating or calling the int-sized LZ4 API.
|
||||||
|
const u64 image_end = module_start + static_cast<u64>(segment.location) + segment.size;
|
||||||
|
if (segment.offset > file_size || stored_size > file_size - segment.offset ||
|
||||||
|
segment.size > static_cast<u32>(std::numeric_limits<int>::max()) ||
|
||||||
|
stored_size > static_cast<u32>(std::numeric_limits<int>::max()) ||
|
||||||
|
image_end > std::numeric_limits<u32>::max() - Core::Memory::YUZU_PAGEMASK)
|
||||||
{
|
{
|
||||||
data = DecompressSegment(data, nso_header.segments[i]);
|
LOG_ERROR(Loader, "Invalid NSO segment {} in {} (offset={:#x}, stored={}, size={})",
|
||||||
|
i, nso_file.GetName(), segment.offset, stored_size, segment.size);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::vector<u8> data = nso_file.ReadBytes(stored_size, segment.offset);
|
||||||
|
if (data.size() != stored_size)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Loader, "Truncated NSO segment {} in {}", i, nso_file.GetName());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (compressed)
|
||||||
|
{
|
||||||
|
if (nso_header.IsZBICCompressed())
|
||||||
|
{
|
||||||
|
std::vector<u8> uncompressed_data(segment.size);
|
||||||
|
if (!Common::Compression::DecompressDataZBIC(uncompressed_data, data))
|
||||||
|
{
|
||||||
|
LOG_ERROR(Loader, "ZBIC decompression failed for segment {} in {}: expected {} bytes",
|
||||||
|
i, nso_file.GetName(), segment.size);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
data = std::move(uncompressed_data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
data = Common::Compression::DecompressDataLZ4(data, segment.size);
|
||||||
|
if (data.size() != segment.size)
|
||||||
|
{
|
||||||
|
LOG_ERROR(Loader, "LZ4 decompression failed for segment {} in {}: expected {} bytes",
|
||||||
|
i, nso_file.GetName(), segment.size);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Preserve earlier segments even when a later segment is empty or located below them.
|
||||||
|
program_image.resize((std::max)(program_image.size(), static_cast<size_t>(image_end)));
|
||||||
|
if (!data.empty())
|
||||||
|
{
|
||||||
|
std::memcpy(program_image.data() + module_start + segment.location, data.data(), data.size());
|
||||||
}
|
}
|
||||||
program_image.resize(module_start + nso_header.segments[i].location + static_cast<u32>(data.size()));
|
|
||||||
std::memcpy(program_image.data() + module_start + nso_header.segments[i].location, data.data(), data.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;
|
||||||
codeset.segments[i].size = nso_header.segments[i].size;
|
codeset.segments[i].size = nso_header.segments[i].size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (should_pass_arguments && !Settings::values.program_args.GetValue().empty())
|
const u64 argument_size = should_pass_arguments && !Settings::values.program_args.GetValue().empty()
|
||||||
|
? NSO_ARGUMENT_DATA_ALLOCATION_SIZE : 0;
|
||||||
|
if (program_image.size() + argument_size + static_cast<u64>(nso_header.segments[2].bss_size) >
|
||||||
|
std::numeric_limits<u32>::max() - Core::Memory::YUZU_PAGEMASK ||
|
||||||
|
(argument_size != 0 && Settings::values.program_args.GetValue().size() >
|
||||||
|
NSO_ARGUMENT_DATA_ALLOCATION_SIZE - sizeof(NSOArgumentHeader)))
|
||||||
|
{
|
||||||
|
LOG_ERROR(Loader, "Invalid NSO BSS or argument size in {}", nso_file.GetName());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument_size != 0)
|
||||||
{
|
{
|
||||||
const auto arg_data{Settings::values.program_args.GetValue()};
|
const auto arg_data{Settings::values.program_args.GetValue()};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
|
@ -47,6 +50,9 @@ 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.");
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,8 @@ add_library(yuzu_common STATIC
|
||||||
wall_clock.h
|
wall_clock.h
|
||||||
yuzu_assert.cpp
|
yuzu_assert.cpp
|
||||||
yuzu_assert.h
|
yuzu_assert.h
|
||||||
|
zbic_compression.cpp
|
||||||
|
zbic_compression.h
|
||||||
zstd_compression.cpp
|
zstd_compression.cpp
|
||||||
zstd_compression.h
|
zstd_compression.h
|
||||||
)
|
)
|
||||||
|
|
@ -170,6 +172,10 @@ endif()
|
||||||
|
|
||||||
set_source_files_properties(stb.cpp PROPERTIES COMPILE_OPTIONS "-Wno-implicit-fallthrough;-Wno-missing-declarations;-Wno-missing-field-initializers")
|
set_source_files_properties(stb.cpp PROPERTIES COMPILE_OPTIONS "-Wno-implicit-fallthrough;-Wno-missing-declarations;-Wno-missing-field-initializers")
|
||||||
|
|
||||||
|
set_source_files_properties(zbic_compression.cpp PROPERTIES
|
||||||
|
INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/external/zbic/src"
|
||||||
|
COMPILE_OPTIONS "$<$<CXX_COMPILER_ID:Clang,GNU>:-Wno-unused-function;-Wno-missing-declarations;-Wno-shadow>")
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
target_compile_options(yuzu_common PRIVATE
|
target_compile_options(yuzu_common PRIVATE
|
||||||
-fsized-deallocation
|
-fsized-deallocation
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,7 @@
|
||||||
<ClInclude Include="x64\xbyak_abi.h" />
|
<ClInclude Include="x64\xbyak_abi.h" />
|
||||||
<ClInclude Include="x64\xbyak_util.h" />
|
<ClInclude Include="x64\xbyak_util.h" />
|
||||||
<ClInclude Include="yuzu_assert.h" />
|
<ClInclude Include="yuzu_assert.h" />
|
||||||
|
<ClInclude Include="zbic_compression.h" />
|
||||||
<ClInclude Include="zstd_compression.h" />
|
<ClInclude Include="zstd_compression.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -177,6 +178,9 @@
|
||||||
<ClCompile Include="x64\native_clock.cpp" />
|
<ClCompile Include="x64\native_clock.cpp" />
|
||||||
<ClCompile Include="x64\rdtsc.cpp" />
|
<ClCompile Include="x64\rdtsc.cpp" />
|
||||||
<ClCompile Include="yuzu_assert.cpp" />
|
<ClCompile Include="yuzu_assert.cpp" />
|
||||||
|
<ClCompile Include="zbic_compression.cpp">
|
||||||
|
<AdditionalIncludeDirectories>$(SolutionDir)external\zbic\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="zstd_compression.cpp" />
|
<ClCompile Include="zstd_compression.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,9 @@
|
||||||
<ClInclude Include="socket_types.h">
|
<ClInclude Include="socket_types.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="zbic_compression.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="zstd_compression.h">
|
<ClInclude Include="zstd_compression.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|
@ -458,6 +461,9 @@
|
||||||
<ClCompile Include="stb.cpp">
|
<ClCompile Include="stb.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="zbic_compression.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="zstd_compression.cpp">
|
<ClCompile Include="zstd_compression.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
// Adapted from Eden PR #4482 (PavelBARABANOV and Eden contributors).
|
||||||
|
// https://git.eden-emu.dev/eden-emu/eden/pulls/4482
|
||||||
|
|
||||||
|
#include "yuzu_common/zbic_compression.h"
|
||||||
|
|
||||||
|
// The C++ amalgamation must use the sanitizer runtime's C declarations.
|
||||||
|
#if defined(__SANITIZE_ADDRESS__)
|
||||||
|
#include <sanitizer/asan_interface.h>
|
||||||
|
#elif defined(__has_feature)
|
||||||
|
#if __has_feature(address_sanitizer)
|
||||||
|
#include <sanitizer/asan_interface.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ZSTD_ZBIC_SUPPORT 1
|
||||||
|
#define ZSTDLIB_VISIBLE static
|
||||||
|
#define ZSTDLIB_HIDDEN static
|
||||||
|
#define ZSTDERRORLIB_VISIBLE static
|
||||||
|
#define ZSTDERRORLIB_HIDDEN static
|
||||||
|
#define ZDICTLIB_VISIBLE static
|
||||||
|
#define ZDICTLIB_HIDDEN static
|
||||||
|
#define g_debuglevel g_ZSTD_zbic_debuglevel
|
||||||
|
#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
|
||||||
|
#undef g_debuglevel
|
||||||
|
|
||||||
|
namespace Common::Compression {
|
||||||
|
|
||||||
|
bool IsZBIC(std::span<const u8> src) {
|
||||||
|
if (src.size() < sizeof(u32)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return src[0] == 'Z' && src[1] == 'B' && src[2] == 'I' && src[3] == 'C';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src) {
|
||||||
|
if (!IsZBIC(src)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t res = ZSTD_decompress(dst.data(), dst.size(), src.data(), src.size());
|
||||||
|
return !ZSTD_isError(res) && res == dst.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common::Compression
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include "yuzu_common/common_types.h"
|
||||||
|
|
||||||
|
namespace Common::Compression {
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsZBIC(std::span<const u8> src);
|
||||||
|
|
||||||
|
// Succeeds only if the frame produces exactly the caller's expected segment size.
|
||||||
|
[[nodiscard]] bool DecompressDataZBIC(std::span<u8> dst, std::span<const u8> src);
|
||||||
|
|
||||||
|
} // namespace Common::Compression
|
||||||
Loading…
Reference in New Issue