Compare commits

..

1 Commits

Author SHA1 Message Date
lizzie 74450b7e46 2026-09-14 00:57:14
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-14 21:33:29 +02:00
46 changed files with 1283 additions and 791 deletions
+13
View File
@@ -0,0 +1,13 @@
diff --git a/libs/cobalt/include/boost/cobalt/concepts.hpp b/libs/cobalt/include/boost/cobalt/concepts.hpp
index d49f2ec..a9bdb80 100644
--- a/libs/cobalt/include/boost/cobalt/concepts.hpp
+++ b/libs/cobalt/include/boost/cobalt/concepts.hpp
@@ -62,7 +62,7 @@ struct enable_awaitables
template <typename T>
concept with_get_executor = requires (T& t)
{
- {t.get_executor()} -> asio::execution::executor;
+ t.get_executor();
};
+34
View File
@@ -0,0 +1,34 @@
diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c
index 2859ee1..766961f 100644
--- a/libavutil/hwcontext_vulkan.c
+++ b/libavutil/hwcontext_vulkan.c
@@ -1688,10 +1688,12 @@ static int setup_queue_families(AVHWDeviceContext *ctx, VkDeviceCreateInfo *cd)
hwctx->nb_qf = 0;
hwctx->queue_flags = 0;
+/* SD gamemode vkroots crash
#ifdef VK_KHR_internally_synchronized_queues
if (p->vkctx.extensions & FF_VK_EXT_INTERNAL_QUEUE_SYNC)
hwctx->queue_flags |= VK_DEVICE_QUEUE_CREATE_INTERNALLY_SYNCHRONIZED_BIT_KHR;
#endif
+*/
/* Pick each queue family to use. */
#define PICK_QF(type, vid_op) \
diff --git a/libavutil/vulkan.c b/libavutil/vulkan.c
index b2e575c..5b3c9cf 100644
--- a/libavutil/vulkan.c
+++ b/libavutil/vulkan.c
@@ -574,10 +574,12 @@ int ff_vk_exec_pool_init(FFVulkanContext *s, AVVulkanDeviceQueueFamily *qf,
e->qf = qf->idx;
VkDeviceQueueInfo2 qinfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2,
+/* SD gamemode vkroots crash
#ifdef VK_KHR_internally_synchronized_queues
.flags = internal_queue_sync ?
VK_DEVICE_QUEUE_CREATE_INTERNALLY_SYNCHRONIZED_BIT_KHR : 0,
#endif
+*/
.queueFamilyIndex = qf->idx,
.queueIndex = e->qi,
};
+6 -2
View File
@@ -417,9 +417,12 @@ AddJsonPackage(boost)
set(BOOST_NO_HEADERS ${Boost_ADDED}) set(BOOST_NO_HEADERS ${Boost_ADDED})
if (Boost_ADDED) if (Boost_ADDED)
if (MSVC OR ANDROID)
add_compile_definitions(YUZU_BOOST_v1) add_compile_definitions(YUZU_BOOST_v1)
endif()
if (NOT MSVC OR CXX_CLANG) if (NOT MSVC OR CXX_CLANG)
# solaris sucks # boost sucks
if (SOLARIS) if (SOLARIS)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthreads>) add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthreads>)
endif() endif()
@@ -428,7 +431,8 @@ if (Boost_ADDED)
target_compile_options(boost_icl INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>) target_compile_options(boost_icl INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>)
target_compile_options(boost_asio INTERFACE target_compile_options(boost_asio INTERFACE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion> $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>) $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>
)
endif() endif()
endif() endif()
+11 -8
View File
@@ -6,19 +6,22 @@
"version": "v0.19.0" "version": "v0.19.0"
}, },
"boost": { "boost": {
"artifact": "boost-%VERSION%.tar.zst", "artifact": "%VERSION%-cmake.tar.xz",
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem", "find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
"hash": "681d97be4386767662e230b2e7c9754d45e709cc5db4e747c23c27d1f5b0e87770bdf19b0bc44dcb0e8349324887bbb8fffb432098f4d0ce74a5043021a90afc", "hash": "6ae6e94664fe7f2fb01976b59b276ac5df8085c7503fa829d810fbfe495960cfec44fa2c36e2cb23480bc19c956ed199d4952b02639a00a6c07625d4e7130c2d",
"min_version": "1.57", "min_version": "1.57",
"package": "Boost", "package": "Boost",
"repo": "eden-emulator/ext-boost", "patches": [
"version": "1.92.0" "0001-clang-cl.patch"
],
"repo": "boostorg/boost",
"version": "boost-1.90.0"
}, },
"boost_headers": { "boost_headers": {
"bundled": true, "bundled": true,
"hash": "c5fa2cd72f6e6666b7963b97bc359c75284b8fb540c30f3629a028b85270c9bc66c8a051383964f2bd4c1e005a4691593d15696e7ef39ea87cf6cff9e5691fb2", "hash": "4ef845775e2277a8104ded6ddf749aa262ce52cf8438042869a048f9a0156dd772fbbcfa74efa1378fecef339b7286f6fe4b4feb5c45d49966b35d08e3e83507",
"repo": "boostorg/headers", "repo": "boostorg/headers",
"version": "boost-1.91.0" "version": "boost-1.90.0"
}, },
"catch2": { "catch2": {
"hash": "7eea385d79d88a5690cde131fe7ccda97d5c54ea09d6f515000d7bf07c828809d61c1ac99912c1ee507cf933f61c1c47ecdcc45df7850ffa82714034b0fccf35", "hash": "7eea385d79d88a5690cde131fe7ccda97d5c54ea09d6f515000d7bf07c828809d61c1ac99912c1ee507cf933f61c1c47ecdcc45df7850ffa82714034b0fccf35",
@@ -70,9 +73,9 @@
"version": "v1.3.18" "version": "v1.3.18"
}, },
"ffmpeg": { "ffmpeg": {
"hash": "ed177621176b3961bdcaa339187d3a7688c1c8b060b79c4bb0257cbc67ad7021ae5d5adca5303b45625abbbe3d9aafdd87ce777b8690ac295290d744c875489a", "hash": "68f46abf0d5dc47ab95083d59e273131a8df7948b82e62db00f00fa416e1d0bd24b799671b5a60752209bb2ac0f75295055ed0085c89a98113323cde23b69710",
"repo": "FFmpeg/FFmpeg", "repo": "FFmpeg/FFmpeg",
"version": "c7b5f1537d" "version": "6efe500d2e"
}, },
"ffmpeg-ci": { "ffmpeg-ci": {
"ci": true, "ci": true,
+1
View File
@@ -110,6 +110,7 @@ add_library(
socket_types.h socket_types.h
sparse_large_vector.cpp sparse_large_vector.cpp
sparse_large_vector.h sparse_large_vector.h
spin_lock.h
stb.cpp stb.cpp
stb.h stb.h
steady_clock.cpp steady_clock.cpp
+11 -64
View File
@@ -5,7 +5,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm> #include <algorithm>
#include <cctype>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include "common/container/unordered_map.h" #include "common/container/unordered_map.h"
@@ -481,80 +480,28 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
[type2](char c1, char c2) { return c1 == type2 && c2 == type2; }), [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }),
path.end()); path.end());
std::string root; const bool absolute = !path.empty() && path[0] == type2;
std::string_view components{path};
bool drive_relative = false;
#ifdef _WIN32
const bool network = path.size() > 1 && path[0] == type2 && path[1] == type2;
const bool drive =
path.size() > 1 && std::isalpha(static_cast<unsigned char>(path[0])) && path[1] == ':';
if (network) {
root.assign(2, type2);
components.remove_prefix(2);
} else if (drive) {
root.assign(path.data(), 2);
components.remove_prefix(2);
if (!components.empty() && components.front() == type2) {
root += type2;
components.remove_prefix(1);
} else {
drive_relative = true;
}
}
#endif
if (root.empty() && !components.empty() && components.front() == type2) {
root += type2;
components.remove_prefix(1);
}
const auto path_parts = SplitPathComponents(components);
std::size_t root_component_count = 0;
#ifdef _WIN32
if (network) {
root_component_count = 2;
const auto is_unc = [](std::string_view part) {
return part.size() == 3 && (part[0] == 'U' || part[0] == 'u') &&
(part[1] == 'N' || part[1] == 'n') && (part[2] == 'C' || part[2] == 'c');
};
if (path_parts.size() >= 2 && path_parts[0] == "?" && is_unc(path_parts[1])) {
root_component_count = 4;
}
}
#endif
std::vector<std::string_view> parts; std::vector<std::string_view> parts;
for (std::size_t i = 0; i < path_parts.size(); ++i) {
const auto part = path_parts[i]; for (const auto part : SplitPathComponents(path))
if (i < root_component_count) { {
parts.push_back(part); if (part.empty() || part == ".")
} else if (part.empty() || part == ".") {
continue; continue;
} else if (part == "..") { if (part == ".." && !parts.empty() && parts.back() != "..")
if (parts.size() > root_component_count) {
parts.pop_back(); parts.pop_back();
} else if (part != "..") parts.push_back(part);
} else {
parts.push_back(part);
}
} }
const std::size_t root_length = root.size(); std::string resolved = absolute ? std::string(1, type2) : std::string{};
std::string resolved = std::move(root); for (std::size_t i = 0; i < parts.size(); ++i)
for (std::size_t i = 0; i < parts.size(); ++i) { {
if (i != 0 || (!resolved.empty() && resolved.back() != type2 && !drive_relative)) if (i != 0)
resolved += type2; resolved += type2;
resolved.append(parts[i].data(), parts[i].size()); resolved.append(parts[i].data(), parts[i].size());
} }
path = std::move(resolved); path = std::move(resolved);
if (!path.empty() && path.size() == root_length) {
return path;
}
return std::string(RemoveTrailingSlash(path)); return std::string(RemoveTrailingSlash(path));
} }
+2 -3
View File
@@ -347,9 +347,8 @@ enum class DirectorySeparator {
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } // i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
[[nodiscard]] std::vector<std::string> SplitPathComponentsCopy(std::string_view filename); [[nodiscard]] std::vector<std::string> SplitPathComponentsCopy(std::string_view filename);
// Normalizes directory separators, removes duplicate and non-root trailing separators, and resolves // Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
// '.' and '..' components without traversing above the path root. Windows drive and UNC roots are // depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
// preserved.
[[nodiscard]] std::string SanitizePath( [[nodiscard]] std::string SanitizePath(
std::string_view path, std::string_view path,
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
+3 -3
View File
@@ -96,15 +96,15 @@ struct PageTable {
} }
/// Write page info atomically /// Write page info atomically
inline void Store(bool marked, PageType type, u16 block, uintptr_t pointer) noexcept { constexpr void Store(bool marked, PageType type, u16 block, uintptr_t pointer) noexcept {
data_raw.store(std::bit_cast<u64>(Data{marked, type, block, pointer})); data_raw.store(std::bit_cast<u64>(Data{marked, type, block, pointer}));
} }
inline void MarkRasterizerCached() noexcept { constexpr void MarkRasterizerCached() noexcept {
data_raw.fetch_or(0b111); data_raw.fetch_or(0b111);
} }
inline void MarkDebug(u64 ptr, u16 block) noexcept { constexpr void MarkDebug(u64 ptr, u16 block) noexcept {
Store(true, PageType::DebugMemory, block, ptr); Store(true, PageType::DebugMemory, block, ptr);
} }
+1 -2
View File
@@ -38,8 +38,7 @@ void FreeMemoryPages(void* base, std::size_t size) noexcept;
/// A large page-aligned buffer that has optimized memory usage for zero-writes. /// A large page-aligned buffer that has optimized memory usage for zero-writes.
template <typename T> template <typename T>
// MSVC doesn't regard structs with atomics as trivially copyable 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; constexpr SparseLargeVector() = default;
+50
View File
@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef _MSC_VER
#include <intrin.h>
#elif defined(ARCHITECTURE_x86_64)
#include <xmmintrin.h>
#endif
#include <atomic>
namespace Common {
/// @brief A lock similar to mutex that forces a thread to spin wait instead calling the
/// supervisor. Should be used on short sequences of code.
struct SpinLock {
SpinLock() noexcept = default;
SpinLock(const SpinLock&) noexcept = delete;
SpinLock& operator=(const SpinLock&) noexcept = delete;
SpinLock(SpinLock&&) noexcept = delete;
SpinLock& operator=(SpinLock&&) noexcept = delete;
inline void lock() noexcept {
while (lck.test_and_set(std::memory_order_acquire)) {
#if defined(ARCHITECTURE_x86_64)
_mm_pause();
#elif defined(ARCHITECTURE_arm64) && defined(_MSC_VER)
__yield();
#elif defined(ARCHITECTURE_arm64)
asm("yield");
#endif
}
}
inline void unlock() noexcept {
lck.clear(std::memory_order_release);
}
[[nodiscard]] inline bool try_lock() noexcept {
return !lck.test_and_set(std::memory_order_acquire);
}
std::atomic_flag lck = ATOMIC_FLAG_INIT;
};
} // namespace Common
+2 -2
View File
@@ -208,9 +208,9 @@ UUID UUID::MakeRandomRFC4122V4() {
return uuid; return uuid;
} }
UUID UUID::MakeRFC4122V5(std::span<u8, 16> sha1) { UUID UUID::MakeRFC4122V5(std::span<u8, 20> sha1) {
UUID uuid{}; UUID uuid{};
std::memcpy(&uuid.uuid, sha1.data(), sha1.size()); std::memcpy(&uuid.uuid, sha1.data(), sizeof(UUID));
uuid.uuid[8] = 0x80 | (uuid.uuid[8] & 0x3F); uuid.uuid[8] = 0x80 | (uuid.uuid[8] & 0x3F);
uuid.uuid[6] = 0x50 | (uuid.uuid[6] & 0xF); uuid.uuid[6] = 0x50 | (uuid.uuid[6] & 0xF);
return uuid; return uuid;
+1 -1
View File
@@ -104,7 +104,7 @@ struct UUID {
/// @returns A random UUID that is RFC 4122 Version 4 compliant. /// @returns A random UUID that is RFC 4122 Version 4 compliant.
[[nodiscard]] static UUID MakeRandomRFC4122V4(); [[nodiscard]] static UUID MakeRandomRFC4122V4();
[[nodiscard]] static UUID MakeRFC4122V5(std::span<u8, 16> sha1); [[nodiscard]] static UUID MakeRFC4122V5(std::span<u8, 20> sha1);
friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default; friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default;
}; };
+16 -6
View File
@@ -796,8 +796,6 @@ add_library(core STATIC
hle/service/ns/application_manager_interface.h hle/service/ns/application_manager_interface.h
hle/service/ns/application_version_interface.cpp hle/service/ns/application_version_interface.cpp
hle/service/ns/application_version_interface.h hle/service/ns/application_version_interface.h
hle/service/ns/async_result.cpp
hle/service/ns/async_result.h
hle/service/ns/content_management_interface.cpp hle/service/ns/content_management_interface.cpp
hle/service/ns/content_management_interface.h hle/service/ns/content_management_interface.h
hle/service/ns/develop_interface.cpp hle/service/ns/develop_interface.cpp
@@ -1263,11 +1261,23 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITE
target_link_libraries(core PRIVATE dynarmic::dynarmic) target_link_libraries(core PRIVATE dynarmic::dynarmic)
endif() endif()
if (TARGET OpenSSL::SSL)
target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp) target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp)
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto) target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
else()
target_sources(core PRIVATE hle/service/ssl/ssl_backend_none.cpp) # TODO
endif()
# elseif (APPLE)
# target_sources(core PRIVATE
# hle/service/ssl/ssl_backend_securetransport.cpp)
# target_link_libraries(core PRIVATE "-framework Security")
# elseif (WIN32)
# target_sources(core PRIVATE
# hle/service/ssl/ssl_backend_schannel.cpp)
# target_link_libraries(core PRIVATE crypt32 secur32)
# else()
# target_sources(core PRIVATE
# hle/service/ssl/ssl_backend_none.cpp)
# endif()
create_target_directory_groups(core) create_target_directory_groups(core)
+2 -2
View File
@@ -177,7 +177,7 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NumPageTableEntries>*>( config.page_table = reinterpret_cast<std::array<std::uint8_t*, NumPageTableEntries>*>(
const_cast<Common::PageTable::PageEntryData*>(page_table->entries.data())); const_cast<Common::PageTable::PageEntryData*>(page_table->entries.data()));
config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK; config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK;
config.page_table_marked_bit = uint8_t(0); config.page_table_marked_bit = 0;
config.absolute_offset_page_table = true; config.absolute_offset_page_table = true;
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
config.only_detect_misalignment_via_page_table_on_page_boundary = true; config.only_detect_misalignment_via_page_table_on_page_boundary = true;
@@ -193,7 +193,7 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) { Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) {
// Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries, // Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries,
// we have to manually sign extend when our actual pointer is negative. // we have to manually sign extend when our actual pointer is negative.
config.page_table_sign_extension = std::uint8_t(Common::PageTable::SIGN_BIT); config.page_table_sign_extension = Common::PageTable::SIGN_BIT;
} }
} }
+2 -2
View File
@@ -216,7 +216,7 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
const_cast<Common::PageTable::PageEntryData*>(page_table->entries.data())); const_cast<Common::PageTable::PageEntryData*>(page_table->entries.data()));
config.page_table_address_space_bits = std::uint32_t(address_space_bits); config.page_table_address_space_bits = std::uint32_t(address_space_bits);
config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK; config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK;
config.page_table_marked_bit = uint8_t(0); config.page_table_marked_bit = 0;
config.silently_mirror_page_table = false; config.silently_mirror_page_table = false;
config.absolute_offset_page_table = true; config.absolute_offset_page_table = true;
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
@@ -235,7 +235,7 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s
Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) { Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) {
// Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries, // Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries,
// we have to manually sign extend when our actual pointer is negative. // we have to manually sign extend when our actual pointer is negative.
config.page_table_sign_extension = std::uint8_t(Common::PageTable::SIGN_BIT); config.page_table_sign_extension = Common::PageTable::SIGN_BIT;
} }
} }
+16 -16
View File
@@ -100,7 +100,8 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
struct IPSwitchRecord { struct IPSwitchRecord {
std::vector<uint8_t> data; std::array<uint8_t, 256 - sizeof(size_t)> data;
size_t count;
}; };
struct IPSwitchCompiler::IPSwitchPatch { struct IPSwitchCompiler::IPSwitchPatch {
::Common::unordered_map<u32, IPSwitchRecord> records; ::Common::unordered_map<u32, IPSwitchRecord> records;
@@ -121,23 +122,22 @@ static IPSwitchRecord EscapeStringSequences(std::string_view sv) {
IPSwitchRecord r{}; IPSwitchRecord r{};
for (auto it = sv.cbegin(); it < sv.cend(); ) { for (auto it = sv.cbegin(); it < sv.cend(); ) {
if (*it == '\\' && it + 1 < sv.cend()) { if (*it == '\\' && it + 1 < sv.cend()) {
r.data.push_back([it]() {
switch (it[1]) { switch (it[1]) {
case 'a': return '\a'; case 'a': r.data[r.count] = '\a'; break;
case 'b': return '\b'; case 'b': r.data[r.count] = '\b'; break;
case 'e': return '\e'; case 'e': r.data[r.count] = '\e'; break;
case 'f': return '\f'; case 'f': r.data[r.count] = '\f'; break;
case 'n': return '\n'; case 'n': r.data[r.count] = '\n'; break;
case 'r': return '\r'; case 'r': r.data[r.count] = '\r'; break;
case 't': return '\t'; case 't': r.data[r.count] = '\t'; break;
case 'v': return '\v'; case 'v': r.data[r.count] = '\v'; break;
case '?': return '\?'; case '?': r.data[r.count] = '\?'; break;
default: return it[1]; default: r.data[r.count] = it[1]; break;
} }
}()); ++r.count;
it += 2; it += 2;
} else { } else {
r.data.push_back(*it); ++r.count;
++it; ++it;
} }
} }
@@ -223,8 +223,8 @@ void IPSwitchCompiler::Parse(std::span<u8 const> bytes) {
if (start <= line.cend() && end <= line.cend()) { if (start <= line.cend() && end <= line.cend()) {
// Actually IPS wants ordering from {lsb, ..., msb} -- so LE and BE are inverted, fun! // Actually IPS wants ordering from {lsb, ..., msb} -- so LE and BE are inverted, fun!
auto const hs = Common::HexStringToVector({start, end}, is_little_endian); auto const hs = Common::HexStringToVector({start, end}, is_little_endian);
r.data.resize(hs.size());
std::memcpy(r.data.data(), hs.data(), hs.size()); std::memcpy(r.data.data(), hs.data(), hs.size());
r.count = hs.size();
LOG_INFO(Loader, "[H] value @ {:#08X}", offset); LOG_INFO(Loader, "[H] value @ {:#08X}", offset);
patches.back().records.insert_or_assign(u32(offset), std::move(r)); patches.back().records.insert_or_assign(u32(offset), std::move(r));
} else { } else {
@@ -293,7 +293,7 @@ VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
if (patch.enabled) { if (patch.enabled) {
for (const auto& record : patch.records) { for (const auto& record : patch.records) {
if (record.first < in_data.size()) { if (record.first < in_data.size()) {
auto replace_size = record.second.data.size(); auto replace_size = record.second.count;
if (record.first + replace_size > in_data.size()) if (record.first + replace_size > in_data.size())
replace_size = in_data.size() - record.first; replace_size = in_data.size() - record.first;
std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size); std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size);
+8 -25
View File
@@ -12,7 +12,6 @@
#include "hid_core/frontend/emulated_controller.h" #include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h" #include "hid_core/hid_core.h"
#include "hid_core/hid_types.h" #include "hid_core/hid_types.h"
#include <array>
namespace Core::Frontend { namespace Core::Frontend {
@@ -30,39 +29,23 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac
const std::size_t min_supported_players = const std::size_t min_supported_players =
parameters.enable_single_mode ? 1 : parameters.min_players; parameters.enable_single_mode ? 1 : parameters.min_players;
using Core::HID::NpadStyleIndex;
const std::size_t max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
std::size_t num_selected_players = 0;
std::array<bool, HID::HIDCore::available_controllers> keep_connected{};
// reserve existing AND valid players before filling slots. include Handheld, but not other // Disconnect Handheld first.
for (std::size_t index = 0; index < hid_core.available_controllers - 1; ++index) {
const auto* controller = hid_core.GetEmulatedControllerByIndex(index);
if (!parameters.keep_controllers_connected || !controller->IsConnected() || num_selected_players >= max_supported_players) continue;
const auto style = controller->GetNpadStyleIndex();
keep_connected[index] =
(style == NpadStyleIndex::Fullkey && parameters.allow_pro_controller) ||
(style == NpadStyleIndex::JoyconDual && parameters.allow_dual_joycons) ||
(style == NpadStyleIndex::JoyconLeft && parameters.allow_left_joycon) ||
(style == NpadStyleIndex::JoyconRight && parameters.allow_right_joycon) ||
(style == NpadStyleIndex::Handheld && parameters.enable_single_mode && parameters.allow_handheld && !Settings::IsDockedMode()) ||
(style == NpadStyleIndex::GameCube && parameters.allow_gamecube_controller);
num_selected_players += keep_connected[index];
}
auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
if (!keep_connected[hid_core.available_controllers - 2]) handheld->Disconnect(); handheld->Disconnect();
// Deduce the best configuration based on the input parameters. // Deduce the best configuration based on the input parameters.
for (std::size_t index = 0; index < hid_core.available_controllers - 2; ++index) { for (std::size_t index = 0; index < hid_core.available_controllers - 2; ++index) {
auto* controller = hid_core.GetEmulatedControllerByIndex(index); auto* controller = hid_core.GetEmulatedControllerByIndex(index);
if (keep_connected[index]) continue; // First, disconnect all controllers regardless of the value of keep_controllers_connected.
// This makes it easy to connect the desired controllers.
controller->Disconnect(); controller->Disconnect();
// only add players still needed to reach the minimum // Only connect the minimum number of required players.
if (num_selected_players >= min_supported_players) continue; if (index >= min_supported_players) {
++num_selected_players; continue;
}
// Connect controllers based on the following priority list from highest to lowest priority: // Connect controllers based on the following priority list from highest to lowest priority:
// Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
-8
View File
@@ -465,10 +465,6 @@ void KScheduler::ScheduleImplFiber(KernelCore& kernel) {
// Check if we need scheduling. If we do, then we can't complete the switch and should // Check if we need scheduling. If we do, then we can't complete the switch and should
// retry. // retry.
if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) { if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) {
// Some libc++ lazily init mutex
[[maybe_unused]] auto const can_lock = highest_priority_thread->m_context_guard.try_lock();
DEBUG_ASSERT(!can_lock);
// Our switch failed. // Our switch failed.
// We should unlock the thread context, and then retry. // We should unlock the thread context, and then retry.
highest_priority_thread->m_context_guard.unlock(); highest_priority_thread->m_context_guard.unlock();
@@ -500,10 +496,6 @@ void KScheduler::Unload(KernelCore& kernel, KThread* thread) {
// Check if the thread is terminated by checking the DPC flags. // Check if the thread is terminated by checking the DPC flags.
if ((thread->GetStackParameters().dpc_flags & static_cast<u32>(DpcFlag::Terminated)) == 0) { if ((thread->GetStackParameters().dpc_flags & static_cast<u32>(DpcFlag::Terminated)) == 0) {
// Some libc++ lazily init mutex
[[maybe_unused]] auto const can_lock = thread->m_context_guard.try_lock();
DEBUG_ASSERT(!can_lock);
// The thread isn't terminated, so we want to unlock it. // The thread isn't terminated, so we want to unlock it.
thread->m_context_guard.unlock(); thread->m_context_guard.unlock();
} }
+4 -3
View File
@@ -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-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -12,6 +12,7 @@
#include "common/atomic_ops.h" #include "common/atomic_ops.h"
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/spin_lock.h"
namespace Kernel { namespace Kernel {
@@ -29,7 +30,7 @@ public:
}; };
public: public:
KSlabHeapImpl() = default; constexpr KSlabHeapImpl() = default;
void Initialize() { void Initialize() {
ASSERT(m_head == nullptr); ASSERT(m_head == nullptr);
@@ -67,7 +68,7 @@ public:
private: private:
std::atomic<Node*> m_head{}; std::atomic<Node*> m_head{};
std::mutex m_lock; Common::SpinLock m_lock;
}; };
} // namespace impl } // namespace impl
+2 -1
View File
@@ -19,6 +19,7 @@
#include "common/intrusive_red_black_tree.h" #include "common/intrusive_red_black_tree.h"
#include "common/scratch_buffer.h" #include "common/scratch_buffer.h"
#include "common/spin_lock.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#include "core/hle/kernel/k_affinity_mask.h" #include "core/hle/kernel/k_affinity_mask.h"
#include "core/hle/kernel/k_light_lock.h" #include "core/hle/kernel/k_light_lock.h"
@@ -919,7 +920,7 @@ private:
bool m_resource_limit_release_hint{}; bool m_resource_limit_release_hint{};
bool m_is_kernel_address_key{}; bool m_is_kernel_address_key{};
StackParameters m_stack_parameters{}; StackParameters m_stack_parameters{};
std::mutex m_context_guard{}; Common::SpinLock m_context_guard{};
// For emulation // For emulation
std::shared_ptr<Common::Fiber> m_host_context{}; std::shared_ptr<Common::Fiber> m_host_context{};
@@ -23,12 +23,6 @@ constexpr std::size_t profile_username_size{32};
using ProfileUsername = std::array<u8, profile_username_size>; using ProfileUsername = std::array<u8, profile_username_size>;
using UserIDArray = std::array<Common::UUID, MAX_USERS>; using UserIDArray = std::array<Common::UUID, MAX_USERS>;
// This is nn::account::Uid
struct Uid {
std::array<u8, 0x10> unk0;
};
static_assert(sizeof(Uid) == 0x10);
/// Contains extra data related to a user. /// Contains extra data related to a user.
/// TODO: RE this structure /// TODO: RE this structure
struct UserData { struct UserData {
@@ -347,22 +347,21 @@ Result IApplicationFunctions::NotifyRunning(Out<bool> out_became_running) {
Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) { Result IApplicationFunctions::GetPseudoDeviceId(Out<Common::UUID> out_pseudo_device_id) {
LOG_WARNING(Service_AM, "(stubbed)"); LOG_WARNING(Service_AM, "(stubbed)");
R_UNLESS(out_pseudo_device_id != nullptr, ResultUnknown); R_UNLESS(out_pseudo_device_id, ResultUnknown);
// This should be hashed with the device specific hash // This should be hashed with the device specific hash
// for now this will do // for now this will do
const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id); const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
R_UNLESS(res.first != nullptr, ResultUnknown); u8 hash[EVP_MAX_MD_SIZE];
std::array<u8, EVP_MAX_MD_SIZE> hash;
unsigned int hash_len = 0; unsigned int hash_len = 0;
auto const seed = res.first->raw.seed_for_pseudo_device_id; auto const seed = res.first->raw.seed_for_pseudo_device_id;
EVP_MD_CTX *ctx = EVP_MD_CTX_new(); EVP_MD_CTX *ctx = EVP_MD_CTX_new();
auto const algorithm = EVP_sha1(); auto const algorithm = EVP_sha1();
EVP_DigestInit_ex(ctx, algorithm, nullptr); EVP_DigestInit_ex(ctx, algorithm, nullptr);
EVP_DigestUpdate(ctx, &seed, sizeof(seed)); EVP_DigestUpdate(ctx, &seed, sizeof(seed));
EVP_DigestFinal_ex(ctx, hash.data(), &hash_len); EVP_DigestFinal_ex(ctx, hash, &hash_len);
EVP_MD_CTX_free(ctx); EVP_MD_CTX_free(ctx);
*out_pseudo_device_id = Common::UUID::MakeRFC4122V5(std::span<u8, 16>{hash.begin(), hash.begin() + 16}); *out_pseudo_device_id = Common::UUID::MakeRFC4122V5(std::span<u8, 20>{hash, std::size(hash)});
R_SUCCEED(); R_SUCCEED();
} }
@@ -154,9 +154,6 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{720, nullptr, "AbandonAccessFailure"}, {720, nullptr, "AbandonAccessFailure"},
{800, nullptr, "GetAndClearFileSystemProxyErrorInfo"}, {800, nullptr, "GetAndClearFileSystemProxyErrorInfo"},
{810, nullptr, "RegisterProgramIndexMapInfo"}, {810, nullptr, "RegisterProgramIndexMapInfo"},
{820, nullptr, "GetContentStorageInfoIndex"},
{830, nullptr, "EncryptStreamPlaySaveData"},
{831, nullptr, "DecryptStreamPlaySaveData"},
{1000, nullptr, "SetBisRootForHost"}, {1000, nullptr, "SetBisRootForHost"},
{1001, nullptr, "SetSaveDataSize"}, {1001, nullptr, "SetSaveDataSize"},
{1002, nullptr, "SetSaveDataRootPath"}, {1002, nullptr, "SetSaveDataRootPath"},
-1
View File
@@ -22,7 +22,6 @@
namespace IPC { namespace IPC {
constexpr Result ResultNotSupported{ErrorModule::HIPC, 1};
constexpr Result ResultSessionClosed{ErrorModule::HIPC, 301}; constexpr Result ResultSessionClosed{ErrorModule::HIPC, 301};
struct ResponseBuilder { struct ResponseBuilder {
-3
View File
@@ -42,7 +42,6 @@ public:
{22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"}, {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"}, {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
{25, &IUser::StartDetection, "StartDetectionWithFilter"},
}; };
// clang-format on // clang-format on
@@ -75,7 +74,6 @@ public:
{20, &ISystem::GetDeviceState, "GetDeviceState"}, {20, &ISystem::GetDeviceState, "GetDeviceState"},
{21, &ISystem::GetNpadId, "GetNpadId"}, {21, &ISystem::GetNpadId, "GetNpadId"},
{23, &ISystem::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, {23, &ISystem::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{25, &ISystem::StartDetection, "StartDetectionWithFilter"},
{100, &ISystem::Format, "Format"}, {100, &ISystem::Format, "Format"},
{101, &ISystem::GetAdminInfo, "GetAdminInfo"}, {101, &ISystem::GetAdminInfo, "GetAdminInfo"},
{102, &ISystem::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"}, {102, &ISystem::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"},
@@ -120,7 +118,6 @@ public:
{22, &IDebug::GetApplicationAreaSize, "GetApplicationAreaSize"}, {22, &IDebug::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IDebug::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, {23, &IDebug::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, &IDebug::RecreateApplicationArea, "RecreateApplicationArea"}, {24, &IDebug::RecreateApplicationArea, "RecreateApplicationArea"},
{25, &IDebug::StartDetection, "StartDetectionWithFilter"},
{100, &IDebug::Format, "Format"}, {100, &IDebug::Format, "Format"},
{101, &IDebug::GetAdminInfo, "GetAdminInfo"}, {101, &IDebug::GetAdminInfo, "GetAdminInfo"},
{102, &IDebug::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"}, {102, &IDebug::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"},
-117
View File
@@ -6,16 +6,10 @@
#include "common/string_util.h" #include "common/string_util.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/kernel/k_client_session.h"
#include "core/hle/result.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ngc/ngc.h" #include "core/hle/service/ngc/ngc.h"
#include "core/hle/service/server_manager.h" #include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "frontend_common/firmware_manager.h"
namespace Service::NGC { namespace Service::NGC {
@@ -172,123 +166,12 @@ public:
} }
}; };
struct SaveDataHandle {
u64 unk0;
};
static_assert(sizeof(SaveDataHandle) == 0x08);
class IUserShimScopedObject final : public ServiceFramework<IUserShimScopedObject> {
public:
explicit IUserShimScopedObject(Core::System& system_) : ServiceFramework(system_, "IUserShimScopedObject") {
// clang-format off
static const FunctionInfo functions[] = {
{450, nullptr, "InitializeForSaveData"},
{451, nullptr, "FinalizeForSaveData"},
{452, D<&IUserShimScopedObject::OpenSaveData>, "OpenSaveData"},
{453, nullptr, "CloseSaveData"},
{454, D<&IUserShimScopedObject::ReadSaveSlot>, "ReadSaveSlot"},
{455, D<&IUserShimScopedObject::WriteSaveSlot>, "WriteSaveSlot"},
{456, nullptr, "FlushSaveSlot"},
{457, nullptr, "CommitSaveData"},
};
// clang-format on
RegisterHandlers(functions);
}
Result OpenSaveData(Account::Uid unk0, Out<SaveDataHandle> unk1) {
LOG_WARNING(Service_NGC, "stubbed");
R_THROW(IPC::ResultNotSupported);
}
Result ReadSaveSlot(s32 offset, SaveDataHandle handle, OutBuffer<BufferAttr_HipcAutoSelect> out_data, Out<u32> out_size) {
LOG_WARNING(Service_NGC, "stubbed");
R_THROW(IPC::ResultNotSupported);
}
Result WriteSaveSlot(s32 offset, SaveDataHandle handle, InBuffer<BufferAttr_HipcAutoSelect> out_data) {
LOG_WARNING(Service_NGC, "stubbed");
// to implement
R_SUCCEED();
}
};
class IUserService final : public ServiceFramework<IUserService> {
public:
explicit IUserService(Core::System& system_) : ServiceFramework(system_, "stpl:u") {
// clang-format off
static const FunctionInfo functions[] = {
{0 , D<&IUserService::Cmd0>, "Cmd0"},
};
// clang-format on
RegisterHandlers(functions);
}
Result Cmd0(u32 unk0, OutInterface<IUserShimScopedObject> out_interface) {
LOG_WARNING(Service_NGC, "stubbed");
*out_interface = std::make_shared<IUserShimScopedObject>(system);
R_SUCCEED();
}
};
class ISystemShimScopedObject final : public ServiceFramework<ISystemShimScopedObject> {
public:
explicit ISystemShimScopedObject(Core::System& system_) : ServiceFramework(system_, "ISystemShimScopedObject") {
// clang-format off
static const FunctionInfo functions[] = {
{106, nullptr, "Cmd106"},
{107, nullptr, "Cmd107"},
{108, D<&ISystemShimScopedObject::Cmd108>, "Cmd108"},
{207, nullptr, "Cmd207"},
{208, D<&ISystemShimScopedObject::Cmd208>, "Cmd208"},
{209, nullptr, "Cmd209"},
{210, nullptr, "Cmd210"},
{211, nullptr, "Cmd211"},
{212, nullptr, "Cmd212"},
};
// clang-format on
RegisterHandlers(functions);
}
Result Cmd108() {
LOG_WARNING(Service_NGC, "stubbed");
R_THROW(IPC::ResultNotSupported);
}
Result Cmd208(Out<std::array<u8, 0x20>> unk0) {
LOG_WARNING(Service_NGC, "stubbed");
R_THROW(IPC::ResultNotSupported);
}
};
class ISystemService final : public ServiceFramework<ISystemService> {
public:
explicit ISystemService(Core::System& system_) : ServiceFramework(system_, "stpl:sys") {
// clang-format off
static const FunctionInfo functions[] = {
{0 , D<&ISystemService::Cmd0>, "Cmd0"},
};
// clang-format on
RegisterHandlers(functions);
}
Result Cmd0(OutInterface<ISystemShimScopedObject> out_interface) {
LOG_WARNING(Service_NGC, "stubbed");
*out_interface = std::make_shared<ISystemShimScopedObject>(system);
R_SUCCEED();
}
};
void LoopProcess(Core::System& system) { void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system); auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system), 4); server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system), 4);
server_manager->RegisterNamedService("ngct:s", std::make_shared<IServiceWithManagementApi>(system), 4); server_manager->RegisterNamedService("ngct:s", std::make_shared<IServiceWithManagementApi>(system), 4);
server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system), 4); server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system), 4);
// +23.0.0
if (FirmwareManager::GetFirmwareVersion(system).first.major >= 23) {
server_manager->RegisterNamedService("stpl:u", std::make_shared<IUserService>(system), 4);
server_manager->RegisterNamedService("stpl:sys", std::make_shared<ISystemService>(system), 4);
}
ServerManager::RunServer(std::move(server_manager)); ServerManager::RunServer(std::move(server_manager));
} }
@@ -228,9 +228,9 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{930, nullptr, "Unknown930"}, //20.0.0+ {930, nullptr, "Unknown930"}, //20.0.0+
{931, nullptr, "Unknown931"}, //20.0.0+ {931, nullptr, "Unknown931"}, //20.0.0+
{933, nullptr, "Unknown933"}, //20.0.0+ {933, nullptr, "Unknown933"}, //20.0.0+
{934, nullptr, "Unknown934"}, //21.0.0+ {934, nullptr, "Unknown934"}, //20.0.0+
{935, nullptr, "Unknown935"}, //21.0.0+ {935, nullptr, "Unknown935"}, //20.0.0+
{936, D<&IApplicationManagerInterface::Unknown936>, "Unknown936"}, //21.0.0+ {936, nullptr, "Unknown936"}, //20.0.0+
{1000, nullptr, "RequestVerifyApplicationDeprecated"}, {1000, nullptr, "RequestVerifyApplicationDeprecated"},
{1001, nullptr, "CorruptApplicationForDebug"}, {1001, nullptr, "CorruptApplicationForDebug"},
{1002, nullptr, "RequestVerifyAddOnContentsRights"}, {1002, nullptr, "RequestVerifyAddOnContentsRights"},
@@ -422,7 +422,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{4039, nullptr, "Unknown4039"}, //20.0.0+ {4039, nullptr, "Unknown4039"}, //20.0.0+
{4040, nullptr, "Unknown4040"}, //20.0.0+ {4040, nullptr, "Unknown4040"}, //20.0.0+
{4041, nullptr, "Unknown4041"}, //20.0.0+ {4041, nullptr, "Unknown4041"}, //20.0.0+
{4042, D<&IApplicationManagerInterface::Unknown4042>, "Unknown4042"}, //20.0.0+ {4042, nullptr, "Unknown4042"}, //20.0.0+
{4043, nullptr, "Unknown4043"}, //20.0.0+ {4043, nullptr, "Unknown4043"}, //20.0.0+
{4044, nullptr, "Unknown4044"}, //20.0.0+ {4044, nullptr, "Unknown4044"}, //20.0.0+
{4045, nullptr, "Unknown4045"}, //20.0.0+ {4045, nullptr, "Unknown4045"}, //20.0.0+
@@ -476,7 +476,6 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{4096, nullptr, "Unknown4096"}, //20.0.0+ {4096, nullptr, "Unknown4096"}, //20.0.0+
{4097, nullptr, "Unknown4097"}, //20.0.0+ {4097, nullptr, "Unknown4097"}, //20.0.0+
{4099, nullptr, "Unknown4099"}, //21.0.0+ {4099, nullptr, "Unknown4099"}, //21.0.0+
{4105, D<&IApplicationManagerInterface::Unknown4105>, "Unknown4105"}, //23.0.0+
{5000, nullptr, "Unknown5000"}, //18.0.0+ {5000, nullptr, "Unknown5000"}, //18.0.0+
{5001, nullptr, "Unknown5001"}, //18.0.0+ {5001, nullptr, "Unknown5001"}, //18.0.0+
{9999, nullptr, "GetApplicationCertificate"}, //10.0.0-10.2.0 {9999, nullptr, "GetApplicationCertificate"}, //10.0.0-10.2.0
@@ -639,12 +638,6 @@ Result IApplicationManagerInterface::IsGameCardApplicationRunning(Out<bool> out_
R_SUCCEED(); R_SUCCEED();
} }
Result IApplicationManagerInterface::Unknown936(Out<u64> out_result) {
LOG_WARNING(Service_NS, "(STUBBED) called.");
*out_result = 0;
R_SUCCEED();
}
Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled( Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled(
Out<bool> out_is_any_application_entity_installed) { Out<bool> out_is_any_application_entity_installed) {
LOG_WARNING(Service_NS, "(STUBBED) called"); LOG_WARNING(Service_NS, "(STUBBED) called");
@@ -868,25 +861,11 @@ Result IApplicationManagerInterface::Unknown4023(Out<u64> out_result) {
R_SUCCEED(); R_SUCCEED();
} }
Result IApplicationManagerInterface::Unknown4042(OutInterface<IAsyncResult> out_interface,
OutCopyHandle<Kernel::KReadableEvent> out_event,
u64 arg1, u64 arg2) {
LOG_WARNING(Service_NS, "(STUBBED) called, arg1={:016X}, arg2={:016X}", arg1, arg2);
*out_event = unknown_event.GetHandle();
*out_interface = std::make_shared<IAsyncResult>(system, &unknown_event);
R_SUCCEED();
}
Result IApplicationManagerInterface::Unknown4053() { Result IApplicationManagerInterface::Unknown4053() {
LOG_WARNING(Service_NS, "(STUBBED) called."); LOG_WARNING(Service_NS, "(STUBBED) called.");
R_SUCCEED(); R_SUCCEED();
} }
Result IApplicationManagerInterface::Unknown4105() {
LOG_WARNING(Service_NS, "(STUBBED) called.");
R_SUCCEED();
}
void IApplicationManagerInterface::PushApplicationRecord(HLERequestContext& ctx) { void IApplicationManagerInterface::PushApplicationRecord(HLERequestContext& ctx) {
const auto record = ctx.ReadBuffer(); const auto record = ctx.ReadBuffer();
u64 application_id{}; u64 application_id{};
@@ -7,7 +7,6 @@
#pragma once #pragma once
#include "core/hle/service/cmif_types.h" #include "core/hle/service/cmif_types.h"
#include "core/hle/service/ns/async_result.h"
#include "core/hle/service/ns/language.h" #include "core/hle/service/ns/language.h"
#include "core/hle/service/ns/ns_types.h" #include "core/hle/service/ns/ns_types.h"
#include "core/hle/service/os/event.h" #include "core/hle/service/os/event.h"
@@ -35,7 +34,6 @@ public:
Result GetGameCardMountFailureEvent(OutCopyHandle<Kernel::KReadableEvent> out_event); Result GetGameCardMountFailureEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetGameCardWakenReadyEvent(OutCopyHandle<Kernel::KReadableEvent> out_event); Result GetGameCardWakenReadyEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result IsGameCardApplicationRunning(Out<bool> out_is_running); Result IsGameCardApplicationRunning(Out<bool> out_is_running);
Result Unknown936(Out<u64> out_result);
Result IsAnyApplicationEntityInstalled(Out<bool> out_is_any_application_entity_installed); Result IsAnyApplicationEntityInstalled(Out<bool> out_is_any_application_entity_installed);
Result GetApplicationViewDeprecated( Result GetApplicationViewDeprecated(
OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views, OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
@@ -73,11 +71,7 @@ public:
InBuffer<BufferAttr_HipcMapAlias> logo_path_buffer); InBuffer<BufferAttr_HipcMapAlias> logo_path_buffer);
Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event); Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result Unknown4023(Out<u64> out_result); Result Unknown4023(Out<u64> out_result);
Result Unknown4042(OutInterface<IAsyncResult> out_interface,
OutCopyHandle<Kernel::KReadableEvent> out_event,
u64 arg1, u64 arg2);
Result Unknown4053(); Result Unknown4053();
Result Unknown4105();
Result RequestDownloadApplicationControlDataInBackground(u64 control_source, Result RequestDownloadApplicationControlDataInBackground(u64 control_source,
u64 application_id); u64 application_id);
-35
View File
@@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ns/async_result.h"
#include <cstring>
namespace Service::NS {
IAsyncResult::IAsyncResult(Core::System& system_, Service::Event* event_)
: ServiceFramework{system_, "nn::ns::detail::IAsyncResult"}, event{event_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Get"},
{1, D<&IAsyncResult::Cancel>, "Cancel"},
{2, nullptr, "GetErrorContext"}, // 4.0.0+
};
// clang-format on
RegisterHandlers(functions);
}
IAsyncResult::~IAsyncResult() = default;
Result IAsyncResult::Cancel() {
LOG_DEBUG(Service_NS, "called");
if (event != nullptr) {
event->Signal(system.Kernel());
}
R_SUCCEED();
}
} // namespace Service::NS
-19
View File
@@ -1,19 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/hle/service/service.h"
namespace Service::NS {
class IAsyncResult final : public ServiceFramework<IAsyncResult> {
public:
explicit IAsyncResult(Core::System& system_, Service::Event* event_);
~IAsyncResult() override;
private:
Result Cancel();
Service::Event* event{};
};
} // namespace Service::NS
@@ -142,7 +142,7 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
{10, &IReadOnlyApplicationControlDataInterface::ListApplicationIcon, "ListApplicationIcon"}, {10, &IReadOnlyApplicationControlDataInterface::ListApplicationIcon, "ListApplicationIcon"},
{13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"}, {13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"},
{19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"}, {19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"},
{23, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"}, //23.0.0+ {23, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"},
}; };
// clang-format on // clang-format on
@@ -311,30 +311,27 @@ void IReadOnlyApplicationControlDataInterface::ListApplicationIcon(HLERequestCon
// u64 - app count // u64 - app count
memory.WriteBlock(t_mem_address + out_length, &app_count, sizeof(u64)); memory.WriteBlock(t_mem_address + out_length, &app_count, sizeof(u64));
out_length += sizeof(u64); out_length += sizeof(u64);
ASSERT(out_length <= t_mem->GetSize());
// [list of u64] - size of icons // [list of u64] - size of icons
for (size_t i = 0; i < app_count; ++i) { for (size_t i = 0; i < app_count; ++i) {
const u64 app_id = app_ids_buffer[i]; const u64 app_id = app_ids_buffer[i];
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()}; const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()};
if (const auto control = pm.GetControlMetadata(); control.second) { const auto control = pm.GetControlMetadata();
u64 full_size = control.second->GetSize(); u64 full_size = control.second->GetSize();
memory.WriteBlock(t_mem_address + out_length, &full_size, sizeof(u64)); memory.WriteBlock(t_mem_address + out_length, &full_size, sizeof(u64));
}
out_length += sizeof(u64); out_length += sizeof(u64);
ASSERT(out_length <= t_mem->GetSize());
} }
// [list of raw icon data] // [list of raw icon data]
std::vector<u8> full_icon_data;
for (size_t i = 0; i < app_count; ++i) { for (size_t i = 0; i < app_count; ++i) {
const u64 app_id = app_ids_buffer[i]; const u64 app_id = app_ids_buffer[i];
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()}; const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()};
if (const auto control = pm.GetControlMetadata(); control.second) { const auto control = pm.GetControlMetadata();
if (auto const full_size = control.second->GetSize(); full_size > 0) { auto const full_size = control.second->GetSize();
std::vector<u8> full_icon_data(full_size); if (full_size > 0) {
full_icon_data.resize(full_size);
control.second->Read(full_icon_data.data(), full_size, 0); control.second->Read(full_icon_data.data(), full_size, 0);
memory.WriteBlock(t_mem_address + out_length, full_icon_data.data(), full_size); memory.WriteBlock(t_mem_address + out_length, full_icon_data.data(), full_size);
out_length += full_size; out_length += full_size;
ASSERT(out_length <= t_mem->GetSize());
}
} }
} }
} }
@@ -348,12 +345,6 @@ void IReadOnlyApplicationControlDataInterface::ListApplicationIcon(HLERequestCon
void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestContext& ctx) { void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestContext& ctx) {
const auto app_ids_buffer = ctx.ReadBuffer(); const auto app_ids_buffer = ctx.ReadBuffer();
const size_t app_count = app_ids_buffer.size() / sizeof(u64); const size_t app_count = app_ids_buffer.size() / sizeof(u64);
std::vector<u64> application_ids(app_count);
if (app_count > 0) {
std::memcpy(application_ids.data(), app_ids_buffer.data(), app_count * sizeof(u64));
}
auto t_mem_obj = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(0)); auto t_mem_obj = ctx.GetObjectFromHandle<Kernel::KTransferMemory>(ctx.GetCopyHandle(0));
auto* t_mem = t_mem_obj.GetPointerUnsafe(); auto* t_mem = t_mem_obj.GetPointerUnsafe();
constexpr size_t title_entry_size = sizeof(FileSys::LanguageEntry); constexpr size_t title_entry_size = sizeof(FileSys::LanguageEntry);
@@ -363,9 +354,8 @@ void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestCo
auto& memory = system.ApplicationMemory(); auto& memory = system.ApplicationMemory();
const auto t_mem_address = t_mem->GetSourceAddress(); const auto t_mem_address = t_mem->GetSourceAddress();
for (size_t i = 0; i < app_count; ++i) { for (size_t i = 0; i < app_count; ++i) {
const u64 app_id = application_ids[i]; const u64 app_id = app_ids_buffer[i];
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()};
system.GetContentProvider()};
const auto control = pm.GetControlMetadata(); const auto control = pm.GetControlMetadata();
FileSys::LanguageEntry entry{}; FileSys::LanguageEntry entry{};
if (control.first != nullptr) { if (control.first != nullptr) {
@@ -36,7 +36,7 @@ ITransferTaskListController::ITransferTaskListController(Core::System& system_)
{18, nullptr, "ListTransferTaskInfo"}, {18, nullptr, "ListTransferTaskInfo"},
{19, nullptr, "DeleteTransferTask"}, {19, nullptr, "DeleteTransferTask"},
{20, nullptr, "RaiseTransferTaskPriority"}, {20, nullptr, "RaiseTransferTaskPriority"},
{21, D<&ITransferTaskListController::GetTransferTaskProgress>, "GetTransferTaskProgress"}, //10.1.0+ {21, nullptr, "GetTransferTaskProgress"},
{22, nullptr, "GetTransferTaskLastResult"}, {22, nullptr, "GetTransferTaskLastResult"},
{23, nullptr, "SuspendTransferTask"}, {23, nullptr, "SuspendTransferTask"},
{24, D<&ITransferTaskListController::GetCurrentTransferTaskInfo>, "GetCurrentTransferTaskInfo"}, {24, D<&ITransferTaskListController::GetCurrentTransferTaskInfo>, "GetCurrentTransferTaskInfo"},
@@ -82,11 +82,6 @@ Result ITransferTaskListController::GetCurrentTransferTaskInfo(Out<std::array<u8
R_SUCCEED(); R_SUCCEED();
} }
Result ITransferTaskListController::GetTransferTaskProgress() {
LOG_WARNING(Service_OLSC, "(STUBBED) called.");
R_SUCCEED();
}
Result ITransferTaskListController::FindTransferTaskInfo(Out<std::array<u8, 0x30>> out_info, Result ITransferTaskListController::FindTransferTaskInfo(Out<std::array<u8, 0x30>> out_info,
InBuffer<BufferAttr_HipcAutoSelect> in) { InBuffer<BufferAttr_HipcAutoSelect> in) {
LOG_WARNING(Service_OLSC, "(STUBBED) called, in_size={}", in.size()); LOG_WARNING(Service_OLSC, "(STUBBED) called, in_size={}", in.size());
@@ -24,7 +24,6 @@ private:
Result GetTransferTaskStartEventNativeHandleHolder(Out<SharedPointer<INativeHandleHolder>> out_holder); Result GetTransferTaskStartEventNativeHandleHolder(Out<SharedPointer<INativeHandleHolder>> out_holder);
Result StopNextTransferTaskExecution(Out<SharedPointer<IStopperObject>> out_stopper); Result StopNextTransferTaskExecution(Out<SharedPointer<IStopperObject>> out_stopper);
Result GetTransferTaskCount(Out<u32> out_count, u8 unknown); Result GetTransferTaskCount(Out<u32> out_count, u8 unknown);
Result GetTransferTaskProgress();
Result GetCurrentTransferTaskInfo(Out<std::array<u8, 0x30>> out_info, u8 unknown); Result GetCurrentTransferTaskInfo(Out<std::array<u8, 0x30>> out_info, u8 unknown);
Result FindTransferTaskInfo(Out<std::array<u8, 0x30>> out_info, InBuffer<BufferAttr_HipcAutoSelect> in); Result FindTransferTaskInfo(Out<std::array<u8, 0x30>> out_info, InBuffer<BufferAttr_HipcAutoSelect> in);
}; };
+237 -211
View File
@@ -48,12 +48,6 @@ enum class OptionType : u32 {
EnableAlpn = 3, EnableAlpn = 3,
}; };
// This is nn::ssl::sf::RenegotiationMode
enum RenegotiationMode : u32 {
None = 0, ///< None
Secure = 1, ///< Secure
};
// This is nn::ssl::sf::SslVersion // This is nn::ssl::sf::SslVersion
struct SslVersion { struct SslVersion {
union { union {
@@ -81,34 +75,34 @@ public:
shared_data{shared_data_in}, backend{std::move(backend_in)} { shared_data{shared_data_in}, backend{std::move(backend_in)} {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, D<&ISslConnection::SetSocketDescriptor>, "SetSocketDescriptor"}, {0, &ISslConnection::SetSocketDescriptor, "SetSocketDescriptor"},
{1, D<&ISslConnection::SetHostName>, "SetHostName"}, {1, &ISslConnection::SetHostName, "SetHostName"},
{2, D<&ISslConnection::SetVerifyOption>, "SetVerifyOption"}, {2, &ISslConnection::SetVerifyOption, "SetVerifyOption"},
{3, D<&ISslConnection::SetIoMode>, "SetIoMode"}, {3, &ISslConnection::SetIoMode, "SetIoMode"},
{4, D<&ISslConnection::GetSocketDescriptor>, "GetSocketDescriptor"}, {4, nullptr, "GetSocketDescriptor"},
{5, D<&ISslConnection::GetHostName>, "GetHostName"}, {5, nullptr, "GetHostName"},
{6, nullptr, "GetVerifyOption"}, {6, nullptr, "GetVerifyOption"},
{7, D<&ISslConnection::GetIoMode>, "GetIoMode"}, {7, nullptr, "GetIoMode"},
{8, D<&ISslConnection::DoHandshake>, "DoHandshake"}, {8, &ISslConnection::DoHandshake, "DoHandshake"},
{9, &ISslConnection::DoHandshakeGetServerCert, "DoHandshakeGetServerCert"}, {9, &ISslConnection::DoHandshakeGetServerCert, "DoHandshakeGetServerCert"},
{10, D<&ISslConnection::Read>, "Read"}, {10, &ISslConnection::Read, "Read"},
{11, D<&ISslConnection::Write>, "Write"}, {11, &ISslConnection::Write, "Write"},
{12, D<&ISslConnection::Pending>, "Pending"}, {12, &ISslConnection::Pending, "Pending"},
{13, D<&ISslConnection::Peek>, "Peek"}, {13, nullptr, "Peek"},
{14, D<&ISslConnection::Poll>, "Poll"}, {14, nullptr, "Poll"},
{15, D<&ISslConnection::GetVerifyCertError>, "GetVerifyCertError"}, {15, nullptr, "GetVerifyCertError"},
{16, D<&ISslConnection::GetNeededServerCertBufferSize>, "GetNeededServerCertBufferSize"}, {16, nullptr, "GetNeededServerCertBufferSize"},
{17, D<&ISslConnection::SetSessionCacheMode>, "SetSessionCacheMode"}, {17, &ISslConnection::SetSessionCacheMode, "SetSessionCacheMode"},
{18, D<&ISslConnection::GetSessionCacheMode>, "GetSessionCacheMode"}, {18, nullptr, "GetSessionCacheMode"},
{19, D<&ISslConnection::FlushSessionCache>, "FlushSessionCache"}, {19, nullptr, "FlushSessionCache"},
{20, D<&ISslConnection::SetRenegotiationMode>, "SetRenegotiationMode"}, {20, nullptr, "SetRenegotiationMode"},
{21, D<&ISslConnection::GetRenegotiationMode>, "GetRenegotiationMode"}, {21, nullptr, "GetRenegotiationMode"},
{22, D<&ISslConnection::SetOption>, "SetOption"}, {22, &ISslConnection::SetOption, "SetOption"},
{23, D<&ISslConnection::GetOption>, "GetOption"}, {23, &ISslConnection::GetOption, "GetOption"},
{24, nullptr, "GetVerifyCertErrors"}, {24, nullptr, "GetVerifyCertErrors"},
{25, nullptr, "GetCipherInfo"}, {25, nullptr, "GetCipherInfo"},
{26, D<&ISslConnection::SetNextAlpnProto>, "SetNextAlpnProto"}, {26, &ISslConnection::SetNextAlpnProto, "SetNextAlpnProto"},
{27, D<&ISslConnection::GetNextAlpnProto>, "GetNextAlpnProto"}, {27, &ISslConnection::GetNextAlpnProto, "GetNextAlpnProto"},
{28, nullptr, "SetDtlsSocketDescriptor"}, {28, nullptr, "SetDtlsSocketDescriptor"},
{29, nullptr, "GetDtlsHandshakeTimeout"}, {29, nullptr, "GetDtlsHandshakeTimeout"},
{30, nullptr, "SetPrivateOption"}, {30, nullptr, "SetPrivateOption"},
@@ -147,6 +141,80 @@ public:
} }
private: private:
SslVersion ssl_version;
std::shared_ptr<SslContextSharedData> shared_data;
std::unique_ptr<SSLConnectionBackend> backend;
std::optional<int> fd_to_close;
bool do_not_close_socket = false;
bool get_server_cert_chain = false;
bool skip_default_verify = false;
bool enable_alpn = false;
std::shared_ptr<Network::SocketBase> socket;
std::vector<u8> next_alpn_proto;
bool did_handshake = false;
u32 verify_option = 0;
Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) {
LOG_DEBUG(Service_SSL, "called, fd={}", fd);
ASSERT(!did_handshake);
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD_USA>("bsd:u");
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; });
auto const res_v = bsd->DuplicateSocketImpl(fd);
if (auto *res = std::get_if<s32>(&res_v)) {
const s32 duplicated_fd = *res;
if (do_not_close_socket) {
*out_fd = duplicated_fd;
} else {
*out_fd = -1;
fd_to_close = duplicated_fd;
}
std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(duplicated_fd);
if (!sock.has_value()) {
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd);
return ResultInvalidSocket;
}
socket = std::move(*sock);
backend->SetSocket(socket);
return ResultSuccess;
}
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd);
return ResultInvalidSocket;
}
Result SetHostNameImpl(const std::string& hostname) {
LOG_DEBUG(Service_SSL, "called. hostname={}", hostname);
ASSERT(!did_handshake);
return backend->SetHostName(hostname);
}
Result SetVerifyOptionImpl(u32 option) {
ASSERT(!did_handshake);
LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option);
verify_option = 0;
backend->SetVerifyOption(0);
return ResultSuccess;
}
Result SetIoModeImpl(u32 input_mode) {
auto mode = static_cast<IoMode>(input_mode);
ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking);
ASSERT_OR_EXECUTE(socket, { return ResultNoSocket; });
const bool non_block = mode == IoMode::NonBlocking;
const Network::Errno error = socket->SetNonBlock(non_block);
if (error != Network::Errno::SUCCESS) {
LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block);
}
return ResultSuccess;
}
Result SetSessionCacheModeImpl(u32 mode) {
ASSERT(!did_handshake);
LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode);
return ResultSuccess;
}
Result DoHandshakeImpl() { Result DoHandshakeImpl() {
ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; }); ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; });
Result res = backend->DoHandshake(); Result res = backend->DoHandshake();
@@ -166,17 +234,19 @@ private:
}; };
if (!get_server_cert_chain) { if (!get_server_cert_chain) {
// Just return the first one, unencoded. // Just return the first one, unencoded.
ASSERT_OR_EXECUTE_MSG(!certs.empty(), { return {}; }, "Should be at least one server cert"); ASSERT_OR_EXECUTE_MSG(
!certs.empty(), { return {}; }, "Should be at least one server cert");
return certs[0]; return certs[0];
} }
std::vector<u8> ret; std::vector<u8> ret;
Header header{0x4E4D684374726543, u32(certs.size()), 0}; Header header{0x4E4D684374726543, static_cast<u32>(certs.size()), 0};
ret.insert(ret.end(), reinterpret_cast<u8*>(&header), reinterpret_cast<u8*>(&header + 1)); ret.insert(ret.end(), reinterpret_cast<u8*>(&header), reinterpret_cast<u8*>(&header + 1));
size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader); size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader);
for (auto& cert : certs) { for (auto& cert : certs) {
EntryHeader entry_header{u32(cert.size()), u32(data_offset)}; EntryHeader entry_header{static_cast<u32>(cert.size()), static_cast<u32>(data_offset)};
data_offset += cert.size(); data_offset += cert.size();
ret.insert(ret.end(), reinterpret_cast<u8*>(&entry_header), reinterpret_cast<u8*>(&entry_header + 1)); ret.insert(ret.end(), reinterpret_cast<u8*>(&entry_header),
reinterpret_cast<u8*>(&entry_header + 1));
} }
for (auto& cert : certs) { for (auto& cert : certs) {
ret.insert(ret.end(), cert.begin(), cert.end()); ret.insert(ret.end(), cert.begin(), cert.end());
@@ -184,77 +254,65 @@ private:
return ret; return ret;
} }
Result SetSocketDescriptor(s32 in_fd, Out<s32> out_fd) { Result ReadImpl(std::vector<u8>* out_data) {
LOG_DEBUG(Service_SSL, "called, fd={}", in_fd); ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
ASSERT(!did_handshake); size_t actual_size{};
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD_USA>("bsd:u"); Result res = backend->Read(&actual_size, *out_data);
ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; }); if (res != ResultSuccess) {
return res;
auto const res_v = bsd->DuplicateSocketImpl(in_fd);
if (auto *res = std::get_if<s32>(&res_v)) {
const s32 dup_fd = *res;
*out_fd = do_not_close_socket ? dup_fd : -1;
if (!do_not_close_socket)
fd_to_close = dup_fd;
auto const sock = bsd->GetSocket(dup_fd);
if (!sock.has_value()) {
LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", dup_fd);
return ResultInvalidSocket;
} }
socket = std::move(*sock); out_data->resize(actual_size);
backend->SetSocket(std::move(socket)); return res;
}
Result WriteImpl(size_t* out_size, std::span<const u8> data) {
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
return backend->Write(out_size, data);
}
Result PendingImpl(s32* out_pending) {
LOG_WARNING(Service_SSL, "(STUBBED) called.");
*out_pending = 0;
return ResultSuccess; return ResultSuccess;
} }
LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", in_fd);
return ResultInvalidSocket; void SetSocketDescriptor(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const s32 in_fd = rp.Pop<s32>();
s32 out_fd{-1};
const Result res = SetSocketDescriptorImpl(&out_fd, in_fd);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(res);
rb.Push<s32>(out_fd);
} }
Result SetHostName(InBuffer<BufferAttr_HipcMapAlias> buf) { void SetHostName(HLERequestContext& ctx) {
auto const hostname = Common::StringFromBuffer(buf); const std::string hostname = Common::StringFromBuffer(ctx.ReadBuffer());
LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); const Result res = SetHostNameImpl(hostname);
ASSERT(!did_handshake); IPC::ResponseBuilder rb{ctx, 2};
return backend->SetHostName(hostname.c_str()); rb.Push(res);
} }
Result SetVerifyOption(u32 option) { void SetVerifyOption(HLERequestContext& ctx) {
LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option); IPC::RequestParser rp{ctx};
ASSERT(!did_handshake); const u32 option = rp.Pop<u32>();
verify_option = 0; const Result res = SetVerifyOptionImpl(option);
backend->SetVerifyOption(0); IPC::ResponseBuilder rb{ctx, 2};
R_SUCCEED(); rb.Push(res);
} }
Result SetIoMode(u32 input_mode) { void SetIoMode(HLERequestContext& ctx) {
auto mode = IoMode(input_mode); IPC::RequestParser rp{ctx};
ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking); const u32 mode = rp.Pop<u32>();
R_UNLESS(socket, ResultNoSocket); const Result res = SetIoModeImpl(mode);
const bool non_block = mode == IoMode::NonBlocking; IPC::ResponseBuilder rb{ctx, 2};
const Network::Errno error = socket->SetNonBlock(non_block); rb.Push(res);
if (error != Network::Errno::SUCCESS) {
LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block);
}
R_SUCCEED();
} }
Result GetSocketDescriptor(Out<u32> out_fd) { void DoHandshake(HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "(STUBBED)"); const Result res = DoHandshakeImpl();
*out_fd = uint32_t(socket->GetFD()); IPC::ResponseBuilder rb{ctx, 2};
R_SUCCEED(); rb.Push(res);
}
Result GetHostName(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> out_size) {
LOG_WARNING(Service_SSL, "(STUBBED)");
ASSERT(!did_handshake);
return backend->GetHostName(std::span<u8>{data.begin(), data.end()}, out_size);
}
Result GetIoMode(Out<u32> out_mode) {
LOG_WARNING(Service_SSL, "(STUBBED)");
R_SUCCEED();
}
Result DoHandshake() {
return DoHandshakeImpl();
} }
void DoHandshakeGetServerCert(HLERequestContext& ctx) { void DoHandshakeGetServerCert(HLERequestContext& ctx) {
@@ -293,158 +351,131 @@ private:
rb.PushRaw(out); rb.PushRaw(out);
} }
Result Read(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> out_size) { void Read(HLERequestContext& ctx) {
R_UNLESS(did_handshake, ResultInternalError); std::vector<u8> output_bytes(ctx.GetWriteBufferSize());
size_t tmp{}; const Result res = ReadImpl(&output_bytes);
auto const res = backend->Read(&tmp, data); IPC::ResponseBuilder rb{ctx, 3};
*out_size = u32(tmp); rb.Push(res);
return res; if (res == ResultSuccess) {
rb.Push(static_cast<u32>(output_bytes.size()));
ctx.WriteBuffer(output_bytes);
} else {
rb.Push(static_cast<u32>(0));
}
} }
Result Write(InBuffer<BufferAttr_HipcMapAlias> data, Out<u32> out_size) { void Write(HLERequestContext& ctx) {
R_UNLESS(did_handshake, ResultInternalError); size_t write_size{0};
size_t tmp{}; const Result res = WriteImpl(&write_size, ctx.ReadBuffer());
auto const res = backend->Write(&tmp, data); IPC::ResponseBuilder rb{ctx, 3};
*out_size = u32(tmp); rb.Push(res);
return res; rb.Push(static_cast<u32>(write_size));
} }
Result Pending(Out<s32> out_pending_size) { void Pending(HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "(STUBBED)"); s32 pending_size{0};
*out_pending_size = s32(backend->Pending()); const Result res = PendingImpl(&pending_size);
R_SUCCEED(); IPC::ResponseBuilder rb{ctx, 3};
rb.Push(res);
rb.Push<s32>(pending_size);
} }
Result Peek(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> out_size) { void SetSessionCacheMode(HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "(STUBBED)"); IPC::RequestParser rp{ctx};
size_t tmp{}; const u32 mode = rp.Pop<u32>();
auto const res = backend->Peek(&tmp, data); const Result res = SetSessionCacheModeImpl(mode);
*out_size = u32(tmp); IPC::ResponseBuilder rb{ctx, 2};
return res; rb.Push(res);
} }
Result Poll(u32 in_pollevent, u32 timer, Out<u32> out_pollevent) { void SetOption(HLERequestContext& ctx) {
LOG_WARNING(Service_SSL, "(STUBBED)"); struct Parameters {
R_SUCCEED(); OptionType option;
} s32 value;
};
static_assert(sizeof(Parameters) == 0x8, "Parameters is an invalid size");
Result GetVerifyCertError() { IPC::RequestParser rp{ctx};
LOG_WARNING(Service_SSL, "(STUBBED)"); const auto parameters = rp.PopRaw<Parameters>();
R_SUCCEED();
}
Result GetNeededServerCertBufferSize(Out<u32> out_needed_buffer_size) { switch (parameters.option) {
LOG_WARNING(Service_SSL, "(STUBBED)");
R_SUCCEED();
}
Result SetSessionCacheMode(u32 mode) {
LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode);
R_UNLESS(!did_handshake, ResultInternalError);
R_SUCCEED();
}
Result GetSessionCacheMode(Out<u32> mode) {
LOG_WARNING(Service_SSL, "(STUBBED)");
R_UNLESS(!did_handshake, ResultInternalError);
R_SUCCEED();
}
Result FlushSessionCache() {
LOG_WARNING(Service_SSL, "(STUBBED)");
R_UNLESS(!did_handshake, ResultInternalError);
R_SUCCEED();
}
Result SetRenegotiationMode(RenegotiationMode mode) {
LOG_WARNING(Service_SSL, "(STUBBED)");
backend->SetRenegotiationMode(u32(mode));
R_SUCCEED();
}
Result GetRenegotiationMode(Out<RenegotiationMode> mode) {
LOG_WARNING(Service_SSL, "(STUBBED)");
u32 tmp{};
auto const res = backend->GetRenegotiationMode(&tmp);
*mode = RenegotiationMode(tmp);
return res;
}
Result SetOption(OptionType option, s32 value) {
switch (option) {
case OptionType::DoNotCloseSocket: case OptionType::DoNotCloseSocket:
do_not_close_socket = bool(value); do_not_close_socket = static_cast<bool>(parameters.value);
break; break;
case OptionType::GetServerCertChain: case OptionType::GetServerCertChain:
get_server_cert_chain = bool(value); get_server_cert_chain = static_cast<bool>(parameters.value);
break; break;
case OptionType::SkipDefaultVerify: case OptionType::SkipDefaultVerify:
skip_default_verify = bool(value); skip_default_verify = static_cast<bool>(parameters.value);
break; break;
case OptionType::EnableAlpn: case OptionType::EnableAlpn:
enable_alpn = bool(value); enable_alpn = static_cast<bool>(parameters.value);
break; break;
default: default:
LOG_WARNING(Service_SSL, "Unknown option={}, value={}", option, value); LOG_WARNING(Service_SSL, "Unknown option={}, value={}", parameters.option,
} parameters.value);
R_SUCCEED();
} }
Result GetOption(OptionType option, Out<u8> value) { IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
void GetOption(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto option = rp.PopRaw<OptionType>();
u8 value = 0;
switch (option) { switch (option) {
case OptionType::DoNotCloseSocket: case OptionType::DoNotCloseSocket:
*value = u8(do_not_close_socket); value = static_cast<u8>(do_not_close_socket);
break; break;
case OptionType::GetServerCertChain: case OptionType::GetServerCertChain:
*value = u8(get_server_cert_chain); value = static_cast<u8>(get_server_cert_chain);
break; break;
case OptionType::SkipDefaultVerify: case OptionType::SkipDefaultVerify:
*value = u8(skip_default_verify); value = static_cast<u8>(skip_default_verify);
break; break;
case OptionType::EnableAlpn: case OptionType::EnableAlpn:
*value = u8(enable_alpn); value = static_cast<u8>(enable_alpn);
break; break;
default: default:
LOG_WARNING(Service_SSL, "Unknown option={}", u32(option)); LOG_WARNING(Service_SSL, "Unknown option={}", option);
*value = 0; value = 0;
break; break;
} }
LOG_DEBUG(Service_SSL, "GetOption called, option={}, ret value={}", u32(option), *value);
R_SUCCEED(); LOG_DEBUG(Service_SSL, "GetOption called, option={}, ret value={}", option, value);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u8>(value);
} }
Result SetNextAlpnProto(InBuffer<BufferAttr_HipcMapAlias> data) { void SetNextAlpnProto(HLERequestContext& ctx) {
auto const to_write = u32((std::min)(next_alpn_proto.size(), data.size())); const auto data = ctx.ReadBuffer(0);
next_alpn_proto.assign(data.begin(), data.begin() + to_write); next_alpn_proto.assign(data.begin(), data.end());
LOG_DEBUG(Service_SSL, "SetNextAlpnProto called, size={}", next_alpn_proto.size()); LOG_DEBUG(Service_SSL, "SetNextAlpnProto called, size={}", next_alpn_proto.size());
R_SUCCEED();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
} }
Result GetNextAlpnProto(OutBuffer<BufferAttr_HipcMapAlias> data, Out<u32> to_write) { void GetNextAlpnProto(HLERequestContext& ctx) {
*to_write = u32((std::min)(next_alpn_proto.size(), data.size())); const size_t writable = ctx.GetWriteBufferSize();
next_alpn_proto.assign(data.begin(), data.begin() + *to_write); const size_t to_write = (std::min)(next_alpn_proto.size(), writable);
LOG_DEBUG(Service_SSL, "GetNextAlpnProto called, size={}", *to_write);
R_SUCCEED(); if (to_write != 0) {
ctx.WriteBuffer(std::span<const u8>(next_alpn_proto.data(), to_write));
} }
Result GetVerifyCertErrors(OutBuffer<BufferAttr_HipcMapAlias> unk0, Out<u32> unk1, Out<u32> unk2) { LOG_DEBUG(Service_SSL, "GetNextAlpnProto called, size={}", to_write);
LOG_WARNING(Service_SSL, "(STUBBED)");
R_SUCCEED(); IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(to_write));
} }
SslVersion ssl_version;
std::shared_ptr<SslContextSharedData> shared_data;
std::unique_ptr<SSLConnectionBackend> backend;
std::optional<int> fd_to_close;
std::shared_ptr<Network::SocketBase> socket;
std::vector<u8> next_alpn_proto;
u32 verify_option = 0;
bool do_not_close_socket = false;
bool get_server_cert_chain = false;
bool skip_default_verify = false;
bool enable_alpn = false;
bool did_handshake = false;
}; };
class ISslContext final : public ServiceFramework<ISslContext> { class ISslContext final : public ServiceFramework<ISslContext> {
@@ -461,7 +492,7 @@ public:
{5, &ISslContext::ImportClientPki, "ImportClientPki"}, {5, &ISslContext::ImportClientPki, "ImportClientPki"},
{6, nullptr, "RemoveServerPki"}, {6, nullptr, "RemoveServerPki"},
{7, nullptr, "RemoveClientPki"}, {7, nullptr, "RemoveClientPki"},
{8, D<&ISslContext::RegisterInternalPki>, "RegisterInternalPki"}, {8, nullptr, "RegisterInternalPki"},
{9, nullptr, "AddPolicyOid"}, {9, nullptr, "AddPolicyOid"},
{10, nullptr, "ImportCrl"}, {10, nullptr, "ImportCrl"},
{11, nullptr, "RemoveCrl"}, {11, nullptr, "RemoveCrl"},
@@ -556,11 +587,6 @@ private:
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(client_id); rb.Push(client_id);
} }
Result RegisterInternalPki() {
LOG_WARNING(Service_SSL, "(STUBBED) called");
R_SUCCEED();
}
}; };
class ISslService final : public ServiceFramework<ISslService> { class ISslService final : public ServiceFramework<ISslService> {
+1 -6
View File
@@ -36,17 +36,12 @@ class SSLConnectionBackend {
public: public:
virtual ~SSLConnectionBackend() {} virtual ~SSLConnectionBackend() {}
virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0; virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0;
virtual Result SetHostName(const std::string& hostname) = 0;
virtual void SetVerifyOption(u32 option) = 0; virtual void SetVerifyOption(u32 option) = 0;
virtual Result DoHandshake() = 0; virtual Result DoHandshake() = 0;
virtual Result Read(size_t* out_size, std::span<u8> data) = 0; virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
virtual Result Peek(size_t* out_size, std::span<u8> data) = 0;
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0; virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0; virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
virtual Result SetHostName(const char* hostname) = 0;
virtual Result GetHostName(std::span<u8> hostname, u32* out_size) = 0;
virtual int Pending() = 0;
virtual Result SetRenegotiationMode(u32 mode) = 0;
virtual Result GetRenegotiationMode(u32* mode) = 0;
}; };
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend); Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
@@ -157,30 +157,18 @@ public:
socket = std::move(socket_in); socket = std::move(socket_in);
} }
Result SetHostName(const char* hostname) override { Result SetHostName(const std::string& hostname) override {
if (!skip_cert_verification) { if (!skip_cert_verification) {
if (!SSL_set1_host(ssl, hostname)) { if (!SSL_set1_host(ssl, hostname.c_str())) {
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname); LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
return CheckOpenSSLErrors(); return CheckOpenSSLErrors();
} }
} }
if (!SSL_set_tlsext_host_name(ssl, hostname)) { // hostname for SNI if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI
LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname); LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname);
return CheckOpenSSLErrors(); return CheckOpenSSLErrors();
} }
R_SUCCEED(); return ResultSuccess;
}
Result GetHostName(std::span<u8> data, u32* out_size) override {
auto const peer_name = SSL_get0_peername(ssl);
if (peer_name == nullptr) {
LOG_ERROR(Service_SSL, "SSL_get0_peername()");
return CheckOpenSSLErrors();
}
auto const s = std::string{peer_name};
*out_size = u32(s.size());
std::memcpy(data.data(), s.data(), (std::min)(s.size(), data.size()));
R_SUCCEED();
} }
void SetVerifyOption(u32 option) override { void SetVerifyOption(u32 option) override {
@@ -225,13 +213,6 @@ public:
return HandleReturn("SSL_read_ex", out_size, ret); return HandleReturn("SSL_read_ex", out_size, ret);
} }
Result Peek(size_t* out_size, std::span<u8> data) override {
auto const n = (std::min)(data.size(), *out_size);
const int ret = SSL_peek(ssl, data.data(), int(n));
*out_size = n;
return HandleReturn("SSL_write_ex", out_size, ret);
}
Result Write(size_t* out_size, std::span<const u8> data) override { Result Write(size_t* out_size, std::span<const u8> data) override {
const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size); const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size);
return HandleReturn("SSL_write_ex", out_size, ret); return HandleReturn("SSL_write_ex", out_size, ret);
@@ -282,25 +263,7 @@ public:
out_certs->emplace_back(buf, buf + len); out_certs->emplace_back(buf, buf + len);
OPENSSL_free(buf); OPENSSL_free(buf);
} }
R_SUCCEED(); return ResultSuccess;
}
int Pending() override {
return SSL_pending(ssl);
}
Result SetRenegotiationMode(u32 mode) override {
if (mode == 0) {
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_RENEGOTIATION);
} else {
SSL_CTX_set_options(ssl_ctx, SSL_OP_ALLOW_CLIENT_RENEGOTIATION);
}
R_SUCCEED();
}
Result GetRenegotiationMode(u32* mode) override {
*mode = SSL_get_secure_renegotiation_support(ssl) ? 1 : 0;
R_SUCCEED();
} }
~SSLConnectionBackendOpenSSL() { ~SSLConnectionBackendOpenSSL() {
@@ -0,0 +1,563 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "common/error.h"
#include "common/fs/file.h"
#include "common/hex_util.h"
#include "common/string_util.h"
#include "core/hle/service/ssl/ssl_backend.h"
#include "core/internal_network/network.h"
#include "core/internal_network/sockets.h"
namespace {
// These includes are inside the namespace to avoid a conflict on MinGW where
// the headers define an enum containing Network and Service as enumerators
// (which clash with the correspondingly named namespaces).
#define SECURITY_WIN32
#include <schnlsp.h>
#include <security.h>
#include <wincrypt.h>
std::once_flag one_time_init_flag;
bool one_time_init_success = false;
SCHANNEL_CRED schannel_cred{};
CredHandle cred_handle;
static void OneTimeInit() {
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
schannel_cred.dwFlags =
SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names
SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
// ^ I'm assuming that nobody would want to connect Yuzu to a
// service that requires some OS-provided corporate client
// certificate, and presenting one to some arbitrary server
// might be a privacy concern? Who knows, though.
const SECURITY_STATUS ret =
AcquireCredentialsHandle(nullptr, const_cast<LPTSTR>(UNISP_NAME), SECPKG_CRED_OUTBOUND,
nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr);
if (ret != SEC_E_OK) {
// SECURITY_STATUS codes are a type of HRESULT and can be used with NativeErrorToString.
LOG_ERROR(Service_SSL, "AcquireCredentialsHandle failed: {}",
Common::NativeErrorToString(ret));
return;
}
if (getenv("SSLKEYLOGFILE")) {
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but Schannel does not support exporting "
"keys; not logging keys!");
// Not fatal.
}
one_time_init_success = true;
}
} // namespace
namespace Service::SSL {
class SSLConnectionBackendSchannel final : public SSLConnectionBackend {
public:
Result Init() {
std::call_once(one_time_init_flag, OneTimeInit);
if (!one_time_init_success) {
LOG_ERROR(
Service_SSL,
"Can't create SSL connection because Schannel one-time initialization failed");
return ResultInternalError;
}
return ResultSuccess;
}
void SetSocket(std::shared_ptr<Network::SocketBase> socket_in) override {
socket = std::move(socket_in);
}
Result SetHostName(const std::string& hostname_in) override {
hostname = hostname_in;
return ResultSuccess;
}
void SetVerifyOption(u32 option) override {
skip_cert_verification = (option == 0);
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
skip_cert_verification);
}
Result DoHandshake() override {
while (1) {
Result r;
switch (handshake_state) {
case HandshakeState::Initial:
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
(r = CallInitializeSecurityContext()) != ResultSuccess) {
return r;
}
// CallInitializeSecurityContext updated `handshake_state`.
continue;
case HandshakeState::ContinueNeeded:
case HandshakeState::IncompleteMessage:
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
(r = FillCiphertextReadBuf()) != ResultSuccess) {
return r;
}
if (ciphertext_read_buf.empty()) {
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
return ResultInternalError;
}
if ((r = CallInitializeSecurityContext()) != ResultSuccess) {
return r;
}
// CallInitializeSecurityContext updated `handshake_state`.
continue;
case HandshakeState::DoneAfterFlush:
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) {
return r;
}
handshake_state = HandshakeState::Connected;
return ResultSuccess;
case HandshakeState::Connected:
LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook");
return ResultInternalError;
case HandshakeState::Error:
return ResultInternalError;
}
}
}
Result FillCiphertextReadBuf() {
const size_t fill_size = read_buf_fill_size ? read_buf_fill_size : 4096;
read_buf_fill_size = 0;
// This unnecessarily zeroes the buffer; oh well.
const size_t offset = ciphertext_read_buf.size();
ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; });
ciphertext_read_buf.resize(offset + fill_size, 0);
const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size);
const auto [actual, err] = socket->Recv(0, read_span);
switch (err) {
case Network::Errno::SUCCESS:
ASSERT(static_cast<size_t>(actual) <= fill_size);
ciphertext_read_buf.resize(offset + actual);
return ResultSuccess;
case Network::Errno::AGAIN:
ciphertext_read_buf.resize(offset);
return ResultWouldBlock;
default:
ciphertext_read_buf.resize(offset);
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
return ResultInternalError;
}
}
// Returns success if the write buffer has been completely emptied.
Result FlushCiphertextWriteBuf() {
while (!ciphertext_write_buf.empty()) {
const auto [actual, err] = socket->Send(ciphertext_write_buf, 0);
switch (err) {
case Network::Errno::SUCCESS:
ASSERT(static_cast<size_t>(actual) <= ciphertext_write_buf.size());
ciphertext_write_buf.erase(ciphertext_write_buf.begin(),
ciphertext_write_buf.begin() + actual);
break;
case Network::Errno::AGAIN:
return ResultWouldBlock;
default:
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
return ResultInternalError;
}
}
return ResultSuccess;
}
Result CallInitializeSecurityContext() {
unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY |
ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT |
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM |
ISC_REQ_USE_SUPPLIED_CREDS;
if (skip_cert_verification) {
req |= ISC_REQ_MANUAL_CRED_VALIDATION;
}
unsigned long attr;
// https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
std::array<SecBuffer, 2> input_buffers{{
// only used if `initial_call_done`
{
// [0]
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
.BufferType = SECBUFFER_TOKEN,
.pvBuffer = ciphertext_read_buf.data(),
},
{
// [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is
// returned, or SECBUFFER_EXTRA when SEC_E_CONTINUE_NEEDED is returned if the
// whole buffer wasn't used)
.cbBuffer = 0,
.BufferType = SECBUFFER_EMPTY,
.pvBuffer = nullptr,
},
}};
std::array<SecBuffer, 2> output_buffers{{
{
.cbBuffer = 0,
.BufferType = SECBUFFER_TOKEN,
.pvBuffer = nullptr,
}, // [0]
{
.cbBuffer = 0,
.BufferType = SECBUFFER_ALERT,
.pvBuffer = nullptr,
}, // [1]
}};
SecBufferDesc input_desc{
.ulVersion = SECBUFFER_VERSION,
.cBuffers = static_cast<unsigned long>(input_buffers.size()),
.pBuffers = input_buffers.data(),
};
SecBufferDesc output_desc{
.ulVersion = SECBUFFER_VERSION,
.cBuffers = static_cast<unsigned long>(output_buffers.size()),
.pBuffers = output_buffers.data(),
};
ASSERT_OR_EXECUTE_MSG(
input_buffers[0].cbBuffer == ciphertext_read_buf.size(),
{ return ResultInternalError; }, "read buffer too large");
bool initial_call_done = handshake_state != HandshakeState::Initial;
if (initial_call_done) {
LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext",
ciphertext_read_buf.size());
}
char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr;
const SECURITY_STATUS ret = InitializeSecurityContextA(
&cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req,
0, // Reserved1
0, // TargetDataRep not used with Schannel
initial_call_done ? &input_desc : nullptr,
0, // Reserved2
initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
nullptr); // ptsExpiry
if (output_buffers[0].pvBuffer) {
const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer),
output_buffers[0].cbBuffer);
ciphertext_write_buf.insert(ciphertext_write_buf.end(), span.begin(), span.end());
FreeContextBuffer(output_buffers[0].pvBuffer);
}
if (output_buffers[1].pvBuffer) {
const std::span span(static_cast<u8*>(output_buffers[1].pvBuffer),
output_buffers[1].cbBuffer);
// The documentation doesn't explain what format this data is in.
LOG_DEBUG(Service_SSL, "Got a {}-byte alert buffer: {}", span.size(),
Common::HexToString(span));
}
switch (ret) {
case SEC_I_CONTINUE_NEEDED:
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_I_CONTINUE_NEEDED");
if (input_buffers[1].BufferType == SECBUFFER_EXTRA) {
LOG_DEBUG(Service_SSL, "EXTRA of size {}", input_buffers[1].cbBuffer);
ASSERT(input_buffers[1].cbBuffer <= ciphertext_read_buf.size());
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
ciphertext_read_buf.end() - input_buffers[1].cbBuffer);
} else {
ASSERT(input_buffers[1].BufferType == SECBUFFER_EMPTY);
ciphertext_read_buf.clear();
}
handshake_state = HandshakeState::ContinueNeeded;
return ResultSuccess;
case SEC_E_INCOMPLETE_MESSAGE:
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_INCOMPLETE_MESSAGE");
ASSERT(input_buffers[1].BufferType == SECBUFFER_MISSING);
read_buf_fill_size = input_buffers[1].cbBuffer;
handshake_state = HandshakeState::IncompleteMessage;
return ResultSuccess;
case SEC_E_OK:
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_OK");
ciphertext_read_buf.clear();
handshake_state = HandshakeState::DoneAfterFlush;
return GrabStreamSizes();
default:
LOG_ERROR(Service_SSL,
"InitializeSecurityContext failed (probably certificate/protocol issue): {}",
Common::NativeErrorToString(ret));
handshake_state = HandshakeState::Error;
return ResultInternalError;
}
}
Result GrabStreamSizes() {
const SECURITY_STATUS ret =
QueryContextAttributes(&ctxt, SECPKG_ATTR_STREAM_SIZES, &stream_sizes);
if (ret != SEC_E_OK) {
LOG_ERROR(Service_SSL, "QueryContextAttributes(SECPKG_ATTR_STREAM_SIZES) failed: {}",
Common::NativeErrorToString(ret));
handshake_state = HandshakeState::Error;
return ResultInternalError;
}
return ResultSuccess;
}
Result Read(size_t* out_size, std::span<u8> data) override {
*out_size = 0;
if (handshake_state != HandshakeState::Connected) {
LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake");
return ResultInternalError;
}
if (data.size() == 0 || got_read_eof) {
return ResultSuccess;
}
while (1) {
if (!cleartext_read_buf.empty()) {
*out_size = (std::min)(cleartext_read_buf.size(), data.size());
std::memcpy(data.data(), cleartext_read_buf.data(), *out_size);
cleartext_read_buf.erase(cleartext_read_buf.begin(),
cleartext_read_buf.begin() + *out_size);
return ResultSuccess;
}
if (!ciphertext_read_buf.empty()) {
SecBuffer empty{
.cbBuffer = 0,
.BufferType = SECBUFFER_EMPTY,
.pvBuffer = nullptr,
};
std::array<SecBuffer, 5> buffers{{
{
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
.BufferType = SECBUFFER_DATA,
.pvBuffer = ciphertext_read_buf.data(),
},
empty,
empty,
empty,
}};
ASSERT_OR_EXECUTE_MSG(
buffers[0].cbBuffer == ciphertext_read_buf.size(),
{ return ResultInternalError; }, "read buffer too large");
SecBufferDesc desc{
.ulVersion = SECBUFFER_VERSION,
.cBuffers = static_cast<unsigned long>(buffers.size()),
.pBuffers = buffers.data(),
};
SECURITY_STATUS ret =
DecryptMessage(&ctxt, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr);
switch (ret) {
case SEC_E_OK:
ASSERT_OR_EXECUTE(buffers[0].BufferType == SECBUFFER_STREAM_HEADER,
{ return ResultInternalError; });
ASSERT_OR_EXECUTE(buffers[1].BufferType == SECBUFFER_DATA,
{ return ResultInternalError; });
ASSERT_OR_EXECUTE(buffers[2].BufferType == SECBUFFER_STREAM_TRAILER,
{ return ResultInternalError; });
cleartext_read_buf.assign(static_cast<u8*>(buffers[1].pvBuffer),
static_cast<u8*>(buffers[1].pvBuffer) +
buffers[1].cbBuffer);
if (buffers[3].BufferType == SECBUFFER_EXTRA) {
ASSERT(buffers[3].cbBuffer <= ciphertext_read_buf.size());
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
ciphertext_read_buf.end() - buffers[3].cbBuffer);
} else {
ASSERT(buffers[3].BufferType == SECBUFFER_EMPTY);
ciphertext_read_buf.clear();
}
continue;
case SEC_E_INCOMPLETE_MESSAGE:
break;
case SEC_I_CONTEXT_EXPIRED:
// Server hung up by sending close_notify.
got_read_eof = true;
*out_size = 0;
return ResultSuccess;
default:
LOG_ERROR(Service_SSL, "DecryptMessage failed: {}",
Common::NativeErrorToString(ret));
return ResultInternalError;
}
}
const Result r = FillCiphertextReadBuf();
if (r != ResultSuccess) {
return r;
}
if (ciphertext_read_buf.empty()) {
got_read_eof = true;
*out_size = 0;
return ResultSuccess;
}
}
}
Result Write(size_t* out_size, std::span<const u8> data) override {
*out_size = 0;
if (handshake_state != HandshakeState::Connected) {
LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake");
return ResultInternalError;
}
if (data.size() == 0) {
return ResultSuccess;
}
data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage));
if (!cleartext_write_buf.empty()) {
// Already in the middle of a write. It wouldn't make sense to not
// finish sending the entire buffer since TLS has
// header/MAC/padding/etc.
if (data.size() != cleartext_write_buf.size() ||
std::memcmp(data.data(), cleartext_write_buf.data(), data.size())) {
LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer");
return ResultInternalError;
}
return WriteAlreadyEncryptedData(out_size);
} else {
cleartext_write_buf.assign(data.begin(), data.end());
}
std::vector<u8> header_buf(stream_sizes.cbHeader, 0);
std::vector<u8> tmp_data_buf = cleartext_write_buf;
std::vector<u8> trailer_buf(stream_sizes.cbTrailer, 0);
std::array<SecBuffer, 3> buffers{{
{
.cbBuffer = stream_sizes.cbHeader,
.BufferType = SECBUFFER_STREAM_HEADER,
.pvBuffer = header_buf.data(),
},
{
.cbBuffer = static_cast<unsigned long>(tmp_data_buf.size()),
.BufferType = SECBUFFER_DATA,
.pvBuffer = tmp_data_buf.data(),
},
{
.cbBuffer = stream_sizes.cbTrailer,
.BufferType = SECBUFFER_STREAM_TRAILER,
.pvBuffer = trailer_buf.data(),
},
}};
ASSERT_OR_EXECUTE_MSG(
buffers[1].cbBuffer == tmp_data_buf.size(), { return ResultInternalError; },
"temp buffer too large");
SecBufferDesc desc{
.ulVersion = SECBUFFER_VERSION,
.cBuffers = static_cast<unsigned long>(buffers.size()),
.pBuffers = buffers.data(),
};
const SECURITY_STATUS ret = EncryptMessage(&ctxt, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0);
if (ret != SEC_E_OK) {
LOG_ERROR(Service_SSL, "EncryptMessage failed: {}", Common::NativeErrorToString(ret));
return ResultInternalError;
}
ciphertext_write_buf.insert(ciphertext_write_buf.end(), header_buf.begin(),
header_buf.end());
ciphertext_write_buf.insert(ciphertext_write_buf.end(), tmp_data_buf.begin(),
tmp_data_buf.end());
ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(),
trailer_buf.end());
return WriteAlreadyEncryptedData(out_size);
}
Result WriteAlreadyEncryptedData(size_t* out_size) {
const Result r = FlushCiphertextWriteBuf();
if (r != ResultSuccess) {
return r;
}
// write buf is empty
*out_size = cleartext_write_buf.size();
cleartext_write_buf.clear();
return ResultSuccess;
}
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
PCCERT_CONTEXT returned_cert = nullptr;
const SECURITY_STATUS ret =
QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert);
if (ret != SEC_E_OK) {
LOG_ERROR(Service_SSL,
"QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed: {}",
Common::NativeErrorToString(ret));
return ResultInternalError;
}
PCCERT_CONTEXT some_cert = nullptr;
while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) !=
nullptr) {
out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
static_cast<u8*>(some_cert->pbCertEncoded) +
some_cert->cbCertEncoded);
}
std::reverse(out_certs->begin(),
out_certs->end()); // Windows returns certs in reverse order from what we want
CertFreeCertificateContext(returned_cert);
return ResultSuccess;
}
~SSLConnectionBackendSchannel() {
if (handshake_state != HandshakeState::Initial) {
DeleteSecurityContext(&ctxt);
}
}
enum class HandshakeState {
// Haven't called anything yet.
Initial,
// `SEC_I_CONTINUE_NEEDED` was returned by
// `InitializeSecurityContext`; must finish sending data (if any) in
// the write buffer, then read at least one byte before calling
// `InitializeSecurityContext` again.
ContinueNeeded,
// `SEC_E_INCOMPLETE_MESSAGE` was returned by
// `InitializeSecurityContext`; hopefully the write buffer is empty;
// must read at least one byte before calling
// `InitializeSecurityContext` again.
IncompleteMessage,
// `SEC_E_OK` was returned by `InitializeSecurityContext`; must
// finish sending data in the write buffer before having `DoHandshake`
// report success.
DoneAfterFlush,
// We finished the above and are now connected. At this point, writing
// and reading are separate 'state machines' represented by the
// nonemptiness of the ciphertext and cleartext read and write buffers.
Connected,
// Another error was returned and we shouldn't allow initialization
// to continue.
Error,
} handshake_state = HandshakeState::Initial;
CtxtHandle ctxt;
SecPkgContext_StreamSizes stream_sizes;
std::shared_ptr<Network::SocketBase> socket;
std::optional<std::string> hostname;
std::vector<u8> ciphertext_read_buf;
std::vector<u8> ciphertext_write_buf;
std::vector<u8> cleartext_read_buf;
std::vector<u8> cleartext_write_buf;
bool got_read_eof = false;
bool skip_cert_verification = false;
size_t read_buf_fill_size = 0;
};
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendSchannel>();
R_TRY(conn->Init());
*out_backend = std::move(conn);
return ResultSuccess;
}
} // namespace Service::SSL
@@ -0,0 +1,236 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
// SecureTransport has been deprecated in its entirety in favor of
// Network.framework, but that does not allow layering TLS on top of an
// arbitrary socket.
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <Security/SecureTransport.h>
#pragma GCC diagnostic pop
#endif
#include "core/hle/service/ssl/ssl_backend.h"
#include "core/internal_network/network.h"
#include "core/internal_network/sockets.h"
namespace {
template <typename T>
struct CFReleaser {
T ptr;
YUZU_NON_COPYABLE(CFReleaser);
constexpr CFReleaser() : ptr(nullptr) {}
constexpr CFReleaser(T ptr) : ptr(ptr) {}
constexpr operator T() {
return ptr;
}
~CFReleaser() {
if (ptr) {
CFRelease(ptr);
}
}
};
std::string CFStringToString(CFStringRef cfstr) {
CFReleaser<CFDataRef> cfdata(
CFStringCreateExternalRepresentation(nullptr, cfstr, kCFStringEncodingUTF8, 0));
ASSERT_OR_EXECUTE(cfdata, { return "???"; });
return std::string(reinterpret_cast<const char*>(CFDataGetBytePtr(cfdata)),
CFDataGetLength(cfdata));
}
std::string OSStatusToString(OSStatus status) {
CFReleaser<CFStringRef> cfstr(SecCopyErrorMessageString(status, nullptr));
if (!cfstr) {
return "[unknown error]";
}
return CFStringToString(cfstr);
}
} // namespace
namespace Service::SSL {
class SSLConnectionBackendSecureTransport final : public SSLConnectionBackend {
public:
Result Init() {
static std::once_flag once_flag;
std::call_once(once_flag, []() {
if (getenv("SSLKEYLOGFILE")) {
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but SecureTransport does not "
"support exporting keys; not logging keys!");
// Not fatal.
}
});
context.ptr = SSLCreateContext(nullptr, kSSLClientSide, kSSLStreamType);
if (!context) {
LOG_ERROR(Service_SSL, "SSLCreateContext failed");
return ResultInternalError;
}
OSStatus status;
if ((status = SSLSetIOFuncs(context, ReadCallback, WriteCallback)) ||
(status = SSLSetConnection(context, this))) {
LOG_ERROR(Service_SSL, "SSLContext initialization failed: {}",
OSStatusToString(status));
return ResultInternalError;
}
return ResultSuccess;
}
void SetSocket(std::shared_ptr<Network::SocketBase> in_socket) override {
socket = std::move(in_socket);
}
Result SetHostName(const std::string& hostname) override {
OSStatus status = SSLSetPeerDomainName(context, hostname.c_str(), hostname.size());
if (status) {
LOG_ERROR(Service_SSL, "SSLSetPeerDomainName failed: {}", OSStatusToString(status));
return ResultInternalError;
}
return ResultSuccess;
}
void SetVerifyOption(u32 option) override {
skip_cert_verification = (option == 0);
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
skip_cert_verification);
if (skip_cert_verification) {
SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true);
}
}
Result DoHandshake() override {
OSStatus status = SSLHandshake(context);
if (skip_cert_verification && status == errSSLServerAuthCompleted) {
LOG_DEBUG(Service_SSL, "Skipping certificate verification as requested");
status = SSLHandshake(context);
}
return HandleReturn("SSLHandshake", 0, status);
}
Result Read(size_t* out_size, std::span<u8> data) override {
OSStatus status = SSLRead(context, data.data(), data.size(), out_size);
return HandleReturn("SSLRead", out_size, status);
}
Result Write(size_t* out_size, std::span<const u8> data) override {
OSStatus status = SSLWrite(context, data.data(), data.size(), out_size);
return HandleReturn("SSLWrite", out_size, status);
}
Result HandleReturn(const char* what, size_t* actual, OSStatus status) {
switch (status) {
case 0:
return ResultSuccess;
case errSSLWouldBlock:
return ResultWouldBlock;
default: {
std::string reason;
if (got_read_eof) {
reason = "server hung up";
} else {
reason = OSStatusToString(status);
}
LOG_ERROR(Service_SSL, "{} failed: {}", what, reason);
return ResultInternalError;
}
}
}
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
CFReleaser<SecTrustRef> trust;
OSStatus status = SSLCopyPeerTrust(context, &trust.ptr);
if (status) {
LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status));
return ResultInternalError;
}
for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
CFReleaser<CFDataRef> data(SecCertificateCopyData(cert));
ASSERT_OR_EXECUTE(data, { return ResultInternalError; });
const u8* ptr = CFDataGetBytePtr(data);
out_certs->emplace_back(ptr, ptr + CFDataGetLength(data));
}
return ResultSuccess;
}
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
return ReadOrWriteCallback(connection, data, dataLength, true);
}
static OSStatus WriteCallback(SSLConnectionRef connection, const void* data,
size_t* dataLength) {
return ReadOrWriteCallback(connection, const_cast<void*>(data), dataLength, false);
}
static OSStatus ReadOrWriteCallback(SSLConnectionRef connection, void* data, size_t* dataLength,
bool is_read) {
auto self =
static_cast<SSLConnectionBackendSecureTransport*>(const_cast<void*>(connection));
ASSERT_OR_EXECUTE_MSG(
self->socket, { return 0; }, "SecureTransport asked to {} but we have no socket",
is_read ? "read" : "write");
// SecureTransport callbacks (unlike OpenSSL BIO callbacks) are
// expected to read/write the full requested dataLength or return an
// error, so we have to add a loop ourselves.
size_t requested_len = *dataLength;
size_t offset = 0;
while (offset < requested_len) {
std::span cur(reinterpret_cast<u8*>(data) + offset, requested_len - offset);
auto [actual, err] = is_read ? self->socket->Recv(0, cur) : self->socket->Send(cur, 0);
LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset,
actual, cur.size(), static_cast<s32>(err));
switch (err) {
case Network::Errno::SUCCESS:
offset += actual;
if (actual == 0) {
ASSERT(is_read);
self->got_read_eof = true;
return errSecEndOfData;
}
break;
case Network::Errno::AGAIN:
*dataLength = offset;
return errSSLWouldBlock;
default:
LOG_ERROR(Service_SSL, "Socket {} returned Network::Errno {}",
is_read ? "recv" : "send", err);
return errSecIO;
}
}
ASSERT(offset == requested_len);
return 0;
}
private:
CFReleaser<SSLContextRef> context = nullptr;
bool got_read_eof = false;
bool skip_cert_verification = false;
std::shared_ptr<Network::SocketBase> socket;
};
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendSecureTransport>();
R_TRY(conn->Init());
*out_backend = std::move(conn);
return ResultSuccess;
}
} // namespace Service::SSL
@@ -85,13 +85,8 @@ template<>
code.mov(tmp, vaddr); code.mov(tmp, vaddr);
code.shr(tmp, int(page_table_const_bits)); code.shr(tmp, int(page_table_const_bits));
if (ctx.conf.page_table_log2_stride > 3) {
code.shl(tmp, int(ctx.conf.page_table_log2_stride)); code.shl(tmp, int(ctx.conf.page_table_log2_stride));
code.mov(page, qword[r14 + tmp.cvt64()]); code.mov(page, qword[r14 + tmp.cvt64()]);
} else {
code.mov(page, qword[r14 + tmp.cvt64() * int(1 << ctx.conf.page_table_log2_stride)]);
}
// check for marked bit, use as unmapped if marked // check for marked bit, use as unmapped if marked
if (ctx.conf.page_table_marked_bit) { if (ctx.conf.page_table_marked_bit) {
@@ -107,10 +102,9 @@ template<>
code.mov(tmp, ctx.conf.page_table_pointer_mask); code.mov(tmp, ctx.conf.page_table_pointer_mask);
code.and_(page, tmp); code.and_(page, tmp);
} }
// check for sign bit, apply sign extension as needed
if (ctx.conf.page_table_sign_extension) { if (ctx.conf.page_table_sign_extension) {
code.shl(page, 63 - int(*ctx.conf.page_table_sign_extension)); code.shl(page, *ctx.conf.page_table_sign_extension);
code.sar(page, 63 - int(*ctx.conf.page_table_sign_extension)); code.sar(page, *ctx.conf.page_table_sign_extension);
} }
code.jz(abort, code.T_NEAR); code.jz(abort, code.T_NEAR);
@@ -161,12 +155,8 @@ template<>
code.jnz(abort, code.T_NEAR); code.jnz(abort, code.T_NEAR);
} }
if (ctx.conf.page_table_log2_stride > 3) {
code.shl(tmp, int(ctx.conf.page_table_log2_stride)); code.shl(tmp, int(ctx.conf.page_table_log2_stride));
code.mov(page, qword[r14 + tmp.cvt64()]); code.mov(page, qword[r14 + tmp]);
} else {
code.mov(page, qword[r14 + tmp.cvt64() * int(1 << ctx.conf.page_table_log2_stride)]);
}
// check for marked bit, use as unmapped if marked // check for marked bit, use as unmapped if marked
if (ctx.conf.page_table_marked_bit) { if (ctx.conf.page_table_marked_bit) {
@@ -182,7 +172,6 @@ template<>
code.mov(tmp, ctx.conf.page_table_pointer_mask); code.mov(tmp, ctx.conf.page_table_pointer_mask);
code.and_(page, tmp); code.and_(page, tmp);
} }
// check for sign bit, apply sign extension as needed
if (ctx.conf.page_table_sign_extension) { if (ctx.conf.page_table_sign_extension) {
code.shl(page, *ctx.conf.page_table_sign_extension); code.shl(page, *ctx.conf.page_table_sign_extension);
code.sar(page, *ctx.conf.page_table_sign_extension); code.sar(page, *ctx.conf.page_table_sign_extension);
+3 -6
View File
@@ -23,7 +23,7 @@ namespace ConfigurationShared {
std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) { std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
std::unique_ptr<TranslationMap> translations = std::make_unique<TranslationMap>(); std::unique_ptr<TranslationMap> translations = std::make_unique<TranslationMap>();
const auto& tr = [](const char* text) -> QString { return QCoreApplication::translate("ConfigurationShared", text); }; const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); };
#define INSERT(SETTINGS, ID, NAME, TOOLTIP) \ #define INSERT(SETTINGS, ID, NAME, TOOLTIP) \
translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}}) translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}})
@@ -151,9 +151,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA " tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA "
"can produce a more stable picture in lower resolutions.")); "can produce a more stable picture in lower resolutions."));
INSERT(Settings, post_shader_chain, QString(), QString()); INSERT(Settings, post_shader_chain, QString(), QString());
INSERT(Settings, post_shader_preset, QString(), QString());
INSERT(Settings, post_shader_enabled, tr("Enable post-processing effects"),
tr("Applies post-processing effects to the final image."));
INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"),
tr("The method used to render the window in fullscreen.\nBorderless offers the best " tr("The method used to render the window in fullscreen.\nBorderless offers the best "
"compatibility with the on-screen keyboard that some games request for " "compatibility with the on-screen keyboard that some games request for "
@@ -371,8 +368,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) { std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent) {
std::unique_ptr<ComboboxTranslationMap> translations = std::unique_ptr<ComboboxTranslationMap> translations =
std::make_unique<ComboboxTranslationMap>(); std::make_unique<ComboboxTranslationMap>();
const auto& tr = [](const char* text, const char* context = "") { const auto& tr = [&](const char* text, const char* context = "") {
return QCoreApplication::translate("ConfigurationShared", text, context); return parent->tr(text, context);
}; };
#define PAIR(ENUM, VALUE, TRANSLATION) {static_cast<u32>(Settings::ENUM::VALUE), (TRANSLATION)} #define PAIR(ENUM, VALUE, TRANSLATION) {static_cast<u32>(Settings::ENUM::VALUE), (TRANSLATION)}
@@ -2850,6 +2850,8 @@ Sampler::VariantKey Sampler::MakeKey(const ImageView& image_view, bool is_depth)
VariantKey key{}; VariantKey key{};
key.reduce_anisotropy = has_added_anisotropy && !image_view.SupportsAnisotropy(); key.reduce_anisotropy = has_added_anisotropy && !image_view.SupportsAnisotropy();
key.force_nearest = has_linear_filtering && IsPixelFormatInteger(image_view.format); key.force_nearest = has_linear_filtering && IsPixelFormatInteger(image_view.format);
key.drop_depth_comparison =
is_depth && has_depth_comparison && !image_view.SupportsDepthComparison();
key.drop_reduction = has_minmax_reduction && !image_view.SupportsMinmaxFilter(); key.drop_reduction = has_minmax_reduction && !image_view.SupportsMinmaxFilter();
key.drop_custom_border = has_custom_border_colors && image_view.RequiresBorderColorFormat(); key.drop_custom_border = has_custom_border_colors && image_view.RequiresBorderColorFormat();
key.srgb_border = has_srgb_border_color && IsPixelFormatSRGB(image_view.format); key.srgb_border = has_srgb_border_color && IsPixelFormatSRGB(image_view.format);
@@ -2927,6 +2929,9 @@ VkSampler Sampler::Emplace(VariantKey key) {
create_info.anisotropyEnable = static_cast<VkBool32>(default_anisotropy > 1.0f); create_info.anisotropyEnable = static_cast<VkBool32>(default_anisotropy > 1.0f);
create_info.maxAnisotropy = default_anisotropy; create_info.maxAnisotropy = default_anisotropy;
} }
if (key.drop_depth_comparison) {
create_info.compareEnable = VK_FALSE;
}
if (!custom_border) { if (!custom_border) {
create_info.borderColor = ConvertBorderColor(color); create_info.borderColor = ConvertBorderColor(color);
} }
@@ -536,6 +536,7 @@ private:
struct VariantKey { struct VariantKey {
bool reduce_anisotropy; bool reduce_anisotropy;
bool force_nearest; bool force_nearest;
bool drop_depth_comparison;
bool drop_reduction; bool drop_reduction;
bool drop_custom_border; bool drop_custom_border;
bool srgb_border; bool srgb_border;
@@ -37,10 +37,6 @@
#include "yuzu/configuration/configure_graphics.h" #include "yuzu/configuration/configure_graphics.h"
#include "yuzu/configuration/shared_widget.h" #include "yuzu/configuration/shared_widget.h"
#ifdef HAS_RESHADE
#include "yuzu/configuration/configure_post_processing.h"
#endif
ConfigureGraphics::ConfigureGraphics( ConfigureGraphics::ConfigureGraphics(
const Core::System& system_, std::vector<VkDeviceInfo::Record>& records_, const Core::System& system_, std::vector<VkDeviceInfo::Record>& records_,
const std::function<void()>& expose_compute_option_, const std::function<void()>& expose_compute_option_,
@@ -220,12 +216,6 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
std::vector<QWidget*> hold_api; std::vector<QWidget*> hold_api;
for (const auto setting : Settings::values.linkage.by_category[Settings::Category::Renderer]) { for (const auto setting : Settings::values.linkage.by_category[Settings::Category::Renderer]) {
#ifndef HAS_RESHADE
if (setting->Id() == Settings::values.post_shader_enabled.Id()) {
continue;
}
#endif
ConfigurationShared::Widget* widget = [&]() { ConfigurationShared::Widget* widget = [&]() {
if (setting->Id() == Settings::values.fsr_sharpening_slider.Id()) { if (setting->Id() == Settings::values.fsr_sharpening_slider.Id()) {
// FSR needs a reversed slider and a 0.5 multiplier // FSR needs a reversed slider and a 0.5 multiplier
@@ -305,23 +295,6 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
// Keep track of the resolution combobox to update other UI tabs that need it // Keep track of the resolution combobox to update other UI tabs that need it
resolution_combobox = widget->combobox; resolution_combobox = widget->combobox;
hold_graphics.emplace(setting->Id(), widget); hold_graphics.emplace(setting->Id(), widget);
#ifdef HAS_RESHADE
} else if (setting->Id() == Settings::values.post_shader_enabled.Id()) {
QPushButton* post_shader_button = new QPushButton(tr("Configure Effects..."), widget);
post_shader_button->setVisible(widget->checkbox->isChecked());
connect(post_shader_button, &QAbstractButton::clicked, this, [this]() {
ConfigurePostProcessing dialog(this);
dialog.exec();
});
connect(widget->checkbox, &QCheckBox::toggled, post_shader_button,
&QWidget::setVisible);
QBoxLayout* row = qobject_cast<QBoxLayout*>(widget->layout());
row->insertWidget(1, post_shader_button);
hold_graphics.emplace(setting->Id(), widget);
#endif
} else { } else {
hold_graphics.emplace(setting->Id(), widget); hold_graphics.emplace(setting->Id(), widget);
} }
@@ -70,8 +70,10 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
auto* root = new QVBoxLayout(this); auto* root = new QVBoxLayout(this);
auto* description = auto* description = new QLabel(
new QLabel(tr("Changes apply immediately while a game is running."), this); tr("ReShade FX effects are loaded from the post_shaders folder in the Eden data "
"directory. Changes apply immediately while a game is running."),
this);
description->setWordWrap(true); description->setWordWrap(true);
root->addWidget(description); root->addWidget(description);
@@ -126,15 +128,17 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
}); });
preset_row->addWidget(save_button); preset_row->addWidget(save_button);
auto* clear_button = new QPushButton(tr("Clear"), this); auto* delete_button = new QPushButton(tr("Delete"), this);
clear_button->setToolTip(tr("Remove every effect in use and deselect the preset.")); connect(delete_button, &QPushButton::clicked, this, [this]() {
connect(clear_button, &QPushButton::clicked, this, [this]() { const QString name = preset_combo->currentData().toString();
VideoCore::FxChain::Instance().Clear(); if (name.isEmpty()) {
VideoCore::SetActiveFxPreset(std::string()); return;
preset_combo->setCurrentIndex(0); }
ApplyStructuralChange(); VideoCore::DeleteFxPreset(name.toStdString());
PopulatePresetCombo();
RefreshPresetStatus();
}); });
preset_row->addWidget(clear_button); preset_row->addWidget(delete_button);
root->addLayout(preset_row); root->addLayout(preset_row);
@@ -199,7 +203,6 @@ void ConfigurePostProcessing::ApplyStructuralChange() {
void ConfigurePostProcessing::PopulatePresetCombo() { void ConfigurePostProcessing::PopulatePresetCombo() {
const QString previous = preset_combo->currentData().toString(); const QString previous = preset_combo->currentData().toString();
preset_combo->clear(); preset_combo->clear();
preset_combo->addItem(tr("None"), QString());
for (const auto& preset : VideoCore::GetFxPresetCatalog()) { for (const auto& preset : VideoCore::GetFxPresetCatalog()) {
const QString name = QString::fromStdString(preset.name); const QString name = QString::fromStdString(preset.name);
@@ -209,7 +212,7 @@ void ConfigurePostProcessing::PopulatePresetCombo() {
} }
const int restored = preset_combo->findData(previous); const int restored = preset_combo->findData(previous);
if (!previous.isEmpty() && restored >= 0) { if (restored >= 0) {
preset_combo->setCurrentIndex(restored); preset_combo->setCurrentIndex(restored);
return; return;
} }
@@ -217,21 +220,12 @@ void ConfigurePostProcessing::PopulatePresetCombo() {
const int active = preset_combo->findData(QString::fromStdString(VideoCore::GetActiveFxPreset())); const int active = preset_combo->findData(QString::fromStdString(VideoCore::GetActiveFxPreset()));
if (active >= 0) { if (active >= 0) {
preset_combo->setCurrentIndex(active); preset_combo->setCurrentIndex(active);
return;
} }
preset_combo->setCurrentIndex(0);
} }
void ConfigurePostProcessing::RefreshPresetStatus() { void ConfigurePostProcessing::RefreshPresetStatus() {
const std::string active = VideoCore::GetActiveFxPreset(); const std::string active = VideoCore::GetActiveFxPreset();
if (active.empty()) { if (active.empty()) {
if (VideoCore::FxChain::Instance().Size() == 0) {
preset_status->setText(
tr("No effects in use. Pick a preset and press Apply, or add effects one "
"by one."));
return;
}
preset_status->setText(tr("Custom chain. Pick a preset above and press Apply to replace it.")); preset_status->setText(tr("Custom chain. Pick a preset above and press Apply to replace it."));
return; return;
} }
-57
View File
@@ -15,7 +15,6 @@
#include "render/performance_overlay.h" #include "render/performance_overlay.h"
#ifdef HAS_RESHADE #ifdef HAS_RESHADE
#include "configuration/configure_post_processing.h" #include "configuration/configure_post_processing.h"
#include "video_core/post_processing/fx_preset.h"
#endif #endif
#include "updater/update_dialog.h" #include "updater/update_dialog.h"
@@ -1091,41 +1090,6 @@ void MainWindow::InitializeWidgets() {
statusBar()->insertPermanentWidget(0, volume_button); statusBar()->insertPermanentWidget(0, volume_button);
#ifdef HAS_RESHADE
post_shader_status_button = new QPushButton();
post_shader_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
post_shader_status_button->setFocusPolicy(Qt::NoFocus);
post_shader_status_button->setCheckable(true);
connect(post_shader_status_button, &QPushButton::clicked, this, [this] {
const bool enabled = Settings::values.post_shader_enabled.GetValue();
Settings::values.post_shader_enabled.SetValue(!enabled);
UpdatePostShaderText();
});
UpdatePostShaderText();
post_shader_status_button->setContextMenuPolicy(Qt::CustomContextMenu);
connect(post_shader_status_button, &QPushButton::customContextMenuRequested,
[this](const QPoint& menu_location) {
QMenu context_menu;
for (auto const& preset : VideoCore::GetFxPresetCatalog()) {
context_menu.addAction(QString::fromStdString(preset.name),
[this, name = preset.name] {
VideoCore::ApplyFxPreset(name);
Settings::values.post_shader_enabled.SetValue(true);
UpdatePostShaderText();
});
}
context_menu.addSeparator();
context_menu.addAction(tr("Configure Effects..."), this,
&MainWindow::OnPostProcessingShaders);
context_menu.exec(post_shader_status_button->mapToGlobal(menu_location));
post_shader_status_button->repaint();
});
statusBar()->insertPermanentWidget(0, post_shader_status_button);
#endif
// setup AA button // setup AA button
aa_status_button = new QPushButton(); aa_status_button = new QPushButton();
aa_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); aa_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
@@ -3944,7 +3908,6 @@ void MainWindow::OnPostProcessingShaders() {
connect(post_processing_dialog, &QDialog::finished, post_processing_dialog, [this]() { connect(post_processing_dialog, &QDialog::finished, post_processing_dialog, [this]() {
post_processing_dialog->deleteLater(); post_processing_dialog->deleteLater();
post_processing_dialog = nullptr; post_processing_dialog = nullptr;
UpdatePostShaderText();
}); });
} }
@@ -4299,26 +4262,6 @@ void MainWindow::UpdateAAText() {
: aa_text.toUpper()); : aa_text.toUpper());
} }
#ifdef HAS_RESHADE
void MainWindow::UpdatePostShaderText() {
const bool enabled = Settings::values.post_shader_enabled.GetValue();
post_shader_status_button->setChecked(enabled);
if (!enabled) {
post_shader_status_button->setText(tr("NO FX"));
return;
}
const std::string preset = VideoCore::GetActiveFxPreset();
if (preset.empty()) {
post_shader_status_button->setText(tr("FX"));
return;
}
post_shader_status_button->setText(QString::fromStdString(preset).toUpper());
}
#endif
void MainWindow::UpdateVolumeUI() { void MainWindow::UpdateVolumeUI() {
const auto volume_value = static_cast<int>(Settings::values.volume.GetValue()); const auto volume_value = static_cast<int>(Settings::values.volume.GetValue());
volume_slider->setValue(volume_value); volume_slider->setValue(volume_value);
-2
View File
@@ -448,7 +448,6 @@ private:
void UpdateAPIText(); void UpdateAPIText();
void UpdateFilterText(); void UpdateFilterText();
void UpdateAAText(); void UpdateAAText();
void UpdatePostShaderText();
void UpdateVolumeUI(); void UpdateVolumeUI();
void UpdateStatusBar(); void UpdateStatusBar();
void UpdateGPUAccuracyButton(); void UpdateGPUAccuracyButton();
@@ -525,7 +524,6 @@ private:
QPushButton* dock_status_button = nullptr; QPushButton* dock_status_button = nullptr;
QPushButton* filter_status_button = nullptr; QPushButton* filter_status_button = nullptr;
QPushButton* aa_status_button = nullptr; QPushButton* aa_status_button = nullptr;
QPushButton* post_shader_status_button = nullptr;
VolumeButton* volume_button = nullptr; VolumeButton* volume_button = nullptr;
QWidget* volume_popup = nullptr; QWidget* volume_popup = nullptr;
QSlider* volume_slider = nullptr; QSlider* volume_slider = nullptr;