mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-17 00:30:46 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90aafeedc1 | |||
| 4fe5f62c38 | |||
| c3f1e6562b | |||
| ac35358b3f | |||
| 7bf95be2c2 | |||
| 4ce45b3b37 | |||
| fd34024f0e | |||
| d76f8f91c4 | |||
| a7061eb4c8 | |||
| fa4e7c6992 | |||
| b77308ced6 | |||
| defddec47f | |||
| c5405250d1 | |||
| 10c07d700c | |||
| 505b157647 | |||
| 1575f55abe | |||
| bfba95fd60 | |||
| ee73920d28 | |||
| 42642f8bad |
@@ -1,13 +0,0 @@
|
|||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
+2
-6
@@ -417,12 +417,9 @@ 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)
|
||||||
# boost sucks
|
# solaris sucks
|
||||||
if (SOLARIS)
|
if (SOLARIS)
|
||||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthreads>)
|
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthreads>)
|
||||||
endif()
|
endif()
|
||||||
@@ -431,8 +428,7 @@ 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()
|
||||||
|
|
||||||
|
|||||||
+6
-9
@@ -6,22 +6,19 @@
|
|||||||
"version": "v0.19.0"
|
"version": "v0.19.0"
|
||||||
},
|
},
|
||||||
"boost": {
|
"boost": {
|
||||||
"artifact": "%VERSION%-cmake.tar.xz",
|
"artifact": "boost-%VERSION%.tar.zst",
|
||||||
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
|
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
|
||||||
"hash": "6ae6e94664fe7f2fb01976b59b276ac5df8085c7503fa829d810fbfe495960cfec44fa2c36e2cb23480bc19c956ed199d4952b02639a00a6c07625d4e7130c2d",
|
"hash": "681d97be4386767662e230b2e7c9754d45e709cc5db4e747c23c27d1f5b0e87770bdf19b0bc44dcb0e8349324887bbb8fffb432098f4d0ce74a5043021a90afc",
|
||||||
"min_version": "1.57",
|
"min_version": "1.57",
|
||||||
"package": "Boost",
|
"package": "Boost",
|
||||||
"patches": [
|
"repo": "eden-emulator/ext-boost",
|
||||||
"0001-clang-cl.patch"
|
"version": "1.92.0"
|
||||||
],
|
|
||||||
"repo": "boostorg/boost",
|
|
||||||
"version": "boost-1.90.0"
|
|
||||||
},
|
},
|
||||||
"boost_headers": {
|
"boost_headers": {
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"hash": "4ef845775e2277a8104ded6ddf749aa262ce52cf8438042869a048f9a0156dd772fbbcfa74efa1378fecef339b7286f6fe4b4feb5c45d49966b35d08e3e83507",
|
"hash": "c5fa2cd72f6e6666b7963b97bc359c75284b8fb540c30f3629a028b85270c9bc66c8a051383964f2bd4c1e005a4691593d15696e7ef39ea87cf6cff9e5691fb2",
|
||||||
"repo": "boostorg/headers",
|
"repo": "boostorg/headers",
|
||||||
"version": "boost-1.90.0"
|
"version": "boost-1.91.0"
|
||||||
},
|
},
|
||||||
"catch2": {
|
"catch2": {
|
||||||
"hash": "7eea385d79d88a5690cde131fe7ccda97d5c54ea09d6f515000d7bf07c828809d61c1ac99912c1ee507cf933f61c1c47ecdcc45df7850ffa82714034b0fccf35",
|
"hash": "7eea385d79d88a5690cde131fe7ccda97d5c54ea09d6f515000d7bf07c828809d61c1ac99912c1ee507cf933f61c1c47ecdcc45df7850ffa82714034b0fccf35",
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ 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
|
||||||
|
|||||||
+66
-13
@@ -5,6 +5,7 @@
|
|||||||
// 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"
|
||||||
@@ -480,28 +481,80 @@ 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());
|
||||||
|
|
||||||
const bool absolute = !path.empty() && path[0] == type2;
|
std::string root;
|
||||||
std::vector<std::string_view> parts;
|
std::string_view components{path};
|
||||||
|
bool drive_relative = false;
|
||||||
|
|
||||||
for (const auto part : SplitPathComponents(path))
|
#ifdef _WIN32
|
||||||
{
|
const bool network = path.size() > 1 && path[0] == type2 && path[1] == type2;
|
||||||
if (part.empty() || part == ".")
|
const bool drive =
|
||||||
continue;
|
path.size() > 1 && std::isalpha(static_cast<unsigned char>(path[0])) && path[1] == ':';
|
||||||
if (part == ".." && !parts.empty() && parts.back() != "..")
|
|
||||||
parts.pop_back();
|
if (network) {
|
||||||
else if (part != "..") parts.push_back(part);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string resolved = absolute ? std::string(1, type2) : std::string{};
|
const auto path_parts = SplitPathComponents(components);
|
||||||
for (std::size_t i = 0; i < parts.size(); ++i)
|
std::size_t root_component_count = 0;
|
||||||
{
|
#ifdef _WIN32
|
||||||
if (i != 0)
|
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;
|
||||||
|
for (std::size_t i = 0; i < path_parts.size(); ++i) {
|
||||||
|
const auto part = path_parts[i];
|
||||||
|
if (i < root_component_count) {
|
||||||
|
parts.push_back(part);
|
||||||
|
} else if (part.empty() || part == ".") {
|
||||||
|
continue;
|
||||||
|
} else if (part == "..") {
|
||||||
|
if (parts.size() > root_component_count) {
|
||||||
|
parts.pop_back();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parts.push_back(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t root_length = root.size();
|
||||||
|
std::string resolved = std::move(root);
|
||||||
|
for (std::size_t i = 0; i < parts.size(); ++i) {
|
||||||
|
if (i != 0 || (!resolved.empty() && resolved.back() != type2 && !drive_relative))
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -347,8 +347,9 @@ 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);
|
||||||
|
|
||||||
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
|
// Normalizes directory separators, removes duplicate and non-root trailing separators, and resolves
|
||||||
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
|
// '.' and '..' components without traversing above the path root. Windows drive and UNC roots are
|
||||||
|
// 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);
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
// 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
@@ -208,9 +208,9 @@ UUID UUID::MakeRandomRFC4122V4() {
|
|||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
UUID UUID::MakeRFC4122V5(std::span<u8, 20> sha1) {
|
UUID UUID::MakeRFC4122V5(std::span<u8, 16> sha1) {
|
||||||
UUID uuid{};
|
UUID uuid{};
|
||||||
std::memcpy(&uuid.uuid, sha1.data(), sizeof(UUID));
|
std::memcpy(&uuid.uuid, sha1.data(), sha1.size());
|
||||||
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
@@ -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, 20> sha1);
|
[[nodiscard]] static UUID MakeRFC4122V5(std::span<u8, 16> sha1);
|
||||||
|
|
||||||
friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default;
|
friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default;
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-16
@@ -796,6 +796,8 @@ 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
|
||||||
@@ -1261,23 +1263,11 @@ 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()
|
||||||
# TODO
|
target_sources(core PRIVATE hle/service/ssl/ssl_backend_none.cpp)
|
||||||
|
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)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#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 {
|
||||||
|
|
||||||
@@ -29,23 +30,39 @@ 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{};
|
||||||
|
|
||||||
// Disconnect Handheld first.
|
// reserve existing AND valid players before filling slots. include Handheld, but not other
|
||||||
|
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);
|
||||||
handheld->Disconnect();
|
if (!keep_connected[hid_core.available_controllers - 2]) 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);
|
||||||
|
|
||||||
// First, disconnect all controllers regardless of the value of keep_controllers_connected.
|
if (keep_connected[index]) continue;
|
||||||
// This makes it easy to connect the desired controllers.
|
|
||||||
controller->Disconnect();
|
controller->Disconnect();
|
||||||
|
|
||||||
// Only connect the minimum number of required players.
|
// only add players still needed to reach the minimum
|
||||||
if (index >= min_supported_players) {
|
if (num_selected_players >= min_supported_players) continue;
|
||||||
continue;
|
++num_selected_players;
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -465,6 +465,10 @@ 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();
|
||||||
@@ -496,6 +500,10 @@ 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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
#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 {
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
constexpr KSlabHeapImpl() = default;
|
KSlabHeapImpl() = default;
|
||||||
|
|
||||||
void Initialize() {
|
void Initialize() {
|
||||||
ASSERT(m_head == nullptr);
|
ASSERT(m_head == nullptr);
|
||||||
@@ -68,7 +67,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::atomic<Node*> m_head{};
|
std::atomic<Node*> m_head{};
|
||||||
Common::SpinLock m_lock;
|
std::mutex m_lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace impl
|
} // namespace impl
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
#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"
|
||||||
@@ -920,7 +919,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{};
|
||||||
Common::SpinLock m_context_guard{};
|
std::mutex m_context_guard{};
|
||||||
|
|
||||||
// For emulation
|
// For emulation
|
||||||
std::shared_ptr<Common::Fiber> m_host_context{};
|
std::shared_ptr<Common::Fiber> m_host_context{};
|
||||||
|
|||||||
@@ -347,21 +347,22 @@ 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, ResultUnknown);
|
R_UNLESS(out_pseudo_device_id != nullptr, 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);
|
||||||
u8 hash[EVP_MAX_MD_SIZE];
|
R_UNLESS(res.first != nullptr, ResultUnknown);
|
||||||
|
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, &hash_len);
|
EVP_DigestFinal_ex(ctx, hash.data(), &hash_len);
|
||||||
EVP_MD_CTX_free(ctx);
|
EVP_MD_CTX_free(ctx);
|
||||||
*out_pseudo_device_id = Common::UUID::MakeRFC4122V5(std::span<u8, 20>{hash, std::size(hash)});
|
*out_pseudo_device_id = Common::UUID::MakeRFC4122V5(std::span<u8, 16>{hash.begin(), hash.begin() + 16});
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ 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
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ 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"},
|
||||||
@@ -118,6 +120,7 @@ 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"},
|
||||||
|
|||||||
@@ -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"}, //20.0.0+
|
{934, nullptr, "Unknown934"}, //21.0.0+
|
||||||
{935, nullptr, "Unknown935"}, //20.0.0+
|
{935, nullptr, "Unknown935"}, //21.0.0+
|
||||||
{936, nullptr, "Unknown936"}, //20.0.0+
|
{936, D<&IApplicationManagerInterface::Unknown936>, "Unknown936"}, //21.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, nullptr, "Unknown4042"}, //20.0.0+
|
{4042, D<&IApplicationManagerInterface::Unknown4042>, "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,6 +476,7 @@ 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
|
||||||
@@ -638,6 +639,12 @@ 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");
|
||||||
@@ -861,11 +868,25 @@ 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,6 +7,7 @@
|
|||||||
#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"
|
||||||
@@ -34,6 +35,7 @@ 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,
|
||||||
@@ -71,7 +73,11 @@ 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);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// 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
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// 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, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"}, //23.0.0+
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
@@ -311,27 +311,30 @@ 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()};
|
||||||
const auto control = pm.GetControlMetadata();
|
if (const auto control = pm.GetControlMetadata(); control.second) {
|
||||||
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()};
|
||||||
const auto control = pm.GetControlMetadata();
|
if (const auto control = pm.GetControlMetadata(); control.second) {
|
||||||
auto const full_size = control.second->GetSize();
|
if (auto const full_size = control.second->GetSize(); full_size > 0) {
|
||||||
if (full_size > 0) {
|
std::vector<u8> full_icon_data(full_size);
|
||||||
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,6 +348,12 @@ 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);
|
||||||
@@ -354,8 +363,9 @@ 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 = app_ids_buffer[i];
|
const u64 app_id = application_ids[i];
|
||||||
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()};
|
const FileSys::PatchManager pm{app_id, system.GetFileSystemController(),
|
||||||
|
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, nullptr, "GetTransferTaskProgress"},
|
{21, D<&ITransferTaskListController::GetTransferTaskProgress>, "GetTransferTaskProgress"}, //10.1.0+
|
||||||
{22, nullptr, "GetTransferTaskLastResult"},
|
{22, nullptr, "GetTransferTaskLastResult"},
|
||||||
{23, nullptr, "SuspendTransferTask"},
|
{23, nullptr, "SuspendTransferTask"},
|
||||||
{24, D<&ITransferTaskListController::GetCurrentTransferTaskInfo>, "GetCurrentTransferTaskInfo"},
|
{24, D<&ITransferTaskListController::GetCurrentTransferTaskInfo>, "GetCurrentTransferTaskInfo"},
|
||||||
@@ -82,6 +82,11 @@ 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,6 +24,7 @@ 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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,563 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
// 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,8 +85,13 @@ 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) {
|
||||||
@@ -102,9 +107,10 @@ 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, 63 - int(*ctx.conf.page_table_sign_extension));
|
||||||
code.sar(page, *ctx.conf.page_table_sign_extension);
|
code.sar(page, 63 - int(*ctx.conf.page_table_sign_extension));
|
||||||
}
|
}
|
||||||
|
|
||||||
code.jz(abort, code.T_NEAR);
|
code.jz(abort, code.T_NEAR);
|
||||||
@@ -155,8 +161,12 @@ 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]);
|
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) {
|
||||||
@@ -172,9 +182,10 @@ 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, 63 - int(*ctx.conf.page_table_sign_extension));
|
||||||
code.sar(page, *ctx.conf.page_table_sign_extension);
|
code.sar(page, 63 - int(*ctx.conf.page_table_sign_extension));
|
||||||
}
|
}
|
||||||
|
|
||||||
code.jz(abort, code.T_NEAR);
|
code.jz(abort, code.T_NEAR);
|
||||||
|
|||||||
@@ -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 = [parent](const char* text) -> QString { return parent->tr(text); };
|
const auto& tr = [](const char* text) -> QString { return QCoreApplication::translate("ConfigurationShared", 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,6 +151,9 @@ 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 "
|
||||||
@@ -368,8 +371,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 parent->tr(text, context);
|
return QCoreApplication::translate("ConfigurationShared", 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,8 +2850,6 @@ 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);
|
||||||
@@ -2929,9 +2927,6 @@ 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,7 +536,6 @@ 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,6 +37,10 @@
|
|||||||
#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_,
|
||||||
@@ -216,6 +220,12 @@ 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
|
||||||
@@ -295,6 +305,23 @@ 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,10 +70,8 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
|
|||||||
|
|
||||||
auto* root = new QVBoxLayout(this);
|
auto* root = new QVBoxLayout(this);
|
||||||
|
|
||||||
auto* description = new QLabel(
|
auto* description =
|
||||||
tr("ReShade FX effects are loaded from the post_shaders folder in the Eden data "
|
new QLabel(tr("Changes apply immediately while a game is running."), this);
|
||||||
"directory. Changes apply immediately while a game is running."),
|
|
||||||
this);
|
|
||||||
description->setWordWrap(true);
|
description->setWordWrap(true);
|
||||||
root->addWidget(description);
|
root->addWidget(description);
|
||||||
|
|
||||||
@@ -128,17 +126,15 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
|
|||||||
});
|
});
|
||||||
preset_row->addWidget(save_button);
|
preset_row->addWidget(save_button);
|
||||||
|
|
||||||
auto* delete_button = new QPushButton(tr("Delete"), this);
|
auto* clear_button = new QPushButton(tr("Clear"), this);
|
||||||
connect(delete_button, &QPushButton::clicked, this, [this]() {
|
clear_button->setToolTip(tr("Remove every effect in use and deselect the preset."));
|
||||||
const QString name = preset_combo->currentData().toString();
|
connect(clear_button, &QPushButton::clicked, this, [this]() {
|
||||||
if (name.isEmpty()) {
|
VideoCore::FxChain::Instance().Clear();
|
||||||
return;
|
VideoCore::SetActiveFxPreset(std::string());
|
||||||
}
|
preset_combo->setCurrentIndex(0);
|
||||||
VideoCore::DeleteFxPreset(name.toStdString());
|
ApplyStructuralChange();
|
||||||
PopulatePresetCombo();
|
|
||||||
RefreshPresetStatus();
|
|
||||||
});
|
});
|
||||||
preset_row->addWidget(delete_button);
|
preset_row->addWidget(clear_button);
|
||||||
|
|
||||||
root->addLayout(preset_row);
|
root->addLayout(preset_row);
|
||||||
|
|
||||||
@@ -203,6 +199,7 @@ 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);
|
||||||
@@ -212,7 +209,7 @@ void ConfigurePostProcessing::PopulatePresetCombo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int restored = preset_combo->findData(previous);
|
const int restored = preset_combo->findData(previous);
|
||||||
if (restored >= 0) {
|
if (!previous.isEmpty() && restored >= 0) {
|
||||||
preset_combo->setCurrentIndex(restored);
|
preset_combo->setCurrentIndex(restored);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -220,12 +217,21 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#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"
|
||||||
|
|
||||||
@@ -1090,6 +1091,41 @@ 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"));
|
||||||
@@ -3908,6 +3944,7 @@ 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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4262,6 +4299,26 @@ 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);
|
||||||
|
|||||||
@@ -448,6 +448,7 @@ 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();
|
||||||
@@ -524,6 +525,7 @@ 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user