Compare commits
262 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e92ba2785 | ||
|
|
99a163478b | ||
|
|
abdb81ccaf | ||
|
|
9f63acac0f | ||
|
|
52980df1aa | ||
|
|
586cab6172 | ||
|
|
1665b70cc6 | ||
|
|
f08db7295a | ||
|
|
6af322a347 | ||
|
|
819dd93257 | ||
|
|
e5893db3e6 | ||
|
|
a7157fe27d | ||
|
|
f3297d8cd1 | ||
|
|
733cf179b8 | ||
|
|
540235bb05 | ||
|
|
611f4666fd | ||
|
|
1cf90f4570 | ||
|
|
90e27ea003 | ||
|
|
ee49e1fcb6 | ||
|
|
7b6d516faa | ||
|
|
b78e7b3454 | ||
|
|
d0dddb3e9d | ||
|
|
4d95adcac5 | ||
|
|
e703772c83 | ||
|
|
639f0c524d | ||
|
|
76f27d1f44 | ||
|
|
18918f5f2f | ||
|
|
56c80a2a21 | ||
|
|
c221308a66 | ||
|
|
839c0f829b | ||
|
|
109b78a6d6 | ||
|
|
3e930304fe | ||
|
|
52f36ea1c7 | ||
|
|
b72664abfd | ||
|
|
2117edd0f8 | ||
|
|
5a5fccaa23 | ||
|
|
f2c41ba256 | ||
|
|
b0d70096a1 | ||
|
|
e76f442a0e | ||
|
|
72837e4b3d | ||
|
|
3ae0de9b53 | ||
|
|
19330f45d3 | ||
|
|
197dcf0b5e | ||
|
|
21eb4cfa7f | ||
|
|
22d3dfbcd4 | ||
|
|
241563d15c | ||
|
|
43b83d6b6a | ||
|
|
1b6adb5308 | ||
|
|
872a7bee72 | ||
|
|
e8ff8a66b0 | ||
|
|
723ad4512f | ||
|
|
c1409602da | ||
|
|
8f454a5c68 | ||
|
|
1b6bd9d6df | ||
|
|
9d11303a36 | ||
|
|
adf07cbe17 | ||
|
|
8a320a6ee2 | ||
|
|
1342c53e27 | ||
|
|
ab00552118 | ||
|
|
ec74a4fd4a | ||
|
|
b8c7072206 | ||
|
|
746167f11a | ||
|
|
eb335f51ca | ||
|
|
874826b6dd | ||
|
|
8dc2f01eae | ||
|
|
e05136f70b | ||
|
|
9f092554c2 | ||
|
|
26b809549b | ||
|
|
c07ebeac19 | ||
|
|
ecd3afdc8e | ||
|
|
30f228a8c9 | ||
|
|
c57d8eb66c | ||
|
|
51f609fee7 | ||
|
|
57ca1e3e69 | ||
|
|
e6612d6d8d | ||
|
|
114060fd87 | ||
|
|
d74aa13bd3 | ||
|
|
834d3fe336 | ||
|
|
13bc74e957 | ||
|
|
41566c615b | ||
|
|
9ad3b01d30 | ||
|
|
fd0533ef4c | ||
|
|
ed7a1e1443 | ||
|
|
93da8e0abf | ||
|
|
032e4c4ca3 | ||
|
|
2392e146b0 | ||
|
|
bf41132aa9 | ||
|
|
059465d496 | ||
|
|
64444ff481 | ||
|
|
f71c598907 | ||
|
|
6abc56672c | ||
|
|
99f982dce2 | ||
|
|
29c242721a | ||
|
|
bdf2da4ee8 | ||
|
|
10118c71e0 | ||
|
|
574e89d924 | ||
|
|
47b622825c | ||
|
|
51d7f6bffc | ||
|
|
c892cf01fa | ||
|
|
db47d7e471 | ||
|
|
e0d1f11968 | ||
|
|
39483b92b7 | ||
|
|
0b78cfcc53 | ||
|
|
06ac6460d3 | ||
|
|
e5b004e903 | ||
|
|
59f16f2e02 | ||
|
|
2eaf6c41a4 | ||
|
|
84d3cdf7d7 | ||
|
|
6788ebffc8 | ||
|
|
d71cad6ed0 | ||
|
|
555cd26ec2 | ||
|
|
2d9546848e | ||
|
|
8bd17aa044 | ||
|
|
4e6c667586 | ||
|
|
ffe2e50458 | ||
|
|
6ed6129b4f | ||
|
|
9030a8259f | ||
|
|
fdf76a25ab | ||
|
|
e7850a7f11 | ||
|
|
c1ea6a39a0 | ||
|
|
0a923b4ab3 | ||
|
|
e8a21f5276 | ||
|
|
71c4e876ef | ||
|
|
a63295a872 | ||
|
|
a3734d7e31 | ||
|
|
aa59d77c3b | ||
|
|
6eddb60db0 | ||
|
|
3bfd199497 | ||
|
|
0f6dd7b64e | ||
|
|
8ebeb9ade2 | ||
|
|
2ad44a453f | ||
|
|
3350c0a779 | ||
|
|
1070c020db | ||
|
|
cd2921a047 | ||
|
|
aa44eb639b | ||
|
|
7ad3d4e49c | ||
|
|
a6c048920e | ||
|
|
debc7442f2 | ||
|
|
73f2ee5484 | ||
|
|
597c00698d | ||
|
|
0c28ab92e6 | ||
|
|
3f602dde0f | ||
|
|
f7ec0bcfc2 | ||
|
|
d870cc5ad7 | ||
|
|
8603f0f9b1 | ||
|
|
0aa824b12f | ||
|
|
037d9bdde3 | ||
|
|
633ce92908 | ||
|
|
4a84921b31 | ||
|
|
add8b1df68 | ||
|
|
ed82bb968a | ||
|
|
0ea2771889 | ||
|
|
9ae680c639 | ||
|
|
46fdf8c819 | ||
|
|
4a4e87e971 | ||
|
|
e6f652ae12 | ||
|
|
6ce8de4b5f | ||
|
|
a0be7b3b92 | ||
|
|
45ef421b6b | ||
|
|
fedef7bda3 | ||
|
|
6ee0ba64c8 | ||
|
|
9909d40530 | ||
|
|
160fc63c72 | ||
|
|
78c803b4f3 | ||
|
|
1143923cdd | ||
|
|
d10dffed44 | ||
|
|
e7ac5a6adf | ||
|
|
fbb82e61e3 | ||
|
|
69749a88cd | ||
|
|
8e510d5afa | ||
|
|
b7f331afa3 | ||
|
|
0209de123b | ||
|
|
d26ee6e01e | ||
|
|
e99a148628 | ||
|
|
c2d4c8b95e | ||
|
|
48a461a629 | ||
|
|
24e2e601d5 | ||
|
|
3b63a46ca4 | ||
|
|
c63a0e88b7 | ||
|
|
1a4d733ec7 | ||
|
|
39e895c5ff | ||
|
|
d9e9e71aec | ||
|
|
4f352833a5 | ||
|
|
d03ae881fd | ||
|
|
960057cba0 | ||
|
|
d41d85766f | ||
|
|
3293877456 | ||
|
|
64e7524f36 | ||
|
|
076c76f4e4 | ||
|
|
ed0bdcc638 | ||
|
|
84ad81ee67 | ||
|
|
63aa08acbe | ||
|
|
3f1b4fb23a | ||
|
|
aaa373585c | ||
|
|
7b574f406b | ||
|
|
65651078e5 | ||
|
|
d2ff93c319 | ||
|
|
ac51d048a9 | ||
|
|
4483089d70 | ||
|
|
d6015ee211 | ||
|
|
81e086b5ac | ||
|
|
75b417489a | ||
|
|
e9b05e86b9 | ||
|
|
8ee78521fa | ||
|
|
c8d6f0cb82 | ||
|
|
9ac176d5a3 | ||
|
|
234f00bdd4 | ||
|
|
5a57b1a09b | ||
|
|
22f105c06d | ||
|
|
10f08ab9ec | ||
|
|
196cc82913 | ||
|
|
f9ee0dc7ee | ||
|
|
221613d4ea | ||
|
|
7526b6fce3 | ||
|
|
c161389a0f | ||
|
|
9d9676f620 | ||
|
|
ec6664f6d6 | ||
|
|
42085ff110 | ||
|
|
52ac6419da | ||
|
|
79f970e6de | ||
|
|
02bc9e9de1 | ||
|
|
7053546687 | ||
|
|
769b346682 | ||
|
|
c100a4b8d4 | ||
|
|
b952a30555 | ||
|
|
4495bf5706 | ||
|
|
c5091bfe00 | ||
|
|
9d1ab766a0 | ||
|
|
4130b07f88 | ||
|
|
40de7f6fe8 | ||
|
|
6c42a23550 | ||
|
|
fad20213e6 | ||
|
|
f59040d752 | ||
|
|
0be8fffc99 | ||
|
|
b114928459 | ||
|
|
319365fdf0 | ||
|
|
697a4669e1 | ||
|
|
b5f0dc95db | ||
|
|
90febaf717 | ||
|
|
1f6571b3de | ||
|
|
71817afbe9 | ||
|
|
8b98f60e3c | ||
|
|
09ac66388c | ||
|
|
c8554d218b | ||
|
|
27ddbeb01c | ||
|
|
92ea1c32d6 | ||
|
|
254b1e3df7 | ||
|
|
0ad3c031f4 | ||
|
|
0ccd490fcd | ||
|
|
1b2872eebc | ||
|
|
5ca63d0675 | ||
|
|
48e6f77c03 | ||
|
|
a8d4927e29 | ||
|
|
ecccfe0337 | ||
|
|
3ea48e8ebe | ||
|
|
5b7ec71fb7 | ||
|
|
efd83570bd | ||
|
|
89abef3518 | ||
|
|
6b81ceb060 | ||
|
|
71530781f3 | ||
|
|
6734c64976 | ||
|
|
94bc48dd78 |
@@ -24,7 +24,7 @@ matrix:
|
||||
- os: osx
|
||||
env: NAME="macos build"
|
||||
sudo: false
|
||||
osx_image: xcode10
|
||||
osx_image: xcode10.1
|
||||
install: "./.travis/macos/deps.sh"
|
||||
script: "./.travis/macos/build.sh"
|
||||
after_success: "./.travis/macos/upload.sh"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -o pipefail
|
||||
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.13
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.14
|
||||
export Qt5_DIR=$(brew --prefix)/opt/qt5
|
||||
export UNICORNDIR=$(pwd)/externals/unicorn
|
||||
export PATH="/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
@@ -104,90 +104,18 @@ endif()
|
||||
message(STATUS "Target architecture: ${ARCHITECTURE}")
|
||||
|
||||
|
||||
# Configure compilation flags
|
||||
# Configure C++ standard
|
||||
# ===========================
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
|
||||
|
||||
if (MINGW)
|
||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
||||
|
||||
if (MINGW_STATIC_BUILD)
|
||||
add_definitions(-DQT_STATICPLUGIN)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
# Silence "deprecation" warnings
|
||||
add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE /D_SCL_SECURE_NO_WARNINGS)
|
||||
# Avoid windows.h junk
|
||||
add_definitions(/DNOMINMAX)
|
||||
# Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
|
||||
add_definitions(/DWIN32_LEAN_AND_MEAN)
|
||||
|
||||
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
||||
|
||||
# Tweak optimization settings
|
||||
# As far as I can tell, there's no way to override the CMake defaults while leaving user
|
||||
# changes intact, so we'll just clobber everything and say sorry.
|
||||
message(STATUS "Cache compiler flags ignored, please edit CMakeLists.txt to change the flags.")
|
||||
|
||||
# /W3 - Level 3 warnings
|
||||
# /MP - Multi-threaded compilation
|
||||
# /Zi - Output debugging information
|
||||
# /Zo - enhanced debug info for optimized builds
|
||||
# /permissive- - enables stricter C++ standards conformance checks
|
||||
set(CMAKE_C_FLAGS "/W3 /MP /Zi /Zo /permissive-" CACHE STRING "" FORCE)
|
||||
# /EHsc - C++-only exception handling semantics
|
||||
# /Zc:throwingNew - let codegen assume `operator new` will never return null
|
||||
# /Zc:inline - let codegen omit inline functions in object files
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} /EHsc /std:c++latest /Zc:throwingNew,inline" CACHE STRING "" FORCE)
|
||||
|
||||
# /MDd - Multi-threaded Debug Runtime DLL
|
||||
set(CMAKE_C_FLAGS_DEBUG "/Od /MDd" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}" CACHE STRING "" FORCE)
|
||||
|
||||
# /O2 - Optimization level 2
|
||||
# /GS- - No stack buffer overflow checks
|
||||
# /MD - Multi-threaded runtime DLL
|
||||
set(CMAKE_C_FLAGS_RELEASE "/O2 /GS- /MD" CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE)
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
# Fix GCC C++17 and Boost.ICL incompatibility (needed to build dynarmic)
|
||||
# See https://bugzilla.redhat.com/show_bug.cgi?id=1485641#c1
|
||||
if (CMAKE_COMPILER_IS_GNUCC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-new-ttp-matching")
|
||||
endif()
|
||||
|
||||
# Set file offset size to 64 bits.
|
||||
#
|
||||
# On modern Unixes, this is typically already the case. The lone exception is
|
||||
# glibc, which may default to 32 bits. glibc allows this to be configured
|
||||
# by setting _FILE_OFFSET_BITS.
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
|
||||
add_definitions(-D_FILE_OFFSET_BITS=64)
|
||||
endif()
|
||||
|
||||
# CMake seems to only define _DEBUG on Windows
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
||||
|
||||
# System imported libraries
|
||||
# ======================
|
||||
|
||||
find_package(Boost 1.63.0 QUIET)
|
||||
find_package(Boost 1.66.0 QUIET)
|
||||
if (NOT Boost_FOUND)
|
||||
message(STATUS "Boost 1.63.0 or newer not found, falling back to externals")
|
||||
message(STATUS "Boost 1.66.0 or newer not found, falling back to externals")
|
||||
|
||||
set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost")
|
||||
set(Boost_NO_SYSTEM_PATHS OFF)
|
||||
@@ -332,25 +260,21 @@ endif()
|
||||
# Platform-specific library requirements
|
||||
# ======================================
|
||||
|
||||
IF (APPLE)
|
||||
find_library(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related
|
||||
if (APPLE)
|
||||
# Umbrella framework for everything GUI-related
|
||||
find_library(COCOA_LIBRARY Cocoa)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
|
||||
endif()
|
||||
ELSEIF (WIN32)
|
||||
elseif (WIN32)
|
||||
# WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista)
|
||||
add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600)
|
||||
set(PLATFORM_LIBRARIES winmm ws2_32)
|
||||
IF (MINGW)
|
||||
if (MINGW)
|
||||
# PSAPI is the Process Status API
|
||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
|
||||
ENDIF (MINGW)
|
||||
ELSEIF (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
||||
endif()
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
||||
set(PLATFORM_LIBRARIES rt)
|
||||
ENDIF (APPLE)
|
||||
endif()
|
||||
|
||||
# Setup a custom clang-format target (if clang-format can be found) that will run
|
||||
# against all the src files. This should be used before making a pull request.
|
||||
|
||||
@@ -73,6 +73,7 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/texture.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/other.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
|
||||
|
||||
2
externals/opus
vendored
2
externals/opus
vendored
Submodule externals/opus updated: b2871922a1...562f8ba555
@@ -1,18 +1,79 @@
|
||||
# Enable modules to include each other's files
|
||||
include_directories(.)
|
||||
|
||||
# CMake seems to only define _DEBUG on Windows
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
||||
|
||||
# Set compilation flags
|
||||
if (MSVC)
|
||||
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
||||
|
||||
# Silence "deprecation" warnings
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
|
||||
|
||||
# Avoid windows.h junk
|
||||
add_definitions(-DNOMINMAX)
|
||||
|
||||
# Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
|
||||
add_definitions(-DWIN32_LEAN_AND_MEAN)
|
||||
|
||||
# /W3 - Level 3 warnings
|
||||
# /MP - Multi-threaded compilation
|
||||
# /Zi - Output debugging information
|
||||
# /Zo - enhanced debug info for optimized builds
|
||||
# /permissive- - enables stricter C++ standards conformance checks
|
||||
# /EHsc - C++-only exception handling semantics
|
||||
# /Zc:throwingNew - let codegen assume `operator new` will never return null
|
||||
# /Zc:inline - let codegen omit inline functions in object files
|
||||
add_compile_options(/W3 /MP /Zi /Zo /permissive- /EHsc /std:c++latest /Zc:throwingNew,inline)
|
||||
|
||||
# /GS- - No stack buffer overflow checks
|
||||
add_compile_options("$<$<CONFIG:Release>:/GS->")
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
|
||||
else()
|
||||
add_compile_options("-Wno-attributes")
|
||||
|
||||
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||
add_compile_options("-stdlib=libc++")
|
||||
endif()
|
||||
|
||||
# Set file offset size to 64 bits.
|
||||
#
|
||||
# On modern Unixes, this is typically already the case. The lone exception is
|
||||
# glibc, which may default to 32 bits. glibc allows this to be configured
|
||||
# by setting _FILE_OFFSET_BITS.
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
|
||||
add_definitions(-D_FILE_OFFSET_BITS=64)
|
||||
endif()
|
||||
|
||||
if (MINGW)
|
||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
||||
|
||||
if (MINGW_STATIC_BUILD)
|
||||
add_definitions(-DQT_STATICPLUGIN)
|
||||
add_compile_options("-static")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(audio_core)
|
||||
add_subdirectory(video_core)
|
||||
add_subdirectory(input_common)
|
||||
add_subdirectory(tests)
|
||||
|
||||
if (ENABLE_SDL2)
|
||||
add_subdirectory(yuzu_cmd)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT)
|
||||
add_subdirectory(yuzu)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
add_subdirectory(web_service)
|
||||
endif()
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "common/ring_buffer.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifdef _WIN32
|
||||
#include <objbase.h>
|
||||
#endif
|
||||
|
||||
@@ -113,7 +113,7 @@ private:
|
||||
|
||||
CubebSink::CubebSink(std::string_view target_device_name) {
|
||||
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
|
||||
#ifdef _MSC_VER
|
||||
#ifdef _WIN32
|
||||
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
@@ -152,7 +152,7 @@ CubebSink::~CubebSink() {
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ private:
|
||||
cubeb_devid output_device{};
|
||||
std::vector<SinkStreamPtr> sink_streams;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifdef _WIN32
|
||||
u32 com_init_result = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -47,6 +47,7 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/texture.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/other.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
|
||||
@@ -91,10 +92,14 @@ add_library(common STATIC
|
||||
logging/text_formatter.cpp
|
||||
logging/text_formatter.h
|
||||
math_util.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
microprofile.cpp
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
misc.cpp
|
||||
page_table.cpp
|
||||
page_table.h
|
||||
param_package.cpp
|
||||
param_package.h
|
||||
quaternion.h
|
||||
@@ -113,6 +118,8 @@ add_library(common STATIC
|
||||
threadsafe_queue.h
|
||||
timer.cpp
|
||||
timer.h
|
||||
uint128.cpp
|
||||
uint128.h
|
||||
vector_math.h
|
||||
web_result.h
|
||||
)
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
/*
|
||||
* Abstract bitfield class
|
||||
@@ -108,15 +109,9 @@
|
||||
* symptoms.
|
||||
*/
|
||||
#pragma pack(1)
|
||||
template <std::size_t Position, std::size_t Bits, typename T>
|
||||
template <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag>
|
||||
struct BitField {
|
||||
private:
|
||||
// We hide the copy assigment operator here, because the default copy
|
||||
// assignment would copy the full storage value, rather than just the bits
|
||||
// relevant to this particular bit field.
|
||||
// We don't delete it because we want BitField to be trivially copyable.
|
||||
constexpr BitField& operator=(const BitField&) = default;
|
||||
|
||||
// UnderlyingType is T for non-enum types and the underlying type of T if
|
||||
// T is an enumeration. Note that T is wrapped within an enable_if in the
|
||||
// former case to workaround compile errors which arise when using
|
||||
@@ -127,6 +122,8 @@ private:
|
||||
// We store the value as the unsigned type to avoid undefined behaviour on value shifting
|
||||
using StorageType = std::make_unsigned_t<UnderlyingType>;
|
||||
|
||||
using StorageTypeWithEndian = typename AddEndian<StorageType, EndianTag>::type;
|
||||
|
||||
public:
|
||||
/// Constants to allow limited introspection of fields if needed
|
||||
static constexpr std::size_t position = Position;
|
||||
@@ -163,16 +160,20 @@ public:
|
||||
BitField(T val) = delete;
|
||||
BitField& operator=(T val) = delete;
|
||||
|
||||
// Force default constructor to be created
|
||||
// so that we can use this within unions
|
||||
constexpr BitField() = default;
|
||||
constexpr BitField() noexcept = default;
|
||||
|
||||
constexpr BitField(const BitField&) noexcept = default;
|
||||
constexpr BitField& operator=(const BitField&) noexcept = default;
|
||||
|
||||
constexpr BitField(BitField&&) noexcept = default;
|
||||
constexpr BitField& operator=(BitField&&) noexcept = default;
|
||||
|
||||
constexpr FORCE_INLINE operator T() const {
|
||||
return Value();
|
||||
}
|
||||
|
||||
constexpr FORCE_INLINE void Assign(const T& value) {
|
||||
storage = (storage & ~mask) | FormatValue(value);
|
||||
storage = (static_cast<StorageType>(storage) & ~mask) | FormatValue(value);
|
||||
}
|
||||
|
||||
constexpr T Value() const {
|
||||
@@ -184,7 +185,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
StorageType storage;
|
||||
StorageTypeWithEndian storage;
|
||||
|
||||
static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range");
|
||||
|
||||
@@ -195,3 +196,6 @@ private:
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField");
|
||||
};
|
||||
#pragma pack()
|
||||
|
||||
template <std::size_t Position, std::size_t Bits, typename T>
|
||||
using BitFieldBE = BitField<Position, Bits, T, BETag>;
|
||||
|
||||
@@ -40,10 +40,9 @@ using s64 = std::int64_t; ///< 64-bit signed int
|
||||
using f32 = float; ///< 32-bit floating point
|
||||
using f64 = double; ///< 64-bit floating point
|
||||
|
||||
// TODO: It would be nice to eventually replace these with strong types that prevent accidental
|
||||
// conversion between each other.
|
||||
using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
|
||||
using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
|
||||
using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
|
||||
using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
|
||||
using GPUVAddr = u64; ///< Represents a pointer in the GPU virtual address space.
|
||||
|
||||
using u128 = std::array<std::uint64_t, 2>;
|
||||
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/memory_hook.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
|
||||
MemoryHook::~MemoryHook() = default;
|
||||
|
||||
} // namespace Memory
|
||||
} // namespace Common
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* Memory hooks have two purposes:
|
||||
@@ -44,4 +44,4 @@ public:
|
||||
};
|
||||
|
||||
using MemoryHookPointer = std::shared_ptr<MemoryHook>;
|
||||
} // namespace Memory
|
||||
} // namespace Common
|
||||
31
src/common/page_table.cpp
Normal file
31
src/common/page_table.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/page_table.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
PageTable::PageTable(std::size_t page_size_in_bits) : page_size_in_bits{page_size_in_bits} {}
|
||||
|
||||
PageTable::~PageTable() = default;
|
||||
|
||||
void PageTable::Resize(std::size_t address_space_width_in_bits) {
|
||||
const std::size_t num_page_table_entries = 1ULL
|
||||
<< (address_space_width_in_bits - page_size_in_bits);
|
||||
|
||||
pointers.resize(num_page_table_entries);
|
||||
attributes.resize(num_page_table_entries);
|
||||
backing_addr.resize(num_page_table_entries);
|
||||
|
||||
// The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
|
||||
// vector size is subsequently decreased (via resize), the vector might not automatically
|
||||
// actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
|
||||
// 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
|
||||
|
||||
pointers.shrink_to_fit();
|
||||
attributes.shrink_to_fit();
|
||||
backing_addr.shrink_to_fit();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
84
src/common/page_table.h
Normal file
84
src/common/page_table.h
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
enum class PageType : u8 {
|
||||
/// Page is unmapped and should cause an access error.
|
||||
Unmapped,
|
||||
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
||||
Memory,
|
||||
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||
/// invalidation
|
||||
RasterizerCachedMemory,
|
||||
/// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
|
||||
Special,
|
||||
/// Page is allocated for use.
|
||||
Allocated,
|
||||
};
|
||||
|
||||
struct SpecialRegion {
|
||||
enum class Type {
|
||||
DebugHook,
|
||||
IODevice,
|
||||
} type;
|
||||
|
||||
MemoryHookPointer handler;
|
||||
|
||||
bool operator<(const SpecialRegion& other) const {
|
||||
return std::tie(type, handler) < std::tie(other.type, other.handler);
|
||||
}
|
||||
|
||||
bool operator==(const SpecialRegion& other) const {
|
||||
return std::tie(type, handler) == std::tie(other.type, other.handler);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
|
||||
* mimics the way a real CPU page table works.
|
||||
*/
|
||||
struct PageTable {
|
||||
explicit PageTable(std::size_t page_size_in_bits);
|
||||
~PageTable();
|
||||
|
||||
/**
|
||||
* Resizes the page table to be able to accomodate enough pages within
|
||||
* a given address space.
|
||||
*
|
||||
* @param address_space_width_in_bits The address size width in bits.
|
||||
*/
|
||||
void Resize(std::size_t address_space_width_in_bits);
|
||||
|
||||
/**
|
||||
* Vector of memory pointers backing each page. An entry can only be non-null if the
|
||||
* corresponding entry in the `attributes` vector is of type `Memory`.
|
||||
*/
|
||||
std::vector<u8*> pointers;
|
||||
|
||||
/**
|
||||
* Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
|
||||
* of type `Special`.
|
||||
*/
|
||||
boost::icl::interval_map<u64, std::set<SpecialRegion>> special_regions;
|
||||
|
||||
/**
|
||||
* Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
|
||||
* the corresponding entry in `pointers` MUST be set to null.
|
||||
*/
|
||||
std::vector<PageType> attributes;
|
||||
|
||||
std::vector<u64> backing_addr;
|
||||
|
||||
const std::size_t page_size_in_bits{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <cstdlib>
|
||||
#elif defined(__linux__)
|
||||
@@ -170,7 +172,7 @@ struct swap_struct_t {
|
||||
using swapped_t = swap_struct_t;
|
||||
|
||||
protected:
|
||||
T value = T();
|
||||
T value;
|
||||
|
||||
static T swap(T v) {
|
||||
return F::swap(v);
|
||||
@@ -605,52 +607,154 @@ struct swap_double_t {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct swap_enum_t {
|
||||
static_assert(std::is_enum_v<T>);
|
||||
using base = std::underlying_type_t<T>;
|
||||
|
||||
public:
|
||||
swap_enum_t() = default;
|
||||
swap_enum_t(const T& v) : value(swap(v)) {}
|
||||
|
||||
swap_enum_t& operator=(const T& v) {
|
||||
value = swap(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
return swap(value);
|
||||
}
|
||||
|
||||
explicit operator base() const {
|
||||
return static_cast<base>(swap(value));
|
||||
}
|
||||
|
||||
protected:
|
||||
T value{};
|
||||
// clang-format off
|
||||
using swap_t = std::conditional_t<
|
||||
std::is_same_v<base, u16>, swap_16_t<u16>, std::conditional_t<
|
||||
std::is_same_v<base, s16>, swap_16_t<s16>, std::conditional_t<
|
||||
std::is_same_v<base, u32>, swap_32_t<u32>, std::conditional_t<
|
||||
std::is_same_v<base, s32>, swap_32_t<s32>, std::conditional_t<
|
||||
std::is_same_v<base, u64>, swap_64_t<u64>, std::conditional_t<
|
||||
std::is_same_v<base, s64>, swap_64_t<s64>, void>>>>>>;
|
||||
// clang-format on
|
||||
static T swap(T x) {
|
||||
return static_cast<T>(swap_t::swap(static_cast<base>(x)));
|
||||
}
|
||||
};
|
||||
|
||||
struct SwapTag {}; // Use the different endianness from the system
|
||||
struct KeepTag {}; // Use the same endianness as the system
|
||||
|
||||
template <typename T, typename Tag>
|
||||
struct AddEndian;
|
||||
|
||||
// KeepTag specializations
|
||||
|
||||
template <typename T>
|
||||
struct AddEndian<T, KeepTag> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
// SwapTag specializations
|
||||
|
||||
template <>
|
||||
struct AddEndian<u8, SwapTag> {
|
||||
using type = u8;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<u16, SwapTag> {
|
||||
using type = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<u32, SwapTag> {
|
||||
using type = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<u64, SwapTag> {
|
||||
using type = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s8, SwapTag> {
|
||||
using type = s8;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s16, SwapTag> {
|
||||
using type = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s32, SwapTag> {
|
||||
using type = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s64, SwapTag> {
|
||||
using type = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<float, SwapTag> {
|
||||
using type = swap_struct_t<float, swap_float_t<float>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<double, SwapTag> {
|
||||
using type = swap_struct_t<double, swap_double_t<double>>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct AddEndian<T, SwapTag> {
|
||||
static_assert(std::is_enum_v<T>);
|
||||
using type = swap_enum_t<T>;
|
||||
};
|
||||
|
||||
// Alias LETag/BETag as KeepTag/SwapTag depending on the system
|
||||
#if COMMON_LITTLE_ENDIAN
|
||||
using u16_le = u16;
|
||||
using u32_le = u32;
|
||||
using u64_le = u64;
|
||||
|
||||
using s16_le = s16;
|
||||
using s32_le = s32;
|
||||
using s64_le = s64;
|
||||
using LETag = KeepTag;
|
||||
using BETag = SwapTag;
|
||||
|
||||
using float_le = float;
|
||||
using double_le = double;
|
||||
|
||||
using u64_be = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_be = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
using u32_be = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_be = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
using u16_be = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_be = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
using float_be = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_be = swap_struct_t<double, swap_double_t<double>>;
|
||||
#else
|
||||
|
||||
using u64_le = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_le = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
using u32_le = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_le = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
using u16_le = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_le = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
using float_le = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_le = swap_struct_t<double, swap_double_t<double>>;
|
||||
|
||||
using u16_be = u16;
|
||||
using u32_be = u32;
|
||||
using u64_be = u64;
|
||||
|
||||
using s16_be = s16;
|
||||
using s32_be = s32;
|
||||
using s64_be = s64;
|
||||
|
||||
using float_be = float;
|
||||
using double_be = double;
|
||||
using BETag = KeepTag;
|
||||
using LETag = SwapTag;
|
||||
|
||||
#endif
|
||||
|
||||
// Aliases for LE types
|
||||
using u16_le = AddEndian<u16, LETag>::type;
|
||||
using u32_le = AddEndian<u32, LETag>::type;
|
||||
using u64_le = AddEndian<u64, LETag>::type;
|
||||
|
||||
using s16_le = AddEndian<s16, LETag>::type;
|
||||
using s32_le = AddEndian<s32, LETag>::type;
|
||||
using s64_le = AddEndian<s64, LETag>::type;
|
||||
|
||||
template <typename T>
|
||||
using enum_le = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, LETag>::type>;
|
||||
|
||||
using float_le = AddEndian<float, LETag>::type;
|
||||
using double_le = AddEndian<double, LETag>::type;
|
||||
|
||||
// Aliases for BE types
|
||||
using u16_be = AddEndian<u16, BETag>::type;
|
||||
using u32_be = AddEndian<u32, BETag>::type;
|
||||
using u64_be = AddEndian<u64, BETag>::type;
|
||||
|
||||
using s16_be = AddEndian<s16, BETag>::type;
|
||||
using s32_be = AddEndian<s32, BETag>::type;
|
||||
using s64_be = AddEndian<s64, BETag>::type;
|
||||
|
||||
template <typename T>
|
||||
using enum_be = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, BETag>::type>;
|
||||
|
||||
using float_be = AddEndian<float, BETag>::type;
|
||||
using double_be = AddEndian<double, BETag>::type;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -111,8 +110,9 @@ struct ThreadQueueList {
|
||||
}
|
||||
|
||||
void remove(Priority priority, const T& thread_id) {
|
||||
Queue* cur = &queues[priority];
|
||||
boost::remove_erase(cur->data, thread_id);
|
||||
Queue* const cur = &queues[priority];
|
||||
const auto iter = std::remove(cur->data.begin(), cur->data.end(), thread_id);
|
||||
cur->data.erase(iter, cur->data.end());
|
||||
}
|
||||
|
||||
void rotate(Priority priority) {
|
||||
|
||||
45
src/common/uint128.cpp
Normal file
45
src/common/uint128.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
|
||||
#pragma intrinsic(_umul128)
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include "common/uint128.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
u128 Multiply64Into128(u64 a, u64 b) {
|
||||
u128 result;
|
||||
#ifdef _MSC_VER
|
||||
result[0] = _umul128(a, b, &result[1]);
|
||||
#else
|
||||
unsigned __int128 tmp = a;
|
||||
tmp *= b;
|
||||
std::memcpy(&result, &tmp, sizeof(u128));
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor) {
|
||||
u64 remainder = dividend[0] % divisor;
|
||||
u64 accum = dividend[0] / divisor;
|
||||
if (dividend[1] == 0)
|
||||
return {accum, remainder};
|
||||
// We ignore dividend[1] / divisor as that overflows
|
||||
const u64 first_segment = (dividend[1] % divisor) << 32;
|
||||
accum += (first_segment / divisor) << 32;
|
||||
const u64 second_segment = (first_segment % divisor) << 32;
|
||||
accum += (second_segment / divisor);
|
||||
remainder += second_segment % divisor;
|
||||
if (remainder >= divisor) {
|
||||
accum++;
|
||||
remainder -= divisor;
|
||||
}
|
||||
return {accum, remainder};
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
19
src/common/uint128.h
Normal file
19
src/common/uint128.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
// This function multiplies 2 u64 values and produces a u128 value;
|
||||
u128 Multiply64Into128(u64 a, u64 b);
|
||||
|
||||
// This function divides a u128 by a u32 value and produces two u64 values:
|
||||
// the result of division and the remainder
|
||||
std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
|
||||
|
||||
} // namespace Common
|
||||
@@ -31,6 +31,8 @@ add_library(core STATIC
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/cheat_engine.cpp
|
||||
file_sys/cheat_engine.h
|
||||
file_sys/content_archive.cpp
|
||||
file_sys/content_archive.h
|
||||
file_sys/control_metadata.cpp
|
||||
@@ -68,6 +70,8 @@ add_library(core STATIC
|
||||
file_sys/system_archive/ng_word.h
|
||||
file_sys/system_archive/system_archive.cpp
|
||||
file_sys/system_archive/system_archive.h
|
||||
file_sys/system_archive/system_version.cpp
|
||||
file_sys/system_archive/system_version.h
|
||||
file_sys/vfs.cpp
|
||||
file_sys/vfs.h
|
||||
file_sys/vfs_concat.cpp
|
||||
@@ -107,6 +111,8 @@ add_library(core STATIC
|
||||
hle/kernel/client_port.h
|
||||
hle/kernel/client_session.cpp
|
||||
hle/kernel/client_session.h
|
||||
hle/kernel/code_set.cpp
|
||||
hle/kernel/code_set.h
|
||||
hle/kernel/errors.h
|
||||
hle/kernel/handle_table.cpp
|
||||
hle/kernel/handle_table.h
|
||||
@@ -419,8 +425,6 @@ add_library(core STATIC
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/elf.cpp
|
||||
loader/elf.h
|
||||
loader/linker.cpp
|
||||
loader/linker.h
|
||||
loader/loader.cpp
|
||||
loader/loader.h
|
||||
loader/nax.cpp
|
||||
@@ -437,8 +441,6 @@ add_library(core STATIC
|
||||
loader/xci.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
memory_setup.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
@@ -119,7 +120,7 @@ public:
|
||||
return std::max(parent.core_timing.GetDowncount(), 0);
|
||||
}
|
||||
u64 GetCNTPCT() override {
|
||||
return parent.core_timing.GetTicks();
|
||||
return Timing::CpuCyclesToClockCycles(parent.core_timing.GetTicks());
|
||||
}
|
||||
|
||||
ARM_Dynarmic& parent;
|
||||
@@ -151,7 +152,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
config.tpidr_el0 = &cb->tpidr_el0;
|
||||
config.dczid_el0 = 4;
|
||||
config.ctr_el0 = 0x8444c004;
|
||||
config.cntfrq_el0 = 19200000; // Value from fusee.
|
||||
config.cntfrq_el0 = Timing::CNTFREQ;
|
||||
|
||||
// Unpredictable instructions
|
||||
config.define_unpredictable_behaviour = true;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ private:
|
||||
Timing::CoreTiming& core_timing;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
|
||||
Memory::PageTable* current_page_table = nullptr;
|
||||
Common::PageTable* current_page_table = nullptr;
|
||||
};
|
||||
|
||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
|
||||
|
||||
@@ -32,11 +32,13 @@
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "file_sys/cheat_engine.h"
|
||||
#include "frontend/applets/profile_select.h"
|
||||
#include "frontend/applets/software_keyboard.h"
|
||||
#include "frontend/applets/web_browser.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/gpu_asynch.h"
|
||||
#include "video_core/gpu_synch.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
@@ -78,6 +80,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
return vfs->OpenFile(path, FileSys::Mode::Read);
|
||||
}
|
||||
struct System::Impl {
|
||||
explicit Impl(System& system) : kernel{system} {}
|
||||
|
||||
Cpu& CurrentCpuCore() {
|
||||
return cpu_core_manager.GetCurrentCore();
|
||||
@@ -95,7 +98,7 @@ struct System::Impl {
|
||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||
|
||||
core_timing.Initialize();
|
||||
kernel.Initialize(core_timing);
|
||||
kernel.Initialize();
|
||||
|
||||
const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch());
|
||||
@@ -114,7 +117,7 @@ struct System::Impl {
|
||||
if (web_browser == nullptr)
|
||||
web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
||||
|
||||
auto main_process = Kernel::Process::Create(kernel, "main");
|
||||
auto main_process = Kernel::Process::Create(system, "main");
|
||||
kernel.MakeCurrentProcess(main_process.get());
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
@@ -128,10 +131,16 @@ struct System::Impl {
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
|
||||
gpu_core = std::make_unique<Tegra::GPU>(system, renderer->Rasterizer());
|
||||
is_powered_on = true;
|
||||
|
||||
if (Settings::values.use_asynchronous_gpu_emulation) {
|
||||
gpu_core = std::make_unique<VideoCommon::GPUAsynch>(system, *renderer);
|
||||
} else {
|
||||
gpu_core = std::make_unique<VideoCommon::GPUSynch>(system, *renderer);
|
||||
}
|
||||
|
||||
cpu_core_manager.Initialize(system);
|
||||
is_powered_on = true;
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
// Reset counters and set time origin to current frame
|
||||
@@ -182,13 +191,13 @@ struct System::Impl {
|
||||
|
||||
void Shutdown() {
|
||||
// Log last frame performance stats
|
||||
auto perf_results = GetAndResetPerfStats();
|
||||
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed",
|
||||
perf_results.emulation_speed * 100.0);
|
||||
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate",
|
||||
perf_results.game_fps);
|
||||
Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
|
||||
perf_results.frametime * 1000.0);
|
||||
const auto perf_results = GetAndResetPerfStats();
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed",
|
||||
perf_results.emulation_speed * 100.0);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate",
|
||||
perf_results.game_fps);
|
||||
telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
|
||||
perf_results.frametime * 1000.0);
|
||||
|
||||
is_powered_on = false;
|
||||
|
||||
@@ -197,6 +206,7 @@ struct System::Impl {
|
||||
GDBStub::Shutdown();
|
||||
Service::Shutdown();
|
||||
service_manager.reset();
|
||||
cheat_engine.reset();
|
||||
telemetry_session.reset();
|
||||
gpu_core.reset();
|
||||
|
||||
@@ -247,6 +257,8 @@ struct System::Impl {
|
||||
CpuCoreManager cpu_core_manager;
|
||||
bool is_powered_on = false;
|
||||
|
||||
std::unique_ptr<FileSys::CheatEngine> cheat_engine;
|
||||
|
||||
/// Frontend applets
|
||||
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
|
||||
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
|
||||
@@ -265,7 +277,7 @@ struct System::Impl {
|
||||
Core::FrameLimiter frame_limiter;
|
||||
};
|
||||
|
||||
System::System() : impl{std::make_unique<Impl>()} {}
|
||||
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
||||
System::~System() = default;
|
||||
|
||||
Cpu& System::CurrentCpuCore() {
|
||||
@@ -445,6 +457,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
|
||||
return impl->debug_context.get();
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
|
||||
const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end) {
|
||||
impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id,
|
||||
code_region_start, code_region_end);
|
||||
}
|
||||
|
||||
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
|
||||
impl->virtual_filesystem = std::move(vfs);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class WebBrowserApplet;
|
||||
} // namespace Core::Frontend
|
||||
|
||||
namespace FileSys {
|
||||
class CheatList;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -253,6 +254,9 @@ public:
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
|
||||
|
||||
void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end);
|
||||
|
||||
void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet);
|
||||
|
||||
const Frontend::ProfileSelectApplet& GetProfileSelector() const;
|
||||
@@ -293,10 +297,6 @@ inline ARM_Interface& CurrentArmInterface() {
|
||||
return System::GetInstance().CurrentArmInterface();
|
||||
}
|
||||
|
||||
inline TelemetrySession& Telemetry() {
|
||||
return System::GetInstance().TelemetrySession();
|
||||
}
|
||||
|
||||
inline Kernel::Process* CurrentProcess() {
|
||||
return System::GetInstance().CurrentProcess();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#endif
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
@@ -49,9 +50,9 @@ bool CpuBarrier::Rendezvous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
CpuBarrier& cpu_barrier, std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_timing{core_timing}, core_index{core_index} {
|
||||
Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
|
||||
std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_timing{system.CoreTiming()}, core_index{core_index} {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(core_timing, exclusive_monitor, core_index);
|
||||
@@ -63,7 +64,7 @@ Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
arm_interface = std::make_unique<ARM_Unicorn>(core_timing);
|
||||
}
|
||||
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface);
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface);
|
||||
}
|
||||
|
||||
Cpu::~Cpu() = default;
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace Kernel {
|
||||
class Scheduler;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
@@ -45,8 +49,8 @@ private:
|
||||
|
||||
class Cpu {
|
||||
public:
|
||||
Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
CpuBarrier& cpu_barrier, std::size_t core_index);
|
||||
Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
|
||||
std::size_t core_index);
|
||||
~Cpu();
|
||||
|
||||
void RunLoop(bool tight_loop = true);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/uint128.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
@@ -60,4 +61,9 @@ s64 nsToCycles(u64 ns) {
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks) {
|
||||
const u128 temporal = Common::Multiply64Into128(ticks, CNTFREQ);
|
||||
return Common::Divide128On32(temporal, static_cast<u32>(BASE_CLOCK_RATE)).first;
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Core::Timing {
|
||||
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
|
||||
// The exact value used is of course unverified.
|
||||
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
|
||||
constexpr u64 CNTFREQ = 19200000; // Value from fusee.
|
||||
|
||||
inline s64 msToCycles(int ms) {
|
||||
// since ms is int there is no way to overflow
|
||||
@@ -61,4 +62,6 @@ inline u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks);
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -27,8 +27,7 @@ void CpuCoreManager::Initialize(System& system) {
|
||||
exclusive_monitor = Cpu::MakeExclusiveMonitor(cores.size());
|
||||
|
||||
for (std::size_t index = 0; index < cores.size(); ++index) {
|
||||
cores[index] =
|
||||
std::make_unique<Cpu>(system.CoreTiming(), *exclusive_monitor, *barrier, index);
|
||||
cores[index] = std::make_unique<Cpu>(system, *exclusive_monitor, *barrier, index);
|
||||
}
|
||||
|
||||
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||
|
||||
490
src/core/file_sys/cheat_engine.cpp
Normal file
490
src/core/file_sys/cheat_engine.cpp
Normal file
@@ -0,0 +1,490 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <locale>
|
||||
#include "common/hex_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
|
||||
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
|
||||
|
||||
u64 Cheat::Address() const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u64));
|
||||
return Common::swap64(out) & 0xFFFFFFFFFF;
|
||||
}
|
||||
|
||||
u64 Cheat::ValueWidth(u64 offset) const {
|
||||
return Value(offset, width);
|
||||
}
|
||||
|
||||
u64 Cheat::Value(u64 offset, u64 width) const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data() + offset, sizeof(u64));
|
||||
out = Common::swap64(out);
|
||||
if (width == 8)
|
||||
return out;
|
||||
return out & ((1ull << (width * CHAR_BIT)) - 1);
|
||||
}
|
||||
|
||||
u32 Cheat::KeypadValue() const {
|
||||
u32 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u32));
|
||||
return Common::swap32(out) & 0x0FFFFFFF;
|
||||
}
|
||||
|
||||
void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
|
||||
VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
|
||||
this->main_region_begin = main_begin;
|
||||
this->main_region_end = main_end;
|
||||
this->heap_region_begin = heap_begin;
|
||||
this->heap_region_end = heap_end;
|
||||
this->writer = writer;
|
||||
this->reader = reader;
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
|
||||
|
||||
void CheatList::Execute() {
|
||||
MICROPROFILE_SCOPE(Cheat_Engine);
|
||||
|
||||
std::fill(scratch.begin(), scratch.end(), 0);
|
||||
in_standard = false;
|
||||
for (std::size_t i = 0; i < master_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(master_list[i].second);
|
||||
}
|
||||
|
||||
in_standard = true;
|
||||
for (std::size_t i = 0; i < standard_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(standard_list[i].second);
|
||||
}
|
||||
}
|
||||
|
||||
CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
|
||||
: master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
|
||||
|
||||
bool CheatList::EvaluateConditional(const Cheat& cheat) const {
|
||||
using ComparisonFunction = bool (*)(u64, u64);
|
||||
constexpr std::array<ComparisonFunction, 6> comparison_functions{
|
||||
[](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
|
||||
[](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
|
||||
[](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
|
||||
};
|
||||
|
||||
if (cheat.type == CodeType::ConditionalInput) {
|
||||
const auto applet_resource =
|
||||
system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
|
||||
if (applet_resource == nullptr) {
|
||||
LOG_WARNING(
|
||||
Common_Filesystem,
|
||||
"Attempted to evaluate input conditional, but applet resource is not initialized!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto press_state =
|
||||
applet_resource
|
||||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
|
||||
.GetAndResetPressState();
|
||||
return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
|
||||
}
|
||||
|
||||
ASSERT(cheat.type == CodeType::Conditional);
|
||||
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
|
||||
auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
|
||||
const auto addr = cheat.Address() + offset;
|
||||
|
||||
return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::ProcessBlockPairs(const Block& block) {
|
||||
block_pairs.clear();
|
||||
|
||||
u64 scope = 0;
|
||||
std::map<u64, u64> pairs;
|
||||
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
const auto& cheat = block[i];
|
||||
|
||||
switch (cheat.type) {
|
||||
case CodeType::Conditional:
|
||||
case CodeType::ConditionalInput:
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
break;
|
||||
case CodeType::EndConditional: {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
break;
|
||||
}
|
||||
case CodeType::Loop: {
|
||||
if (cheat.end_of_loop) {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
} else {
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheatList::WriteImmediate(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = cheat.Address() + offset + register_3;
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
|
||||
cheat.Value(8, cheat.width));
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditional(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
void CheatList::EndConditional(const Cheat& cheat) {
|
||||
LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
|
||||
}
|
||||
|
||||
void CheatList::Loop(const Cheat& cheat) {
|
||||
if (cheat.end_of_loop.Value())
|
||||
ASSERT(!cheat.end_of_loop.Value());
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
ASSERT(iter->first < iter->second);
|
||||
|
||||
const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32)));
|
||||
for (s32 i = initial_value; i >= 0; --i) {
|
||||
register_3 = static_cast<u64>(i);
|
||||
for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
|
||||
current_index = c;
|
||||
ExecuteSingleCheat(
|
||||
(in_standard ? standard_list : master_list)[current_block].second[c]);
|
||||
}
|
||||
}
|
||||
|
||||
current_index = iter->second;
|
||||
}
|
||||
|
||||
void CheatList::LoadImmediate(const Cheat& cheat) {
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
|
||||
cheat.Value(4, 8));
|
||||
register_3 = cheat.Value(4, 8);
|
||||
}
|
||||
|
||||
void CheatList::LoadIndexed(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
|
||||
LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
|
||||
cheat.register_3, addr);
|
||||
register_3 = reader(cheat.width, SanitizeAddress(addr));
|
||||
}
|
||||
|
||||
void CheatList::StoreIndexed(const Cheat& cheat) {
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr =
|
||||
register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
|
||||
cheat.Value(4, cheat.width), addr);
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::RegisterArithmetic(const Cheat& cheat) {
|
||||
using ArithmeticFunction = u64 (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
|
||||
[](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
|
||||
[](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
|
||||
[](u64 a, u64 b) { return a >> b; },
|
||||
};
|
||||
|
||||
using ArithmeticOverflowCheck = bool (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
|
||||
};
|
||||
|
||||
static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
|
||||
"Missing or have extra arithmetic overflow checks compared to functions!");
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
|
||||
auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
auto* overflow_function =
|
||||
arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
|
||||
cheat.register_3, cheat.ValueWidth(4));
|
||||
|
||||
if (overflow_function(register_3, cheat.ValueWidth(4))) {
|
||||
LOG_WARNING(Common_Filesystem,
|
||||
"overflow will occur when performing arithmetic operation={:02X} with operands "
|
||||
"a={:016X}, b={:016X}!",
|
||||
static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
register_3 = function(register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditionalInput(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat))
|
||||
return;
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
VAddr CheatList::SanitizeAddress(VAddr in) const {
|
||||
if ((in < main_region_begin || in >= main_region_end) &&
|
||||
(in < heap_region_begin || in >= heap_region_end)) {
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this persists, "
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if "
|
||||
"the game has not properly set up yet.",
|
||||
in);
|
||||
return 0; ///< Invalid addresses will hard crash
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
|
||||
using CheatOperationFunction = void (CheatList::*)(const Cheat&);
|
||||
constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
|
||||
&CheatList::WriteImmediate, &CheatList::BeginConditional,
|
||||
&CheatList::EndConditional, &CheatList::Loop,
|
||||
&CheatList::LoadImmediate, &CheatList::LoadIndexed,
|
||||
&CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
|
||||
&CheatList::BeginConditionalInput,
|
||||
};
|
||||
|
||||
const auto index = static_cast<u8>(cheat.type.Value());
|
||||
ASSERT(index < sizeof(cheat_operation_functions));
|
||||
const auto op = cheat_operation_functions[index];
|
||||
(this->*op)(cheat);
|
||||
}
|
||||
|
||||
void CheatList::ExecuteBlock(const Block& block) {
|
||||
encountered_loops.clear();
|
||||
|
||||
ProcessBlockPairs(block);
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
current_index = i;
|
||||
ExecuteSingleCheat(block[i]);
|
||||
i = current_index;
|
||||
}
|
||||
}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const {
|
||||
return {system, std::move(master), std::move(standard)};
|
||||
}
|
||||
|
||||
TextCheatParser::~TextCheatParser() = default;
|
||||
|
||||
CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const {
|
||||
std::stringstream ss;
|
||||
ss.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string stream_line;
|
||||
while (std::getline(ss, stream_line)) {
|
||||
// Remove a trailing \r
|
||||
if (!stream_line.empty() && stream_line.back() == '\r')
|
||||
stream_line.pop_back();
|
||||
lines.push_back(std::move(stream_line));
|
||||
}
|
||||
|
||||
CheatList::ProgramSegment master_list;
|
||||
CheatList::ProgramSegment standard_list;
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
auto line = lines[i];
|
||||
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
const auto master = line[0] == '{';
|
||||
const auto begin = master ? line.find('{') : line.find('[');
|
||||
const auto end = master ? line.rfind('}') : line.rfind(']');
|
||||
|
||||
ASSERT(begin != std::string::npos && end != std::string::npos);
|
||||
|
||||
const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
|
||||
CheatList::Block block{};
|
||||
|
||||
while (i < lines.size() - 1) {
|
||||
line = lines[++i];
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.size() < 8)
|
||||
continue;
|
||||
|
||||
Cheat out{};
|
||||
out.raw = ParseSingleLineCheat(line);
|
||||
block.push_back(out);
|
||||
}
|
||||
|
||||
(master ? master_list : standard_list).emplace_back(patch_name, block);
|
||||
}
|
||||
}
|
||||
|
||||
return MakeCheatList(system, master_list, standard_list);
|
||||
}
|
||||
|
||||
std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
|
||||
std::array<u8, 16> out{};
|
||||
|
||||
if (line.size() < 8)
|
||||
return out;
|
||||
|
||||
const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
|
||||
std::memcpy(out.data(), word1.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 17 || line[8] != ' ')
|
||||
return out;
|
||||
|
||||
const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
|
||||
std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 26 || line[17] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
|
||||
type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
|
||||
std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
|
||||
std::memset(out.data() + 4, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
|
||||
std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 35 || line[26] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
|
||||
std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
|
||||
std::memset(out.data() + 8, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
|
||||
std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
u64 MemoryReadImpl(u32 width, VAddr addr) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return Memory::Read8(addr);
|
||||
case 2:
|
||||
return Memory::Read16(addr);
|
||||
case 4:
|
||||
return Memory::Read32(addr);
|
||||
case 8:
|
||||
return Memory::Read64(addr);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
Memory::Write8(addr, static_cast<u8>(value));
|
||||
break;
|
||||
case 2:
|
||||
Memory::Write16(addr, static_cast<u16>(value));
|
||||
break;
|
||||
case 4:
|
||||
Memory::Write32(addr, static_cast<u32>(value));
|
||||
break;
|
||||
case 8:
|
||||
Memory::Write64(addr, value);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_,
|
||||
const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end)
|
||||
: cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} {
|
||||
event = core_timing.RegisterEvent(
|
||||
"CheatEngine::FrameCallback::" + build_id,
|
||||
[this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
|
||||
|
||||
const auto& vm_manager = system.CurrentProcess()->VMManager();
|
||||
for (auto& list : this->cheats) {
|
||||
list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
|
||||
code_region_end, vm_manager.GetHeapRegionEndAddress(),
|
||||
&MemoryWriteImpl, &MemoryReadImpl);
|
||||
}
|
||||
}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
core_timing.UnscheduleEvent(event, 0);
|
||||
}
|
||||
|
||||
void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
|
||||
for (auto& list : cheats) {
|
||||
list.Execute();
|
||||
}
|
||||
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
234
src/core/file_sys/cheat_engine.h
Normal file
234
src/core/file_sys/cheat_engine.h
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class CodeType : u32 {
|
||||
// 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Writes a T sized value Y to the address A added to the value of register R in memory domain M
|
||||
WriteImmediate = 0,
|
||||
|
||||
// 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Compares the T sized value Y to the value at address A in memory domain M using the
|
||||
// conditional function C. If success, continues execution. If failure, jumps to the matching
|
||||
// EndConditional statement.
|
||||
Conditional = 1,
|
||||
|
||||
// 20000000
|
||||
// Terminates a Conditional or ConditionalInput block.
|
||||
EndConditional = 2,
|
||||
|
||||
// 300R0000 VVVVVVVV
|
||||
// Starts looping V times, storing the current count in register R.
|
||||
// Loop block is terminated with a matching 310R0000.
|
||||
Loop = 3,
|
||||
|
||||
// 400R0000 VVVVVVVV VVVVVVVV
|
||||
// Sets the value of register R to the value V.
|
||||
LoadImmediate = 4,
|
||||
|
||||
// 5TMRI0AA AAAAAAAA
|
||||
// Sets the value of register R to the value of width T at address A in memory domain M, with
|
||||
// the current value of R added to the address if I == 1.
|
||||
LoadIndexed = 5,
|
||||
|
||||
// 6T0RIFG0 VVVVVVVV VVVVVVVV
|
||||
// Writes the value V of width T to the memory address stored in register R. Adds the value of
|
||||
// register G to the final calculation if F is nonzero. Increments the value of register R by T
|
||||
// after operation if I is nonzero.
|
||||
StoreIndexed = 6,
|
||||
|
||||
// 7T0RA000 VVVVVVVV
|
||||
// Performs the arithmetic operation A on the value in register R and the value V of width T,
|
||||
// storing the result in register R.
|
||||
RegisterArithmetic = 7,
|
||||
|
||||
// 8KKKKKKK
|
||||
// Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
|
||||
// execution continues. If none are, execution skips to the next EndConditional command.
|
||||
ConditionalInput = 8,
|
||||
};
|
||||
|
||||
enum class MemoryType : u32 {
|
||||
// Addressed relative to start of main NSO
|
||||
MainNSO = 0,
|
||||
|
||||
// Addressed relative to start of heap
|
||||
Heap = 1,
|
||||
};
|
||||
|
||||
enum class ArithmeticOp : u32 {
|
||||
Add = 0,
|
||||
Sub = 1,
|
||||
Mult = 2,
|
||||
LShift = 3,
|
||||
RShift = 4,
|
||||
};
|
||||
|
||||
enum class ComparisonOp : u32 {
|
||||
GreaterThan = 1,
|
||||
GreaterThanEqual = 2,
|
||||
LessThan = 3,
|
||||
LessThanEqual = 4,
|
||||
Equal = 5,
|
||||
Inequal = 6,
|
||||
};
|
||||
|
||||
union Cheat {
|
||||
std::array<u8, 16> raw;
|
||||
|
||||
BitField<4, 4, CodeType> type;
|
||||
BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
|
||||
BitField<0, 4, u32> end_of_loop;
|
||||
BitField<12, 4, MemoryType> memory_type;
|
||||
BitField<8, 4, u32> register_3;
|
||||
BitField<8, 4, ComparisonOp> comparison_op;
|
||||
BitField<20, 4, u32> load_from_register;
|
||||
BitField<20, 4, u32> increment_register;
|
||||
BitField<20, 4, ArithmeticOp> arithmetic_op;
|
||||
BitField<16, 4, u32> add_additional_register;
|
||||
BitField<28, 4, u32> register_6;
|
||||
|
||||
u64 Address() const;
|
||||
u64 ValueWidth(u64 offset) const;
|
||||
u64 Value(u64 offset, u64 width) const;
|
||||
u32 KeypadValue() const;
|
||||
};
|
||||
|
||||
class CheatParser;
|
||||
|
||||
// Represents a full collection of cheats for a game. The Execute function should be called every
|
||||
// interval that all cheats should be executed. Clients should not directly instantiate this class
|
||||
// (hence private constructor), they should instead receive an instance from CheatParser, which
|
||||
// guarantees the list is always in an acceptable state.
|
||||
class CheatList {
|
||||
public:
|
||||
friend class CheatParser;
|
||||
|
||||
using Block = std::vector<Cheat>;
|
||||
using ProgramSegment = std::vector<std::pair<std::string, Block>>;
|
||||
|
||||
// (width in bytes, address, value)
|
||||
using MemoryWriter = void (*)(u32, VAddr, u64);
|
||||
// (width in bytes, address) -> value
|
||||
using MemoryReader = u64 (*)(u32, VAddr);
|
||||
|
||||
void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
|
||||
MemoryWriter writer, MemoryReader reader);
|
||||
|
||||
void Execute();
|
||||
|
||||
private:
|
||||
CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard);
|
||||
|
||||
void ProcessBlockPairs(const Block& block);
|
||||
void ExecuteSingleCheat(const Cheat& cheat);
|
||||
|
||||
void ExecuteBlock(const Block& block);
|
||||
|
||||
bool EvaluateConditional(const Cheat& cheat) const;
|
||||
|
||||
// Individual cheat operations
|
||||
void WriteImmediate(const Cheat& cheat);
|
||||
void BeginConditional(const Cheat& cheat);
|
||||
void EndConditional(const Cheat& cheat);
|
||||
void Loop(const Cheat& cheat);
|
||||
void LoadImmediate(const Cheat& cheat);
|
||||
void LoadIndexed(const Cheat& cheat);
|
||||
void StoreIndexed(const Cheat& cheat);
|
||||
void RegisterArithmetic(const Cheat& cheat);
|
||||
void BeginConditionalInput(const Cheat& cheat);
|
||||
|
||||
VAddr SanitizeAddress(VAddr in) const;
|
||||
|
||||
// Master Codes are defined as codes that cannot be disabled and are run prior to all
|
||||
// others.
|
||||
ProgramSegment master_list;
|
||||
// All other codes
|
||||
ProgramSegment standard_list;
|
||||
|
||||
bool in_standard = false;
|
||||
|
||||
// 16 (0x0-0xF) scratch registers that can be used by cheats
|
||||
std::array<u64, 16> scratch{};
|
||||
|
||||
MemoryWriter writer = nullptr;
|
||||
MemoryReader reader = nullptr;
|
||||
|
||||
u64 main_region_begin{};
|
||||
u64 heap_region_begin{};
|
||||
u64 main_region_end{};
|
||||
u64 heap_region_end{};
|
||||
|
||||
u64 current_block{};
|
||||
// The current index of the cheat within the current Block
|
||||
u64 current_index{};
|
||||
|
||||
// The 'stack' of the program. When a conditional or loop statement is encountered, its index is
|
||||
// pushed onto this queue. When a end block is encountered, the condition is checked.
|
||||
std::map<u64, u64> block_pairs;
|
||||
|
||||
std::set<u64> encountered_loops;
|
||||
|
||||
const Core::System* system;
|
||||
};
|
||||
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
|
||||
// CheatList object, that can be used for execution.
|
||||
class CheatParser {
|
||||
public:
|
||||
virtual ~CheatParser();
|
||||
|
||||
virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
|
||||
|
||||
protected:
|
||||
CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const;
|
||||
};
|
||||
|
||||
// CheatParser implementation that parses text files
|
||||
class TextCheatParser final : public CheatParser {
|
||||
public:
|
||||
~TextCheatParser() override;
|
||||
|
||||
CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
|
||||
|
||||
private:
|
||||
std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
|
||||
};
|
||||
|
||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
|
||||
class CheatEngine final {
|
||||
public:
|
||||
CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end);
|
||||
~CheatEngine();
|
||||
|
||||
private:
|
||||
void FrameCallback(u64 userdata, s64 cycles_late);
|
||||
|
||||
std::vector<CheatList> cheats;
|
||||
|
||||
Core::Timing::EventType* event;
|
||||
Core::Timing::CoreTiming& core_timing;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -24,13 +24,26 @@ namespace FileSys {
|
||||
|
||||
union NCASectionHeader;
|
||||
|
||||
/// Describes the type of content within an NCA archive.
|
||||
enum class NCAContentType : u8 {
|
||||
/// Executable-related data
|
||||
Program = 0,
|
||||
|
||||
/// Metadata.
|
||||
Meta = 1,
|
||||
|
||||
/// Access control data.
|
||||
Control = 2,
|
||||
|
||||
/// Information related to the game manual
|
||||
/// e.g. Legal information, etc.
|
||||
Manual = 3,
|
||||
|
||||
/// System data.
|
||||
Data = 4,
|
||||
Data_Unknown5 = 5, ///< Seems to be used on some system archives
|
||||
|
||||
/// Data that can be accessed by applications.
|
||||
PublicData = 5,
|
||||
};
|
||||
|
||||
enum class NCASectionCryptoType : u8 {
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace FileSys {
|
||||
constexpr ResultCode ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1};
|
||||
constexpr ResultCode ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002};
|
||||
constexpr ResultCode ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001};
|
||||
constexpr ResultCode ERROR_OUT_OF_BOUNDS{ErrorModule::FS, 3005};
|
||||
constexpr ResultCode ERROR_FAILED_MOUNT_ARCHIVE{ErrorModule::FS, 3223};
|
||||
constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
|
||||
constexpr ResultCode ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
|
||||
constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -31,14 +33,6 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
|
||||
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
|
||||
};
|
||||
|
||||
struct NSOBuildHeader {
|
||||
u32_le magic;
|
||||
INSERT_PADDING_BYTES(0x3C);
|
||||
std::array<u8, 0x20> build_id;
|
||||
INSERT_PADDING_BYTES(0xA0);
|
||||
};
|
||||
static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
|
||||
|
||||
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
||||
std::array<u8, sizeof(u32)> bytes{};
|
||||
bytes[0] = version % SINGLE_BYTE_MODULUS;
|
||||
@@ -162,14 +156,16 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
if (nso.size() < 0x100)
|
||||
if (nso.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
}
|
||||
|
||||
NSOBuildHeader header;
|
||||
std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
|
||||
Loader::NSOHeader header;
|
||||
std::memcpy(&header, nso.data(), sizeof(header));
|
||||
|
||||
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
||||
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
|
||||
return nso;
|
||||
}
|
||||
|
||||
const auto build_id_raw = Common::HexArrayToString(header.build_id);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
@@ -212,9 +208,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
}
|
||||
}
|
||||
|
||||
if (out.size() < 0x100)
|
||||
if (out.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
|
||||
}
|
||||
|
||||
std::memcpy(out.data(), &header, sizeof(header));
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -232,6 +230,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id,
|
||||
const std::array<u8, 0x20>& build_id_,
|
||||
const VirtualDir& base_path, bool upper) {
|
||||
const auto build_id_raw = Common::HexArrayToString(build_id_, upper);
|
||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||
|
||||
if (file == nullptr) {
|
||||
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != data.size()) {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TextCheatParser parser;
|
||||
return parser.Parse(system, data);
|
||||
}
|
||||
|
||||
std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 32>& build_id_) const {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<CheatList> out;
|
||||
out.reserve(patch_dirs.size());
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto cheats_dir = subdir->GetSubdirectory("cheats");
|
||||
if (cheats_dir != nullptr) {
|
||||
auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
|
||||
if (res.has_value()) {
|
||||
out.push_back(std::move(*res));
|
||||
continue;
|
||||
}
|
||||
|
||||
res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
|
||||
if (res.has_value())
|
||||
out.push_back(std::move(*res));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
|
||||
@@ -403,6 +452,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
}
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats")))
|
||||
AppendCommaIfNotEmpty(types, "Cheats");
|
||||
|
||||
if (types.empty())
|
||||
continue;
|
||||
|
||||
@@ -8,9 +8,14 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NCA;
|
||||
@@ -45,6 +50,10 @@ public:
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
std::vector<CheatList> CreateCheatList(const Core::System& system,
|
||||
const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - Game Updates
|
||||
// - LayeredFS
|
||||
|
||||
@@ -94,7 +94,7 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
case NCAContentType::Control:
|
||||
return ContentRecordType::Control;
|
||||
case NCAContentType::Data:
|
||||
case NCAContentType::Data_Unknown5:
|
||||
case NCAContentType::PublicData:
|
||||
return ContentRecordType::Data;
|
||||
case NCAContentType::Manual:
|
||||
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/system_archive/ng_word.h"
|
||||
#include "core/file_sys/system_archive/system_archive.h"
|
||||
#include "core/file_sys/system_archive/system_version.h"
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
@@ -30,7 +31,7 @@ constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHI
|
||||
{0x0100000000000806, "NgWord", &NgWord1},
|
||||
{0x0100000000000807, "SsidList", nullptr},
|
||||
{0x0100000000000808, "Dictionary", nullptr},
|
||||
{0x0100000000000809, "SystemVersion", nullptr},
|
||||
{0x0100000000000809, "SystemVersion", &SystemVersion},
|
||||
{0x010000000000080A, "AvatarImage", nullptr},
|
||||
{0x010000000000080B, "LocalNews", nullptr},
|
||||
{0x010000000000080C, "Eula", nullptr},
|
||||
|
||||
52
src/core/file_sys/system_archive/system_version.cpp
Normal file
52
src/core/file_sys/system_archive/system_version.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/file_sys/system_archive/system_version.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
namespace SystemVersionData {
|
||||
|
||||
// This section should reflect the best system version to describe yuzu's HLE api.
|
||||
// TODO(DarkLordZach): Update when HLE gets better.
|
||||
|
||||
constexpr u8 VERSION_MAJOR = 5;
|
||||
constexpr u8 VERSION_MINOR = 1;
|
||||
constexpr u8 VERSION_MICRO = 0;
|
||||
|
||||
constexpr u8 REVISION_MAJOR = 3;
|
||||
constexpr u8 REVISION_MINOR = 0;
|
||||
|
||||
constexpr char PLATFORM_STRING[] = "NX";
|
||||
constexpr char VERSION_HASH[] = "23f9df53e25709d756e0c76effcb2473bd3447dd";
|
||||
constexpr char DISPLAY_VERSION[] = "5.1.0";
|
||||
constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 5.1.0-3.0";
|
||||
|
||||
} // namespace SystemVersionData
|
||||
|
||||
std::string GetLongDisplayVersion() {
|
||||
return SystemVersionData::DISPLAY_TITLE;
|
||||
}
|
||||
|
||||
VirtualDir SystemVersion() {
|
||||
VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file");
|
||||
file->WriteObject(SystemVersionData::VERSION_MAJOR, 0);
|
||||
file->WriteObject(SystemVersionData::VERSION_MINOR, 1);
|
||||
file->WriteObject(SystemVersionData::VERSION_MICRO, 2);
|
||||
file->WriteObject(SystemVersionData::REVISION_MAJOR, 4);
|
||||
file->WriteObject(SystemVersionData::REVISION_MINOR, 5);
|
||||
file->WriteArray(SystemVersionData::PLATFORM_STRING,
|
||||
std::min<u64>(sizeof(SystemVersionData::PLATFORM_STRING), 0x20ULL), 0x8);
|
||||
file->WriteArray(SystemVersionData::VERSION_HASH,
|
||||
std::min<u64>(sizeof(SystemVersionData::VERSION_HASH), 0x40ULL), 0x28);
|
||||
file->WriteArray(SystemVersionData::DISPLAY_VERSION,
|
||||
std::min<u64>(sizeof(SystemVersionData::DISPLAY_VERSION), 0x18ULL), 0x68);
|
||||
file->WriteArray(SystemVersionData::DISPLAY_TITLE,
|
||||
std::min<u64>(sizeof(SystemVersionData::DISPLAY_TITLE), 0x80ULL), 0x80);
|
||||
return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file},
|
||||
std::vector<VirtualDir>{}, "data");
|
||||
}
|
||||
|
||||
} // namespace FileSys::SystemArchive
|
||||
16
src/core/file_sys/system_archive/system_version.h
Normal file
16
src/core/file_sys/system_archive/system_version.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace FileSys::SystemArchive {
|
||||
|
||||
std::string GetLongDisplayVersion();
|
||||
|
||||
VirtualDir SystemVersion();
|
||||
|
||||
} // namespace FileSys::SystemArchive
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
@@ -39,10 +39,10 @@ struct CommandHeader {
|
||||
union {
|
||||
u32_le raw_low;
|
||||
BitField<0, 16, CommandType> type;
|
||||
BitField<16, 4, u32_le> num_buf_x_descriptors;
|
||||
BitField<20, 4, u32_le> num_buf_a_descriptors;
|
||||
BitField<24, 4, u32_le> num_buf_b_descriptors;
|
||||
BitField<28, 4, u32_le> num_buf_w_descriptors;
|
||||
BitField<16, 4, u32> num_buf_x_descriptors;
|
||||
BitField<20, 4, u32> num_buf_a_descriptors;
|
||||
BitField<24, 4, u32> num_buf_b_descriptors;
|
||||
BitField<28, 4, u32> num_buf_w_descriptors;
|
||||
};
|
||||
|
||||
enum class BufferDescriptorCFlag : u32 {
|
||||
@@ -53,28 +53,28 @@ struct CommandHeader {
|
||||
|
||||
union {
|
||||
u32_le raw_high;
|
||||
BitField<0, 10, u32_le> data_size;
|
||||
BitField<0, 10, u32> data_size;
|
||||
BitField<10, 4, BufferDescriptorCFlag> buf_c_descriptor_flags;
|
||||
BitField<31, 1, u32_le> enable_handle_descriptor;
|
||||
BitField<31, 1, u32> enable_handle_descriptor;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(CommandHeader) == 8, "CommandHeader size is incorrect");
|
||||
|
||||
union HandleDescriptorHeader {
|
||||
u32_le raw_high;
|
||||
BitField<0, 1, u32_le> send_current_pid;
|
||||
BitField<1, 4, u32_le> num_handles_to_copy;
|
||||
BitField<5, 4, u32_le> num_handles_to_move;
|
||||
BitField<0, 1, u32> send_current_pid;
|
||||
BitField<1, 4, u32> num_handles_to_copy;
|
||||
BitField<5, 4, u32> num_handles_to_move;
|
||||
};
|
||||
static_assert(sizeof(HandleDescriptorHeader) == 4, "HandleDescriptorHeader size is incorrect");
|
||||
|
||||
struct BufferDescriptorX {
|
||||
union {
|
||||
BitField<0, 6, u32_le> counter_bits_0_5;
|
||||
BitField<6, 3, u32_le> address_bits_36_38;
|
||||
BitField<9, 3, u32_le> counter_bits_9_11;
|
||||
BitField<12, 4, u32_le> address_bits_32_35;
|
||||
BitField<16, 16, u32_le> size;
|
||||
BitField<0, 6, u32> counter_bits_0_5;
|
||||
BitField<6, 3, u32> address_bits_36_38;
|
||||
BitField<9, 3, u32> counter_bits_9_11;
|
||||
BitField<12, 4, u32> address_bits_32_35;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
|
||||
u32_le address_bits_0_31;
|
||||
@@ -103,10 +103,10 @@ struct BufferDescriptorABW {
|
||||
u32_le address_bits_0_31;
|
||||
|
||||
union {
|
||||
BitField<0, 2, u32_le> flags;
|
||||
BitField<2, 3, u32_le> address_bits_36_38;
|
||||
BitField<24, 4, u32_le> size_bits_32_35;
|
||||
BitField<28, 4, u32_le> address_bits_32_35;
|
||||
BitField<0, 2, u32> flags;
|
||||
BitField<2, 3, u32> address_bits_36_38;
|
||||
BitField<24, 4, u32> size_bits_32_35;
|
||||
BitField<28, 4, u32> address_bits_32_35;
|
||||
};
|
||||
|
||||
VAddr Address() const {
|
||||
@@ -128,8 +128,8 @@ struct BufferDescriptorC {
|
||||
u32_le address_bits_0_31;
|
||||
|
||||
union {
|
||||
BitField<0, 16, u32_le> address_bits_32_47;
|
||||
BitField<16, 16, u32_le> size;
|
||||
BitField<0, 16, u32> address_bits_32_47;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
|
||||
VAddr Address() const {
|
||||
@@ -167,8 +167,8 @@ struct DomainMessageHeader {
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 8, CommandType> command;
|
||||
BitField<8, 8, u32_le> input_object_count;
|
||||
BitField<16, 16, u32_le> size;
|
||||
BitField<8, 8, u32> input_object_count;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
u32_le object_id;
|
||||
INSERT_PADDING_WORDS(2);
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace IPC {
|
||||
|
||||
constexpr ResultCode ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301};
|
||||
|
||||
class RequestHelperBase {
|
||||
protected:
|
||||
Kernel::HLERequestContext* context = nullptr;
|
||||
@@ -271,6 +274,20 @@ inline void ResponseBuilder::Push(u64 value) {
|
||||
Push(static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(float value) {
|
||||
u32 integral;
|
||||
std::memcpy(&integral, &value, sizeof(u32));
|
||||
Push(integral);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(double value) {
|
||||
u64 integral;
|
||||
std::memcpy(&integral, &value, sizeof(u64));
|
||||
Push(integral);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(bool value) {
|
||||
Push(static_cast<u8>(value));
|
||||
@@ -350,7 +367,7 @@ public:
|
||||
template <class T>
|
||||
std::shared_ptr<T> PopIpcInterface() {
|
||||
ASSERT(context->Session()->IsDomain());
|
||||
ASSERT(context->GetDomainMessageHeader()->input_object_count > 0);
|
||||
ASSERT(context->GetDomainMessageHeader().input_object_count > 0);
|
||||
return context->GetDomainRequestHandler<T>(Pop<u32>() - 1);
|
||||
}
|
||||
};
|
||||
@@ -362,6 +379,11 @@ inline u32 RequestParser::Pop() {
|
||||
return cmdbuf[index++];
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s32 RequestParser::Pop() {
|
||||
return static_cast<s32>(Pop<u32>());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RequestParser::PopRaw(T& value) {
|
||||
std::memcpy(&value, cmdbuf + index, sizeof(T));
|
||||
@@ -392,11 +414,37 @@ inline u64 RequestParser::Pop() {
|
||||
return msw << 32 | lsw;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s8 RequestParser::Pop() {
|
||||
return static_cast<s8>(Pop<u8>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s16 RequestParser::Pop() {
|
||||
return static_cast<s16>(Pop<u16>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline s64 RequestParser::Pop() {
|
||||
return static_cast<s64>(Pop<u64>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float RequestParser::Pop() {
|
||||
const u32 value = Pop<u32>();
|
||||
float real;
|
||||
std::memcpy(&real, &value, sizeof(real));
|
||||
return real;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline double RequestParser::Pop() {
|
||||
const u64 value = Pop<u64>();
|
||||
float real;
|
||||
std::memcpy(&real, &value, sizeof(real));
|
||||
return real;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool RequestParser::Pop() {
|
||||
return Pop<u8>() != 0;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -17,32 +18,172 @@
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Kernel::AddressArbiter {
|
||||
namespace Kernel {
|
||||
namespace {
|
||||
// Wake up num_to_wake (or all) threads in a vector.
|
||||
void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) {
|
||||
// Only process up to 'target' threads, unless 'target' is <= 0, in which case process
|
||||
// them all.
|
||||
std::size_t last = waiting_threads.size();
|
||||
if (num_to_wake > 0) {
|
||||
last = num_to_wake;
|
||||
}
|
||||
|
||||
// Performs actual address waiting logic.
|
||||
static ResultCode WaitForAddress(VAddr address, s64 timeout) {
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
// Signal the waiting threads.
|
||||
for (std::size_t i = 0; i < last; i++) {
|
||||
ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
|
||||
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
waiting_threads[i]->SetArbiterWaitAddress(0);
|
||||
waiting_threads[i]->ResumeFromWait();
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
AddressArbiter::AddressArbiter(Core::System& system) : system{system} {}
|
||||
AddressArbiter::~AddressArbiter() = default;
|
||||
|
||||
ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 value,
|
||||
s32 num_to_wake) {
|
||||
switch (type) {
|
||||
case SignalType::Signal:
|
||||
return SignalToAddressOnly(address, num_to_wake);
|
||||
case SignalType::IncrementAndSignalIfEqual:
|
||||
return IncrementAndSignalToAddressIfEqual(address, value, num_to_wake);
|
||||
case SignalType::ModifyByWaitingCountAndSignalIfEqual:
|
||||
return ModifyByWaitingCountAndSignalToAddressIfEqual(address, value, num_to_wake);
|
||||
default:
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
|
||||
const std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
Memory::Write32(address, static_cast<u32>(value + 1));
|
||||
return SignalToAddressOnly(address, num_to_wake);
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
// Get threads waiting on the address.
|
||||
const std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
|
||||
// Determine the modified value depending on the waiting count.
|
||||
s32 updated_value;
|
||||
if (waiting_threads.empty()) {
|
||||
updated_value = value - 1;
|
||||
} else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
|
||||
updated_value = value + 1;
|
||||
} else {
|
||||
updated_value = value;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
Memory::Write32(address, static_cast<u32>(updated_value));
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddress(VAddr address, ArbitrationType type, s32 value,
|
||||
s64 timeout_ns) {
|
||||
switch (type) {
|
||||
case ArbitrationType::WaitIfLessThan:
|
||||
return WaitForAddressIfLessThan(address, value, timeout_ns, false);
|
||||
case ArbitrationType::DecrementAndWaitIfLessThan:
|
||||
return WaitForAddressIfLessThan(address, value, timeout_ns, true);
|
||||
case ArbitrationType::WaitIfEqual:
|
||||
return WaitForAddressIfEqual(address, value, timeout_ns);
|
||||
default:
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout,
|
||||
bool should_decrement) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
const s32 cur_value = static_cast<s32>(Memory::Read32(address));
|
||||
if (cur_value >= value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (should_decrement) {
|
||||
Memory::Write32(address, static_cast<u32>(cur_value - 1));
|
||||
}
|
||||
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddressImpl(address, timeout);
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
// Only wait for the address if equal.
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddressImpl(address, timeout);
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::WaitForAddressImpl(VAddr address, s64 timeout) {
|
||||
SharedPtr<Thread> current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
current_thread->SetArbiterWaitAddress(address);
|
||||
current_thread->SetStatus(ThreadStatus::WaitArb);
|
||||
current_thread->InvalidateWakeupCallback();
|
||||
|
||||
current_thread->WakeAfterDelay(timeout);
|
||||
|
||||
Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
system.CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
// Gets the threads waiting on an address.
|
||||
static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) {
|
||||
const auto RetrieveWaitingThreads = [](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr arb_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
std::vector<SharedPtr<Thread>> AddressArbiter::GetThreadsWaitingOnAddress(VAddr address) const {
|
||||
const auto RetrieveWaitingThreads = [this](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr arb_addr) {
|
||||
const auto& scheduler = system.Scheduler(core_index);
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetArbiterWaitAddress() == arb_addr)
|
||||
if (thread->GetArbiterWaitAddress() == arb_addr) {
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -61,118 +202,4 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
|
||||
|
||||
return threads;
|
||||
}
|
||||
|
||||
// Wake up num_to_wake (or all) threads in a vector.
|
||||
static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) {
|
||||
// Only process up to 'target' threads, unless 'target' is <= 0, in which case process
|
||||
// them all.
|
||||
std::size_t last = waiting_threads.size();
|
||||
if (num_to_wake > 0)
|
||||
last = num_to_wake;
|
||||
|
||||
// Signal the waiting threads.
|
||||
for (std::size_t i = 0; i < last; i++) {
|
||||
ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
|
||||
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
waiting_threads[i]->SetArbiterWaitAddress(0);
|
||||
waiting_threads[i]->ResumeFromWait();
|
||||
}
|
||||
}
|
||||
|
||||
// Signals an address being waited on.
|
||||
ResultCode SignalToAddress(VAddr address, s32 num_to_wake) {
|
||||
std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Signals an address being waited on and increments its value if equal to the value argument.
|
||||
ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) == value) {
|
||||
Memory::Write32(address, static_cast<u32>(value + 1));
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return SignalToAddress(address, num_to_wake);
|
||||
}
|
||||
|
||||
// Signals an address being waited on and modifies its value based on waiting thread count if equal
|
||||
// to the value argument.
|
||||
ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake) {
|
||||
// Ensure that we can write to the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
// Get threads waiting on the address.
|
||||
std::vector<SharedPtr<Thread>> waiting_threads = GetThreadsWaitingOnAddress(address);
|
||||
|
||||
// Determine the modified value depending on the waiting count.
|
||||
s32 updated_value;
|
||||
if (waiting_threads.empty()) {
|
||||
updated_value = value - 1;
|
||||
} else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
|
||||
updated_value = value + 1;
|
||||
} else {
|
||||
updated_value = value;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(Memory::Read32(address)) == value) {
|
||||
Memory::Write32(address, static_cast<u32>(updated_value));
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Waits on an address if the value passed is less than the argument value, optionally decrementing.
|
||||
ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
s32 cur_value = static_cast<s32>(Memory::Read32(address));
|
||||
if (cur_value < value) {
|
||||
if (should_decrement) {
|
||||
Memory::Write32(address, static_cast<u32>(cur_value - 1));
|
||||
}
|
||||
} else {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddress(address, timeout);
|
||||
}
|
||||
|
||||
// Waits on an address if the value passed is equal to the argument value.
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
|
||||
// Ensure that we can read the address.
|
||||
if (!Memory::IsValidVirtualAddress(address)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
// Only wait for the address if equal.
|
||||
if (static_cast<s32>(Memory::Read32(address)) != value) {
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
// Short-circuit without rescheduling, if timeout is zero.
|
||||
if (timeout == 0) {
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
return WaitForAddress(address, timeout);
|
||||
}
|
||||
} // namespace Kernel::AddressArbiter
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -4,29 +4,77 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Kernel::AddressArbiter {
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
enum class ArbitrationType {
|
||||
WaitIfLessThan = 0,
|
||||
DecrementAndWaitIfLessThan = 1,
|
||||
WaitIfEqual = 2,
|
||||
namespace Kernel {
|
||||
|
||||
class Thread;
|
||||
|
||||
class AddressArbiter {
|
||||
public:
|
||||
enum class ArbitrationType {
|
||||
WaitIfLessThan = 0,
|
||||
DecrementAndWaitIfLessThan = 1,
|
||||
WaitIfEqual = 2,
|
||||
};
|
||||
|
||||
enum class SignalType {
|
||||
Signal = 0,
|
||||
IncrementAndSignalIfEqual = 1,
|
||||
ModifyByWaitingCountAndSignalIfEqual = 2,
|
||||
};
|
||||
|
||||
explicit AddressArbiter(Core::System& system);
|
||||
~AddressArbiter();
|
||||
|
||||
AddressArbiter(const AddressArbiter&) = delete;
|
||||
AddressArbiter& operator=(const AddressArbiter&) = delete;
|
||||
|
||||
AddressArbiter(AddressArbiter&&) = default;
|
||||
AddressArbiter& operator=(AddressArbiter&&) = delete;
|
||||
|
||||
/// Signals an address being waited on with a particular signaling type.
|
||||
ResultCode SignalToAddress(VAddr address, SignalType type, s32 value, s32 num_to_wake);
|
||||
|
||||
/// Waits on an address with a particular arbitration type.
|
||||
ResultCode WaitForAddress(VAddr address, ArbitrationType type, s32 value, s64 timeout_ns);
|
||||
|
||||
private:
|
||||
/// Signals an address being waited on.
|
||||
ResultCode SignalToAddressOnly(VAddr address, s32 num_to_wake);
|
||||
|
||||
/// Signals an address being waited on and increments its value if equal to the value argument.
|
||||
ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
|
||||
|
||||
/// Signals an address being waited on and modifies its value based on waiting thread count if
|
||||
/// equal to the value argument.
|
||||
ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake);
|
||||
|
||||
/// Waits on an address if the value passed is less than the argument value,
|
||||
/// optionally decrementing.
|
||||
ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout,
|
||||
bool should_decrement);
|
||||
|
||||
/// Waits on an address if the value passed is equal to the argument value.
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
|
||||
|
||||
// Waits on the given address with a timeout in nanoseconds
|
||||
ResultCode WaitForAddressImpl(VAddr address, s64 timeout);
|
||||
|
||||
// Gets the threads waiting on an address.
|
||||
std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) const;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
enum class SignalType {
|
||||
Signal = 0,
|
||||
IncrementAndSignalIfEqual = 1,
|
||||
ModifyByWaitingCountAndSignalIfEqual = 2,
|
||||
};
|
||||
|
||||
ResultCode SignalToAddress(VAddr address, s32 num_to_wake);
|
||||
ResultCode IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
|
||||
ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake);
|
||||
|
||||
ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement);
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
|
||||
|
||||
} // namespace Kernel::AddressArbiter
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -33,10 +33,11 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
|
||||
// Create a new session pair, let the created sessions inherit the parent port's HLE handler.
|
||||
auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
|
||||
|
||||
if (server_port->hle_handler)
|
||||
server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
else
|
||||
server_port->pending_sessions.push_back(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
if (server_port->HasHLEHandler()) {
|
||||
server_port->GetHLEHandler()->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
} else {
|
||||
server_port->AppendPendingSession(std::get<SharedPtr<ServerSession>>(sessions));
|
||||
}
|
||||
|
||||
// Wake the threads waiting on the ServerPort
|
||||
server_port->WakeupAllWaitingThreads();
|
||||
|
||||
@@ -17,21 +17,11 @@ ClientSession::~ClientSession() {
|
||||
// This destructor will be called automatically when the last ClientSession handle is closed by
|
||||
// the emulated application.
|
||||
|
||||
// Local references to ServerSession and SessionRequestHandler are necessary to guarantee they
|
||||
// A local reference to the ServerSession is necessary to guarantee it
|
||||
// will be kept alive until after ClientDisconnected() returns.
|
||||
SharedPtr<ServerSession> server = parent->server;
|
||||
if (server) {
|
||||
std::shared_ptr<SessionRequestHandler> hle_handler = server->hle_handler;
|
||||
if (hle_handler)
|
||||
hle_handler->ClientDisconnected(server);
|
||||
|
||||
// TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set
|
||||
// their WaitSynchronization result to 0xC920181A.
|
||||
|
||||
// Clean up the list of client threads with pending requests, they are unneeded now that the
|
||||
// client endpoint is closed.
|
||||
server->pending_requesting_threads.clear();
|
||||
server->currently_handling = nullptr;
|
||||
server->ClientDisconnected();
|
||||
}
|
||||
|
||||
parent->client = nullptr;
|
||||
|
||||
@@ -36,14 +36,15 @@ public:
|
||||
|
||||
ResultCode SendSyncRequest(SharedPtr<Thread> thread);
|
||||
|
||||
std::string name; ///< Name of client port (optional)
|
||||
private:
|
||||
explicit ClientSession(KernelCore& kernel);
|
||||
~ClientSession() override;
|
||||
|
||||
/// The parent session, which links to the server endpoint.
|
||||
std::shared_ptr<Session> parent;
|
||||
|
||||
private:
|
||||
explicit ClientSession(KernelCore& kernel);
|
||||
~ClientSession() override;
|
||||
/// Name of the client session (optional)
|
||||
std::string name;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
12
src/core/hle/kernel/code_set.cpp
Normal file
12
src/core/hle/kernel/code_set.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
CodeSet::CodeSet() = default;
|
||||
CodeSet::~CodeSet() = default;
|
||||
|
||||
} // namespace Kernel
|
||||
90
src/core/hle/kernel/code_set.h
Normal file
90
src/core/hle/kernel/code_set.h
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
/**
|
||||
* Represents executable data that may be loaded into a kernel process.
|
||||
*
|
||||
* A code set consists of three basic segments:
|
||||
* - A code (AKA text) segment,
|
||||
* - A read-only data segment (rodata)
|
||||
* - A data segment
|
||||
*
|
||||
* The code segment is the portion of the object file that contains
|
||||
* executable instructions.
|
||||
*
|
||||
* The read-only data segment in the portion of the object file that
|
||||
* contains (as one would expect) read-only data, such as fixed constant
|
||||
* values and data structures.
|
||||
*
|
||||
* The data segment is similar to the read-only data segment -- it contains
|
||||
* variables and data structures that have predefined values, however,
|
||||
* entities within this segment can be modified.
|
||||
*/
|
||||
struct CodeSet final {
|
||||
/// A single segment within a code set.
|
||||
struct Segment final {
|
||||
/// The byte offset that this segment is located at.
|
||||
std::size_t offset = 0;
|
||||
|
||||
/// The address to map this segment to.
|
||||
VAddr addr = 0;
|
||||
|
||||
/// The size of this segment in bytes.
|
||||
u32 size = 0;
|
||||
};
|
||||
|
||||
explicit CodeSet();
|
||||
~CodeSet();
|
||||
|
||||
CodeSet(const CodeSet&) = delete;
|
||||
CodeSet& operator=(const CodeSet&) = delete;
|
||||
|
||||
CodeSet(CodeSet&&) = default;
|
||||
CodeSet& operator=(CodeSet&&) = default;
|
||||
|
||||
Segment& CodeSegment() {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
const Segment& CodeSegment() const {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
Segment& RODataSegment() {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
const Segment& RODataSegment() const {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
Segment& DataSegment() {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
const Segment& DataSegment() const {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
/// The overall data that backs this code set.
|
||||
std::shared_ptr<std::vector<u8>> memory;
|
||||
|
||||
/// The segments that comprise this code set.
|
||||
std::array<Segment, 3> segments;
|
||||
|
||||
/// The entry point address for this code set.
|
||||
VAddr entrypoint = 0;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -86,7 +86,7 @@ HLERequestContext::~HLERequestContext() = default;
|
||||
void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf,
|
||||
bool incoming) {
|
||||
IPC::RequestParser rp(src_cmdbuf);
|
||||
command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>());
|
||||
command_header = rp.PopRaw<IPC::CommandHeader>();
|
||||
|
||||
if (command_header->type == IPC::CommandType::Close) {
|
||||
// Close does not populate the rest of the IPC header
|
||||
@@ -95,8 +95,7 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_
|
||||
|
||||
// If handle descriptor is present, add size of it
|
||||
if (command_header->enable_handle_descriptor) {
|
||||
handle_descriptor_header =
|
||||
std::make_shared<IPC::HandleDescriptorHeader>(rp.PopRaw<IPC::HandleDescriptorHeader>());
|
||||
handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>();
|
||||
if (handle_descriptor_header->send_current_pid) {
|
||||
rp.Skip(2, false);
|
||||
}
|
||||
@@ -140,16 +139,15 @@ void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_
|
||||
// If this is an incoming message, only CommandType "Request" has a domain header
|
||||
// All outgoing domain messages have the domain header, if only incoming has it
|
||||
if (incoming || domain_message_header) {
|
||||
domain_message_header =
|
||||
std::make_shared<IPC::DomainMessageHeader>(rp.PopRaw<IPC::DomainMessageHeader>());
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
} else {
|
||||
if (Session()->IsDomain())
|
||||
if (Session()->IsDomain()) {
|
||||
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data_payload_header =
|
||||
std::make_shared<IPC::DataPayloadHeader>(rp.PopRaw<IPC::DataPayloadHeader>());
|
||||
data_payload_header = rp.PopRaw<IPC::DataPayloadHeader>();
|
||||
|
||||
data_payload_offset = rp.GetCurrentOffset();
|
||||
|
||||
@@ -264,11 +262,11 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
|
||||
// Write the domain objects to the command buffer, these go after the raw untranslated data.
|
||||
// TODO(Subv): This completely ignores C buffers.
|
||||
std::size_t domain_offset = size - domain_message_header->num_objects;
|
||||
auto& request_handlers = server_session->domain_request_handlers;
|
||||
|
||||
for (auto& object : domain_objects) {
|
||||
request_handlers.emplace_back(object);
|
||||
dst_cmdbuf[domain_offset++] = static_cast<u32_le>(request_handlers.size());
|
||||
for (const auto& object : domain_objects) {
|
||||
server_session->AppendDomainRequestHandler(object);
|
||||
dst_cmdbuf[domain_offset++] =
|
||||
static_cast<u32_le>(server_session->NumDomainRequestHandlers());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
@@ -15,6 +16,8 @@
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Service {
|
||||
class ServiceFrameworkBase;
|
||||
}
|
||||
@@ -166,12 +169,12 @@ public:
|
||||
return buffer_c_desciptors;
|
||||
}
|
||||
|
||||
const IPC::DomainMessageHeader* GetDomainMessageHeader() const {
|
||||
return domain_message_header.get();
|
||||
const IPC::DomainMessageHeader& GetDomainMessageHeader() const {
|
||||
return domain_message_header.value();
|
||||
}
|
||||
|
||||
bool HasDomainMessageHeader() const {
|
||||
return domain_message_header != nullptr;
|
||||
return domain_message_header.has_value();
|
||||
}
|
||||
|
||||
/// Helper function to read a buffer using the appropriate buffer descriptor
|
||||
@@ -208,14 +211,12 @@ public:
|
||||
|
||||
template <typename T>
|
||||
SharedPtr<T> GetCopyObject(std::size_t index) {
|
||||
ASSERT(index < copy_objects.size());
|
||||
return DynamicObjectCast<T>(copy_objects[index]);
|
||||
return DynamicObjectCast<T>(copy_objects.at(index));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SharedPtr<T> GetMoveObject(std::size_t index) {
|
||||
ASSERT(index < move_objects.size());
|
||||
return DynamicObjectCast<T>(move_objects[index]);
|
||||
return DynamicObjectCast<T>(move_objects.at(index));
|
||||
}
|
||||
|
||||
void AddMoveObject(SharedPtr<Object> object) {
|
||||
@@ -232,7 +233,7 @@ public:
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> GetDomainRequestHandler(std::size_t index) const {
|
||||
return std::static_pointer_cast<T>(domain_request_handlers[index]);
|
||||
return std::static_pointer_cast<T>(domain_request_handlers.at(index));
|
||||
}
|
||||
|
||||
void SetDomainRequestHandlers(
|
||||
@@ -272,10 +273,10 @@ private:
|
||||
boost::container::small_vector<SharedPtr<Object>, 8> copy_objects;
|
||||
boost::container::small_vector<std::shared_ptr<SessionRequestHandler>, 8> domain_objects;
|
||||
|
||||
std::shared_ptr<IPC::CommandHeader> command_header;
|
||||
std::shared_ptr<IPC::HandleDescriptorHeader> handle_descriptor_header;
|
||||
std::shared_ptr<IPC::DataPayloadHeader> data_payload_header;
|
||||
std::shared_ptr<IPC::DomainMessageHeader> domain_message_header;
|
||||
std::optional<IPC::CommandHeader> command_header;
|
||||
std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header;
|
||||
std::optional<IPC::DataPayloadHeader> data_payload_header;
|
||||
std::optional<IPC::DomainMessageHeader> domain_message_header;
|
||||
std::vector<IPC::BufferDescriptorX> buffer_x_desciptors;
|
||||
std::vector<IPC::BufferDescriptorABW> buffer_a_desciptors;
|
||||
std::vector<IPC::BufferDescriptorABW> buffer_b_desciptors;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -86,11 +87,13 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
|
||||
}
|
||||
|
||||
struct KernelCore::Impl {
|
||||
void Initialize(KernelCore& kernel, Core::Timing::CoreTiming& core_timing) {
|
||||
explicit Impl(Core::System& system) : system{system} {}
|
||||
|
||||
void Initialize(KernelCore& kernel) {
|
||||
Shutdown();
|
||||
|
||||
InitializeSystemResourceLimit(kernel);
|
||||
InitializeThreads(core_timing);
|
||||
InitializeThreads();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
@@ -122,9 +125,9 @@ struct KernelCore::Impl {
|
||||
ASSERT(system_resource_limit->SetLimitValue(ResourceType::Sessions, 900).IsSuccess());
|
||||
}
|
||||
|
||||
void InitializeThreads(Core::Timing::CoreTiming& core_timing) {
|
||||
void InitializeThreads() {
|
||||
thread_wakeup_event_type =
|
||||
core_timing.RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
|
||||
system.CoreTiming().RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
|
||||
}
|
||||
|
||||
std::atomic<u32> next_object_id{0};
|
||||
@@ -145,15 +148,18 @@ struct KernelCore::Impl {
|
||||
/// Map of named ports managed by the kernel, which can be retrieved using
|
||||
/// the ConnectToPort SVC.
|
||||
NamedPortTable named_ports;
|
||||
|
||||
// System context
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
KernelCore::KernelCore() : impl{std::make_unique<Impl>()} {}
|
||||
KernelCore::KernelCore(Core::System& system) : impl{std::make_unique<Impl>(system)} {}
|
||||
KernelCore::~KernelCore() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void KernelCore::Initialize(Core::Timing::CoreTiming& core_timing) {
|
||||
impl->Initialize(*this, core_timing);
|
||||
void KernelCore::Initialize() {
|
||||
impl->Initialize(*this);
|
||||
}
|
||||
|
||||
void KernelCore::Shutdown() {
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
template <typename T>
|
||||
class ResultVal;
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
@@ -18,6 +22,7 @@ struct EventType;
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class AddressArbiter;
|
||||
class ClientPort;
|
||||
class HandleTable;
|
||||
class Process;
|
||||
@@ -30,7 +35,14 @@ private:
|
||||
using NamedPortTable = std::unordered_map<std::string, SharedPtr<ClientPort>>;
|
||||
|
||||
public:
|
||||
KernelCore();
|
||||
/// Constructs an instance of the kernel using the given System
|
||||
/// instance as a context for any necessary system-related state,
|
||||
/// such as threads, CPU core state, etc.
|
||||
///
|
||||
/// @post After execution of the constructor, the provided System
|
||||
/// object *must* outlive the kernel instance itself.
|
||||
///
|
||||
explicit KernelCore(Core::System& system);
|
||||
~KernelCore();
|
||||
|
||||
KernelCore(const KernelCore&) = delete;
|
||||
@@ -40,11 +52,7 @@ public:
|
||||
KernelCore& operator=(KernelCore&&) = delete;
|
||||
|
||||
/// Resets the kernel to a clean slate for use.
|
||||
///
|
||||
/// @param core_timing CoreTiming instance used to create any necessary
|
||||
/// kernel-specific callback events.
|
||||
///
|
||||
void Initialize(Core::Timing::CoreTiming& core_timing);
|
||||
void Initialize();
|
||||
|
||||
/// Clears all resources in use by the kernel instance.
|
||||
void Shutdown();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -10,8 +9,11 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
@@ -57,41 +59,47 @@ static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_t
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle holding_thread_handle,
|
||||
Mutex::Mutex(Core::System& system) : system{system} {}
|
||||
Mutex::~Mutex() = default;
|
||||
|
||||
ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle) {
|
||||
// The mutex address must be 4-byte aligned
|
||||
if ((address % sizeof(u32)) != 0) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||
Thread* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
SharedPtr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
|
||||
SharedPtr<Thread> requesting_thread = handle_table.Get<Thread>(requesting_thread_handle);
|
||||
|
||||
// TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
|
||||
// thread.
|
||||
ASSERT(requesting_thread == GetCurrentThread());
|
||||
ASSERT(requesting_thread == current_thread);
|
||||
|
||||
u32 addr_value = Memory::Read32(address);
|
||||
const u32 addr_value = Memory::Read32(address);
|
||||
|
||||
// If the mutex isn't being held, just return success.
|
||||
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
if (holding_thread == nullptr)
|
||||
if (holding_thread == nullptr) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// Wait until the mutex is released
|
||||
GetCurrentThread()->SetMutexWaitAddress(address);
|
||||
GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
|
||||
current_thread->SetMutexWaitAddress(address);
|
||||
current_thread->SetWaitHandle(requesting_thread_handle);
|
||||
|
||||
GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
|
||||
GetCurrentThread()->InvalidateWakeupCallback();
|
||||
current_thread->SetStatus(ThreadStatus::WaitMutex);
|
||||
current_thread->InvalidateWakeupCallback();
|
||||
|
||||
// Update the lock holder thread's priority to prevent priority inversion.
|
||||
holding_thread->AddMutexWaiter(GetCurrentThread());
|
||||
holding_thread->AddMutexWaiter(current_thread);
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -102,7 +110,8 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address);
|
||||
|
||||
// There are no more threads waiting for the mutex, release it completely.
|
||||
if (thread == nullptr) {
|
||||
@@ -111,7 +120,7 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
}
|
||||
|
||||
// Transfer the ownership of the mutex from the previous owner to the new one.
|
||||
TransferMutexOwnership(address, GetCurrentThread(), thread);
|
||||
TransferMutexOwnership(address, current_thread, thread);
|
||||
|
||||
u32 mutex_value = thread->GetWaitHandle();
|
||||
|
||||
|
||||
@@ -5,32 +5,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Kernel {
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
class HandleTable;
|
||||
class Thread;
|
||||
namespace Kernel {
|
||||
|
||||
class Mutex final {
|
||||
public:
|
||||
explicit Mutex(Core::System& system);
|
||||
~Mutex();
|
||||
|
||||
/// Flag that indicates that a mutex still has threads waiting for it.
|
||||
static constexpr u32 MutexHasWaitersFlag = 0x40000000;
|
||||
/// Mask of the bits in a mutex address value that contain the mutex owner.
|
||||
static constexpr u32 MutexOwnerMask = 0xBFFFFFFF;
|
||||
|
||||
/// Attempts to acquire a mutex at the specified address.
|
||||
static ResultCode TryAcquire(HandleTable& handle_table, VAddr address,
|
||||
Handle holding_thread_handle, Handle requesting_thread_handle);
|
||||
ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle);
|
||||
|
||||
/// Releases the mutex at the specified address.
|
||||
static ResultCode Release(VAddr address);
|
||||
ResultCode Release(VAddr address);
|
||||
|
||||
private:
|
||||
Mutex() = default;
|
||||
~Mutex() = default;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -31,7 +32,7 @@ namespace {
|
||||
*/
|
||||
void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) {
|
||||
// Setup page table so we can write to memory
|
||||
SetCurrentPageTable(&owner_process.VMManager().page_table);
|
||||
Memory::SetCurrentPageTable(&owner_process.VMManager().page_table);
|
||||
|
||||
// Initialize new "main" thread
|
||||
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
|
||||
@@ -50,12 +51,10 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_poi
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
CodeSet::CodeSet() = default;
|
||||
CodeSet::~CodeSet() = default;
|
||||
|
||||
SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
|
||||
SharedPtr<Process> process(new Process(kernel));
|
||||
SharedPtr<Process> Process::Create(Core::System& system, std::string&& name) {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
SharedPtr<Process> process(new Process(system));
|
||||
process->name = std::move(name);
|
||||
process->resource_limit = kernel.GetSystemResourceLimit();
|
||||
process->status = ProcessStatus::Created;
|
||||
@@ -132,7 +131,7 @@ void Process::PrepareForTermination() {
|
||||
if (thread->GetOwnerProcess() != this)
|
||||
continue;
|
||||
|
||||
if (thread == GetCurrentThread())
|
||||
if (thread == system.CurrentScheduler().GetCurrentThread())
|
||||
continue;
|
||||
|
||||
// TODO(Subv): When are the other running/ready threads terminated?
|
||||
@@ -144,7 +143,6 @@ void Process::PrepareForTermination() {
|
||||
}
|
||||
};
|
||||
|
||||
const auto& system = Core::System::GetInstance();
|
||||
stop_threads(system.Scheduler(0).GetThreadList());
|
||||
stop_threads(system.Scheduler(1).GetThreadList());
|
||||
stop_threads(system.Scheduler(2).GetThreadList());
|
||||
@@ -212,7 +210,7 @@ void Process::FreeTLSSlot(VAddr tls_address) {
|
||||
}
|
||||
|
||||
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
|
||||
const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions,
|
||||
MemoryState memory_state) {
|
||||
const auto vma = vm_manager
|
||||
.MapMemoryBlock(segment.addr + base_addr, module_.memory,
|
||||
@@ -222,19 +220,18 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
};
|
||||
|
||||
// Map CodeSet segments
|
||||
MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
|
||||
MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
|
||||
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
|
||||
MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code);
|
||||
MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeData);
|
||||
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeData);
|
||||
|
||||
// Clear instruction cache in CPU JIT
|
||||
Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
|
||||
Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
|
||||
Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
|
||||
Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
|
||||
system.InvalidateCpuInstructionCaches();
|
||||
}
|
||||
|
||||
Kernel::Process::Process(KernelCore& kernel) : WaitObject{kernel} {}
|
||||
Kernel::Process::~Process() {}
|
||||
Process::Process(Core::System& system)
|
||||
: WaitObject{system.Kernel()}, address_arbiter{system}, mutex{system}, system{system} {}
|
||||
|
||||
Process::~Process() = default;
|
||||
|
||||
void Process::Acquire(Thread* thread) {
|
||||
ASSERT_MSG(!ShouldWait(thread), "Object unavailable!");
|
||||
|
||||
@@ -7,17 +7,22 @@
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/process_capability.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
class ProgramMetadata;
|
||||
}
|
||||
@@ -28,6 +33,8 @@ class KernelCore;
|
||||
class ResourceLimit;
|
||||
class Thread;
|
||||
|
||||
struct CodeSet;
|
||||
|
||||
struct AddressMapping {
|
||||
// Address and size must be page-aligned
|
||||
VAddr address;
|
||||
@@ -60,46 +67,6 @@ enum class ProcessStatus {
|
||||
DebugBreak,
|
||||
};
|
||||
|
||||
struct CodeSet final {
|
||||
struct Segment {
|
||||
std::size_t offset = 0;
|
||||
VAddr addr = 0;
|
||||
u32 size = 0;
|
||||
};
|
||||
|
||||
explicit CodeSet();
|
||||
~CodeSet();
|
||||
|
||||
Segment& CodeSegment() {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
const Segment& CodeSegment() const {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
Segment& RODataSegment() {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
const Segment& RODataSegment() const {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
Segment& DataSegment() {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
const Segment& DataSegment() const {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<u8>> memory;
|
||||
|
||||
std::array<Segment, 3> segments;
|
||||
VAddr entrypoint = 0;
|
||||
};
|
||||
|
||||
class Process final : public WaitObject {
|
||||
public:
|
||||
enum : u64 {
|
||||
@@ -116,7 +83,7 @@ public:
|
||||
|
||||
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
|
||||
|
||||
static SharedPtr<Process> Create(KernelCore& kernel, std::string&& name);
|
||||
static SharedPtr<Process> Create(Core::System& system, std::string&& name);
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "Process";
|
||||
@@ -150,6 +117,26 @@ public:
|
||||
return handle_table;
|
||||
}
|
||||
|
||||
/// Gets a reference to the process' address arbiter.
|
||||
AddressArbiter& GetAddressArbiter() {
|
||||
return address_arbiter;
|
||||
}
|
||||
|
||||
/// Gets a const reference to the process' address arbiter.
|
||||
const AddressArbiter& GetAddressArbiter() const {
|
||||
return address_arbiter;
|
||||
}
|
||||
|
||||
/// Gets a reference to the process' mutex lock.
|
||||
Mutex& GetMutex() {
|
||||
return mutex;
|
||||
}
|
||||
|
||||
/// Gets a const reference to the process' mutex lock
|
||||
const Mutex& GetMutex() const {
|
||||
return mutex;
|
||||
}
|
||||
|
||||
/// Gets the current status of the process
|
||||
ProcessStatus GetStatus() const {
|
||||
return status;
|
||||
@@ -251,7 +238,7 @@ public:
|
||||
void FreeTLSSlot(VAddr tls_address);
|
||||
|
||||
private:
|
||||
explicit Process(KernelCore& kernel);
|
||||
explicit Process(Core::System& system);
|
||||
~Process() override;
|
||||
|
||||
/// Checks if the specified thread should wait until this process is available.
|
||||
@@ -309,9 +296,21 @@ private:
|
||||
/// Per-process handle table for storing created object handles in.
|
||||
HandleTable handle_table;
|
||||
|
||||
/// Per-process address arbiter.
|
||||
AddressArbiter address_arbiter;
|
||||
|
||||
/// The per-process mutex lock instance used for handling various
|
||||
/// forms of services, such as lock arbitration, and condition
|
||||
/// variable related facilities.
|
||||
Mutex mutex;
|
||||
|
||||
/// Random values for svcGetInfo RandomEntropy
|
||||
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy;
|
||||
|
||||
/// System context
|
||||
Core::System& system;
|
||||
|
||||
/// Name of this process
|
||||
std::string name;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ namespace Kernel {
|
||||
|
||||
std::mutex Scheduler::scheduler_mutex;
|
||||
|
||||
Scheduler::Scheduler(Core::ARM_Interface& cpu_core) : cpu_core(cpu_core) {}
|
||||
Scheduler::Scheduler(Core::System& system, Core::ARM_Interface& cpu_core)
|
||||
: cpu_core{cpu_core}, system{system} {}
|
||||
|
||||
Scheduler::~Scheduler() {
|
||||
for (auto& thread : thread_list) {
|
||||
@@ -61,7 +62,7 @@ Thread* Scheduler::PopNextReadyThread() {
|
||||
|
||||
void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
Thread* const previous_thread = GetCurrentThread();
|
||||
Process* const previous_process = Core::CurrentProcess();
|
||||
Process* const previous_process = system.Kernel().CurrentProcess();
|
||||
|
||||
UpdateLastContextSwitchTime(previous_thread, previous_process);
|
||||
|
||||
@@ -94,8 +95,8 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
auto* const thread_owner_process = current_thread->GetOwnerProcess();
|
||||
if (previous_process != thread_owner_process) {
|
||||
Core::System::GetInstance().Kernel().MakeCurrentProcess(thread_owner_process);
|
||||
SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
|
||||
system.Kernel().MakeCurrentProcess(thread_owner_process);
|
||||
Memory::SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
|
||||
}
|
||||
|
||||
cpu_core.LoadContext(new_thread->GetContext());
|
||||
@@ -111,7 +112,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
|
||||
const u64 prev_switch_ticks = last_context_switch_time;
|
||||
const u64 most_recent_switch_ticks = Core::System::GetInstance().CoreTiming().GetTicks();
|
||||
const u64 most_recent_switch_ticks = system.CoreTiming().GetTicks();
|
||||
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
|
||||
|
||||
if (thread != nullptr) {
|
||||
@@ -198,8 +199,7 @@ void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
|
||||
ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
|
||||
|
||||
// Yield this thread -- sleep for zero time and force reschedule to different thread
|
||||
WaitCurrentThread_Sleep();
|
||||
GetCurrentThread()->WakeAfterDelay(0);
|
||||
GetCurrentThread()->Sleep(0);
|
||||
}
|
||||
|
||||
void Scheduler::YieldWithLoadBalancing(Thread* thread) {
|
||||
@@ -214,8 +214,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) {
|
||||
ASSERT(priority < THREADPRIO_COUNT);
|
||||
|
||||
// Sleep for zero time to be able to force reschedule to different thread
|
||||
WaitCurrentThread_Sleep();
|
||||
GetCurrentThread()->WakeAfterDelay(0);
|
||||
GetCurrentThread()->Sleep(0);
|
||||
|
||||
Thread* suggested_thread = nullptr;
|
||||
|
||||
@@ -223,8 +222,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) {
|
||||
// Take the first non-nullptr one
|
||||
for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) {
|
||||
const auto res =
|
||||
Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread(
|
||||
core, priority);
|
||||
system.CpuCore(cur_core).Scheduler().GetNextSuggestedThread(core, priority);
|
||||
|
||||
// If scheduler provides a suggested thread
|
||||
if (res != nullptr) {
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
namespace Core {
|
||||
class ARM_Interface;
|
||||
}
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -21,7 +22,7 @@ class Process;
|
||||
|
||||
class Scheduler final {
|
||||
public:
|
||||
explicit Scheduler(Core::ARM_Interface& cpu_core);
|
||||
explicit Scheduler(Core::System& system, Core::ARM_Interface& cpu_core);
|
||||
~Scheduler();
|
||||
|
||||
/// Returns whether there are any threads that are ready to run.
|
||||
@@ -162,6 +163,7 @@ private:
|
||||
Core::ARM_Interface& cpu_core;
|
||||
u64 last_context_switch_time = 0;
|
||||
|
||||
Core::System& system;
|
||||
static std::mutex scheduler_mutex;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
|
||||
return MakeResult(std::move(session));
|
||||
}
|
||||
|
||||
void ServerPort::AppendPendingSession(SharedPtr<ServerSession> pending_session) {
|
||||
pending_sessions.push_back(std::move(pending_session));
|
||||
}
|
||||
|
||||
bool ServerPort::ShouldWait(Thread* thread) const {
|
||||
// If there are no pending sessions, we wait until a new one is added.
|
||||
return pending_sessions.empty();
|
||||
|
||||
@@ -22,6 +22,8 @@ class SessionRequestHandler;
|
||||
|
||||
class ServerPort final : public WaitObject {
|
||||
public:
|
||||
using HLEHandler = std::shared_ptr<SessionRequestHandler>;
|
||||
|
||||
/**
|
||||
* Creates a pair of ServerPort and an associated ClientPort.
|
||||
*
|
||||
@@ -51,22 +53,27 @@ public:
|
||||
*/
|
||||
ResultVal<SharedPtr<ServerSession>> Accept();
|
||||
|
||||
/// Whether or not this server port has an HLE handler available.
|
||||
bool HasHLEHandler() const {
|
||||
return hle_handler != nullptr;
|
||||
}
|
||||
|
||||
/// Gets the HLE handler for this port.
|
||||
HLEHandler GetHLEHandler() const {
|
||||
return hle_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
|
||||
* will inherit a reference to this handler.
|
||||
*/
|
||||
void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) {
|
||||
void SetHleHandler(HLEHandler hle_handler_) {
|
||||
hle_handler = std::move(hle_handler_);
|
||||
}
|
||||
|
||||
std::string name; ///< Name of port (optional)
|
||||
|
||||
/// ServerSessions waiting to be accepted by the port
|
||||
std::vector<SharedPtr<ServerSession>> pending_sessions;
|
||||
|
||||
/// This session's HLE request handler template (optional)
|
||||
/// ServerSessions created from this port inherit a reference to this handler.
|
||||
std::shared_ptr<SessionRequestHandler> hle_handler;
|
||||
/// Appends a ServerSession to the collection of ServerSessions
|
||||
/// waiting to be accepted by this port.
|
||||
void AppendPendingSession(SharedPtr<ServerSession> pending_session);
|
||||
|
||||
bool ShouldWait(Thread* thread) const override;
|
||||
void Acquire(Thread* thread) override;
|
||||
@@ -74,6 +81,16 @@ public:
|
||||
private:
|
||||
explicit ServerPort(KernelCore& kernel);
|
||||
~ServerPort() override;
|
||||
|
||||
/// ServerSessions waiting to be accepted by the port
|
||||
std::vector<SharedPtr<ServerSession>> pending_sessions;
|
||||
|
||||
/// This session's HLE request handler template (optional)
|
||||
/// ServerSessions created from this port inherit a reference to this handler.
|
||||
HLEHandler hle_handler;
|
||||
|
||||
/// Name of the port (optional)
|
||||
std::string name;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -63,42 +63,71 @@ void ServerSession::Acquire(Thread* thread) {
|
||||
pending_requesting_threads.pop_back();
|
||||
}
|
||||
|
||||
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
|
||||
auto* const domain_message_header = context.GetDomainMessageHeader();
|
||||
if (domain_message_header) {
|
||||
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
|
||||
context.SetDomainRequestHandlers(domain_request_handlers);
|
||||
|
||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||
const u32 object_id{context.GetDomainMessageHeader()->object_id};
|
||||
switch (domain_message_header->command) {
|
||||
case IPC::DomainMessageHeader::CommandType::SendMessage:
|
||||
if (object_id > domain_request_handlers.size()) {
|
||||
LOG_CRITICAL(IPC,
|
||||
"object_id {} is too big! This probably means a recent service call "
|
||||
"to {} needed to return a new interface!",
|
||||
object_id, name);
|
||||
UNREACHABLE();
|
||||
return RESULT_SUCCESS; // Ignore error if asserts are off
|
||||
}
|
||||
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
|
||||
|
||||
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
|
||||
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
|
||||
|
||||
domain_request_handlers[object_id - 1] = nullptr;
|
||||
|
||||
IPC::ResponseBuilder rb{context, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_CRITICAL(IPC, "Unknown domain command={}",
|
||||
static_cast<int>(domain_message_header->command.Value()));
|
||||
ASSERT(false);
|
||||
void ServerSession::ClientDisconnected() {
|
||||
// We keep a shared pointer to the hle handler to keep it alive throughout
|
||||
// the call to ClientDisconnected, as ClientDisconnected invalidates the
|
||||
// hle_handler member itself during the course of the function executing.
|
||||
std::shared_ptr<SessionRequestHandler> handler = hle_handler;
|
||||
if (handler) {
|
||||
// Note that after this returns, this server session's hle_handler is
|
||||
// invalidated (set to null).
|
||||
handler->ClientDisconnected(this);
|
||||
}
|
||||
|
||||
// TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set
|
||||
// their WaitSynchronization result to 0xC920181A.
|
||||
|
||||
// Clean up the list of client threads with pending requests, they are unneeded now that the
|
||||
// client endpoint is closed.
|
||||
pending_requesting_threads.clear();
|
||||
currently_handling = nullptr;
|
||||
}
|
||||
|
||||
void ServerSession::AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler) {
|
||||
domain_request_handlers.push_back(std::move(handler));
|
||||
}
|
||||
|
||||
std::size_t ServerSession::NumDomainRequestHandlers() const {
|
||||
return domain_request_handlers.size();
|
||||
}
|
||||
|
||||
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
|
||||
if (!context.HasDomainMessageHeader()) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
|
||||
context.SetDomainRequestHandlers(domain_request_handlers);
|
||||
|
||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||
const auto& domain_message_header = context.GetDomainMessageHeader();
|
||||
const u32 object_id{domain_message_header.object_id};
|
||||
switch (domain_message_header.command) {
|
||||
case IPC::DomainMessageHeader::CommandType::SendMessage:
|
||||
if (object_id > domain_request_handlers.size()) {
|
||||
LOG_CRITICAL(IPC,
|
||||
"object_id {} is too big! This probably means a recent service call "
|
||||
"to {} needed to return a new interface!",
|
||||
object_id, name);
|
||||
UNREACHABLE();
|
||||
return RESULT_SUCCESS; // Ignore error if asserts are off
|
||||
}
|
||||
return domain_request_handlers[object_id - 1]->HandleSyncRequest(context);
|
||||
|
||||
case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
|
||||
LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);
|
||||
|
||||
domain_request_handlers[object_id - 1] = nullptr;
|
||||
|
||||
IPC::ResponseBuilder rb{context, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_CRITICAL(IPC, "Unknown domain command={}",
|
||||
static_cast<int>(domain_message_header.command.Value()));
|
||||
ASSERT(false);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,14 @@ public:
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
Session* GetParent() {
|
||||
return parent.get();
|
||||
}
|
||||
|
||||
const Session* GetParent() const {
|
||||
return parent.get();
|
||||
}
|
||||
|
||||
using SessionPair = std::tuple<SharedPtr<ServerSession>, SharedPtr<ClientSession>>;
|
||||
|
||||
/**
|
||||
@@ -78,23 +86,16 @@ public:
|
||||
|
||||
void Acquire(Thread* thread) override;
|
||||
|
||||
std::string name; ///< The name of this session (optional)
|
||||
std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint.
|
||||
std::shared_ptr<SessionRequestHandler>
|
||||
hle_handler; ///< This session's HLE request handler (applicable when not a domain)
|
||||
/// Called when a client disconnection occurs.
|
||||
void ClientDisconnected();
|
||||
|
||||
/// This is the list of domain request handlers (after conversion to a domain)
|
||||
std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
|
||||
/// Adds a new domain request handler to the collection of request handlers within
|
||||
/// this ServerSession instance.
|
||||
void AppendDomainRequestHandler(std::shared_ptr<SessionRequestHandler> handler);
|
||||
|
||||
/// List of threads that are pending a response after a sync request. This list is processed in
|
||||
/// a LIFO manner, thus, the last request will be dispatched first.
|
||||
/// TODO(Subv): Verify if this is indeed processed in LIFO using a hardware test.
|
||||
std::vector<SharedPtr<Thread>> pending_requesting_threads;
|
||||
|
||||
/// Thread whose request is currently being handled. A request is considered "handled" when a
|
||||
/// response is sent via svcReplyAndReceive.
|
||||
/// TODO(Subv): Find a better name for this.
|
||||
SharedPtr<Thread> currently_handling;
|
||||
/// Retrieves the total number of domain request handlers that have been
|
||||
/// appended to this ServerSession instance.
|
||||
std::size_t NumDomainRequestHandlers() const;
|
||||
|
||||
/// Returns true if the session has been converted to a domain, otherwise False
|
||||
bool IsDomain() const {
|
||||
@@ -129,8 +130,30 @@ private:
|
||||
/// object handle.
|
||||
ResultCode HandleDomainSyncRequest(Kernel::HLERequestContext& context);
|
||||
|
||||
/// The parent session, which links to the client endpoint.
|
||||
std::shared_ptr<Session> parent;
|
||||
|
||||
/// This session's HLE request handler (applicable when not a domain)
|
||||
std::shared_ptr<SessionRequestHandler> hle_handler;
|
||||
|
||||
/// This is the list of domain request handlers (after conversion to a domain)
|
||||
std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
|
||||
|
||||
/// List of threads that are pending a response after a sync request. This list is processed in
|
||||
/// a LIFO manner, thus, the last request will be dispatched first.
|
||||
/// TODO(Subv): Verify if this is indeed processed in LIFO using a hardware test.
|
||||
std::vector<SharedPtr<Thread>> pending_requesting_threads;
|
||||
|
||||
/// Thread whose request is currently being handled. A request is considered "handled" when a
|
||||
/// response is sent via svcReplyAndReceive.
|
||||
/// TODO(Subv): Find a better name for this.
|
||||
SharedPtr<Thread> currently_handling;
|
||||
|
||||
/// When set to True, converts the session to a domain at the end of the command
|
||||
bool convert_to_domain{};
|
||||
|
||||
/// The name of this session (optional)
|
||||
std::string name;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
@@ -34,8 +33,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_
|
||||
shared_memory->backing_block_offset = 0;
|
||||
|
||||
// Refresh the address mappings for the current process.
|
||||
if (Core::CurrentProcess() != nullptr) {
|
||||
Core::CurrentProcess()->VMManager().RefreshMemoryBlockMappings(
|
||||
if (kernel.CurrentProcess() != nullptr) {
|
||||
kernel.CurrentProcess()->VMManager().RefreshMemoryBlockMappings(
|
||||
shared_memory->backing_block.get());
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
@@ -47,23 +48,6 @@ constexpr bool IsValidAddressRange(VAddr address, u64 size) {
|
||||
return address + size > address;
|
||||
}
|
||||
|
||||
// Checks if a given address range lies within a larger address range.
|
||||
constexpr bool IsInsideAddressRange(VAddr address, u64 size, VAddr address_range_begin,
|
||||
VAddr address_range_end) {
|
||||
const VAddr end_address = address + size - 1;
|
||||
return address_range_begin <= address && end_address <= address_range_end - 1;
|
||||
}
|
||||
|
||||
bool IsInsideAddressSpace(const VMManager& vm, VAddr address, u64 size) {
|
||||
return IsInsideAddressRange(address, size, vm.GetAddressSpaceBaseAddress(),
|
||||
vm.GetAddressSpaceEndAddress());
|
||||
}
|
||||
|
||||
bool IsInsideNewMapRegion(const VMManager& vm, VAddr address, u64 size) {
|
||||
return IsInsideAddressRange(address, size, vm.GetNewMapRegionBaseAddress(),
|
||||
vm.GetNewMapRegionEndAddress());
|
||||
}
|
||||
|
||||
// 8 GiB
|
||||
constexpr u64 MAIN_MEMORY_SIZE = 0x200000000;
|
||||
|
||||
@@ -105,14 +89,14 @@ ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_add
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!IsInsideAddressSpace(vm_manager, src_addr, size)) {
|
||||
if (!vm_manager.IsWithinAddressSpace(src_addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Source is not within the address space, addr=0x{:016X}, size=0x{:016X}",
|
||||
src_addr, size);
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!IsInsideNewMapRegion(vm_manager, dst_addr, size)) {
|
||||
if (!vm_manager.IsWithinNewMapRegion(dst_addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Destination is not within the new map region, addr=0x{:016X}, size=0x{:016X}",
|
||||
dst_addr, size);
|
||||
@@ -190,11 +174,8 @@ static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
auto& vm_manager = Core::CurrentProcess()->VMManager();
|
||||
const VAddr heap_base = vm_manager.GetHeapRegionBaseAddress();
|
||||
const auto alloc_result =
|
||||
vm_manager.HeapAllocate(heap_base, heap_size, VMAPermission::ReadWrite);
|
||||
|
||||
auto& vm_manager = Core::System::GetInstance().Kernel().CurrentProcess()->VMManager();
|
||||
const auto alloc_result = vm_manager.SetHeapSize(heap_size);
|
||||
if (alloc_result.Failed()) {
|
||||
return alloc_result.Code();
|
||||
}
|
||||
@@ -238,7 +219,7 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
auto& vm_manager = current_process->VMManager();
|
||||
|
||||
if (!IsInsideAddressSpace(vm_manager, addr, size)) {
|
||||
if (!vm_manager.IsWithinAddressSpace(addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr,
|
||||
size);
|
||||
@@ -299,7 +280,7 @@ static ResultCode SetMemoryAttribute(VAddr address, u64 size, u32 mask, u32 attr
|
||||
}
|
||||
|
||||
auto& vm_manager = Core::CurrentProcess()->VMManager();
|
||||
if (!IsInsideAddressSpace(vm_manager, address, size)) {
|
||||
if (!vm_manager.IsWithinAddressSpace(address, size)) {
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Given address (0x{:016X}) is outside the bounds of the address space.", address);
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
@@ -567,9 +548,9 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
|
||||
requesting_thread_handle);
|
||||
auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess();
|
||||
return current_process->GetMutex().TryAcquire(mutex_addr, holding_thread_handle,
|
||||
requesting_thread_handle);
|
||||
}
|
||||
|
||||
/// Unlock a mutex
|
||||
@@ -587,7 +568,8 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
return Mutex::Release(mutex_addr);
|
||||
auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess();
|
||||
return current_process->GetMutex().Release(mutex_addr);
|
||||
}
|
||||
|
||||
enum class BreakType : u32 {
|
||||
@@ -823,7 +805,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::TotalHeapUsage:
|
||||
*result = process->VMManager().GetTotalHeapUsage();
|
||||
*result = process->VMManager().GetCurrentHeapSize();
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::IsVirtualAddressMemoryEnabled:
|
||||
@@ -1300,10 +1282,14 @@ static ResultCode StartThread(Handle thread_handle) {
|
||||
|
||||
/// Called when a thread exits
|
||||
static void ExitThread() {
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC());
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
||||
ExitCurrentThread();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
|
||||
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
current_thread->Stop();
|
||||
system.CurrentScheduler().RemoveThread(current_thread);
|
||||
system.PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Sleep the current thread
|
||||
@@ -1316,32 +1302,32 @@ static void SleepThread(s64 nanoseconds) {
|
||||
YieldAndWaitForLoadBalancing = -2,
|
||||
};
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& scheduler = system.CurrentScheduler();
|
||||
auto* const current_thread = scheduler.GetCurrentThread();
|
||||
|
||||
if (nanoseconds <= 0) {
|
||||
auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
|
||||
switch (static_cast<SleepType>(nanoseconds)) {
|
||||
case SleepType::YieldWithoutLoadBalancing:
|
||||
scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
|
||||
scheduler.YieldWithoutLoadBalancing(current_thread);
|
||||
break;
|
||||
case SleepType::YieldWithLoadBalancing:
|
||||
scheduler.YieldWithLoadBalancing(GetCurrentThread());
|
||||
scheduler.YieldWithLoadBalancing(current_thread);
|
||||
break;
|
||||
case SleepType::YieldAndWaitForLoadBalancing:
|
||||
scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
|
||||
scheduler.YieldAndWaitForLoadBalancing(current_thread);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
|
||||
}
|
||||
} else {
|
||||
// Sleep current thread and check for next thread to schedule
|
||||
WaitCurrentThread_Sleep();
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
GetCurrentThread()->WakeAfterDelay(nanoseconds);
|
||||
current_thread->Sleep(nanoseconds);
|
||||
}
|
||||
|
||||
// Reschedule all CPU cores
|
||||
for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
|
||||
Core::System::GetInstance().CpuCore(i).PrepareReschedule();
|
||||
for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) {
|
||||
system.CpuCore(i).PrepareReschedule();
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait process wide key atomic
|
||||
@@ -1352,11 +1338,15 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
|
||||
"called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
|
||||
mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
|
||||
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess();
|
||||
const auto& handle_table = current_process->GetHandleTable();
|
||||
SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
ASSERT(thread);
|
||||
|
||||
CASCADE_CODE(Mutex::Release(mutex_addr));
|
||||
const auto release_result = current_process->GetMutex().Release(mutex_addr);
|
||||
if (release_result.IsError()) {
|
||||
return release_result;
|
||||
}
|
||||
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
current_thread->SetCondVarWaitAddress(condition_variable_addr);
|
||||
@@ -1495,20 +1485,10 @@ static ResultCode WaitForAddress(VAddr address, u32 type, s32 value, s64 timeout
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
switch (static_cast<AddressArbiter::ArbitrationType>(type)) {
|
||||
case AddressArbiter::ArbitrationType::WaitIfLessThan:
|
||||
return AddressArbiter::WaitForAddressIfLessThan(address, value, timeout, false);
|
||||
case AddressArbiter::ArbitrationType::DecrementAndWaitIfLessThan:
|
||||
return AddressArbiter::WaitForAddressIfLessThan(address, value, timeout, true);
|
||||
case AddressArbiter::ArbitrationType::WaitIfEqual:
|
||||
return AddressArbiter::WaitForAddressIfEqual(address, value, timeout);
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Invalid arbitration type, expected WaitIfLessThan, DecrementAndWaitIfLessThan "
|
||||
"or WaitIfEqual but got {}",
|
||||
type);
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
const auto arbitration_type = static_cast<AddressArbiter::ArbitrationType>(type);
|
||||
auto& address_arbiter =
|
||||
Core::System::GetInstance().Kernel().CurrentProcess()->GetAddressArbiter();
|
||||
return address_arbiter.WaitForAddress(address, arbitration_type, value, timeout);
|
||||
}
|
||||
|
||||
// Signals to an address (via Address Arbiter)
|
||||
@@ -1526,21 +1506,10 @@ static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
switch (static_cast<AddressArbiter::SignalType>(type)) {
|
||||
case AddressArbiter::SignalType::Signal:
|
||||
return AddressArbiter::SignalToAddress(address, num_to_wake);
|
||||
case AddressArbiter::SignalType::IncrementAndSignalIfEqual:
|
||||
return AddressArbiter::IncrementAndSignalToAddressIfEqual(address, value, num_to_wake);
|
||||
case AddressArbiter::SignalType::ModifyByWaitingCountAndSignalIfEqual:
|
||||
return AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(address, value,
|
||||
num_to_wake);
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC,
|
||||
"Invalid signal type, expected Signal, IncrementAndSignalIfEqual "
|
||||
"or ModifyByWaitingCountAndSignalIfEqual but got {}",
|
||||
type);
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
const auto signal_type = static_cast<AddressArbiter::SignalType>(type);
|
||||
auto& address_arbiter =
|
||||
Core::System::GetInstance().Kernel().CurrentProcess()->GetAddressArbiter();
|
||||
return address_arbiter.SignalToAddress(address, signal_type, value, num_to_wake);
|
||||
}
|
||||
|
||||
/// This returns the total CPU ticks elapsed since the CPU was powered-on
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -68,17 +66,6 @@ void Thread::Stop() {
|
||||
owner_process->FreeTLSSlot(tls_address);
|
||||
}
|
||||
|
||||
void WaitCurrentThread_Sleep() {
|
||||
Thread* thread = GetCurrentThread();
|
||||
thread->SetStatus(ThreadStatus::WaitSleep);
|
||||
}
|
||||
|
||||
void ExitCurrentThread() {
|
||||
Thread* thread = GetCurrentThread();
|
||||
thread->Stop();
|
||||
Core::System::GetInstance().CurrentScheduler().RemoveThread(thread);
|
||||
}
|
||||
|
||||
void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
// Don't schedule a wakeup if the thread wants to wait forever
|
||||
if (nanoseconds == -1)
|
||||
@@ -184,8 +171,6 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
return ERR_INVALID_PROCESSOR_ID;
|
||||
}
|
||||
|
||||
// TODO(yuriks): Other checks, returning 0xD9001BEA
|
||||
|
||||
if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) {
|
||||
LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point);
|
||||
// TODO (bunnei): Find the correct error code to use here
|
||||
@@ -271,8 +256,8 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
if (thread->lock_owner == this) {
|
||||
// If the thread is already waiting for this thread to release the mutex, ensure that the
|
||||
// waiters list is consistent and return without doing anything.
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter != wait_mutex_threads.end());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -280,11 +265,16 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
ASSERT(thread->lock_owner == nullptr);
|
||||
|
||||
// Ensure that the thread is not already in the list of mutex waiters
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr == wait_mutex_threads.end());
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter == wait_mutex_threads.end());
|
||||
|
||||
// Keep the list in an ordered fashion
|
||||
const auto insertion_point = std::find_if(
|
||||
wait_mutex_threads.begin(), wait_mutex_threads.end(),
|
||||
[&thread](const auto& entry) { return entry->GetPriority() > thread->GetPriority(); });
|
||||
wait_mutex_threads.insert(insertion_point, thread);
|
||||
thread->lock_owner = this;
|
||||
wait_mutex_threads.emplace_back(std::move(thread));
|
||||
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
@@ -292,32 +282,44 @@ void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
|
||||
ASSERT(thread->lock_owner == this);
|
||||
|
||||
// Ensure that the thread is in the list of mutex waiters
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter != wait_mutex_threads.end());
|
||||
|
||||
wait_mutex_threads.erase(iter);
|
||||
|
||||
boost::remove_erase(wait_mutex_threads, thread);
|
||||
thread->lock_owner = nullptr;
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::UpdatePriority() {
|
||||
// Find the highest priority among all the threads that are waiting for this thread's lock
|
||||
// If any of the threads waiting on the mutex have a higher priority
|
||||
// (taking into account priority inheritance), then this thread inherits
|
||||
// that thread's priority.
|
||||
u32 new_priority = nominal_priority;
|
||||
for (const auto& thread : wait_mutex_threads) {
|
||||
if (thread->nominal_priority < new_priority)
|
||||
new_priority = thread->nominal_priority;
|
||||
if (!wait_mutex_threads.empty()) {
|
||||
if (wait_mutex_threads.front()->current_priority < new_priority) {
|
||||
new_priority = wait_mutex_threads.front()->current_priority;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_priority == current_priority)
|
||||
if (new_priority == current_priority) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler->SetThreadPriority(this, new_priority);
|
||||
|
||||
current_priority = new_priority;
|
||||
|
||||
if (!lock_owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the thread is within the correct location in the waiting list.
|
||||
auto old_owner = lock_owner;
|
||||
lock_owner->RemoveMutexWaiter(this);
|
||||
old_owner->AddMutexWaiter(this);
|
||||
|
||||
// Recursively update the priority of the thread that depends on the priority of this one.
|
||||
if (lock_owner)
|
||||
lock_owner->UpdatePriority();
|
||||
lock_owner->UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
@@ -393,6 +395,14 @@ void Thread::SetActivity(ThreadActivity value) {
|
||||
}
|
||||
}
|
||||
|
||||
void Thread::Sleep(s64 nanoseconds) {
|
||||
// Sleep current thread and check for next thread to schedule
|
||||
SetStatus(ThreadStatus::WaitSleep);
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
WakeAfterDelay(nanoseconds);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
|
||||
@@ -383,6 +383,9 @@ public:
|
||||
|
||||
void SetActivity(ThreadActivity value);
|
||||
|
||||
/// Sleeps this thread for the given amount of nanoseconds.
|
||||
void Sleep(s64 nanoseconds);
|
||||
|
||||
private:
|
||||
explicit Thread(KernelCore& kernel);
|
||||
~Thread() override;
|
||||
@@ -398,8 +401,14 @@ private:
|
||||
VAddr entry_point = 0;
|
||||
VAddr stack_top = 0;
|
||||
|
||||
u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
|
||||
u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
|
||||
/// Nominal thread priority, as set by the emulated application.
|
||||
/// The nominal priority is the thread priority without priority
|
||||
/// inheritance taken into account.
|
||||
u32 nominal_priority = 0;
|
||||
|
||||
/// Current thread priority. This may change over the course of the
|
||||
/// thread's lifetime in order to facilitate priority inheritance.
|
||||
u32 current_priority = 0;
|
||||
|
||||
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
@@ -460,14 +469,4 @@ private:
|
||||
*/
|
||||
Thread* GetCurrentThread();
|
||||
|
||||
/**
|
||||
* Waits the current thread on a sleep
|
||||
*/
|
||||
void WaitCurrentThread_Sleep();
|
||||
|
||||
/**
|
||||
* Stops the current thread and removes it from the thread_list
|
||||
*/
|
||||
void ExitCurrentThread();
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -7,34 +7,42 @@
|
||||
#include <utility>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/memory_hook.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_hook.h"
|
||||
#include "core/memory_setup.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
static const char* GetMemoryStateName(MemoryState state) {
|
||||
namespace {
|
||||
const char* GetMemoryStateName(MemoryState state) {
|
||||
static constexpr const char* names[] = {
|
||||
"Unmapped", "Io",
|
||||
"Normal", "CodeStatic",
|
||||
"CodeMutable", "Heap",
|
||||
"Shared", "Unknown1",
|
||||
"ModuleCodeStatic", "ModuleCodeMutable",
|
||||
"IpcBuffer0", "Stack",
|
||||
"ThreadLocal", "TransferMemoryIsolated",
|
||||
"TransferMemory", "ProcessMemory",
|
||||
"Inaccessible", "IpcBuffer1",
|
||||
"IpcBuffer3", "KernelStack",
|
||||
"Unmapped", "Io",
|
||||
"Normal", "Code",
|
||||
"CodeData", "Heap",
|
||||
"Shared", "Unknown1",
|
||||
"ModuleCode", "ModuleCodeData",
|
||||
"IpcBuffer0", "Stack",
|
||||
"ThreadLocal", "TransferMemoryIsolated",
|
||||
"TransferMemory", "ProcessMemory",
|
||||
"Inaccessible", "IpcBuffer1",
|
||||
"IpcBuffer3", "KernelStack",
|
||||
};
|
||||
|
||||
return names[ToSvcMemoryState(state)];
|
||||
}
|
||||
|
||||
// Checks if a given address range lies within a larger address range.
|
||||
constexpr bool IsInsideAddressRange(VAddr address, u64 size, VAddr address_range_begin,
|
||||
VAddr address_range_end) {
|
||||
const VAddr end_address = address + size - 1;
|
||||
return address_range_begin <= address && end_address <= address_range_end - 1;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
|
||||
ASSERT(base + size == next.base);
|
||||
if (permissions != next.permissions || state != next.state || attribute != next.attribute ||
|
||||
@@ -169,7 +177,7 @@ ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
|
||||
|
||||
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
|
||||
MemoryState state,
|
||||
Memory::MemoryHookPointer mmio_handler) {
|
||||
Common::MemoryHookPointer mmio_handler) {
|
||||
// This is the appropriately sized VMA that will turn into our allocation.
|
||||
CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
|
||||
VirtualMemoryArea& final_vma = vma_handle->second;
|
||||
@@ -248,59 +256,50 @@ ResultCode VMManager::ReprotectRange(VAddr target, u64 size, VMAPermission new_p
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<VAddr> VMManager::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {
|
||||
if (target < GetHeapRegionBaseAddress() || target + size > GetHeapRegionEndAddress() ||
|
||||
target + size < target) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
|
||||
if (size > GetHeapRegionSize()) {
|
||||
return ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// No need to do any additional work if the heap is already the given size.
|
||||
if (size == GetCurrentHeapSize()) {
|
||||
return MakeResult(heap_region_base);
|
||||
}
|
||||
|
||||
if (heap_memory == nullptr) {
|
||||
// Initialize heap
|
||||
heap_memory = std::make_shared<std::vector<u8>>();
|
||||
heap_start = heap_end = target;
|
||||
heap_memory = std::make_shared<std::vector<u8>>(size);
|
||||
heap_end = heap_region_base + size;
|
||||
} else {
|
||||
UnmapRange(heap_start, heap_end - heap_start);
|
||||
UnmapRange(heap_region_base, GetCurrentHeapSize());
|
||||
}
|
||||
|
||||
// If necessary, expand backing vector to cover new heap extents.
|
||||
if (target < heap_start) {
|
||||
heap_memory->insert(begin(*heap_memory), heap_start - target, 0);
|
||||
heap_start = target;
|
||||
// If necessary, expand backing vector to cover new heap extents in
|
||||
// the case of allocating. Otherwise, shrink the backing memory,
|
||||
// if a smaller heap has been requested.
|
||||
const u64 old_heap_size = GetCurrentHeapSize();
|
||||
if (size > old_heap_size) {
|
||||
const u64 alloc_size = size - old_heap_size;
|
||||
|
||||
heap_memory->insert(heap_memory->end(), alloc_size, 0);
|
||||
RefreshMemoryBlockMappings(heap_memory.get());
|
||||
} else if (size < old_heap_size) {
|
||||
heap_memory->resize(size);
|
||||
heap_memory->shrink_to_fit();
|
||||
|
||||
RefreshMemoryBlockMappings(heap_memory.get());
|
||||
}
|
||||
if (target + size > heap_end) {
|
||||
heap_memory->insert(end(*heap_memory), (target + size) - heap_end, 0);
|
||||
heap_end = target + size;
|
||||
RefreshMemoryBlockMappings(heap_memory.get());
|
||||
}
|
||||
ASSERT(heap_end - heap_start == heap_memory->size());
|
||||
|
||||
CASCADE_RESULT(auto vma, MapMemoryBlock(target, heap_memory, target - heap_start, size,
|
||||
MemoryState::Heap));
|
||||
Reprotect(vma, perms);
|
||||
heap_end = heap_region_base + size;
|
||||
ASSERT(GetCurrentHeapSize() == heap_memory->size());
|
||||
|
||||
heap_used = size;
|
||||
|
||||
return MakeResult<VAddr>(heap_end - size);
|
||||
}
|
||||
|
||||
ResultCode VMManager::HeapFree(VAddr target, u64 size) {
|
||||
if (target < GetHeapRegionBaseAddress() || target + size > GetHeapRegionEndAddress() ||
|
||||
target + size < target) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
const auto mapping_result =
|
||||
MapMemoryBlock(heap_region_base, heap_memory, 0, size, MemoryState::Heap);
|
||||
if (mapping_result.Failed()) {
|
||||
return mapping_result.Code();
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
const ResultCode result = UnmapRange(target, size);
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
heap_used -= size;
|
||||
return RESULT_SUCCESS;
|
||||
return MakeResult<VAddr>(heap_region_base);
|
||||
}
|
||||
|
||||
MemoryInfo VMManager::QueryMemory(VAddr address) const {
|
||||
@@ -592,6 +591,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
|
||||
|
||||
heap_region_base = map_region_end;
|
||||
heap_region_end = heap_region_base + heap_region_size;
|
||||
heap_end = heap_region_base;
|
||||
|
||||
new_map_region_base = heap_region_end;
|
||||
new_map_region_end = new_map_region_base + new_map_region_size;
|
||||
@@ -618,7 +618,7 @@ void VMManager::ClearPageTable() {
|
||||
std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
|
||||
page_table.special_regions.clear();
|
||||
std::fill(page_table.attributes.begin(), page_table.attributes.end(),
|
||||
Memory::PageType::Unmapped);
|
||||
Common::PageType::Unmapped);
|
||||
}
|
||||
|
||||
VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask,
|
||||
@@ -686,10 +686,6 @@ u64 VMManager::GetTotalMemoryUsage() const {
|
||||
return 0xF8000000;
|
||||
}
|
||||
|
||||
u64 VMManager::GetTotalHeapUsage() const {
|
||||
return heap_used;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetAddressSpaceBaseAddress() const {
|
||||
return address_space_base;
|
||||
}
|
||||
@@ -706,6 +702,11 @@ u64 VMManager::GetAddressSpaceWidth() const {
|
||||
return address_space_width;
|
||||
}
|
||||
|
||||
bool VMManager::IsWithinAddressSpace(VAddr address, u64 size) const {
|
||||
return IsInsideAddressRange(address, size, GetAddressSpaceBaseAddress(),
|
||||
GetAddressSpaceEndAddress());
|
||||
}
|
||||
|
||||
VAddr VMManager::GetASLRRegionBaseAddress() const {
|
||||
return aslr_region_base;
|
||||
}
|
||||
@@ -750,6 +751,11 @@ u64 VMManager::GetCodeRegionSize() const {
|
||||
return code_region_end - code_region_base;
|
||||
}
|
||||
|
||||
bool VMManager::IsWithinCodeRegion(VAddr address, u64 size) const {
|
||||
return IsInsideAddressRange(address, size, GetCodeRegionBaseAddress(),
|
||||
GetCodeRegionEndAddress());
|
||||
}
|
||||
|
||||
VAddr VMManager::GetHeapRegionBaseAddress() const {
|
||||
return heap_region_base;
|
||||
}
|
||||
@@ -762,6 +768,15 @@ u64 VMManager::GetHeapRegionSize() const {
|
||||
return heap_region_end - heap_region_base;
|
||||
}
|
||||
|
||||
u64 VMManager::GetCurrentHeapSize() const {
|
||||
return heap_end - heap_region_base;
|
||||
}
|
||||
|
||||
bool VMManager::IsWithinHeapRegion(VAddr address, u64 size) const {
|
||||
return IsInsideAddressRange(address, size, GetHeapRegionBaseAddress(),
|
||||
GetHeapRegionEndAddress());
|
||||
}
|
||||
|
||||
VAddr VMManager::GetMapRegionBaseAddress() const {
|
||||
return map_region_base;
|
||||
}
|
||||
@@ -774,6 +789,10 @@ u64 VMManager::GetMapRegionSize() const {
|
||||
return map_region_end - map_region_base;
|
||||
}
|
||||
|
||||
bool VMManager::IsWithinMapRegion(VAddr address, u64 size) const {
|
||||
return IsInsideAddressRange(address, size, GetMapRegionBaseAddress(), GetMapRegionEndAddress());
|
||||
}
|
||||
|
||||
VAddr VMManager::GetNewMapRegionBaseAddress() const {
|
||||
return new_map_region_base;
|
||||
}
|
||||
@@ -786,6 +805,11 @@ u64 VMManager::GetNewMapRegionSize() const {
|
||||
return new_map_region_end - new_map_region_base;
|
||||
}
|
||||
|
||||
bool VMManager::IsWithinNewMapRegion(VAddr address, u64 size) const {
|
||||
return IsInsideAddressRange(address, size, GetNewMapRegionBaseAddress(),
|
||||
GetNewMapRegionEndAddress());
|
||||
}
|
||||
|
||||
VAddr VMManager::GetTLSIORegionBaseAddress() const {
|
||||
return tls_io_region_base;
|
||||
}
|
||||
@@ -798,4 +822,9 @@ u64 VMManager::GetTLSIORegionSize() const {
|
||||
return tls_io_region_end - tls_io_region_base;
|
||||
}
|
||||
|
||||
bool VMManager::IsWithinTLSIORegion(VAddr address, u64 size) const {
|
||||
return IsInsideAddressRange(address, size, GetTLSIORegionBaseAddress(),
|
||||
GetTLSIORegionEndAddress());
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
#include "common/page_table.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace FileSys {
|
||||
enum class ProgramAddressSpaceType : u8;
|
||||
@@ -164,12 +165,12 @@ enum class MemoryState : u32 {
|
||||
Unmapped = 0x00,
|
||||
Io = 0x01 | FlagMapped,
|
||||
Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed,
|
||||
CodeStatic = 0x03 | CodeFlags | FlagMapProcess,
|
||||
CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory,
|
||||
Code = 0x03 | CodeFlags | FlagMapProcess,
|
||||
CodeData = 0x04 | DataFlags | FlagMapProcess | FlagCodeMemory,
|
||||
Heap = 0x05 | DataFlags | FlagCodeMemory,
|
||||
Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated,
|
||||
ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
|
||||
ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
|
||||
ModuleCode = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
|
||||
ModuleCodeData = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
|
||||
|
||||
IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated |
|
||||
IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned,
|
||||
@@ -290,7 +291,7 @@ struct VirtualMemoryArea {
|
||||
// Settings for type = MMIO
|
||||
/// Physical address of the register area this VMA maps to.
|
||||
PAddr paddr = 0;
|
||||
Memory::MemoryHookPointer mmio_handler = nullptr;
|
||||
Common::MemoryHookPointer mmio_handler = nullptr;
|
||||
|
||||
/// Tests if this area can be merged to the right with `next`.
|
||||
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
|
||||
@@ -368,7 +369,7 @@ public:
|
||||
* @param mmio_handler The handler that will implement read and write for this MMIO region.
|
||||
*/
|
||||
ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state,
|
||||
Memory::MemoryHookPointer mmio_handler);
|
||||
Common::MemoryHookPointer mmio_handler);
|
||||
|
||||
/// Unmaps a range of addresses, splitting VMAs as necessary.
|
||||
ResultCode UnmapRange(VAddr target, u64 size);
|
||||
@@ -379,11 +380,41 @@ public:
|
||||
/// Changes the permissions of a range of addresses, splitting VMAs as necessary.
|
||||
ResultCode ReprotectRange(VAddr target, u64 size, VMAPermission new_perms);
|
||||
|
||||
ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
|
||||
ResultCode HeapFree(VAddr target, u64 size);
|
||||
|
||||
ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
|
||||
|
||||
/// Attempts to allocate a heap with the given size.
|
||||
///
|
||||
/// @param size The size of the heap to allocate in bytes.
|
||||
///
|
||||
/// @note If a heap is currently allocated, and this is called
|
||||
/// with a size that is equal to the size of the current heap,
|
||||
/// then this function will do nothing and return the current
|
||||
/// heap's starting address, as there's no need to perform
|
||||
/// any additional heap allocation work.
|
||||
///
|
||||
/// @note If a heap is currently allocated, and this is called
|
||||
/// with a size less than the current heap's size, then
|
||||
/// this function will attempt to shrink the heap.
|
||||
///
|
||||
/// @note If a heap is currently allocated, and this is called
|
||||
/// with a size larger than the current heap's size, then
|
||||
/// this function will attempt to extend the size of the heap.
|
||||
///
|
||||
/// @returns A result indicating either success or failure.
|
||||
/// <p>
|
||||
/// If successful, this function will return a result
|
||||
/// containing the starting address to the allocated heap.
|
||||
/// <p>
|
||||
/// If unsuccessful, this function will return a result
|
||||
/// containing an error code.
|
||||
///
|
||||
/// @pre The given size must lie within the allowable heap
|
||||
/// memory region managed by this VMManager instance.
|
||||
/// Failure to abide by this will result in ERR_OUT_OF_MEMORY
|
||||
/// being returned as the result.
|
||||
///
|
||||
ResultVal<VAddr> SetHeapSize(u64 size);
|
||||
|
||||
/// Queries the memory manager for information about the given address.
|
||||
///
|
||||
/// @param address The address to query the memory manager about for information.
|
||||
@@ -417,9 +448,6 @@ public:
|
||||
/// Gets the total memory usage, used by svcGetInfo
|
||||
u64 GetTotalMemoryUsage() const;
|
||||
|
||||
/// Gets the total heap usage, used by svcGetInfo
|
||||
u64 GetTotalHeapUsage() const;
|
||||
|
||||
/// Gets the address space base address
|
||||
VAddr GetAddressSpaceBaseAddress() const;
|
||||
|
||||
@@ -432,18 +460,21 @@ public:
|
||||
/// Gets the address space width in bits.
|
||||
u64 GetAddressSpaceWidth() const;
|
||||
|
||||
/// Determines whether or not the given address range lies within the address space.
|
||||
bool IsWithinAddressSpace(VAddr address, u64 size) const;
|
||||
|
||||
/// Gets the base address of the ASLR region.
|
||||
VAddr GetASLRRegionBaseAddress() const;
|
||||
|
||||
/// Gets the end address of the ASLR region.
|
||||
VAddr GetASLRRegionEndAddress() const;
|
||||
|
||||
/// Determines whether or not the specified address range is within the ASLR region.
|
||||
bool IsWithinASLRRegion(VAddr address, u64 size) const;
|
||||
|
||||
/// Gets the size of the ASLR region
|
||||
u64 GetASLRRegionSize() const;
|
||||
|
||||
/// Determines whether or not the specified address range is within the ASLR region.
|
||||
bool IsWithinASLRRegion(VAddr address, u64 size) const;
|
||||
|
||||
/// Gets the base address of the code region.
|
||||
VAddr GetCodeRegionBaseAddress() const;
|
||||
|
||||
@@ -453,6 +484,9 @@ public:
|
||||
/// Gets the total size of the code region in bytes.
|
||||
u64 GetCodeRegionSize() const;
|
||||
|
||||
/// Determines whether or not the specified range is within the code region.
|
||||
bool IsWithinCodeRegion(VAddr address, u64 size) const;
|
||||
|
||||
/// Gets the base address of the heap region.
|
||||
VAddr GetHeapRegionBaseAddress() const;
|
||||
|
||||
@@ -462,6 +496,16 @@ public:
|
||||
/// Gets the total size of the heap region in bytes.
|
||||
u64 GetHeapRegionSize() const;
|
||||
|
||||
/// Gets the total size of the current heap in bytes.
|
||||
///
|
||||
/// @note This is the current allocated heap size, not the size
|
||||
/// of the region it's allowed to exist within.
|
||||
///
|
||||
u64 GetCurrentHeapSize() const;
|
||||
|
||||
/// Determines whether or not the specified range is within the heap region.
|
||||
bool IsWithinHeapRegion(VAddr address, u64 size) const;
|
||||
|
||||
/// Gets the base address of the map region.
|
||||
VAddr GetMapRegionBaseAddress() const;
|
||||
|
||||
@@ -471,6 +515,9 @@ public:
|
||||
/// Gets the total size of the map region in bytes.
|
||||
u64 GetMapRegionSize() const;
|
||||
|
||||
/// Determines whether or not the specified range is within the map region.
|
||||
bool IsWithinMapRegion(VAddr address, u64 size) const;
|
||||
|
||||
/// Gets the base address of the new map region.
|
||||
VAddr GetNewMapRegionBaseAddress() const;
|
||||
|
||||
@@ -480,6 +527,9 @@ public:
|
||||
/// Gets the total size of the new map region in bytes.
|
||||
u64 GetNewMapRegionSize() const;
|
||||
|
||||
/// Determines whether or not the given address range is within the new map region
|
||||
bool IsWithinNewMapRegion(VAddr address, u64 size) const;
|
||||
|
||||
/// Gets the base address of the TLS IO region.
|
||||
VAddr GetTLSIORegionBaseAddress() const;
|
||||
|
||||
@@ -489,9 +539,12 @@ public:
|
||||
/// Gets the total size of the TLS IO region in bytes.
|
||||
u64 GetTLSIORegionSize() const;
|
||||
|
||||
/// Determines if the given address range is within the TLS IO region.
|
||||
bool IsWithinTLSIORegion(VAddr address, u64 size) const;
|
||||
|
||||
/// Each VMManager has its own page table, which is set as the main one when the owning process
|
||||
/// is scheduled.
|
||||
Memory::PageTable page_table;
|
||||
Common::PageTable page_table{Memory::PAGE_BITS};
|
||||
|
||||
private:
|
||||
using VMAIter = VMAMap::iterator;
|
||||
@@ -606,9 +659,9 @@ private:
|
||||
// This makes deallocation and reallocation of holes fast and keeps process memory contiguous
|
||||
// in the emulator address space, allowing Memory::GetPointer to be reasonably safe.
|
||||
std::shared_ptr<std::vector<u8>> heap_memory;
|
||||
// The left/right bounds of the address space covered by heap_memory.
|
||||
VAddr heap_start = 0;
|
||||
|
||||
// The end of the currently allocated heap. This is not an inclusive
|
||||
// end of the range. This is essentially 'base_address + current_size'.
|
||||
VAddr heap_end = 0;
|
||||
u64 heap_used = 0;
|
||||
};
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -8,19 +8,10 @@
|
||||
#include <utility>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
// All the constants in this file come from http://switchbrew.org/index.php?title=Error_codes
|
||||
|
||||
/**
|
||||
* Detailed description of the error. Code 0 always means success.
|
||||
*/
|
||||
enum class ErrorDescription : u32 {
|
||||
Success = 0,
|
||||
RemoteProcessDead = 301,
|
||||
};
|
||||
|
||||
/**
|
||||
* Identifies the module which caused the error. Error codes can be propagated through a call
|
||||
* chain, meaning that this doesn't always correspond to the module where the API call made is
|
||||
@@ -121,7 +112,7 @@ enum class ErrorModule : u32 {
|
||||
ShopN = 811,
|
||||
};
|
||||
|
||||
/// Encapsulates a CTR-OS error code, allowing it to be separated into its constituent fields.
|
||||
/// Encapsulates a Horizon OS error code, allowing it to be separated into its constituent fields.
|
||||
union ResultCode {
|
||||
u32 raw;
|
||||
|
||||
@@ -134,17 +125,9 @@ union ResultCode {
|
||||
|
||||
constexpr explicit ResultCode(u32 raw) : raw(raw) {}
|
||||
|
||||
constexpr ResultCode(ErrorModule module, ErrorDescription description)
|
||||
: ResultCode(module, static_cast<u32>(description)) {}
|
||||
|
||||
constexpr ResultCode(ErrorModule module_, u32 description_)
|
||||
: raw(module.FormatValue(module_) | description.FormatValue(description_)) {}
|
||||
|
||||
constexpr ResultCode& operator=(const ResultCode& o) {
|
||||
raw = o.raw;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool IsSuccess() const {
|
||||
return raw == 0;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <stack>
|
||||
#include "audio_core/audio_renderer.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
@@ -93,38 +93,84 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx)
|
||||
}
|
||||
|
||||
IAudioController::IAudioController() : ServiceFramework("IAudioController") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"},
|
||||
{1, &IAudioController::GetMainAppletExpectedMasterVolume,
|
||||
"GetMainAppletExpectedMasterVolume"},
|
||||
{2, &IAudioController::GetLibraryAppletExpectedMasterVolume,
|
||||
"GetLibraryAppletExpectedMasterVolume"},
|
||||
{3, nullptr, "ChangeMainAppletMasterVolume"},
|
||||
{4, nullptr, "SetTransparentVolumeRate"},
|
||||
{1, &IAudioController::GetMainAppletExpectedMasterVolume, "GetMainAppletExpectedMasterVolume"},
|
||||
{2, &IAudioController::GetLibraryAppletExpectedMasterVolume, "GetLibraryAppletExpectedMasterVolume"},
|
||||
{3, &IAudioController::ChangeMainAppletMasterVolume, "ChangeMainAppletMasterVolume"},
|
||||
{4, &IAudioController::SetTransparentAudioRate, "SetTransparentVolumeRate"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IAudioController::~IAudioController() = default;
|
||||
|
||||
void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const float main_applet_volume_tmp = rp.Pop<float>();
|
||||
const float library_applet_volume_tmp = rp.Pop<float>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called. main_applet_volume={}, library_applet_volume={}",
|
||||
main_applet_volume_tmp, library_applet_volume_tmp);
|
||||
|
||||
// Ensure the volume values remain within the 0-100% range
|
||||
main_applet_volume = std::clamp(main_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
|
||||
library_applet_volume =
|
||||
std::clamp(library_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called. main_applet_volume={}", main_applet_volume);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(volume);
|
||||
rb.Push(main_applet_volume);
|
||||
}
|
||||
|
||||
void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called. library_applet_volume={}", library_applet_volume);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(volume);
|
||||
rb.Push(library_applet_volume);
|
||||
}
|
||||
|
||||
void IAudioController::ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
struct Parameters {
|
||||
float volume;
|
||||
s64 fade_time_ns;
|
||||
};
|
||||
static_assert(sizeof(Parameters) == 16);
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto parameters = rp.PopRaw<Parameters>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called. volume={}, fade_time_ns={}", parameters.volume,
|
||||
parameters.fade_time_ns);
|
||||
|
||||
main_applet_volume = std::clamp(parameters.volume, min_allowed_volume, max_allowed_volume);
|
||||
fade_time_ns = std::chrono::nanoseconds{parameters.fade_time_ns};
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const float transparent_volume_rate_tmp = rp.Pop<float>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called. transparent_volume_rate={}", transparent_volume_rate_tmp);
|
||||
|
||||
// Clamp volume range to 0-100%.
|
||||
transparent_volume_rate =
|
||||
std::clamp(transparent_volume_rate_tmp, min_allowed_volume, max_allowed_volume);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
|
||||
@@ -169,7 +215,21 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController"
|
||||
|
||||
IDisplayController::~IDisplayController() = default;
|
||||
|
||||
IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {}
|
||||
IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "NotifyMessageToHomeMenuForDebug"},
|
||||
{1, nullptr, "OpenMainApplication"},
|
||||
{10, nullptr, "EmulateButtonEvent"},
|
||||
{20, nullptr, "InvalidateTransitionLayer"},
|
||||
{30, nullptr, "RequestLaunchApplicationWithUserAndArgumentForDebug"},
|
||||
{40, nullptr, "GetAppletResourceUsageInfo"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IDebugFunctions::~IDebugFunctions() = default;
|
||||
|
||||
ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
@@ -81,8 +82,21 @@ private:
|
||||
void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void SetTransparentAudioRate(Kernel::HLERequestContext& ctx);
|
||||
|
||||
u32 volume{100};
|
||||
static constexpr float min_allowed_volume = 0.0f;
|
||||
static constexpr float max_allowed_volume = 1.0f;
|
||||
|
||||
float main_applet_volume{0.25f};
|
||||
float library_applet_volume{max_allowed_volume};
|
||||
float transparent_volume_rate{min_allowed_volume};
|
||||
|
||||
// Volume transition fade time in nanoseconds.
|
||||
// e.g. If the main applet volume was 0% and was changed to 50%
|
||||
// with a fade of 50ns, then over the course of 50ns,
|
||||
// the volume will gradually fade up to 50%
|
||||
std::chrono::nanoseconds fade_time_ns{0};
|
||||
};
|
||||
|
||||
class IDisplayController final : public ServiceFramework<IDisplayController> {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/applets/software_keyboard.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applets/software_keyboard.h"
|
||||
|
||||
|
||||
@@ -9,10 +9,13 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
enum class KeysetDisable : u32 {
|
||||
|
||||
@@ -107,7 +107,9 @@ private:
|
||||
void StopAudioOut(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
audio_core.StopStream(stream);
|
||||
if (stream->IsPlaying()) {
|
||||
audio_core.StopStream(stream);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -138,6 +140,7 @@ private:
|
||||
if (!audio_core.QueueBuffer(stream, tag, std::move(samples))) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERR_BUFFER_COUNT_EXCEEDED);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
||||
@@ -8,44 +8,34 @@
|
||||
#include <vector>
|
||||
|
||||
#include <opus.h>
|
||||
#include <opus_multistream.h>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/service/audio/hwopus.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
namespace {
|
||||
struct OpusDeleter {
|
||||
void operator()(void* ptr) const {
|
||||
operator delete(ptr);
|
||||
void operator()(OpusMSDecoder* ptr) const {
|
||||
opus_multistream_decoder_destroy(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
|
||||
using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
|
||||
|
||||
struct OpusPacketHeader {
|
||||
// Packet size in bytes.
|
||||
u32_be size;
|
||||
// Indicates the final range of the codec's entropy coder.
|
||||
u32_be final_range;
|
||||
};
|
||||
static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
|
||||
|
||||
class OpusDecoderState {
|
||||
public:
|
||||
IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoder, OpusDeleter> decoder, u32 sample_rate,
|
||||
u32 channel_count)
|
||||
: ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)),
|
||||
sample_rate(sample_rate), channel_count(channel_count) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
|
||||
{1, nullptr, "SetContext"},
|
||||
{2, nullptr, "DecodeInterleavedForMultiStreamOld"},
|
||||
{3, nullptr, "SetContextForMultiStream"},
|
||||
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
|
||||
{5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
|
||||
{6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
|
||||
{7, nullptr, "DecodeInterleavedForMultiStream"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Describes extra behavior that may be asked of the decoding context.
|
||||
enum class ExtraBehavior {
|
||||
/// No extra behavior.
|
||||
@@ -55,30 +45,27 @@ private:
|
||||
ResetContext,
|
||||
};
|
||||
|
||||
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
enum class PerfTime {
|
||||
Disabled,
|
||||
Enabled,
|
||||
};
|
||||
|
||||
DecodeInterleavedHelper(ctx, nullptr, ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
u64 performance = 0;
|
||||
DecodeInterleavedHelper(ctx, &performance, ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto extra_behavior =
|
||||
rp.Pop<bool>() ? ExtraBehavior::ResetContext : ExtraBehavior::None;
|
||||
|
||||
u64 performance = 0;
|
||||
DecodeInterleavedHelper(ctx, &performance, extra_behavior);
|
||||
explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count)
|
||||
: decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {}
|
||||
|
||||
// Decodes interleaved Opus packets. Optionally allows reporting time taken to
|
||||
// perform the decoding, as well as any relevant extra behavior.
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
|
||||
ExtraBehavior extra_behavior) {
|
||||
if (perf_time == PerfTime::Disabled) {
|
||||
DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
|
||||
} else {
|
||||
u64 performance = 0;
|
||||
DecodeInterleavedHelper(ctx, &performance, extra_behavior);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void DecodeInterleavedHelper(Kernel::HLERequestContext& ctx, u64* performance,
|
||||
ExtraBehavior extra_behavior) {
|
||||
u32 consumed = 0;
|
||||
@@ -89,8 +76,7 @@ private:
|
||||
ResetDecoderContext();
|
||||
}
|
||||
|
||||
if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples,
|
||||
performance)) {
|
||||
if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) {
|
||||
LOG_ERROR(Audio, "Failed to decode opus data");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
@@ -109,27 +95,27 @@ private:
|
||||
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
|
||||
}
|
||||
|
||||
bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input,
|
||||
std::vector<opus_int16>& output, u64* out_performance_time) {
|
||||
bool DecodeOpusData(u32& consumed, u32& sample_count, const std::vector<u8>& input,
|
||||
std::vector<opus_int16>& output, u64* out_performance_time) const {
|
||||
const auto start_time = std::chrono::high_resolution_clock::now();
|
||||
const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
|
||||
if (sizeof(OpusHeader) > input.size()) {
|
||||
if (sizeof(OpusPacketHeader) > input.size()) {
|
||||
LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
|
||||
sizeof(OpusHeader), input.size());
|
||||
sizeof(OpusPacketHeader), input.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
OpusHeader hdr{};
|
||||
std::memcpy(&hdr, input.data(), sizeof(OpusHeader));
|
||||
if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) {
|
||||
OpusPacketHeader hdr{};
|
||||
std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader));
|
||||
if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) {
|
||||
LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}",
|
||||
sizeof(OpusHeader) + static_cast<u32>(hdr.sz), input.size());
|
||||
sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto frame = input.data() + sizeof(OpusHeader);
|
||||
const auto frame = input.data() + sizeof(OpusPacketHeader);
|
||||
const auto decoded_sample_count = opus_packet_get_nb_samples(
|
||||
frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)),
|
||||
frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)),
|
||||
static_cast<opus_int32>(sample_rate));
|
||||
if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
|
||||
LOG_ERROR(
|
||||
@@ -141,18 +127,18 @@ private:
|
||||
|
||||
const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
|
||||
const auto out_sample_count =
|
||||
opus_decode(decoder.get(), frame, hdr.sz, output.data(), frame_size, 0);
|
||||
opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
|
||||
if (out_sample_count < 0) {
|
||||
LOG_ERROR(Audio,
|
||||
"Incorrect sample count received from opus_decode, "
|
||||
"output_sample_count={}, frame_size={}, data_sz_from_hdr={}",
|
||||
out_sample_count, frame_size, static_cast<u32>(hdr.sz));
|
||||
out_sample_count, frame_size, static_cast<u32>(hdr.size));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
|
||||
sample_count = out_sample_count;
|
||||
consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz);
|
||||
consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size);
|
||||
if (out_performance_time != nullptr) {
|
||||
*out_performance_time =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
|
||||
@@ -164,25 +150,86 @@ private:
|
||||
void ResetDecoderContext() {
|
||||
ASSERT(decoder != nullptr);
|
||||
|
||||
opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
struct OpusHeader {
|
||||
u32_be sz; // Needs to be BE for some odd reason
|
||||
INSERT_PADDING_WORDS(1);
|
||||
};
|
||||
static_assert(sizeof(OpusHeader) == 0x8, "OpusHeader is an invalid size");
|
||||
|
||||
std::unique_ptr<OpusDecoder, OpusDeleter> decoder;
|
||||
OpusDecoderPtr decoder;
|
||||
u32 sample_rate;
|
||||
u32 channel_count;
|
||||
};
|
||||
|
||||
static std::size_t WorkerBufferSize(u32 channel_count) {
|
||||
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
|
||||
public:
|
||||
explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state)
|
||||
: ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
|
||||
{1, nullptr, "SetContext"},
|
||||
{2, nullptr, "DecodeInterleavedForMultiStreamOld"},
|
||||
{3, nullptr, "SetContextForMultiStream"},
|
||||
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
|
||||
{5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
|
||||
{6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
|
||||
{7, nullptr, "DecodeInterleavedForMultiStream"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
|
||||
: OpusDecoderState::ExtraBehavior::None;
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
|
||||
}
|
||||
|
||||
OpusDecoderState decoder_state;
|
||||
};
|
||||
|
||||
std::size_t WorkerBufferSize(u32 channel_count) {
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
return opus_decoder_get_size(static_cast<int>(channel_count));
|
||||
constexpr int num_streams = 1;
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
|
||||
}
|
||||
|
||||
// Creates the mapping table that maps the input channels to the particular
|
||||
// output channels. In the stereo case, we map the left and right input channels
|
||||
// to the left and right output channels respectively.
|
||||
//
|
||||
// However, in the monophonic case, we only map the one available channel
|
||||
// to the sole output channel. We specify 255 for the would-be right channel
|
||||
// as this is a special value defined by Opus to indicate to the decoder to
|
||||
// ignore that channel.
|
||||
std::array<u8, 2> CreateMappingTable(u32 channel_count) {
|
||||
if (channel_count == 2) {
|
||||
return {{0, 1}};
|
||||
}
|
||||
|
||||
return {{0, 255}};
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto sample_rate = rp.Pop<u32>();
|
||||
@@ -220,10 +267,15 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
const std::size_t worker_sz = WorkerBufferSize(channel_count);
|
||||
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
|
||||
|
||||
std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
|
||||
static_cast<OpusDecoder*>(operator new(worker_sz))};
|
||||
if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
|
||||
LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err);
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
const auto mapping_table = CreateMappingTable(channel_count);
|
||||
|
||||
int error = 0;
|
||||
OpusDecoderPtr decoder{
|
||||
opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
|
||||
num_stereo_streams, mapping_table.data(), &error)};
|
||||
if (error != OPUS_OK || decoder == nullptr) {
|
||||
LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultCode(-1));
|
||||
@@ -232,8 +284,8 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(std::move(decoder), sample_rate,
|
||||
channel_count);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
|
||||
OpusDecoderState{std::move(decoder), sample_rate, channel_count});
|
||||
}
|
||||
|
||||
HwOpus::HwOpus() : ServiceFramework("hwopus") {
|
||||
|
||||
@@ -733,7 +733,10 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
FSP_SRV::~FSP_SRV() = default;
|
||||
|
||||
void FSP_SRV::SetCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
current_process_id = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
@@ -32,6 +32,7 @@ private:
|
||||
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
u64 current_process_id = 0;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -41,20 +41,20 @@ private:
|
||||
struct PadState {
|
||||
union {
|
||||
u32_le raw{};
|
||||
BitField<0, 1, u32_le> a;
|
||||
BitField<1, 1, u32_le> b;
|
||||
BitField<2, 1, u32_le> x;
|
||||
BitField<3, 1, u32_le> y;
|
||||
BitField<4, 1, u32_le> l;
|
||||
BitField<5, 1, u32_le> r;
|
||||
BitField<6, 1, u32_le> zl;
|
||||
BitField<7, 1, u32_le> zr;
|
||||
BitField<8, 1, u32_le> plus;
|
||||
BitField<9, 1, u32_le> minus;
|
||||
BitField<10, 1, u32_le> d_left;
|
||||
BitField<11, 1, u32_le> d_up;
|
||||
BitField<12, 1, u32_le> d_right;
|
||||
BitField<13, 1, u32_le> d_down;
|
||||
BitField<0, 1, u32> a;
|
||||
BitField<1, 1, u32> b;
|
||||
BitField<2, 1, u32> x;
|
||||
BitField<3, 1, u32> y;
|
||||
BitField<4, 1, u32> l;
|
||||
BitField<5, 1, u32> r;
|
||||
BitField<6, 1, u32> zl;
|
||||
BitField<7, 1, u32> zr;
|
||||
BitField<8, 1, u32> plus;
|
||||
BitField<9, 1, u32> minus;
|
||||
BitField<10, 1, u32> d_left;
|
||||
BitField<11, 1, u32> d_up;
|
||||
BitField<12, 1, u32> d_right;
|
||||
BitField<13, 1, u32> d_down;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size");
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
struct Attributes {
|
||||
union {
|
||||
u32_le raw{};
|
||||
BitField<0, 1, u32_le> connected;
|
||||
BitField<0, 1, u32> connected;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
|
||||
|
||||
@@ -39,13 +39,13 @@ public:
|
||||
union {
|
||||
u32_le raw{};
|
||||
|
||||
BitField<0, 1, u32_le> pro_controller;
|
||||
BitField<1, 1, u32_le> handheld;
|
||||
BitField<2, 1, u32_le> joycon_dual;
|
||||
BitField<3, 1, u32_le> joycon_left;
|
||||
BitField<4, 1, u32_le> joycon_right;
|
||||
BitField<0, 1, u32> pro_controller;
|
||||
BitField<1, 1, u32> handheld;
|
||||
BitField<2, 1, u32> joycon_dual;
|
||||
BitField<3, 1, u32> joycon_left;
|
||||
BitField<4, 1, u32> joycon_right;
|
||||
|
||||
BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible
|
||||
BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
|
||||
@@ -150,43 +150,43 @@ private:
|
||||
union {
|
||||
u64_le raw{};
|
||||
// Button states
|
||||
BitField<0, 1, u64_le> a;
|
||||
BitField<1, 1, u64_le> b;
|
||||
BitField<2, 1, u64_le> x;
|
||||
BitField<3, 1, u64_le> y;
|
||||
BitField<4, 1, u64_le> l_stick;
|
||||
BitField<5, 1, u64_le> r_stick;
|
||||
BitField<6, 1, u64_le> l;
|
||||
BitField<7, 1, u64_le> r;
|
||||
BitField<8, 1, u64_le> zl;
|
||||
BitField<9, 1, u64_le> zr;
|
||||
BitField<10, 1, u64_le> plus;
|
||||
BitField<11, 1, u64_le> minus;
|
||||
BitField<0, 1, u64> a;
|
||||
BitField<1, 1, u64> b;
|
||||
BitField<2, 1, u64> x;
|
||||
BitField<3, 1, u64> y;
|
||||
BitField<4, 1, u64> l_stick;
|
||||
BitField<5, 1, u64> r_stick;
|
||||
BitField<6, 1, u64> l;
|
||||
BitField<7, 1, u64> r;
|
||||
BitField<8, 1, u64> zl;
|
||||
BitField<9, 1, u64> zr;
|
||||
BitField<10, 1, u64> plus;
|
||||
BitField<11, 1, u64> minus;
|
||||
|
||||
// D-Pad
|
||||
BitField<12, 1, u64_le> d_left;
|
||||
BitField<13, 1, u64_le> d_up;
|
||||
BitField<14, 1, u64_le> d_right;
|
||||
BitField<15, 1, u64_le> d_down;
|
||||
BitField<12, 1, u64> d_left;
|
||||
BitField<13, 1, u64> d_up;
|
||||
BitField<14, 1, u64> d_right;
|
||||
BitField<15, 1, u64> d_down;
|
||||
|
||||
// Left JoyStick
|
||||
BitField<16, 1, u64_le> l_stick_left;
|
||||
BitField<17, 1, u64_le> l_stick_up;
|
||||
BitField<18, 1, u64_le> l_stick_right;
|
||||
BitField<19, 1, u64_le> l_stick_down;
|
||||
BitField<16, 1, u64> l_stick_left;
|
||||
BitField<17, 1, u64> l_stick_up;
|
||||
BitField<18, 1, u64> l_stick_right;
|
||||
BitField<19, 1, u64> l_stick_down;
|
||||
|
||||
// Right JoyStick
|
||||
BitField<20, 1, u64_le> r_stick_left;
|
||||
BitField<21, 1, u64_le> r_stick_up;
|
||||
BitField<22, 1, u64_le> r_stick_right;
|
||||
BitField<23, 1, u64_le> r_stick_down;
|
||||
BitField<20, 1, u64> r_stick_left;
|
||||
BitField<21, 1, u64> r_stick_up;
|
||||
BitField<22, 1, u64> r_stick_right;
|
||||
BitField<23, 1, u64> r_stick_down;
|
||||
|
||||
// Not always active?
|
||||
BitField<24, 1, u64_le> left_sl;
|
||||
BitField<25, 1, u64_le> left_sr;
|
||||
BitField<24, 1, u64> left_sl;
|
||||
BitField<25, 1, u64> left_sr;
|
||||
|
||||
BitField<26, 1, u64_le> right_sl;
|
||||
BitField<27, 1, u64_le> right_sr;
|
||||
BitField<26, 1, u64> right_sl;
|
||||
BitField<27, 1, u64> right_sr;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
|
||||
@@ -200,12 +200,12 @@ private:
|
||||
struct ConnectionState {
|
||||
union {
|
||||
u32_le raw{};
|
||||
BitField<0, 1, u32_le> IsConnected;
|
||||
BitField<1, 1, u32_le> IsWired;
|
||||
BitField<2, 1, u32_le> IsLeftJoyConnected;
|
||||
BitField<3, 1, u32_le> IsLeftJoyWired;
|
||||
BitField<4, 1, u32_le> IsRightJoyConnected;
|
||||
BitField<5, 1, u32_le> IsRightJoyWired;
|
||||
BitField<0, 1, u32> IsConnected;
|
||||
BitField<1, 1, u32> IsWired;
|
||||
BitField<2, 1, u32> IsLeftJoyConnected;
|
||||
BitField<3, 1, u32> IsLeftJoyWired;
|
||||
BitField<4, 1, u32> IsRightJoyConnected;
|
||||
BitField<5, 1, u32> IsRightJoyWired;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
|
||||
@@ -240,23 +240,23 @@ private:
|
||||
struct NPadProperties {
|
||||
union {
|
||||
s64_le raw{};
|
||||
BitField<11, 1, s64_le> is_vertical;
|
||||
BitField<12, 1, s64_le> is_horizontal;
|
||||
BitField<13, 1, s64_le> use_plus;
|
||||
BitField<14, 1, s64_le> use_minus;
|
||||
BitField<11, 1, s64> is_vertical;
|
||||
BitField<12, 1, s64> is_horizontal;
|
||||
BitField<13, 1, s64> use_plus;
|
||||
BitField<14, 1, s64> use_minus;
|
||||
};
|
||||
};
|
||||
|
||||
struct NPadDevice {
|
||||
union {
|
||||
u32_le raw{};
|
||||
BitField<0, 1, s32_le> pro_controller;
|
||||
BitField<1, 1, s32_le> handheld;
|
||||
BitField<2, 1, s32_le> handheld_left;
|
||||
BitField<3, 1, s32_le> handheld_right;
|
||||
BitField<4, 1, s32_le> joycon_left;
|
||||
BitField<5, 1, s32_le> joycon_right;
|
||||
BitField<6, 1, s32_le> pokeball;
|
||||
BitField<0, 1, s32> pro_controller;
|
||||
BitField<1, 1, s32> handheld;
|
||||
BitField<2, 1, s32> handheld_left;
|
||||
BitField<3, 1, s32> handheld_right;
|
||||
BitField<4, 1, s32> joycon_left;
|
||||
BitField<5, 1, s32> joycon_right;
|
||||
BitField<6, 1, s32> pokeball;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ private:
|
||||
struct Attributes {
|
||||
union {
|
||||
u32 raw{};
|
||||
BitField<0, 1, u32_le> start_touch;
|
||||
BitField<1, 1, u32_le> end_touch;
|
||||
BitField<0, 1, u32> start_touch;
|
||||
BitField<1, 1, u32> end_touch;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
#include "controllers/controller_base.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
|
||||
@@ -319,15 +319,14 @@ public:
|
||||
}
|
||||
|
||||
ASSERT(vm_manager
|
||||
.MirrorMemory(*map_address, nro_addr, nro_size,
|
||||
Kernel::MemoryState::ModuleCodeStatic)
|
||||
.MirrorMemory(*map_address, nro_addr, nro_size, Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(nro_addr, nro_size).IsSuccess());
|
||||
|
||||
if (bss_size > 0) {
|
||||
ASSERT(vm_manager
|
||||
.MirrorMemory(*map_address + nro_size, bss_addr, bss_size,
|
||||
Kernel::MemoryState::ModuleCodeStatic)
|
||||
Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(bss_addr, bss_size).IsSuccess());
|
||||
}
|
||||
@@ -388,8 +387,7 @@ public:
|
||||
const auto& nro_size = iter->second.size;
|
||||
|
||||
ASSERT(vm_manager
|
||||
.MirrorMemory(heap_addr, mapped_addr, nro_size,
|
||||
Kernel::MemoryState::ModuleCodeStatic)
|
||||
.MirrorMemory(heap_addr, mapped_addr, nro_size, Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(mapped_addr, nro_size).IsSuccess());
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ private:
|
||||
union {
|
||||
BitField<0, 16, Flags> flags;
|
||||
BitField<16, 8, Severity> severity;
|
||||
BitField<24, 8, u32_le> verbosity;
|
||||
BitField<24, 8, u32> verbosity;
|
||||
};
|
||||
u32_le payload_size;
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ public:
|
||||
virtual ~nvdevice() = default;
|
||||
union Ioctl {
|
||||
u32_le raw;
|
||||
BitField<0, 8, u32_le> cmd;
|
||||
BitField<8, 8, u32_le> group;
|
||||
BitField<16, 14, u32_le> length;
|
||||
BitField<30, 1, u32_le> is_in;
|
||||
BitField<31, 1, u32_le> is_out;
|
||||
BitField<0, 8, u32> cmd;
|
||||
BitField<8, 8, u32> group;
|
||||
BitField<16, 14, u32> length;
|
||||
BitField<30, 1, u32> is_in;
|
||||
BitField<31, 1, u32> is_out;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3
|
||||
|
||||
auto& instance = Core::System::GetInstance();
|
||||
instance.GetPerfStats().EndGameFrame();
|
||||
instance.Renderer().SwapBuffers(framebuffer);
|
||||
instance.GPU().SwapBuffers(framebuffer);
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
@@ -88,7 +89,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
|
||||
for (const auto& entry : entries) {
|
||||
LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
|
||||
entry.offset, entry.nvmap_handle, entry.pages);
|
||||
Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10;
|
||||
GPUVAddr offset = static_cast<GPUVAddr>(entry.offset) << 0x10;
|
||||
auto object = nvmap_dev->GetObject(entry.nvmap_handle);
|
||||
if (!object) {
|
||||
LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle);
|
||||
@@ -101,7 +102,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
|
||||
u64 size = static_cast<u64>(entry.pages) << 0x10;
|
||||
ASSERT(size <= object->size);
|
||||
|
||||
Tegra::GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size);
|
||||
GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size);
|
||||
ASSERT(returned == offset);
|
||||
}
|
||||
std::memcpy(output.data(), entries.data(), output.size());
|
||||
@@ -172,16 +173,8 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& system_instance = Core::System::GetInstance();
|
||||
|
||||
// Remove this memory region from the rasterizer cache.
|
||||
auto& gpu = system_instance.GPU();
|
||||
auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset);
|
||||
ASSERT(cpu_addr);
|
||||
system_instance.Renderer().Rasterizer().FlushAndInvalidateRegion(*cpu_addr, itr->second.size);
|
||||
|
||||
params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size);
|
||||
|
||||
params.offset = Core::System::GetInstance().GPU().MemoryManager().UnmapBuffer(params.offset,
|
||||
itr->second.size);
|
||||
buffer_mappings.erase(itr->second.offset);
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
|
||||
@@ -136,16 +136,6 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void PushGPUEntries(Tegra::CommandList&& entries) {
|
||||
if (entries.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& dma_pusher{Core::System::GetInstance().GPU().DmaPusher()};
|
||||
dma_pusher.Push(std::move(entries));
|
||||
dma_pusher.DispatchCalls();
|
||||
}
|
||||
|
||||
u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
if (input.size() < sizeof(IoctlSubmitGpfifo)) {
|
||||
UNIMPLEMENTED();
|
||||
@@ -163,7 +153,7 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp
|
||||
std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)],
|
||||
params.num_entries * sizeof(Tegra::CommandListHeader));
|
||||
|
||||
PushGPUEntries(std::move(entries));
|
||||
Core::System::GetInstance().GPU().PushGPUEntries(std::move(entries));
|
||||
|
||||
params.fence_out.id = 0;
|
||||
params.fence_out.value = 0;
|
||||
@@ -184,7 +174,7 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output)
|
||||
Memory::ReadBlock(params.address, entries.data(),
|
||||
params.num_entries * sizeof(Tegra::CommandListHeader));
|
||||
|
||||
PushGPUEntries(std::move(entries));
|
||||
Core::System::GetInstance().GPU().PushGPUEntries(std::move(entries));
|
||||
|
||||
params.fence_out.id = 0;
|
||||
params.fence_out.value = 0;
|
||||
|
||||
@@ -186,7 +186,7 @@ void NVFlinger::Compose() {
|
||||
|
||||
// There was no queued buffer to draw, render previous frame
|
||||
system_instance.GetPerfStats().EndGameFrame();
|
||||
system_instance.Renderer().SwapBuffers({});
|
||||
system_instance.GPU().SwapBuffers({});
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/server_port.h"
|
||||
@@ -76,7 +75,8 @@ namespace Service {
|
||||
* Creates a function string for logging, complete with the name (or header code, depending
|
||||
* on what's passed in) the port name, and all the cmd_buff arguments.
|
||||
*/
|
||||
[[maybe_unused]] static std::string MakeFunctionString(const char* name, const char* port_name,
|
||||
[[maybe_unused]] static std::string MakeFunctionString(std::string_view name,
|
||||
std::string_view port_name,
|
||||
const u32* cmd_buff) {
|
||||
// Number of params == bits 0-5 + bits 6-11
|
||||
int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F);
|
||||
@@ -158,9 +158,7 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {
|
||||
return ReportUnimplementedFunction(ctx, info);
|
||||
}
|
||||
|
||||
LOG_TRACE(
|
||||
Service, "{}",
|
||||
MakeFunctionString(info->name, GetServiceName().c_str(), ctx.CommandBuffer()).c_str());
|
||||
LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer()));
|
||||
handler_invoker(this, info->handler_callback, ctx);
|
||||
}
|
||||
|
||||
@@ -169,7 +167,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
|
||||
case IPC::CommandType::Close: {
|
||||
IPC::ResponseBuilder rb{context, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead);
|
||||
return IPC::ERR_REMOTE_PROCESS_DEAD;
|
||||
}
|
||||
case IPC::CommandType::ControlWithContext:
|
||||
case IPC::CommandType::Control: {
|
||||
|
||||
@@ -2,13 +2,88 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/file_sys/system_archive/system_version.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/set/set_sys.h"
|
||||
|
||||
namespace Service::Set {
|
||||
|
||||
namespace {
|
||||
constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05;
|
||||
|
||||
enum class GetFirmwareVersionType {
|
||||
Version1,
|
||||
Version2,
|
||||
};
|
||||
|
||||
void GetFirmwareVersionImpl(Kernel::HLERequestContext& ctx, GetFirmwareVersionType type) {
|
||||
LOG_WARNING(Service_SET, "called - Using hardcoded firmware version '{}'",
|
||||
FileSys::SystemArchive::GetLongDisplayVersion());
|
||||
|
||||
ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
|
||||
"FirmwareVersion output buffer must be 0x100 bytes in size!");
|
||||
|
||||
// Instead of using the normal procedure of checking for the real system archive and if it
|
||||
// doesn't exist, synthesizing one, I feel that that would lead to strange bugs because a
|
||||
// used is using a really old or really new SystemVersion title. The synthesized one ensures
|
||||
// consistence (currently reports as 5.1.0-0.0)
|
||||
const auto archive = FileSys::SystemArchive::SystemVersion();
|
||||
|
||||
const auto early_exit_failure = [&ctx](const std::string& desc, ResultCode code) {
|
||||
LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
|
||||
desc.c_str());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(code);
|
||||
};
|
||||
|
||||
if (archive == nullptr) {
|
||||
early_exit_failure("The system version archive couldn't be synthesized.",
|
||||
FileSys::ERROR_FAILED_MOUNT_ARCHIVE);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ver_file = archive->GetFile("file");
|
||||
if (ver_file == nullptr) {
|
||||
early_exit_failure("The system version archive didn't contain the file 'file'.",
|
||||
FileSys::ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = ver_file->ReadAllBytes();
|
||||
if (data.size() != 0x100) {
|
||||
early_exit_failure("The system version file 'file' was not the correct size.",
|
||||
FileSys::ERROR_OUT_OF_BOUNDS);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will
|
||||
// zero out the REVISION_MINOR field.
|
||||
if (type == GetFirmwareVersionType::Version1) {
|
||||
data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0;
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
void SET_SYS::GetFirmwareVersion(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_SET, "called");
|
||||
GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version1);
|
||||
}
|
||||
|
||||
void SET_SYS::GetFirmwareVersion2(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_SET, "called");
|
||||
GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version2);
|
||||
}
|
||||
|
||||
void SET_SYS::GetColorSetId(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_SET, "called");
|
||||
|
||||
@@ -33,8 +108,8 @@ SET_SYS::SET_SYS() : ServiceFramework("set:sys") {
|
||||
{0, nullptr, "SetLanguageCode"},
|
||||
{1, nullptr, "SetNetworkSettings"},
|
||||
{2, nullptr, "GetNetworkSettings"},
|
||||
{3, nullptr, "GetFirmwareVersion"},
|
||||
{4, nullptr, "GetFirmwareVersion2"},
|
||||
{3, &SET_SYS::GetFirmwareVersion, "GetFirmwareVersion"},
|
||||
{4, &SET_SYS::GetFirmwareVersion2, "GetFirmwareVersion2"},
|
||||
{5, nullptr, "GetFirmwareVersionDigest"},
|
||||
{7, nullptr, "GetLockScreenFlag"},
|
||||
{8, nullptr, "SetLockScreenFlag"},
|
||||
|
||||
@@ -20,6 +20,8 @@ private:
|
||||
BasicBlack = 1,
|
||||
};
|
||||
|
||||
void GetFirmwareVersion(Kernel::HLERequestContext& ctx);
|
||||
void GetFirmwareVersion2(Kernel::HLERequestContext& ctx);
|
||||
void GetColorSetId(Kernel::HLERequestContext& ctx);
|
||||
void SetColorSetId(Kernel::HLERequestContext& ctx);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
Kernel::SharedPtr<Kernel::ClientSession> session{ctx.Session()->parent->client};
|
||||
Kernel::SharedPtr<Kernel::ClientSession> session{ctx.Session()->GetParent()->client};
|
||||
rb.PushMoveObjects(session);
|
||||
|
||||
LOG_DEBUG(Service, "session={}", session->GetObjectId());
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
if (port == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::static_pointer_cast<T>(port->hle_handler);
|
||||
return std::static_pointer_cast<T>(port->GetHLEHandler());
|
||||
}
|
||||
|
||||
void InvokeControlRequest(Kernel::HLERequestContext& context);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/vi/vi_m.h"
|
||||
#include "core/hle/service/vi/vi_s.h"
|
||||
@@ -33,6 +34,7 @@
|
||||
namespace Service::VI {
|
||||
|
||||
constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1};
|
||||
constexpr ResultCode ERR_PERMISSION_DENIED{ErrorModule::VI, 5};
|
||||
constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6};
|
||||
constexpr ResultCode ERR_NOT_FOUND{ErrorModule::VI, 7};
|
||||
|
||||
@@ -1203,26 +1205,40 @@ IApplicationDisplayService::IApplicationDisplayService(
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: ServiceFramework(name), module(std::move(module)), nv_flinger(std::move(nv_flinger)) {}
|
||||
static bool IsValidServiceAccess(Permission permission, Policy policy) {
|
||||
if (permission == Permission::User) {
|
||||
return policy == Policy::User;
|
||||
}
|
||||
|
||||
Module::Interface::~Interface() = default;
|
||||
if (permission == Permission::System || permission == Permission::Manager) {
|
||||
return policy == Policy::User || policy == Policy::Compositor;
|
||||
}
|
||||
|
||||
void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_VI, "(STUBBED) called");
|
||||
return false;
|
||||
}
|
||||
|
||||
void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger,
|
||||
Permission permission) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto policy = rp.PopEnum<Policy>();
|
||||
|
||||
if (!IsValidServiceAccess(permission, policy)) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERR_PERMISSION_DENIED);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
|
||||
rb.PushIpcInterface<IApplicationDisplayService>(std::move(nv_flinger));
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) {
|
||||
auto module = std::make_shared<Module>();
|
||||
std::make_shared<VI_M>(module, nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_S>(module, nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_U>(module, nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_M>(nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_S>(nv_flinger)->InstallAsService(service_manager);
|
||||
std::make_shared<VI_U>(nv_flinger)->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -4,12 +4,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
class HLERequestContext;
|
||||
}
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
class NVFlinger;
|
||||
}
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
enum class DisplayResolution : u32 {
|
||||
@@ -19,22 +28,25 @@ enum class DisplayResolution : u32 {
|
||||
UndockedHeight = 720,
|
||||
};
|
||||
|
||||
class Module final {
|
||||
public:
|
||||
class Interface : public ServiceFramework<Interface> {
|
||||
public:
|
||||
explicit Interface(std::shared_ptr<Module> module, const char* name,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
~Interface() override;
|
||||
|
||||
void GetDisplayService(Kernel::HLERequestContext& ctx);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Module> module;
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
};
|
||||
/// Permission level for a particular VI service instance
|
||||
enum class Permission {
|
||||
User,
|
||||
System,
|
||||
Manager,
|
||||
};
|
||||
|
||||
/// A policy type that may be requested via GetDisplayService and
|
||||
/// GetDisplayServiceWithProxyNameExchange
|
||||
enum class Policy {
|
||||
User,
|
||||
Compositor,
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, Permission permission);
|
||||
} // namespace detail
|
||||
|
||||
/// Registers all VI services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager,
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/vi/vi_m.h"
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: Module::Interface(std::move(module), "vi:m", std::move(nv_flinger)) {
|
||||
VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: ServiceFramework{"vi:m"}, nv_flinger{std::move(nv_flinger)} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{2, &VI_M::GetDisplayService, "GetDisplayService"},
|
||||
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
|
||||
@@ -17,4 +19,10 @@ VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
|
||||
|
||||
VI_M::~VI_M() = default;
|
||||
|
||||
void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_VI, "called");
|
||||
|
||||
detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::Manager);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -4,14 +4,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class HLERequestContext;
|
||||
}
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
class NVFlinger;
|
||||
}
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
class VI_M final : public Module::Interface {
|
||||
class VI_M final : public ServiceFramework<VI_M> {
|
||||
public:
|
||||
explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
explicit VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
~VI_M() override;
|
||||
|
||||
private:
|
||||
void GetDisplayService(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
};
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/vi/vi_s.h"
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: Module::Interface(std::move(module), "vi:s", std::move(nv_flinger)) {
|
||||
VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: ServiceFramework{"vi:s"}, nv_flinger{std::move(nv_flinger)} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{1, &VI_S::GetDisplayService, "GetDisplayService"},
|
||||
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
|
||||
@@ -17,4 +19,10 @@ VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
|
||||
|
||||
VI_S::~VI_S() = default;
|
||||
|
||||
void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_VI, "called");
|
||||
|
||||
detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::System);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -4,14 +4,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class HLERequestContext;
|
||||
}
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
class NVFlinger;
|
||||
}
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
class VI_S final : public Module::Interface {
|
||||
class VI_S final : public ServiceFramework<VI_S> {
|
||||
public:
|
||||
explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
explicit VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
|
||||
~VI_S() override;
|
||||
|
||||
private:
|
||||
void GetDisplayService(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
};
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
#include "core/hle/service/vi/vi_u.h"
|
||||
|
||||
namespace Service::VI {
|
||||
|
||||
VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: Module::Interface(std::move(module), "vi:u", std::move(nv_flinger)) {
|
||||
VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
|
||||
: ServiceFramework{"vi:u"}, nv_flinger{std::move(nv_flinger)} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &VI_U::GetDisplayService, "GetDisplayService"},
|
||||
};
|
||||
@@ -16,4 +18,10 @@ VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
|
||||
|
||||
VI_U::~VI_U() = default;
|
||||
|
||||
void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_VI, "called");
|
||||
|
||||
detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::User);
|
||||
}
|
||||
|
||||
} // namespace Service::VI
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user