Compare commits
405 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
289adf87ac | ||
|
|
1291f3f820 | ||
|
|
e7e209d900 | ||
|
|
5716496239 | ||
|
|
0f3d8c2574 | ||
|
|
75d807788c | ||
|
|
d9ca6351dd | ||
|
|
2ff2732a78 | ||
|
|
7d6dca0d0a | ||
|
|
5dfb43531c | ||
|
|
848a49112a | ||
|
|
496d155d7b | ||
|
|
40c63073a9 | ||
|
|
4cccfb4190 | ||
|
|
259da93567 | ||
|
|
8e1239fbc5 | ||
|
|
ff6b2d4574 | ||
|
|
59a004f915 | ||
|
|
17315cee16 | ||
|
|
bcb5b924fd | ||
|
|
7b39107e3a | ||
|
|
aa620c14af | ||
|
|
65df593951 | ||
|
|
d9923b0dbc | ||
|
|
1226a5706e | ||
|
|
5c5b4e8e7d | ||
|
|
fcad3a734d | ||
|
|
38fa3aae73 | ||
|
|
e2416bbd1f | ||
|
|
314a948373 | ||
|
|
12fa570d49 | ||
|
|
ca5a93167e | ||
|
|
981faea4d6 | ||
|
|
5ea4cfd499 | ||
|
|
edb1c36a87 | ||
|
|
ae7f55947e | ||
|
|
a806c78a1a | ||
|
|
f034121620 | ||
|
|
a279d80a19 | ||
|
|
10a2d20e26 | ||
|
|
3b8c0f8885 | ||
|
|
98a27b1ec7 | ||
|
|
adb9eda105 | ||
|
|
c1e5525fc6 | ||
|
|
d53c73adaa | ||
|
|
dd1ee39426 | ||
|
|
08e574eec4 | ||
|
|
896c0f61a0 | ||
|
|
8a86c8d48b | ||
|
|
381baf783d | ||
|
|
61ef8af1e2 | ||
|
|
8dc7db7e33 | ||
|
|
3ec795d95e | ||
|
|
a03600ba28 | ||
|
|
b1f8bff7db | ||
|
|
60317e6306 | ||
|
|
4849569565 | ||
|
|
bf66930fb9 | ||
|
|
52b25e0fb9 | ||
|
|
298ebf444f | ||
|
|
7e0d2fc994 | ||
|
|
c91be25b93 | ||
|
|
0aef2b9c26 | ||
|
|
12401a0d87 | ||
|
|
7a7dad05c0 | ||
|
|
1833498617 | ||
|
|
a653be3510 | ||
|
|
2b9fd23058 | ||
|
|
4f52800822 | ||
|
|
f6c5a48dd1 | ||
|
|
d0cda7fe40 | ||
|
|
d16bafc99d | ||
|
|
a056b284cf | ||
|
|
7e665c2721 | ||
|
|
bcde71d4d9 | ||
|
|
a5d853a9f8 | ||
|
|
6b333d862b | ||
|
|
fdd82b754a | ||
|
|
7f152f2273 | ||
|
|
e5d428cf1e | ||
|
|
0291a86f60 | ||
|
|
4b5ae8dbaa | ||
|
|
541e9624eb | ||
|
|
d27f4a4928 | ||
|
|
f109615be0 | ||
|
|
d4ff4152ad | ||
|
|
6acd8d166a | ||
|
|
98c7a6d622 | ||
|
|
33830aa65a | ||
|
|
452aa30cb7 | ||
|
|
7f52dc1790 | ||
|
|
7eb2328d8e | ||
|
|
929ed59f1f | ||
|
|
aeda743446 | ||
|
|
aeca224890 | ||
|
|
46202e984e | ||
|
|
119b47f366 | ||
|
|
41fb25349a | ||
|
|
7dee60d7d2 | ||
|
|
77e2d68df7 | ||
|
|
caaa9914fd | ||
|
|
cb9fdc7a26 | ||
|
|
dbc34db6ce | ||
|
|
fd9e2d0073 | ||
|
|
f912a82a8e | ||
|
|
6e8752881c | ||
|
|
86dcf2942b | ||
|
|
afe22d8405 | ||
|
|
648b55c6b9 | ||
|
|
2a035a1f6f | ||
|
|
8144fa42bd | ||
|
|
43b9494a0f | ||
|
|
ee7c2dbf5a | ||
|
|
91602de7f2 | ||
|
|
871350ae35 | ||
|
|
441b5b97bd | ||
|
|
53e77ffbfe | ||
|
|
d6604fa765 | ||
|
|
4783ad54de | ||
|
|
73e1e929a2 | ||
|
|
0e59291310 | ||
|
|
949d7832fa | ||
|
|
5f79ba04bd | ||
|
|
58be4dff79 | ||
|
|
cf7b46c101 | ||
|
|
3afdfd7bfa | ||
|
|
b4e29ccb81 | ||
|
|
4e9683e9d5 | ||
|
|
37575eae65 | ||
|
|
0be7e82289 | ||
|
|
9b929e934b | ||
|
|
78f2a6a9e1 | ||
|
|
92d8ad3770 | ||
|
|
88b8383da2 | ||
|
|
59c1ca8b0c | ||
|
|
d6e390bc5c | ||
|
|
9d4e6176eb | ||
|
|
74890cf2da | ||
|
|
39ae73b356 | ||
|
|
41674d20ac | ||
|
|
548958bcaf | ||
|
|
870c18b078 | ||
|
|
89fe950d3c | ||
|
|
76fc8b59b2 | ||
|
|
9b21fbd1eb | ||
|
|
50e6205c21 | ||
|
|
7665411317 | ||
|
|
bed872ed38 | ||
|
|
123df8f7d7 | ||
|
|
936c36a514 | ||
|
|
b461342a84 | ||
|
|
27916764b1 | ||
|
|
5484742fda | ||
|
|
59f872a8e0 | ||
|
|
aeadbfa790 | ||
|
|
c34efbbd60 | ||
|
|
6312eec5ef | ||
|
|
4fc8ad67bf | ||
|
|
3d65aa4caf | ||
|
|
d93cdc2750 | ||
|
|
d46e2a6e7a | ||
|
|
08d751d882 | ||
|
|
720d36ca71 | ||
|
|
bb9cf8a127 | ||
|
|
0732786ddc | ||
|
|
90f8474fc1 | ||
|
|
5737441374 | ||
|
|
b3cca34f50 | ||
|
|
3203193a67 | ||
|
|
14286f70f0 | ||
|
|
0d2ba0a320 | ||
|
|
b82bbfba77 | ||
|
|
2f8ca32020 | ||
|
|
b183ce4365 | ||
|
|
1d6559fbd3 | ||
|
|
92fae7e1ab | ||
|
|
7e2096db8a | ||
|
|
d880b77698 | ||
|
|
331ce2942c | ||
|
|
1c7a7ed79b | ||
|
|
1ff20d8538 | ||
|
|
e0ca938b22 | ||
|
|
d4ae43f9c1 | ||
|
|
4d959c6bdc | ||
|
|
736db284d2 | ||
|
|
0149162dba | ||
|
|
a4c57436fc | ||
|
|
53a0221484 | ||
|
|
cbf723896f | ||
|
|
6467b01de2 | ||
|
|
781fd7983c | ||
|
|
e0c76226ad | ||
|
|
3d9df49619 | ||
|
|
bc2196bb09 | ||
|
|
6da2ed4232 | ||
|
|
f56a8da46a | ||
|
|
d257a3b56c | ||
|
|
e96d69c328 | ||
|
|
aaca7543f0 | ||
|
|
06898263f6 | ||
|
|
e70c08b543 | ||
|
|
ef5639bfbb | ||
|
|
82ea1cf35a | ||
|
|
f61379f8d2 | ||
|
|
90c07e0d33 | ||
|
|
1584fb6b38 | ||
|
|
c2aa4293ec | ||
|
|
38b027aa81 | ||
|
|
ffcda6c08e | ||
|
|
97b6405a17 | ||
|
|
2946d4bdbe | ||
|
|
1abed2f4c4 | ||
|
|
0f7ab3e21a | ||
|
|
f9d03b1d41 | ||
|
|
dc328440c8 | ||
|
|
b492d43e63 | ||
|
|
4d2de6564f | ||
|
|
c55b5de0fb | ||
|
|
4ccf30dfaa | ||
|
|
98b760c645 | ||
|
|
9bf409f275 | ||
|
|
3fd26b7147 | ||
|
|
bc293e1751 | ||
|
|
83ac3e6395 | ||
|
|
85b0d9a7be | ||
|
|
c7763603ef | ||
|
|
5dd538cace | ||
|
|
17290a4416 | ||
|
|
bf795edac4 | ||
|
|
28ec921d0d | ||
|
|
183a664405 | ||
|
|
fa10905e1e | ||
|
|
6d82c4adf9 | ||
|
|
a7725d354c | ||
|
|
c422f146ee | ||
|
|
881bb2295d | ||
|
|
a34e5e51d8 | ||
|
|
6e6ce2ce39 | ||
|
|
72e9cb523e | ||
|
|
03ec936ca0 | ||
|
|
ee1b204749 | ||
|
|
68b3d8b7a9 | ||
|
|
9e924f2ef2 | ||
|
|
3d75c9cd7a | ||
|
|
5c0408596f | ||
|
|
46cdeb4549 | ||
|
|
5461b21c7a | ||
|
|
3ac874c32e | ||
|
|
5f4ee6f0c8 | ||
|
|
f43815af5d | ||
|
|
b79c294c02 | ||
|
|
5857aea94e | ||
|
|
56f35ab262 | ||
|
|
2db37ddea9 | ||
|
|
09b6dda8f0 | ||
|
|
a4412c8e22 | ||
|
|
af653906d0 | ||
|
|
bc6939beaa | ||
|
|
0b3d4db98b | ||
|
|
fe16905de1 | ||
|
|
89939be9e6 | ||
|
|
141a0d9386 | ||
|
|
6aab309e41 | ||
|
|
6e27c5d4d1 | ||
|
|
e3b4d31f4e | ||
|
|
8aa4889e76 | ||
|
|
1964f4bbb3 | ||
|
|
8723cc8798 | ||
|
|
6636f3ff47 | ||
|
|
4f24343f32 | ||
|
|
465175cdf5 | ||
|
|
9ff743bc0a | ||
|
|
f7d2889fb4 | ||
|
|
93ac8d0fea | ||
|
|
567e818440 | ||
|
|
be97fc884d | ||
|
|
f5631e78d1 | ||
|
|
30ff42b8cc | ||
|
|
a47c1c77e6 | ||
|
|
af3ba94b2a | ||
|
|
c50f66a8eb | ||
|
|
561d79e034 | ||
|
|
6b48ba5271 | ||
|
|
fd891ee9c0 | ||
|
|
3f1f82a8c4 | ||
|
|
ae982a9bdf | ||
|
|
c5c184246d | ||
|
|
7c2d6ef210 | ||
|
|
ee4d538850 | ||
|
|
4d0c682468 | ||
|
|
f945e9767c | ||
|
|
081f5c1dbf | ||
|
|
8bbc12b9c2 | ||
|
|
95dff555a4 | ||
|
|
e09505ff61 | ||
|
|
3ec054643e | ||
|
|
8f958b89e7 | ||
|
|
3edafc6802 | ||
|
|
29dc6f4519 | ||
|
|
4aad010f7a | ||
|
|
d041d6231c | ||
|
|
a57aac5772 | ||
|
|
d7398283e3 | ||
|
|
d6a0d5d432 | ||
|
|
c79d2ca6cf | ||
|
|
e4602748d6 | ||
|
|
9e34303fb9 | ||
|
|
1fa6ee4723 | ||
|
|
ce05df0a6d | ||
|
|
721632fe66 | ||
|
|
89ad82ce5c | ||
|
|
fa3f3cd07f | ||
|
|
6e4d2e672d | ||
|
|
ceef334c1c | ||
|
|
2534af040e | ||
|
|
2c0b0ad50d | ||
|
|
1cc5e6e9bc | ||
|
|
2de52e3af6 | ||
|
|
6f420a40cf | ||
|
|
44a3baf410 | ||
|
|
e92251795a | ||
|
|
450c0a5adf | ||
|
|
72a804b149 | ||
|
|
0ecd181cca | ||
|
|
2fbb20b2b5 | ||
|
|
752faff2bc | ||
|
|
6d4f5b9673 | ||
|
|
8e6311bfd2 | ||
|
|
d210170f36 | ||
|
|
690f326613 | ||
|
|
a45b86f0fb | ||
|
|
612ce89eca | ||
|
|
9aec85d39c | ||
|
|
011cf77796 | ||
|
|
749aef3dd0 | ||
|
|
6cc7656e81 | ||
|
|
efd956e6ff | ||
|
|
f84b9ed4e8 | ||
|
|
b8b90ce6e6 | ||
|
|
095c8d999b | ||
|
|
6ddf8f34db | ||
|
|
e6ee31a8e9 | ||
|
|
d3bfb102d8 | ||
|
|
d43769f93f | ||
|
|
1a5d6de0d4 | ||
|
|
e51d715700 | ||
|
|
fe292573de | ||
|
|
38c2ac95af | ||
|
|
5acaeb04c4 | ||
|
|
cf7aba4817 | ||
|
|
d79d4fd764 | ||
|
|
5045748829 | ||
|
|
e948fbf5d0 | ||
|
|
6f16826260 | ||
|
|
d0e6b93695 | ||
|
|
c1e069c066 | ||
|
|
bc4bec8a60 | ||
|
|
110d578470 | ||
|
|
70bd2bb1d3 | ||
|
|
9669cdb710 | ||
|
|
8886f2e55e | ||
|
|
306739c2c4 | ||
|
|
f62227aa95 | ||
|
|
30dfd89126 | ||
|
|
f85f2b3728 | ||
|
|
baed7e1fba | ||
|
|
3e2380327a | ||
|
|
cf3a6dd4a1 | ||
|
|
af672d8abf | ||
|
|
839dbd9710 | ||
|
|
39f08e551d | ||
|
|
5ae5610c13 | ||
|
|
15b2e2ec13 | ||
|
|
e7e347a828 | ||
|
|
137d43fa2f | ||
|
|
8679934693 | ||
|
|
f664437ae8 | ||
|
|
024eec02a5 | ||
|
|
dade709f63 | ||
|
|
02841052aa | ||
|
|
37ee05f7c0 | ||
|
|
ccf0a9cb38 | ||
|
|
fd312abedd | ||
|
|
4f18d35888 | ||
|
|
0d83f8f255 | ||
|
|
cf463a9b67 | ||
|
|
9aaf1c0df8 | ||
|
|
cba78dc70a | ||
|
|
d80dd2b812 | ||
|
|
61b144bbf3 | ||
|
|
1c7c798e9e | ||
|
|
57e47bae32 | ||
|
|
226dc914b3 | ||
|
|
8f5e2a2b83 | ||
|
|
fdb35760a7 | ||
|
|
bfe84f06f2 | ||
|
|
a4595bb939 | ||
|
|
e4daf4bee5 | ||
|
|
ac06105dfe | ||
|
|
aa48468862 | ||
|
|
62f9409ba3 | ||
|
|
120d8f3bf7 | ||
|
|
b4ace6ec6f | ||
|
|
4d139943f2 | ||
|
|
5f30f95e94 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -34,3 +34,9 @@
|
||||
[submodule "soundtouch"]
|
||||
path = externals/soundtouch
|
||||
url = https://github.com/citra-emu/ext-soundtouch.git
|
||||
[submodule "libressl"]
|
||||
path = externals/libressl
|
||||
url = https://github.com/citra-emu/ext-libressl-portable.git
|
||||
[submodule "discord-rpc"]
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/discordapp/discord-rpc.git
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
if grep -nr '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \
|
||||
if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \
|
||||
dist/*.svg dist/*.xml; then
|
||||
echo Trailing whitespace found, aborting
|
||||
exit 1
|
||||
|
||||
@@ -10,3 +10,7 @@ TRAVIS_JOB_ID
|
||||
TRAVIS_JOB_NUMBER
|
||||
TRAVIS_REPO_SLUG
|
||||
TRAVIS_TAG
|
||||
|
||||
# yuzu specific flags
|
||||
ENABLE_COMPATIBILITY_REPORTING
|
||||
USE_DISCORD_PRESENCE
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
mkdir -p "$HOME/.ccache"
|
||||
docker run --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
|
||||
docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
|
||||
|
||||
@@ -6,7 +6,7 @@ apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev
|
||||
cd /yuzu
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -G Ninja
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja
|
||||
ninja
|
||||
|
||||
ccache -s
|
||||
|
||||
@@ -9,7 +9,7 @@ export PATH="/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
mkdir build && cd build
|
||||
cmake --version
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
|
||||
make -j4
|
||||
|
||||
ccache -s
|
||||
|
||||
@@ -15,10 +15,14 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON
|
||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC" OFF)
|
||||
|
||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
|
||||
option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
message(STATUS "Copying pre-commit hook")
|
||||
file(COPY hooks/pre-commit
|
||||
@@ -166,7 +170,7 @@ endif()
|
||||
# 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")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
|
||||
add_definitions(-D_FILE_OFFSET_BITS=64)
|
||||
endif()
|
||||
|
||||
@@ -174,10 +178,6 @@ endif()
|
||||
set_property(DIRECTORY APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
|
||||
|
||||
|
||||
math(EXPR EMU_ARCH_BITS ${CMAKE_SIZEOF_VOID_P}*8)
|
||||
add_definitions(-DEMU_ARCH_BITS=${EMU_ARCH_BITS})
|
||||
|
||||
# System imported libraries
|
||||
# ======================
|
||||
|
||||
@@ -344,14 +344,6 @@ ELSEIF (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
||||
set(PLATFORM_LIBRARIES rt)
|
||||
ENDIF (APPLE)
|
||||
|
||||
# MINGW: GCC does not support codecvt, so use iconv instead
|
||||
if (UNIX OR MINGW)
|
||||
find_library(ICONV_LIBRARY NAMES iconv)
|
||||
if (ICONV_LIBRARY)
|
||||
list(APPEND PLATFORM_LIBRARIES ${ICONV_LIBRARY})
|
||||
endif()
|
||||
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.
|
||||
# =======================================================================
|
||||
|
||||
@@ -22,7 +22,7 @@ If clang format is found, then cmake will add a custom build target that can be
|
||||
* Don't ever introduce new external dependencies into Core
|
||||
* Don't use any platform specific code in Core
|
||||
* Use namespaces often
|
||||
* Avoid the use of C-style casts and instead prefer C++-style `static_cast` and `reinterpret_cast`. Try to avoid using `dynamic_cast`. Never use `const_cast`. The only exception to this rule is for casting between two numeric types, where C-style casts are encouraged for brevity and readability.
|
||||
* Avoid the use of C-style casts and instead prefer C++-style `static_cast` and `reinterpret_cast`. Try to avoid using `dynamic_cast`. Never use `const_cast`.
|
||||
|
||||
### Naming Rules
|
||||
* Functions: `PascalCase`
|
||||
|
||||
@@ -39,11 +39,12 @@ before_build:
|
||||
- mkdir %BUILD_TYPE%_build
|
||||
- cd %BUILD_TYPE%_build
|
||||
- ps: |
|
||||
$COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
|
||||
if ($env:BUILD_TYPE -eq 'msvc') {
|
||||
# redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1 && exit 0'
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
|
||||
} else {
|
||||
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1"
|
||||
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1"
|
||||
}
|
||||
- cd ..
|
||||
|
||||
|
||||
25
externals/CMakeLists.txt
vendored
25
externals/CMakeLists.txt
vendored
@@ -70,3 +70,28 @@ if(ENABLE_CUBEB)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# DiscordRPC
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
|
||||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
add_subdirectory(libressl EXCLUDE_FROM_ALL)
|
||||
target_include_directories(ssl INTERFACE ./libressl/include)
|
||||
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
|
||||
|
||||
# lurlparser
|
||||
add_subdirectory(lurlparser EXCLUDE_FROM_ALL)
|
||||
|
||||
# httplib
|
||||
add_library(httplib INTERFACE)
|
||||
target_include_directories(httplib INTERFACE ./httplib)
|
||||
|
||||
# JSON
|
||||
add_library(json-headers INTERFACE)
|
||||
target_include_directories(json-headers INTERFACE ./json)
|
||||
endif()
|
||||
|
||||
1
externals/discord-rpc
vendored
Submodule
1
externals/discord-rpc
vendored
Submodule
Submodule externals/discord-rpc added at e32d001809
2
externals/fmt
vendored
2
externals/fmt
vendored
Submodule externals/fmt updated: 62010520ed...3e75ad9822
15
externals/httplib/README.md
vendored
Normal file
15
externals/httplib/README.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
From https://github.com/yhirose/cpp-httplib/commit/d9479bc0b12e8a1e8bce2d34da4feeef488581f3
|
||||
|
||||
MIT License
|
||||
|
||||
===
|
||||
|
||||
cpp-httplib
|
||||
|
||||
A C++11 header-only HTTP library.
|
||||
|
||||
It's extremely easy to setup. Just include httplib.h file in your code!
|
||||
|
||||
Inspired by Sinatra and express.
|
||||
|
||||
© 2017 Yuji Hirose
|
||||
2344
externals/httplib/httplib.h
vendored
Normal file
2344
externals/httplib/httplib.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
externals/json/README.md
vendored
Normal file
9
externals/json/README.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
JSON for Modern C++
|
||||
===================
|
||||
|
||||
v3.1.2
|
||||
|
||||
This is a mirror providing the single required header file.
|
||||
|
||||
The original repository can be found at:
|
||||
https://github.com/nlohmann/json/commit/d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe
|
||||
17300
externals/json/json.hpp
vendored
Normal file
17300
externals/json/json.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
externals/libressl
vendored
Submodule
1
externals/libressl
vendored
Submodule
Submodule externals/libressl added at 7d01cb01cb
8
externals/lurlparser/CMakeLists.txt
vendored
Normal file
8
externals/lurlparser/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
add_library(lurlparser
|
||||
LUrlParser.cpp
|
||||
LUrlParser.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(lurlparser)
|
||||
|
||||
target_include_directories(lurlparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
265
externals/lurlparser/LUrlParser.cpp
vendored
Normal file
265
externals/lurlparser/LUrlParser.cpp
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
* https://github.com/corporateshark/LUrlParser
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "LUrlParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
|
||||
// check if the scheme name is valid
|
||||
static bool IsSchemeValid( const std::string& SchemeName )
|
||||
{
|
||||
for ( auto c : SchemeName )
|
||||
{
|
||||
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
|
||||
{
|
||||
if ( !IsValid() ) { return false; }
|
||||
|
||||
int Port = atoi( m_Port.c_str() );
|
||||
|
||||
if ( Port <= 0 || Port > 65535 ) { return false; }
|
||||
|
||||
if ( OutPort ) { *OutPort = Port; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// based on RFC 1738 and RFC 3986
|
||||
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
|
||||
{
|
||||
LUrlParser::clParseURL Result;
|
||||
|
||||
const char* CurrentString = URL.c_str();
|
||||
|
||||
/*
|
||||
* <scheme>:<scheme-specific-part>
|
||||
* <scheme> := [a-z\+\-\.]+
|
||||
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
|
||||
*/
|
||||
|
||||
// try to read scheme
|
||||
{
|
||||
const char* LocalString = strchr( CurrentString, ':' );
|
||||
|
||||
if ( !LocalString )
|
||||
{
|
||||
return clParseURL( LUrlParserError_NoUrlCharacter );
|
||||
}
|
||||
|
||||
// save the scheme name
|
||||
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
if ( !IsSchemeValid( Result.m_Scheme ) )
|
||||
{
|
||||
return clParseURL( LUrlParserError_InvalidSchemeName );
|
||||
}
|
||||
|
||||
// scheme should be lowercase
|
||||
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
|
||||
|
||||
// skip ':'
|
||||
CurrentString = LocalString+1;
|
||||
}
|
||||
|
||||
/*
|
||||
* //<user>:<password>@<host>:<port>/<url-path>
|
||||
* any ":", "@" and "/" must be normalized
|
||||
*/
|
||||
|
||||
// skip "//"
|
||||
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
||||
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
||||
|
||||
// check if the user name and password are specified
|
||||
bool bHasUserName = false;
|
||||
|
||||
const char* LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString )
|
||||
{
|
||||
if ( *LocalString == '@' )
|
||||
{
|
||||
// user name and password are specified
|
||||
bHasUserName = true;
|
||||
break;
|
||||
}
|
||||
else if ( *LocalString == '/' )
|
||||
{
|
||||
// end of <host>:<port> specification
|
||||
bHasUserName = false;
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
// user name and password
|
||||
LocalString = CurrentString;
|
||||
|
||||
if ( bHasUserName )
|
||||
{
|
||||
// read user name
|
||||
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
|
||||
|
||||
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
// proceed with the current pointer
|
||||
CurrentString = LocalString;
|
||||
|
||||
if ( *CurrentString == ':' )
|
||||
{
|
||||
// skip ':'
|
||||
CurrentString++;
|
||||
|
||||
// read password
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '@' ) LocalString++;
|
||||
|
||||
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// skip '@'
|
||||
if ( *CurrentString != '@' )
|
||||
{
|
||||
return clParseURL( LUrlParserError_NoAtSign );
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
}
|
||||
|
||||
bool bHasBracket = ( *CurrentString == '[' );
|
||||
|
||||
// go ahead, read the host name
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString )
|
||||
{
|
||||
if ( bHasBracket && *LocalString == ']' )
|
||||
{
|
||||
// end of IPv6 address
|
||||
LocalString++;
|
||||
break;
|
||||
}
|
||||
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
|
||||
{
|
||||
// port number is specified
|
||||
break;
|
||||
}
|
||||
|
||||
LocalString++;
|
||||
}
|
||||
|
||||
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// is port number specified?
|
||||
if ( *CurrentString == ':' )
|
||||
{
|
||||
CurrentString++;
|
||||
|
||||
// read port number
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '/' ) LocalString++;
|
||||
|
||||
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// end of string
|
||||
if ( !*CurrentString )
|
||||
{
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// skip '/'
|
||||
if ( *CurrentString != '/' )
|
||||
{
|
||||
return clParseURL( LUrlParserError_NoSlash );
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
|
||||
// parse the path
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
|
||||
|
||||
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
|
||||
// check for query
|
||||
if ( *CurrentString == '?' )
|
||||
{
|
||||
// skip '?'
|
||||
CurrentString++;
|
||||
|
||||
// read query
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString && *LocalString != '#' ) LocalString++;
|
||||
|
||||
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
// check for fragment
|
||||
if ( *CurrentString == '#' )
|
||||
{
|
||||
// skip '#'
|
||||
CurrentString++;
|
||||
|
||||
// read fragment
|
||||
LocalString = CurrentString;
|
||||
|
||||
while ( *LocalString ) LocalString++;
|
||||
|
||||
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
|
||||
|
||||
CurrentString = LocalString;
|
||||
}
|
||||
|
||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||
|
||||
return Result;
|
||||
}
|
||||
78
externals/lurlparser/LUrlParser.h
vendored
Normal file
78
externals/lurlparser/LUrlParser.h
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
* https://github.com/corporateshark/LUrlParser
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace LUrlParser
|
||||
{
|
||||
enum LUrlParserError
|
||||
{
|
||||
LUrlParserError_Ok = 0,
|
||||
LUrlParserError_Uninitialized = 1,
|
||||
LUrlParserError_NoUrlCharacter = 2,
|
||||
LUrlParserError_InvalidSchemeName = 3,
|
||||
LUrlParserError_NoDoubleSlash = 4,
|
||||
LUrlParserError_NoAtSign = 5,
|
||||
LUrlParserError_UnexpectedEndOfLine = 6,
|
||||
LUrlParserError_NoSlash = 7,
|
||||
};
|
||||
|
||||
class clParseURL
|
||||
{
|
||||
public:
|
||||
LUrlParserError m_ErrorCode;
|
||||
std::string m_Scheme;
|
||||
std::string m_Host;
|
||||
std::string m_Port;
|
||||
std::string m_Path;
|
||||
std::string m_Query;
|
||||
std::string m_Fragment;
|
||||
std::string m_UserName;
|
||||
std::string m_Password;
|
||||
|
||||
clParseURL()
|
||||
: m_ErrorCode( LUrlParserError_Uninitialized )
|
||||
{}
|
||||
|
||||
/// return 'true' if the parsing was successful
|
||||
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
|
||||
|
||||
/// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
|
||||
bool GetPort( int* OutPort ) const;
|
||||
|
||||
/// parse the URL
|
||||
static clParseURL ParseURL( const std::string& URL );
|
||||
|
||||
private:
|
||||
explicit clParseURL( LUrlParserError ErrorCode )
|
||||
: m_ErrorCode( ErrorCode )
|
||||
{}
|
||||
};
|
||||
|
||||
} // namespace LUrlParser
|
||||
19
externals/lurlparser/README.md
vendored
Normal file
19
externals/lurlparser/README.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
From https://github.com/corporateshark/LUrlParser/commit/455d5e2d27e3946f11ad0328fee9ee2628e6a8e2
|
||||
|
||||
MIT License
|
||||
|
||||
===
|
||||
|
||||
Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||
|
||||
(C) Sergey Kosarevsky, 2015
|
||||
|
||||
@corporateshark sk@linderdaum.com
|
||||
|
||||
http://www.linderdaum.com
|
||||
|
||||
http://blog.linderdaum.com
|
||||
|
||||
=============================
|
||||
|
||||
A tiny and lightweight URL & URI parser (RFC 1738, RFC 3986) written in C++.
|
||||
2
externals/mbedtls
vendored
2
externals/mbedtls
vendored
Submodule externals/mbedtls updated: d409b75a4c...a280e602f3
@@ -13,3 +13,6 @@ endif()
|
||||
if (ENABLE_QT)
|
||||
add_subdirectory(yuzu)
|
||||
endif()
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
add_subdirectory(web_service)
|
||||
endif()
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
return info;
|
||||
}
|
||||
|
||||
VoiceInfo& Info() {
|
||||
VoiceInfo& GetInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -51,9 +51,30 @@ private:
|
||||
VoiceInfo info{};
|
||||
};
|
||||
|
||||
class AudioRenderer::EffectState {
|
||||
public:
|
||||
const EffectOutStatus& GetOutStatus() const {
|
||||
return out_status;
|
||||
}
|
||||
|
||||
const EffectInStatus& GetInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
EffectInStatus& GetInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
void UpdateState();
|
||||
|
||||
private:
|
||||
EffectOutStatus out_status{};
|
||||
EffectInStatus info{};
|
||||
};
|
||||
AudioRenderer::AudioRenderer(AudioRendererParameter params,
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
|
||||
effects(params.effect_count) {
|
||||
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
@@ -96,11 +117,29 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
|
||||
memory_pool_count * sizeof(MemoryPoolInfo));
|
||||
|
||||
// Copy VoiceInfo structs
|
||||
std::size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size +
|
||||
config.voice_resource_size};
|
||||
std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size + config.voice_resource_size};
|
||||
for (auto& voice : voices) {
|
||||
std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo));
|
||||
offset += sizeof(VoiceInfo);
|
||||
std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
|
||||
voice_offset += sizeof(VoiceInfo);
|
||||
}
|
||||
|
||||
std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size + config.voice_resource_size +
|
||||
config.voices_size};
|
||||
for (auto& effect : effects) {
|
||||
std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
|
||||
effect_offset += sizeof(EffectInStatus);
|
||||
}
|
||||
|
||||
// Update memory pool state
|
||||
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
|
||||
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
|
||||
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Attached;
|
||||
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Detached;
|
||||
}
|
||||
}
|
||||
|
||||
// Update voices
|
||||
@@ -114,14 +153,8 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
|
||||
}
|
||||
}
|
||||
|
||||
// Update memory pool state
|
||||
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
|
||||
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
|
||||
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Attached;
|
||||
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Detached;
|
||||
}
|
||||
for (auto& effect : effects) {
|
||||
effect.UpdateState();
|
||||
}
|
||||
|
||||
// Release previous buffers and queue next ones for playback
|
||||
@@ -144,6 +177,14 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
|
||||
voice_out_status_offset += sizeof(VoiceOutStatus);
|
||||
}
|
||||
|
||||
std::size_t effect_out_status_offset{
|
||||
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
|
||||
response_data.voice_resource_size};
|
||||
for (const auto& effect : effects) {
|
||||
std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
|
||||
sizeof(EffectOutStatus));
|
||||
effect_out_status_offset += sizeof(EffectOutStatus);
|
||||
}
|
||||
return output_params;
|
||||
}
|
||||
|
||||
@@ -244,11 +285,29 @@ void AudioRenderer::VoiceState::RefreshBuffer() {
|
||||
break;
|
||||
}
|
||||
|
||||
samples = Interpolate(interp_state, std::move(samples), Info().sample_rate, STREAM_SAMPLE_RATE);
|
||||
samples =
|
||||
Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE);
|
||||
|
||||
is_refresh_pending = false;
|
||||
}
|
||||
|
||||
void AudioRenderer::EffectState::UpdateState() {
|
||||
if (info.is_new) {
|
||||
out_status.state = EffectStatus::New;
|
||||
} else {
|
||||
if (info.type == Effect::Aux) {
|
||||
ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_info) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_info) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_base) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_base) == 0,
|
||||
"Aux buffers tried to update");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr s16 ClampToS16(s32 value) {
|
||||
return static_cast<s16>(std::clamp(value, -32768, 32767));
|
||||
}
|
||||
|
||||
@@ -28,6 +28,16 @@ enum class PlayState : u8 {
|
||||
Paused = 2,
|
||||
};
|
||||
|
||||
enum class Effect : u8 {
|
||||
None = 0,
|
||||
Aux = 2,
|
||||
};
|
||||
|
||||
enum class EffectStatus : u8 {
|
||||
None = 0,
|
||||
New = 1,
|
||||
};
|
||||
|
||||
struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
@@ -128,6 +138,43 @@ struct VoiceOutStatus {
|
||||
};
|
||||
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
|
||||
|
||||
struct AuxInfo {
|
||||
std::array<u8, 24> input_mix_buffers;
|
||||
std::array<u8, 24> output_mix_buffers;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le sample_rate; // Stored in the aux buffer currently
|
||||
u32_le sampe_count;
|
||||
u64_le send_buffer_info;
|
||||
u64_le send_buffer_base;
|
||||
|
||||
u64_le return_buffer_info;
|
||||
u64_le return_buffer_base;
|
||||
};
|
||||
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
|
||||
|
||||
struct EffectInStatus {
|
||||
Effect type;
|
||||
u8 is_new;
|
||||
u8 is_enabled;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u32_le mix_id;
|
||||
u64_le buffer_base;
|
||||
u64_le buffer_sz;
|
||||
s32_le priority;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
union {
|
||||
std::array<u8, 0xa0> raw;
|
||||
AuxInfo aux_info;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
|
||||
|
||||
struct EffectOutStatus {
|
||||
EffectStatus state;
|
||||
INSERT_PADDING_BYTES(0xf);
|
||||
};
|
||||
static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
|
||||
|
||||
struct UpdateDataHeader {
|
||||
UpdateDataHeader() {}
|
||||
|
||||
@@ -173,11 +220,13 @@ public:
|
||||
Stream::State GetStreamState() const;
|
||||
|
||||
private:
|
||||
class EffectState;
|
||||
class VoiceState;
|
||||
|
||||
AudioRendererParameter worker_params;
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event;
|
||||
std::vector<VoiceState> voices;
|
||||
std::vector<EffectState> effects;
|
||||
std::unique_ptr<AudioOut> audio_out;
|
||||
AudioCore::StreamPtr stream;
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ if ($ENV{CI})
|
||||
if (BUILD_VERSION)
|
||||
# This leaves a trailing space on the last word, but we actually want that
|
||||
# because of how it's styled in the title bar.
|
||||
set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ")
|
||||
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
|
||||
else()
|
||||
set(BUILD_FULLNAME "")
|
||||
endif()
|
||||
@@ -41,6 +41,8 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU
|
||||
add_library(common STATIC
|
||||
alignment.h
|
||||
assert.h
|
||||
detached_tasks.cpp
|
||||
detached_tasks.h
|
||||
bit_field.h
|
||||
bit_set.h
|
||||
cityhash.cpp
|
||||
@@ -62,8 +64,6 @@ add_library(common STATIC
|
||||
logging/text_formatter.cpp
|
||||
logging/text_formatter.h
|
||||
math_util.h
|
||||
memory_util.cpp
|
||||
memory_util.h
|
||||
microprofile.cpp
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
@@ -87,6 +87,7 @@ add_library(common STATIC
|
||||
timer.cpp
|
||||
timer.h
|
||||
vector_math.h
|
||||
web_result.h
|
||||
)
|
||||
|
||||
if(ARCHITECTURE_x86_64)
|
||||
|
||||
@@ -19,4 +19,16 @@ constexpr T AlignDown(T value, std::size_t size) {
|
||||
return static_cast<T>(value - value % size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr bool Is4KBAligned(T value) {
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
return (value & 0xFFF) == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr bool IsWordAligned(T value) {
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
return (value & 0b11) == 0;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
41
src/common/detached_tasks.cpp
Normal file
41
src/common/detached_tasks.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <thread>
|
||||
#include "common/assert.h"
|
||||
#include "common/detached_tasks.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
DetachedTasks* DetachedTasks::instance = nullptr;
|
||||
|
||||
DetachedTasks::DetachedTasks() {
|
||||
ASSERT(instance == nullptr);
|
||||
instance = this;
|
||||
}
|
||||
|
||||
void DetachedTasks::WaitForAllTasks() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
cv.wait(lock, [this]() { return count == 0; });
|
||||
}
|
||||
|
||||
DetachedTasks::~DetachedTasks() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
ASSERT(count == 0);
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
void DetachedTasks::AddTask(std::function<void()> task) {
|
||||
std::unique_lock<std::mutex> lock(instance->mutex);
|
||||
++instance->count;
|
||||
std::thread([task{std::move(task)}]() {
|
||||
task();
|
||||
std::unique_lock<std::mutex> lock(instance->mutex);
|
||||
--instance->count;
|
||||
std::notify_all_at_thread_exit(instance->cv, std::move(lock));
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
40
src/common/detached_tasks.h
Normal file
40
src/common/detached_tasks.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* A background manager which ensures that all detached task is finished before program exits.
|
||||
*
|
||||
* Some tasks, telemetry submission for example, prefer executing asynchronously and don't care
|
||||
* about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if
|
||||
* the task is launched just before the program exits (which is a common case for telemetry), so we
|
||||
* need to block on these tasks on program exit.
|
||||
*
|
||||
* To make detached task safe, a single DetachedTasks object should be placed in the main(), and
|
||||
* call WaitForAllTasks() after all program execution but before global/static variable destruction.
|
||||
* Any potentially unsafe detached task should be executed via DetachedTasks::AddTask.
|
||||
*/
|
||||
class DetachedTasks {
|
||||
public:
|
||||
DetachedTasks();
|
||||
~DetachedTasks();
|
||||
void WaitForAllTasks();
|
||||
|
||||
static void AddTask(std::function<void()> task);
|
||||
|
||||
private:
|
||||
static DetachedTasks* instance;
|
||||
|
||||
std::condition_variable cv;
|
||||
std::mutex mutex;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -15,21 +15,24 @@
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
// windows.h needs to be included before other windows headers
|
||||
#include <commdlg.h> // for GetSaveFileName
|
||||
#include <direct.h> // getcwd
|
||||
#include <direct.h> // getcwd
|
||||
#include <io.h>
|
||||
#include <shellapi.h>
|
||||
#include <shlobj.h> // for SHGetFolderPath
|
||||
#include <tchar.h>
|
||||
#include "common/string_util.h"
|
||||
|
||||
// 64 bit offsets for windows
|
||||
#ifdef _MSC_VER
|
||||
// 64 bit offsets for MSVC
|
||||
#define fseeko _fseeki64
|
||||
#define ftello _ftelli64
|
||||
#define atoll _atoi64
|
||||
#define fileno _fileno
|
||||
#endif
|
||||
|
||||
// 64 bit offsets for MSVC and MinGW. MinGW also needs this for using _wstat64
|
||||
#define stat _stat64
|
||||
#define fstat _fstat64
|
||||
#define fileno _fileno
|
||||
|
||||
#else
|
||||
#ifdef __APPLE__
|
||||
#include <sys/param.h>
|
||||
|
||||
@@ -285,7 +285,7 @@ private:
|
||||
template <typename T>
|
||||
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
|
||||
#ifdef _MSC_VER
|
||||
fstream.open(Common::UTF8ToTStr(filename).c_str(), openmode);
|
||||
fstream.open(Common::UTF8ToUTF16W(filename).c_str(), openmode);
|
||||
#else
|
||||
fstream.open(filename.c_str(), openmode);
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,25 @@ u8 ToHexNibble(char c1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
|
||||
std::vector<u8> out(str.size() / 2);
|
||||
if (little_endian) {
|
||||
for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
} else {
|
||||
for (std::size_t i = 0; i < str.size(); i += 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string HexVectorToString(const std::vector<u8>& vector, bool upper) {
|
||||
std::string out;
|
||||
for (u8 c : vector)
|
||||
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
|
||||
if (len != 32) {
|
||||
LOG_ERROR(Common,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -14,6 +15,8 @@ namespace Common {
|
||||
|
||||
u8 ToHexNibble(char c1);
|
||||
|
||||
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
|
||||
|
||||
template <std::size_t Size, bool le = false>
|
||||
std::array<u8, Size> HexStringToArray(std::string_view str) {
|
||||
std::array<u8, Size> out{};
|
||||
@@ -27,6 +30,8 @@ std::array<u8, Size> HexStringToArray(std::string_view str) {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true);
|
||||
|
||||
template <std::size_t Size>
|
||||
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
|
||||
std::string out;
|
||||
|
||||
@@ -91,6 +91,7 @@ enum class Class : ClassType {
|
||||
Service_PM, ///< The PM service
|
||||
Service_PREPO, ///< The PREPO (Play report) service
|
||||
Service_PSC, ///< The PSC service
|
||||
Service_PSM, ///< The PSM service
|
||||
Service_SET, ///< The SET (Settings) service
|
||||
Service_SM, ///< The SM (Service manager) service
|
||||
Service_SPL, ///< The SPL service
|
||||
|
||||
@@ -31,7 +31,7 @@ std::string FormatLogMessage(const Entry& entry) {
|
||||
}
|
||||
|
||||
void PrintMessage(const Entry& entry) {
|
||||
auto str = FormatLogMessage(entry) + '\n';
|
||||
const auto str = FormatLogMessage(entry).append(1, '\n');
|
||||
fputs(str.c_str(), stderr);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/memory_util.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
// Windows.h needs to be included before psapi.h
|
||||
#include <psapi.h>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/string_util.h"
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
#include <unistd.h>
|
||||
#define PAGE_MASK (getpagesize() - 1)
|
||||
#define round_page(x) ((((unsigned long)(x)) + PAGE_MASK) & ~(PAGE_MASK))
|
||||
#endif
|
||||
|
||||
// This is purposely not a full wrapper for virtualalloc/mmap, but it
|
||||
// provides exactly the primitive operations that Dolphin needs.
|
||||
|
||||
void* AllocateExecutableMemory(std::size_t size, bool low) {
|
||||
#if defined(_WIN32)
|
||||
void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
#else
|
||||
static char* map_hint = nullptr;
|
||||
#if defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
// This OS has no flag to enforce allocation below the 4 GB boundary,
|
||||
// but if we hint that we want a low address it is very likely we will
|
||||
// get one.
|
||||
// An older version of this code used MAP_FIXED, but that has the side
|
||||
// effect of discarding already mapped pages that happen to be in the
|
||||
// requested virtual memory range (such as the emulated RAM, sometimes).
|
||||
if (low && (!map_hint))
|
||||
map_hint = (char*)round_page(512 * 1024 * 1024); /* 0.5 GB rounded up to the next page */
|
||||
#endif
|
||||
void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_ANON | MAP_PRIVATE
|
||||
#if defined(ARCHITECTURE_x86_64) && defined(MAP_32BIT)
|
||||
| (low ? MAP_32BIT : 0)
|
||||
#endif
|
||||
,
|
||||
-1, 0);
|
||||
#endif /* defined(_WIN32) */
|
||||
|
||||
#ifdef _WIN32
|
||||
if (ptr == nullptr) {
|
||||
#else
|
||||
if (ptr == MAP_FAILED) {
|
||||
ptr = nullptr;
|
||||
#endif
|
||||
LOG_ERROR(Common_Memory, "Failed to allocate executable memory");
|
||||
}
|
||||
#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
|
||||
else {
|
||||
if (low) {
|
||||
map_hint += size;
|
||||
map_hint = (char*)round_page(map_hint); /* round up to the next page */
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if EMU_ARCH_BITS == 64
|
||||
if ((u64)ptr >= 0x80000000 && low == true)
|
||||
LOG_ERROR(Common_Memory, "Executable memory ended up above 2GB!");
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void* AllocateMemoryPages(std::size_t size) {
|
||||
#ifdef _WIN32
|
||||
void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
|
||||
#else
|
||||
void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
|
||||
if (ptr == MAP_FAILED)
|
||||
ptr = nullptr;
|
||||
#endif
|
||||
|
||||
if (ptr == nullptr)
|
||||
LOG_ERROR(Common_Memory, "Failed to allocate raw memory");
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void* AllocateAlignedMemory(std::size_t size, std::size_t alignment) {
|
||||
#ifdef _WIN32
|
||||
void* ptr = _aligned_malloc(size, alignment);
|
||||
#else
|
||||
void* ptr = nullptr;
|
||||
#ifdef ANDROID
|
||||
ptr = memalign(alignment, size);
|
||||
#else
|
||||
if (posix_memalign(&ptr, alignment, size) != 0)
|
||||
LOG_ERROR(Common_Memory, "Failed to allocate aligned memory");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (ptr == nullptr)
|
||||
LOG_ERROR(Common_Memory, "Failed to allocate aligned memory");
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void FreeMemoryPages(void* ptr, std::size_t size) {
|
||||
if (ptr) {
|
||||
#ifdef _WIN32
|
||||
if (!VirtualFree(ptr, 0, MEM_RELEASE))
|
||||
LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n{}", GetLastErrorMsg());
|
||||
#else
|
||||
munmap(ptr, size);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void FreeAlignedMemory(void* ptr) {
|
||||
if (ptr) {
|
||||
#ifdef _WIN32
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
free(ptr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void WriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) {
|
||||
#ifdef _WIN32
|
||||
DWORD oldValue;
|
||||
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue))
|
||||
LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n{}", GetLastErrorMsg());
|
||||
#else
|
||||
mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) {
|
||||
#ifdef _WIN32
|
||||
DWORD oldValue;
|
||||
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE,
|
||||
&oldValue))
|
||||
LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n{}", GetLastErrorMsg());
|
||||
#else
|
||||
mprotect(ptr, size,
|
||||
allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string MemUsage() {
|
||||
#ifdef _WIN32
|
||||
#pragma comment(lib, "psapi")
|
||||
DWORD processID = GetCurrentProcessId();
|
||||
HANDLE hProcess;
|
||||
PROCESS_MEMORY_COUNTERS pmc;
|
||||
std::string Ret;
|
||||
|
||||
// Print information about the memory usage of the process.
|
||||
|
||||
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);
|
||||
if (nullptr == hProcess)
|
||||
return "MemUsage Error";
|
||||
|
||||
if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc)))
|
||||
Ret = fmt::format("{} K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7));
|
||||
|
||||
CloseHandle(hProcess);
|
||||
return Ret;
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
void* AllocateExecutableMemory(std::size_t size, bool low = true);
|
||||
void* AllocateMemoryPages(std::size_t size);
|
||||
void FreeMemoryPages(void* ptr, std::size_t size);
|
||||
void* AllocateAlignedMemory(std::size_t size, std::size_t alignment);
|
||||
void FreeAlignedMemory(void* ptr);
|
||||
void WriteProtectMemory(void* ptr, std::size_t size, bool executable = false);
|
||||
void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute = false);
|
||||
std::string MemUsage();
|
||||
|
||||
inline int GetPageSize() {
|
||||
return 4096;
|
||||
}
|
||||
@@ -20,7 +20,15 @@ constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
|
||||
constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
|
||||
constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
|
||||
|
||||
/// A placeholder for empty param packages to avoid empty strings
|
||||
/// (they may be recognized as "not set" by some frontend libraries like qt)
|
||||
constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
|
||||
|
||||
ParamPackage::ParamPackage(const std::string& serialized) {
|
||||
if (serialized == EMPTY_PLACEHOLDER) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> pairs;
|
||||
Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
|
||||
|
||||
@@ -46,7 +54,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d
|
||||
|
||||
std::string ParamPackage::Serialize() const {
|
||||
if (data.empty())
|
||||
return "";
|
||||
return EMPTY_PLACEHOLDER;
|
||||
|
||||
std::string result;
|
||||
|
||||
@@ -120,4 +128,12 @@ bool ParamPackage::Has(const std::string& key) const {
|
||||
return data.find(key) != data.end();
|
||||
}
|
||||
|
||||
void ParamPackage::Erase(const std::string& key) {
|
||||
data.erase(key);
|
||||
}
|
||||
|
||||
void ParamPackage::Clear() {
|
||||
data.clear();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -32,6 +32,8 @@ public:
|
||||
void Set(const std::string& key, int value);
|
||||
void Set(const std::string& key, float value);
|
||||
bool Has(const std::string& key) const;
|
||||
void Erase(const std::string& key);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
DataType data;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <codecvt>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -13,11 +14,7 @@
|
||||
#include "common/string_util.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <codecvt>
|
||||
#include <windows.h>
|
||||
#include "common/common_funcs.h"
|
||||
#else
|
||||
#include <iconv.h>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
@@ -195,11 +192,9 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
std::string UTF16ToUTF8(const std::u16string& input) {
|
||||
#if _MSC_VER >= 1900
|
||||
// Workaround for missing char16_t/char32_t instantiations in MSVC2015
|
||||
#ifdef _MSC_VER
|
||||
// Workaround for missing char16_t/char32_t instantiations in MSVC2017
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
|
||||
std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend());
|
||||
return convert.to_bytes(tmp_buffer);
|
||||
@@ -210,8 +205,8 @@ std::string UTF16ToUTF8(const std::u16string& input) {
|
||||
}
|
||||
|
||||
std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
#if _MSC_VER >= 1900
|
||||
// Workaround for missing char16_t/char32_t instantiations in MSVC2015
|
||||
#ifdef _MSC_VER
|
||||
// Workaround for missing char16_t/char32_t instantiations in MSVC2017
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
|
||||
auto tmp_buffer = convert.from_bytes(input);
|
||||
return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend());
|
||||
@@ -221,6 +216,7 @@ std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static std::wstring CPToUTF16(u32 code_page, const std::string& input) {
|
||||
const auto size =
|
||||
MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
|
||||
@@ -261,124 +257,6 @@ std::wstring UTF8ToUTF16W(const std::string& input) {
|
||||
return CPToUTF16(CP_UTF8, input);
|
||||
}
|
||||
|
||||
std::string SHIFTJISToUTF8(const std::string& input) {
|
||||
return UTF16ToUTF8(CPToUTF16(932, input));
|
||||
}
|
||||
|
||||
std::string CP1252ToUTF8(const std::string& input) {
|
||||
return UTF16ToUTF8(CPToUTF16(1252, input));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template <typename T>
|
||||
static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& input) {
|
||||
iconv_t const conv_desc = iconv_open("UTF-8", fromcode);
|
||||
if ((iconv_t)(-1) == conv_desc) {
|
||||
LOG_ERROR(Common, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno));
|
||||
iconv_close(conv_desc);
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::size_t in_bytes = sizeof(T) * input.size();
|
||||
// Multiply by 4, which is the max number of bytes to encode a codepoint
|
||||
const std::size_t out_buffer_size = 4 * in_bytes;
|
||||
|
||||
std::string out_buffer(out_buffer_size, '\0');
|
||||
|
||||
auto src_buffer = &input[0];
|
||||
std::size_t src_bytes = in_bytes;
|
||||
auto dst_buffer = &out_buffer[0];
|
||||
std::size_t dst_bytes = out_buffer.size();
|
||||
|
||||
while (0 != src_bytes) {
|
||||
std::size_t const iconv_result =
|
||||
iconv(conv_desc, (char**)(&src_buffer), &src_bytes, &dst_buffer, &dst_bytes);
|
||||
|
||||
if (static_cast<std::size_t>(-1) == iconv_result) {
|
||||
if (EILSEQ == errno || EINVAL == errno) {
|
||||
// Try to skip the bad character
|
||||
if (0 != src_bytes) {
|
||||
--src_bytes;
|
||||
++src_buffer;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Common, "iconv failure [{}]: {}", fromcode, strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string result;
|
||||
out_buffer.resize(out_buffer_size - dst_bytes);
|
||||
out_buffer.swap(result);
|
||||
|
||||
iconv_close(conv_desc);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8");
|
||||
if ((iconv_t)(-1) == conv_desc) {
|
||||
LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: {}", strerror(errno));
|
||||
iconv_close(conv_desc);
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::size_t in_bytes = sizeof(char) * input.size();
|
||||
// Multiply by 4, which is the max number of bytes to encode a codepoint
|
||||
const std::size_t out_buffer_size = 4 * sizeof(char16_t) * in_bytes;
|
||||
|
||||
std::u16string out_buffer(out_buffer_size, char16_t{});
|
||||
|
||||
char* src_buffer = const_cast<char*>(&input[0]);
|
||||
std::size_t src_bytes = in_bytes;
|
||||
char* dst_buffer = (char*)(&out_buffer[0]);
|
||||
std::size_t dst_bytes = out_buffer.size();
|
||||
|
||||
while (0 != src_bytes) {
|
||||
std::size_t const iconv_result =
|
||||
iconv(conv_desc, &src_buffer, &src_bytes, &dst_buffer, &dst_bytes);
|
||||
|
||||
if (static_cast<std::size_t>(-1) == iconv_result) {
|
||||
if (EILSEQ == errno || EINVAL == errno) {
|
||||
// Try to skip the bad character
|
||||
if (0 != src_bytes) {
|
||||
--src_bytes;
|
||||
++src_buffer;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Common, "iconv failure [UTF-8]: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::u16string result;
|
||||
out_buffer.resize(out_buffer_size - dst_bytes);
|
||||
out_buffer.swap(result);
|
||||
|
||||
iconv_close(conv_desc);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string UTF16ToUTF8(const std::u16string& input) {
|
||||
return CodeToUTF8("UTF-16LE", input);
|
||||
}
|
||||
|
||||
std::string CP1252ToUTF8(const std::string& input) {
|
||||
// return CodeToUTF8("CP1252//TRANSLIT", input);
|
||||
// return CodeToUTF8("CP1252//IGNORE", input);
|
||||
return CodeToUTF8("CP1252", input);
|
||||
}
|
||||
|
||||
std::string SHIFTJISToUTF8(const std::string& input) {
|
||||
// return CodeToUTF8("CP932", input);
|
||||
return CodeToUTF8("SJIS", input);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len) {
|
||||
|
||||
@@ -72,31 +72,10 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
|
||||
std::string UTF16ToUTF8(const std::u16string& input);
|
||||
std::u16string UTF8ToUTF16(const std::string& input);
|
||||
|
||||
std::string CP1252ToUTF8(const std::string& str);
|
||||
std::string SHIFTJISToUTF8(const std::string& str);
|
||||
|
||||
#ifdef _WIN32
|
||||
std::string UTF16ToUTF8(const std::wstring& input);
|
||||
std::wstring UTF8ToUTF16W(const std::string& str);
|
||||
|
||||
#ifdef _UNICODE
|
||||
inline std::string TStrToUTF8(const std::wstring& str) {
|
||||
return UTF16ToUTF8(str);
|
||||
}
|
||||
|
||||
inline std::wstring UTF8ToTStr(const std::string& str) {
|
||||
return UTF8ToUTF16W(str);
|
||||
}
|
||||
#else
|
||||
inline std::string TStrToUTF8(const std::string& str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
inline std::string UTF8ToTStr(const std::string& str) {
|
||||
return str;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
25
src/common/web_result.h
Normal file
25
src/common/web_result.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
struct WebResult {
|
||||
enum class Code : u32 {
|
||||
Success,
|
||||
InvalidURL,
|
||||
CredentialsMissing,
|
||||
LibError,
|
||||
HttpError,
|
||||
WrongContent,
|
||||
NoWebservice,
|
||||
};
|
||||
Code result_code;
|
||||
std::string result_string;
|
||||
std::string returned_data;
|
||||
};
|
||||
} // namespace Common
|
||||
@@ -18,6 +18,8 @@ add_library(core STATIC
|
||||
crypto/encryption_layer.h
|
||||
crypto/key_manager.cpp
|
||||
crypto/key_manager.h
|
||||
crypto/partition_data_manager.cpp
|
||||
crypto/partition_data_manager.h
|
||||
crypto/ctr_encryption_layer.cpp
|
||||
crypto/ctr_encryption_layer.h
|
||||
crypto/xts_encryption_layer.cpp
|
||||
@@ -70,6 +72,7 @@ add_library(core STATIC
|
||||
file_sys/vfs_real.cpp
|
||||
file_sys/vfs_real.h
|
||||
file_sys/vfs_static.h
|
||||
file_sys/vfs_types.h
|
||||
file_sys/vfs_vector.cpp
|
||||
file_sys/vfs_vector.h
|
||||
file_sys/xts_archive.cpp
|
||||
@@ -153,6 +156,8 @@ add_library(core STATIC
|
||||
hle/service/am/omm.h
|
||||
hle/service/am/spsm.cpp
|
||||
hle/service/am/spsm.h
|
||||
hle/service/am/tcap.cpp
|
||||
hle/service/am/tcap.h
|
||||
hle/service/aoc/aoc_u.cpp
|
||||
hle/service/aoc/aoc_u.h
|
||||
hle/service/apm/apm.cpp
|
||||
@@ -233,6 +238,24 @@ add_library(core STATIC
|
||||
hle/service/hid/irs.h
|
||||
hle/service/hid/xcd.cpp
|
||||
hle/service/hid/xcd.h
|
||||
hle/service/hid/controllers/controller_base.cpp
|
||||
hle/service/hid/controllers/controller_base.h
|
||||
hle/service/hid/controllers/debug_pad.cpp
|
||||
hle/service/hid/controllers/debug_pad.h
|
||||
hle/service/hid/controllers/gesture.cpp
|
||||
hle/service/hid/controllers/gesture.h
|
||||
hle/service/hid/controllers/keyboard.cpp
|
||||
hle/service/hid/controllers/keyboard.h
|
||||
hle/service/hid/controllers/mouse.cpp
|
||||
hle/service/hid/controllers/mouse.h
|
||||
hle/service/hid/controllers/npad.cpp
|
||||
hle/service/hid/controllers/npad.h
|
||||
hle/service/hid/controllers/stubbed.cpp
|
||||
hle/service/hid/controllers/stubbed.h
|
||||
hle/service/hid/controllers/touchscreen.cpp
|
||||
hle/service/hid/controllers/touchscreen.h
|
||||
hle/service/hid/controllers/xpad.cpp
|
||||
hle/service/hid/controllers/xpad.h
|
||||
hle/service/lbl/lbl.cpp
|
||||
hle/service/lbl/lbl.h
|
||||
hle/service/ldn/ldn.cpp
|
||||
@@ -259,6 +282,8 @@ add_library(core STATIC
|
||||
hle/service/nifm/nifm.h
|
||||
hle/service/nim/nim.cpp
|
||||
hle/service/nim/nim.h
|
||||
hle/service/npns/npns.cpp
|
||||
hle/service/npns/npns.h
|
||||
hle/service/ns/ns.cpp
|
||||
hle/service/ns/ns.h
|
||||
hle/service/ns/pl_u.cpp
|
||||
@@ -306,6 +331,8 @@ add_library(core STATIC
|
||||
hle/service/prepo/prepo.h
|
||||
hle/service/psc/psc.cpp
|
||||
hle/service/psc/psc.h
|
||||
hle/service/ptm/psm.cpp
|
||||
hle/service/ptm/psm.h
|
||||
hle/service/service.cpp
|
||||
hle/service/service.h
|
||||
hle/service/set/set.cpp
|
||||
@@ -396,6 +423,10 @@ create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
|
||||
target_link_libraries(core PRIVATE web_service)
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
target_sources(core PRIVATE
|
||||
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
parent.jit->HaltExecution();
|
||||
parent.SetPC(pc);
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
parent.SaveContext(thread->context);
|
||||
parent.SaveContext(thread->GetContext());
|
||||
GDBStub::Break();
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
return;
|
||||
@@ -129,7 +129,7 @@ public:
|
||||
};
|
||||
|
||||
std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
auto& current_process = Core::CurrentProcess();
|
||||
auto* current_process = Core::CurrentProcess();
|
||||
auto** const page_table = current_process->VMManager().page_table.pointers.data();
|
||||
|
||||
Dynarmic::A64::UserConfig config;
|
||||
@@ -144,7 +144,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
|
||||
// Multi-process state
|
||||
config.processor_id = core_index;
|
||||
config.global_monitor = &exclusive_monitor->monitor;
|
||||
config.global_monitor = &exclusive_monitor.monitor;
|
||||
|
||||
// System registers
|
||||
config.tpidrro_el0 = &cb->tpidrro_el0;
|
||||
@@ -171,10 +171,9 @@ void ARM_Dynarmic::Step() {
|
||||
cb->InterpreterFallback(jit->GetPC(), 1);
|
||||
}
|
||||
|
||||
ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
std::size_t core_index)
|
||||
ARM_Dynarmic::ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index)
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index},
|
||||
exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} {
|
||||
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {
|
||||
ThreadContext ctx{};
|
||||
inner_unicorn.SaveContext(ctx);
|
||||
PageTableChanged();
|
||||
|
||||
@@ -23,7 +23,7 @@ class DynarmicExclusiveMonitor;
|
||||
|
||||
class ARM_Dynarmic final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, std::size_t core_index);
|
||||
ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
~ARM_Dynarmic();
|
||||
|
||||
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
ARM_Unicorn inner_unicorn;
|
||||
|
||||
std::size_t core_index;
|
||||
std::shared_ptr<DynarmicExclusiveMonitor> exclusive_monitor;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
|
||||
Memory::PageTable* current_page_table = nullptr;
|
||||
};
|
||||
|
||||
@@ -195,7 +195,7 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
|
||||
}
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
SaveContext(thread->context);
|
||||
SaveContext(thread->GetContext());
|
||||
if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) {
|
||||
last_bkpt_hit = false;
|
||||
GDBStub::Break();
|
||||
|
||||
@@ -71,9 +71,9 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
}
|
||||
|
||||
/// Runs a CPU core while the system is powered on
|
||||
void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
|
||||
void RunCpuCore(Cpu& cpu_state) {
|
||||
while (Core::System::GetInstance().IsPoweredOn()) {
|
||||
cpu_state->RunLoop(true);
|
||||
cpu_state.RunLoop(true);
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
@@ -95,7 +95,7 @@ struct System::Impl {
|
||||
status = ResultStatus::Success;
|
||||
|
||||
// Update thread_to_cpu in case Core 0 is run from a different host thread
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get();
|
||||
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
GDBStub::HandlePacket();
|
||||
@@ -136,18 +136,19 @@ struct System::Impl {
|
||||
if (virtual_filesystem == nullptr)
|
||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
|
||||
kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main"));
|
||||
auto main_process = Kernel::Process::Create(kernel, "main");
|
||||
kernel.MakeCurrentProcess(main_process.get());
|
||||
|
||||
cpu_barrier = std::make_shared<CpuBarrier>();
|
||||
cpu_barrier = std::make_unique<CpuBarrier>();
|
||||
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
|
||||
for (std::size_t index = 0; index < cpu_cores.size(); ++index) {
|
||||
cpu_cores[index] = std::make_shared<Cpu>(cpu_exclusive_monitor, cpu_barrier, index);
|
||||
cpu_cores[index] = std::make_unique<Cpu>(*cpu_exclusive_monitor, *cpu_barrier, index);
|
||||
}
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
||||
|
||||
Service::Init(service_manager, virtual_filesystem);
|
||||
Service::Init(service_manager, *virtual_filesystem);
|
||||
GDBStub::Init();
|
||||
|
||||
renderer = VideoCore::CreateRenderer(emu_window);
|
||||
@@ -159,12 +160,12 @@ struct System::Impl {
|
||||
|
||||
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||
// CPU core 0 is run on the main thread
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get();
|
||||
if (Settings::values.use_multi_core) {
|
||||
for (std::size_t index = 0; index < cpu_core_threads.size(); ++index) {
|
||||
cpu_core_threads[index] =
|
||||
std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
|
||||
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
|
||||
std::make_unique<std::thread>(RunCpuCore, std::ref(*cpu_cores[index + 1]));
|
||||
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1].get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +245,7 @@ struct System::Impl {
|
||||
for (auto& cpu_core : cpu_cores) {
|
||||
cpu_core.reset();
|
||||
}
|
||||
cpu_exclusive_monitor.reset();
|
||||
cpu_barrier.reset();
|
||||
|
||||
// Shutdown kernel and core timing
|
||||
@@ -281,9 +283,9 @@ struct System::Impl {
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||
std::unique_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
||||
std::unique_ptr<CpuBarrier> cpu_barrier;
|
||||
std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
|
||||
std::size_t active_core{}; ///< Active core, only used in single thread mode
|
||||
|
||||
@@ -297,7 +299,7 @@ struct System::Impl {
|
||||
std::string status_details = "";
|
||||
|
||||
/// Map of guest threads to CPU cores
|
||||
std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
|
||||
std::map<std::thread::id, Cpu*> thread_to_cpu;
|
||||
|
||||
Core::PerfStats perf_stats;
|
||||
Core::FrameLimiter frame_limiter;
|
||||
@@ -353,25 +355,27 @@ std::size_t System::CurrentCoreIndex() {
|
||||
}
|
||||
|
||||
Kernel::Scheduler& System::CurrentScheduler() {
|
||||
return *CurrentCpuCore().Scheduler();
|
||||
return CurrentCpuCore().Scheduler();
|
||||
}
|
||||
|
||||
const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return impl->cpu_cores[core_index]->Scheduler();
|
||||
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
|
||||
return CpuCore(core_index).Scheduler();
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() {
|
||||
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
|
||||
return CpuCore(core_index).Scheduler();
|
||||
}
|
||||
|
||||
Kernel::Process* System::CurrentProcess() {
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const {
|
||||
const Kernel::Process* System::CurrentProcess() const {
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
ARM_Interface& System::ArmInterface(std::size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return impl->cpu_cores[core_index]->ArmInterface();
|
||||
return CpuCore(core_index).ArmInterface();
|
||||
}
|
||||
|
||||
Cpu& System::CpuCore(std::size_t core_index) {
|
||||
@@ -379,6 +383,11 @@ Cpu& System::CpuCore(std::size_t core_index) {
|
||||
return *impl->cpu_cores[core_index];
|
||||
}
|
||||
|
||||
const Cpu& System::CpuCore(std::size_t core_index) const {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return *impl->cpu_cores[core_index];
|
||||
}
|
||||
|
||||
ExclusiveMonitor& System::Monitor() {
|
||||
return *impl->cpu_exclusive_monitor;
|
||||
}
|
||||
|
||||
@@ -156,6 +156,9 @@ public:
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
Cpu& CpuCore(std::size_t core_index);
|
||||
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
const Cpu& CpuCore(std::size_t core_index) const;
|
||||
|
||||
/// Gets the exclusive monitor
|
||||
ExclusiveMonitor& Monitor();
|
||||
|
||||
@@ -172,13 +175,16 @@ public:
|
||||
const VideoCore::RendererBase& Renderer() const;
|
||||
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::size_t core_index);
|
||||
Kernel::Scheduler& Scheduler(std::size_t core_index);
|
||||
|
||||
/// Provides a reference to the current process
|
||||
Kernel::SharedPtr<Kernel::Process>& CurrentProcess();
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
|
||||
|
||||
/// Provides a constant reference to the current process.
|
||||
const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const;
|
||||
/// Provides a pointer to the current process
|
||||
Kernel::Process* CurrentProcess();
|
||||
|
||||
/// Provides a constant pointer to the current process.
|
||||
const Kernel::Process* CurrentProcess() const;
|
||||
|
||||
/// Provides a reference to the kernel instance.
|
||||
Kernel::KernelCore& Kernel();
|
||||
@@ -246,7 +252,7 @@ inline TelemetrySession& Telemetry() {
|
||||
return System::GetInstance().TelemetrySession();
|
||||
}
|
||||
|
||||
inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
|
||||
inline Kernel::Process* CurrentProcess() {
|
||||
return System::GetInstance().CurrentProcess();
|
||||
}
|
||||
|
||||
|
||||
@@ -49,10 +49,8 @@ bool CpuBarrier::Rendezvous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index)
|
||||
: cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} {
|
||||
|
||||
Cpu::Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_index{core_index} {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index);
|
||||
@@ -64,15 +62,15 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
arm_interface = std::make_unique<ARM_Unicorn>();
|
||||
}
|
||||
|
||||
scheduler = std::make_shared<Kernel::Scheduler>(*arm_interface);
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface);
|
||||
}
|
||||
|
||||
Cpu::~Cpu() = default;
|
||||
|
||||
std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
|
||||
std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
return std::make_shared<DynarmicExclusiveMonitor>(num_cores);
|
||||
return std::make_unique<DynarmicExclusiveMonitor>(num_cores);
|
||||
#else
|
||||
return nullptr; // TODO(merry): Passthrough exclusive monitor
|
||||
#endif
|
||||
@@ -83,7 +81,7 @@ std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_core
|
||||
|
||||
void Cpu::RunLoop(bool tight_loop) {
|
||||
// Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
|
||||
if (!cpu_barrier->Rendezvous()) {
|
||||
if (!cpu_barrier.Rendezvous()) {
|
||||
// If rendezvous failed, session has been killed
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@ private:
|
||||
|
||||
class Cpu {
|
||||
public:
|
||||
Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index);
|
||||
Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index);
|
||||
~Cpu();
|
||||
|
||||
void RunLoop(bool tight_loop = true);
|
||||
@@ -59,8 +58,12 @@ public:
|
||||
return *arm_interface;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler() const {
|
||||
return scheduler;
|
||||
Kernel::Scheduler& Scheduler() {
|
||||
return *scheduler;
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& Scheduler() const {
|
||||
return *scheduler;
|
||||
}
|
||||
|
||||
bool IsMainCore() const {
|
||||
@@ -71,14 +74,14 @@ public:
|
||||
return core_index;
|
||||
}
|
||||
|
||||
static std::shared_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
|
||||
static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
|
||||
|
||||
private:
|
||||
void Reschedule();
|
||||
|
||||
std::unique_ptr<ARM_Interface> arm_interface;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::shared_ptr<Kernel::Scheduler> scheduler;
|
||||
CpuBarrier& cpu_barrier;
|
||||
std::unique_ptr<Kernel::Scheduler> scheduler;
|
||||
|
||||
std::atomic<bool> reschedule_pending = false;
|
||||
std::size_t core_index;
|
||||
|
||||
@@ -4,23 +4,56 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <locale>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <mbedtls/bignum.h>
|
||||
#include <mbedtls/cipher.h>
|
||||
#include <mbedtls/cmac.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
|
||||
|
||||
using namespace Common;
|
||||
|
||||
const std::array<SHA256Hash, 2> eticket_source_hashes{
|
||||
"B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source
|
||||
"E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source
|
||||
};
|
||||
|
||||
const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
|
||||
{{S128KeyType::Master, 0}, "master_key_"},
|
||||
{{S128KeyType::Package1, 0}, "package1_key_"},
|
||||
{{S128KeyType::Package2, 0}, "package2_key_"},
|
||||
{{S128KeyType::Titlekek, 0}, "titlekek_"},
|
||||
{{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"},
|
||||
{{S128KeyType::Keyblob, 0}, "keyblob_key_"},
|
||||
{{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
|
||||
};
|
||||
|
||||
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
|
||||
Key128 out{};
|
||||
|
||||
@@ -37,57 +70,136 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K
|
||||
return out;
|
||||
}
|
||||
|
||||
Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source) {
|
||||
AESCipher<Key128> sbk_cipher(sbk, Mode::ECB);
|
||||
AESCipher<Key128> tsec_cipher(tsec, Mode::ECB);
|
||||
tsec_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
|
||||
sbk_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
|
||||
return source;
|
||||
}
|
||||
|
||||
Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source) {
|
||||
Key128 master_root;
|
||||
std::memcpy(master_root.data(), keyblob.data(), sizeof(Key128));
|
||||
|
||||
AESCipher<Key128> master_cipher(master_root, Mode::ECB);
|
||||
|
||||
Key128 master{};
|
||||
master_cipher.Transcode(master_source.data(), master_source.size(), master.data(), Op::Decrypt);
|
||||
return master;
|
||||
}
|
||||
|
||||
std::array<u8, 144> DecryptKeyblob(const std::array<u8, 176>& encrypted_keyblob,
|
||||
const Key128& key) {
|
||||
std::array<u8, 0x90> keyblob;
|
||||
AESCipher<Key128> cipher(key, Mode::CTR);
|
||||
cipher.SetIV(std::vector<u8>(encrypted_keyblob.data() + 0x10, encrypted_keyblob.data() + 0x20));
|
||||
cipher.Transcode(encrypted_keyblob.data() + 0x20, keyblob.size(), keyblob.data(), Op::Decrypt);
|
||||
return keyblob;
|
||||
}
|
||||
|
||||
void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
|
||||
const auto kek_generation_source =
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
|
||||
const auto key_generation_source =
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
|
||||
|
||||
if (HasKey(S128KeyType::Master, crypto_revision)) {
|
||||
for (auto kak_type :
|
||||
{KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) {
|
||||
if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(kak_type))) {
|
||||
const auto source =
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(kak_type));
|
||||
const auto kek =
|
||||
GenerateKeyEncryptionKey(source, GetKey(S128KeyType::Master, crypto_revision),
|
||||
kek_generation_source, key_generation_source);
|
||||
SetKey(S128KeyType::KeyArea, kek, crypto_revision, static_cast<u64>(kak_type));
|
||||
}
|
||||
}
|
||||
|
||||
AESCipher<Key128> master_cipher(GetKey(S128KeyType::Master, crypto_revision), Mode::ECB);
|
||||
for (auto key_type : {SourceKeyType::Titlekek, SourceKeyType::Package2}) {
|
||||
if (HasKey(S128KeyType::Source, static_cast<u64>(key_type))) {
|
||||
Key128 key{};
|
||||
master_cipher.Transcode(
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(key_type)).data(), key.size(),
|
||||
key.data(), Op::Decrypt);
|
||||
SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek
|
||||
: S128KeyType::Package2,
|
||||
key, crypto_revision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
|
||||
AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
|
||||
Key128 mac_key{};
|
||||
mac_cipher.Transcode(mac_source.data(), mac_key.size(), mac_key.data(), Op::Decrypt);
|
||||
return mac_key;
|
||||
}
|
||||
|
||||
boost::optional<Key128> DeriveSDSeed() {
|
||||
const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/8000000000000043",
|
||||
"rb+");
|
||||
if (!save_43.IsOpen())
|
||||
return boost::none;
|
||||
|
||||
const FileUtil::IOFile sd_private(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
|
||||
if (!sd_private.IsOpen())
|
||||
return boost::none;
|
||||
|
||||
sd_private.Seek(0, SEEK_SET);
|
||||
std::array<u8, 0x10> private_seed{};
|
||||
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != 0x10)
|
||||
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
std::array<u8, 0x10> buffer{};
|
||||
std::size_t offset = 0;
|
||||
for (; offset + 0x10 < save_43.GetSize(); ++offset) {
|
||||
save_43.Seek(offset, SEEK_SET);
|
||||
if (!save_43.Seek(offset, SEEK_SET)) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
save_43.ReadBytes(buffer.data(), buffer.size());
|
||||
if (buffer == private_seed)
|
||||
if (buffer == private_seed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset + 0x10 >= save_43.GetSize())
|
||||
if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
Key128 seed{};
|
||||
save_43.Seek(offset + 0x10, SEEK_SET);
|
||||
save_43.ReadBytes(seed.data(), seed.size());
|
||||
if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
|
||||
return boost::none;
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys) {
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK)))
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) {
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)))
|
||||
return Loader::ResultStatus::ErrorMissingSDKEKSource;
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration)))
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)))
|
||||
return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)))
|
||||
return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;
|
||||
|
||||
const auto sd_kek_source =
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK));
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek));
|
||||
const auto aes_kek_gen =
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration));
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
|
||||
const auto aes_key_gen =
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
|
||||
const auto master_00 = keys.GetKey(S128KeyType::Master);
|
||||
const auto sd_kek =
|
||||
GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen);
|
||||
keys.SetKey(S128KeyType::SDKek, sd_kek);
|
||||
|
||||
if (!keys.HasKey(S128KeyType::SDSeed))
|
||||
return Loader::ResultStatus::ErrorMissingSDSeed;
|
||||
@@ -118,9 +230,147 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManag
|
||||
return source; ///< Return unaltered source to satisfy output requirement.
|
||||
});
|
||||
|
||||
keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast<u64>(SDKeyType::Save));
|
||||
keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast<u64>(SDKeyType::NCA));
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
|
||||
if (!ticket_save.IsOpen())
|
||||
return {};
|
||||
|
||||
std::vector<u8> buffer(ticket_save.GetSize());
|
||||
if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TicketRaw> out;
|
||||
u32 magic{};
|
||||
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
|
||||
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
|
||||
buffer[offset + 3] == 0x0) {
|
||||
out.emplace_back();
|
||||
auto& next = out.back();
|
||||
std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
|
||||
offset += next.size();
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t size>
|
||||
static std::array<u8, size> operator^(const std::array<u8, size>& lhs,
|
||||
const std::array<u8, size>& rhs) {
|
||||
std::array<u8, size> out{};
|
||||
std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), std::bit_xor<>());
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t target_size, size_t in_size>
|
||||
static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) {
|
||||
// Avoids truncation overflow within the loop below.
|
||||
static_assert(target_size <= 0xFF);
|
||||
|
||||
std::array<u8, in_size + 4> seed_exp{};
|
||||
std::memcpy(seed_exp.data(), seed.data(), in_size);
|
||||
|
||||
std::vector<u8> out;
|
||||
size_t i = 0;
|
||||
while (out.size() < target_size) {
|
||||
out.resize(out.size() + 0x20);
|
||||
seed_exp[in_size + 3] = static_cast<u8>(i);
|
||||
mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0);
|
||||
++i;
|
||||
}
|
||||
|
||||
std::array<u8, target_size> target;
|
||||
std::memcpy(target.data(), out.data(), target_size);
|
||||
return target;
|
||||
}
|
||||
|
||||
template <size_t size>
|
||||
static boost::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
|
||||
u64 offset = 0;
|
||||
for (size_t i = 0x20; i < data.size() - 0x10; ++i) {
|
||||
if (data[i] == 0x1) {
|
||||
offset = i + 1;
|
||||
break;
|
||||
} else if (data[i] != 0x0) {
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
const RSAKeyPair<2048>& key) {
|
||||
u32 cert_authority;
|
||||
std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
|
||||
if (cert_authority == 0)
|
||||
return boost::none;
|
||||
if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
|
||||
LOG_INFO(Crypto,
|
||||
"Attempting to parse ticket with non-standard certificate authority {:08X}.",
|
||||
cert_authority);
|
||||
}
|
||||
|
||||
Key128 rights_id;
|
||||
std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
|
||||
|
||||
if (rights_id == Key128{})
|
||||
return boost::none;
|
||||
|
||||
Key128 key_temp{};
|
||||
|
||||
if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) {
|
||||
std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
|
||||
return std::make_pair(rights_id, key_temp);
|
||||
}
|
||||
|
||||
mbedtls_mpi D; // RSA Private Exponent
|
||||
mbedtls_mpi N; // RSA Modulus
|
||||
mbedtls_mpi S; // Input
|
||||
mbedtls_mpi M; // Output
|
||||
|
||||
mbedtls_mpi_init(&D);
|
||||
mbedtls_mpi_init(&N);
|
||||
mbedtls_mpi_init(&S);
|
||||
mbedtls_mpi_init(&M);
|
||||
|
||||
mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
|
||||
mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
|
||||
mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100);
|
||||
|
||||
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
|
||||
|
||||
std::array<u8, 0x100> rsa_step;
|
||||
mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size());
|
||||
|
||||
u8 m_0 = rsa_step[0];
|
||||
std::array<u8, 0x20> m_1;
|
||||
std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size());
|
||||
std::array<u8, 0xDF> m_2;
|
||||
std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
|
||||
|
||||
if (m_0 != 0)
|
||||
return boost::none;
|
||||
|
||||
m_1 = m_1 ^ MGF1<0x20>(m_2);
|
||||
m_2 = m_2 ^ MGF1<0xDF>(m_1);
|
||||
|
||||
const auto offset = FindTicketOffset(m_2);
|
||||
if (offset == boost::none)
|
||||
return boost::none;
|
||||
ASSERT(offset.get() > 0);
|
||||
|
||||
std::memcpy(key_temp.data(), m_2.data() + offset.get(), key_temp.size());
|
||||
|
||||
return std::make_pair(rights_id, key_temp);
|
||||
}
|
||||
|
||||
KeyManager::KeyManager() {
|
||||
// Initialize keys
|
||||
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
|
||||
@@ -137,6 +387,15 @@ KeyManager::KeyManager() {
|
||||
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false);
|
||||
}
|
||||
|
||||
static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
|
||||
if (base.size() < begin + length)
|
||||
return false;
|
||||
return std::all_of(base.begin() + begin, base.begin() + begin + length,
|
||||
[](u8 c) { return std::isdigit(c); });
|
||||
}
|
||||
|
||||
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
@@ -158,6 +417,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
|
||||
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
|
||||
|
||||
if (out[0].compare(0, 1, "#") == 0)
|
||||
continue;
|
||||
|
||||
if (is_title_keys) {
|
||||
auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
|
||||
u128 rights_id{};
|
||||
@@ -174,6 +436,50 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
const auto index = s256_file_id.at(out[0]);
|
||||
Key256 key = Common::HexStringToArray<32>(out[1]);
|
||||
s256_keys[{index.type, index.field1, index.field2}] = key;
|
||||
} else if (out[0].compare(0, 8, "keyblob_") == 0 &&
|
||||
out[0].compare(0, 9, "keyblob_k") != 0) {
|
||||
if (!ValidCryptoRevisionString(out[0], 8, 2))
|
||||
continue;
|
||||
|
||||
const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
|
||||
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
|
||||
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
|
||||
if (!ValidCryptoRevisionString(out[0], 18, 2))
|
||||
continue;
|
||||
|
||||
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
|
||||
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
|
||||
} else {
|
||||
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
|
||||
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
|
||||
continue;
|
||||
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
|
||||
const auto index =
|
||||
std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
|
||||
const auto sub = kv.first.second;
|
||||
if (sub == 0) {
|
||||
s128_keys[{kv.first.first, index, 0}] =
|
||||
Common::HexStringToArray<16>(out[1]);
|
||||
} else {
|
||||
s128_keys[{kv.first.first, kv.first.second, index}] =
|
||||
Common::HexStringToArray<16>(out[1]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<const char*, 3> kak_names = {
|
||||
"key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_"};
|
||||
for (size_t j = 0; j < kak_names.size(); ++j) {
|
||||
const auto& match = kak_names[j];
|
||||
if (out[0].compare(0, std::strlen(match), match) == 0) {
|
||||
const auto index =
|
||||
std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16);
|
||||
s128_keys[{S128KeyType::KeyArea, index, j}] =
|
||||
Common::HexStringToArray<16>(out[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,6 +493,28 @@ void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string&
|
||||
LoadFromFile(dir2 + DIR_SEP + filename, title);
|
||||
}
|
||||
|
||||
bool KeyManager::BaseDeriveNecessary() const {
|
||||
const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) {
|
||||
return !HasKey(key_type, index1, index2);
|
||||
};
|
||||
|
||||
if (check_key_existence(S256KeyType::Header))
|
||||
return true;
|
||||
|
||||
for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
|
||||
if (check_key_existence(S128KeyType::Master, i) ||
|
||||
check_key_existence(S128KeyType::KeyArea, i,
|
||||
static_cast<u64>(KeyAreaKeyType::Application)) ||
|
||||
check_key_existence(S128KeyType::KeyArea, i, static_cast<u64>(KeyAreaKeyType::Ocean)) ||
|
||||
check_key_existence(S128KeyType::KeyArea, i,
|
||||
static_cast<u64>(KeyAreaKeyType::System)) ||
|
||||
check_key_existence(S128KeyType::Titlekek, i))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {
|
||||
return s128_keys.find({id, field1, field2}) != s128_keys.end();
|
||||
}
|
||||
@@ -207,13 +535,30 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
|
||||
return s256_keys.at({id, field1, field2});
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
|
||||
Key256 KeyManager::GetBISKey(u8 partition_id) const {
|
||||
Key256 out{};
|
||||
|
||||
for (const auto& bis_type : {BISKeyType::Crypto, BISKeyType::Tweak}) {
|
||||
if (HasKey(S128KeyType::BIS, partition_id, static_cast<u64>(bis_type))) {
|
||||
std::memcpy(
|
||||
out.data() + sizeof(Key128) * static_cast<u64>(bis_type),
|
||||
s128_keys.at({S128KeyType::BIS, partition_id, static_cast<u64>(bis_type)}).data(),
|
||||
sizeof(Key128));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
|
||||
const std::array<u8, Size>& key) {
|
||||
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
|
||||
std::string filename = "title.keys_autogenerated";
|
||||
if (!title_key)
|
||||
if (category == KeyCategory::Standard)
|
||||
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
|
||||
else if (category == KeyCategory::Console)
|
||||
filename = "console.keys_autogenerated";
|
||||
const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename);
|
||||
FileUtil::CreateFullPath(yuzu_keys_dir + DIR_SEP + filename);
|
||||
std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app);
|
||||
@@ -227,7 +572,7 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
|
||||
}
|
||||
|
||||
file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key));
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title);
|
||||
}
|
||||
|
||||
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
@@ -237,8 +582,15 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
Key128 rights_id;
|
||||
std::memcpy(rights_id.data(), &field2, sizeof(u64));
|
||||
std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
|
||||
WriteKeyToFile(true, Common::HexArrayToString(rights_id), key);
|
||||
WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key);
|
||||
}
|
||||
|
||||
auto category = KeyCategory::Standard;
|
||||
if (id == S128KeyType::Keyblob || id == S128KeyType::KeyblobMAC || id == S128KeyType::TSEC ||
|
||||
id == S128KeyType::SecureBoot || id == S128KeyType::SDSeed || id == S128KeyType::BIS) {
|
||||
category = KeyCategory::Console;
|
||||
}
|
||||
|
||||
const auto iter2 = std::find_if(
|
||||
s128_file_id.begin(), s128_file_id.end(),
|
||||
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
|
||||
@@ -246,7 +598,30 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
std::tie(id, field1, field2);
|
||||
});
|
||||
if (iter2 != s128_file_id.end())
|
||||
WriteKeyToFile(false, iter2->first, key);
|
||||
WriteKeyToFile(category, iter2->first, key);
|
||||
|
||||
// Variable cases
|
||||
if (id == S128KeyType::KeyArea) {
|
||||
static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}",
|
||||
"key_area_key_ocean_{:02X}",
|
||||
"key_area_key_system_{:02X}"};
|
||||
WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key);
|
||||
} else if (id == S128KeyType::Master) {
|
||||
WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Package1) {
|
||||
WriteKeyToFile(category, fmt::format("package1_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Package2) {
|
||||
WriteKeyToFile(category, fmt::format("package2_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Titlekek) {
|
||||
WriteKeyToFile(category, fmt::format("titlekek_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Keyblob) {
|
||||
WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::KeyblobMAC) {
|
||||
WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Source && field1 == static_cast<u64>(SourceKeyType::Keyblob)) {
|
||||
WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key);
|
||||
}
|
||||
|
||||
s128_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
@@ -260,7 +635,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
|
||||
std::tie(id, field1, field2);
|
||||
});
|
||||
if (iter != s256_file_id.end())
|
||||
WriteKeyToFile(false, iter->first, key);
|
||||
WriteKeyToFile(KeyCategory::Standard, iter->first, key);
|
||||
s256_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
@@ -290,59 +665,388 @@ void KeyManager::DeriveSDSeedLazy() {
|
||||
SetKey(S128KeyType::SDSeed, res.get());
|
||||
}
|
||||
|
||||
static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
|
||||
Key128 out{};
|
||||
|
||||
mbedtls_cipher_cmac(mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB), key.data(),
|
||||
key.size() * 8, source, size, out.data());
|
||||
return out;
|
||||
}
|
||||
|
||||
void KeyManager::DeriveBase() {
|
||||
if (!BaseDeriveNecessary())
|
||||
return;
|
||||
|
||||
if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC))
|
||||
return;
|
||||
|
||||
const auto has_bis = [this](u64 id) {
|
||||
return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) &&
|
||||
HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Tweak));
|
||||
};
|
||||
|
||||
const auto copy_bis = [this](u64 id_from, u64 id_to) {
|
||||
SetKey(S128KeyType::BIS,
|
||||
GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Crypto)), id_to,
|
||||
static_cast<u64>(BISKeyType::Crypto));
|
||||
|
||||
SetKey(S128KeyType::BIS,
|
||||
GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Tweak)), id_to,
|
||||
static_cast<u64>(BISKeyType::Tweak));
|
||||
};
|
||||
|
||||
if (has_bis(2) && !has_bis(3))
|
||||
copy_bis(2, 3);
|
||||
else if (has_bis(3) && !has_bis(2))
|
||||
copy_bis(3, 2);
|
||||
|
||||
std::bitset<32> revisions(0xFFFFFFFF);
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i) ||
|
||||
encrypted_keyblobs[i] == std::array<u8, 0xB0>{}) {
|
||||
revisions.reset(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!revisions.any())
|
||||
return;
|
||||
|
||||
const auto sbk = GetKey(S128KeyType::SecureBoot);
|
||||
const auto tsec = GetKey(S128KeyType::TSEC);
|
||||
const auto master_source = GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master));
|
||||
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!revisions[i])
|
||||
continue;
|
||||
|
||||
// Derive keyblob key
|
||||
const auto key = DeriveKeyblobKey(
|
||||
sbk, tsec, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i));
|
||||
|
||||
SetKey(S128KeyType::Keyblob, key, i);
|
||||
|
||||
// Derive keyblob MAC key
|
||||
if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)))
|
||||
continue;
|
||||
|
||||
const auto mac_key = DeriveKeyblobMACKey(
|
||||
key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)));
|
||||
SetKey(S128KeyType::KeyblobMAC, mac_key, i);
|
||||
|
||||
Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key);
|
||||
if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0)
|
||||
continue;
|
||||
|
||||
// Decrypt keyblob
|
||||
if (keyblobs[i] == std::array<u8, 0x90>{}) {
|
||||
keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key);
|
||||
WriteKeyToFile<0x90>(KeyCategory::Console, fmt::format("keyblob_{:02X}", i),
|
||||
keyblobs[i]);
|
||||
}
|
||||
|
||||
Key128 package1;
|
||||
std::memcpy(package1.data(), keyblobs[i].data() + 0x80, sizeof(Key128));
|
||||
SetKey(S128KeyType::Package1, package1, i);
|
||||
|
||||
// Derive master key
|
||||
if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master))) {
|
||||
SetKey(S128KeyType::Master,
|
||||
DeriveMasterKey(keyblobs[i], GetKey(S128KeyType::Source,
|
||||
static_cast<u64>(SourceKeyType::Master))),
|
||||
i);
|
||||
}
|
||||
}
|
||||
|
||||
revisions.set();
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!HasKey(S128KeyType::Master, i))
|
||||
revisions.reset(i);
|
||||
}
|
||||
|
||||
if (!revisions.any())
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!revisions[i])
|
||||
continue;
|
||||
|
||||
// Derive general purpose keys
|
||||
DeriveGeneralPurposeKeys(i);
|
||||
}
|
||||
|
||||
if (HasKey(S128KeyType::Master, 0) &&
|
||||
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)) &&
|
||||
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)) &&
|
||||
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)) &&
|
||||
HasKey(S256KeyType::HeaderSource)) {
|
||||
const auto header_kek = GenerateKeyEncryptionKey(
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)),
|
||||
GetKey(S128KeyType::Master, 0),
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)),
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)));
|
||||
SetKey(S128KeyType::HeaderKek, header_kek);
|
||||
|
||||
AESCipher<Key128> header_cipher(header_kek, Mode::ECB);
|
||||
Key256 out = GetKey(S256KeyType::HeaderSource);
|
||||
header_cipher.Transcode(out.data(), out.size(), out.data(), Op::Decrypt);
|
||||
SetKey(S256KeyType::Header, out);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyManager::DeriveETicket(PartitionDataManager& data) {
|
||||
// ETicket keys
|
||||
const auto es = Service::FileSystem::GetUnionContents()->GetEntry(
|
||||
0x0100000000000033, FileSys::ContentRecordType::Program);
|
||||
|
||||
if (es == nullptr)
|
||||
return;
|
||||
|
||||
const auto exefs = es->GetExeFS();
|
||||
if (exefs == nullptr)
|
||||
return;
|
||||
|
||||
const auto main = exefs->GetFile("main");
|
||||
if (main == nullptr)
|
||||
return;
|
||||
|
||||
const auto bytes = main->ReadAllBytes();
|
||||
|
||||
const auto eticket_kek = FindKeyFromHex16(bytes, eticket_source_hashes[0]);
|
||||
const auto eticket_kekek = FindKeyFromHex16(bytes, eticket_source_hashes[1]);
|
||||
|
||||
const auto seed3 = data.GetRSAKekSeed3();
|
||||
const auto mask0 = data.GetRSAKekMask0();
|
||||
|
||||
if (eticket_kek != Key128{})
|
||||
SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek));
|
||||
if (eticket_kekek != Key128{}) {
|
||||
SetKey(S128KeyType::Source, eticket_kekek,
|
||||
static_cast<size_t>(SourceKeyType::ETicketKekek));
|
||||
}
|
||||
if (seed3 != Key128{})
|
||||
SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3));
|
||||
if (mask0 != Key128{})
|
||||
SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0));
|
||||
if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} ||
|
||||
mask0 == Key128{}) {
|
||||
return;
|
||||
}
|
||||
|
||||
Key128 rsa_oaep_kek{};
|
||||
std::transform(seed3.begin(), seed3.end(), mask0.begin(), rsa_oaep_kek.begin(),
|
||||
std::bit_xor<>());
|
||||
|
||||
if (rsa_oaep_kek == Key128{})
|
||||
return;
|
||||
|
||||
SetKey(S128KeyType::Source, rsa_oaep_kek,
|
||||
static_cast<u64>(SourceKeyType::RSAOaepKekGeneration));
|
||||
|
||||
Key128 temp_kek{};
|
||||
Key128 temp_kekek{};
|
||||
Key128 eticket_final{};
|
||||
|
||||
// Derive ETicket RSA Kek
|
||||
AESCipher<Key128> es_master(GetKey(S128KeyType::Master), Mode::ECB);
|
||||
es_master.Transcode(rsa_oaep_kek.data(), rsa_oaep_kek.size(), temp_kek.data(), Op::Decrypt);
|
||||
AESCipher<Key128> es_kekek(temp_kek, Mode::ECB);
|
||||
es_kekek.Transcode(eticket_kekek.data(), eticket_kekek.size(), temp_kekek.data(), Op::Decrypt);
|
||||
AESCipher<Key128> es_kek(temp_kekek, Mode::ECB);
|
||||
es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt);
|
||||
|
||||
if (eticket_final == Key128{})
|
||||
return;
|
||||
|
||||
SetKey(S128KeyType::ETicketRSAKek, eticket_final);
|
||||
|
||||
// Titlekeys
|
||||
data.DecryptProdInfo(GetBISKey(0));
|
||||
|
||||
const auto eticket_extended_kek = data.GetETicketExtendedKek();
|
||||
|
||||
std::vector<u8> extended_iv(0x10);
|
||||
std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
|
||||
std::array<u8, 0x230> extended_dec{};
|
||||
AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
|
||||
rsa_1.SetIV(extended_iv);
|
||||
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
|
||||
extended_dec.data(), Op::Decrypt);
|
||||
|
||||
RSAKeyPair<2048> rsa_key{};
|
||||
std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
|
||||
std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
|
||||
std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
|
||||
|
||||
const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/80000000000000e1",
|
||||
"rb+");
|
||||
const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/80000000000000e2",
|
||||
"rb+");
|
||||
|
||||
const auto blob2 = GetTicketblob(save2);
|
||||
auto res = GetTicketblob(save1);
|
||||
res.insert(res.end(), blob2.begin(), blob2.end());
|
||||
|
||||
for (const auto& raw : res) {
|
||||
const auto pair = ParseTicket(raw, rsa_key);
|
||||
if (pair == boost::none)
|
||||
continue;
|
||||
const auto& [rid, key] = pair.value();
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rid.data(), rid.size());
|
||||
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
if (key == Key128{})
|
||||
return;
|
||||
SetKey(id, key, field1, field2);
|
||||
}
|
||||
|
||||
void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) {
|
||||
if (key == Key256{})
|
||||
return;
|
||||
SetKey(id, key, field1, field2);
|
||||
}
|
||||
|
||||
void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
|
||||
if (!BaseDeriveNecessary())
|
||||
return;
|
||||
|
||||
if (!data.HasBoot0())
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) {
|
||||
if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{})
|
||||
continue;
|
||||
encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
|
||||
WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
|
||||
encrypted_keyblobs[i]);
|
||||
}
|
||||
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(),
|
||||
static_cast<u64>(SourceKeyType::Package2));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetAESKekGenerationSource(),
|
||||
static_cast<u64>(SourceKeyType::AESKekGeneration));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetTitlekekSource(),
|
||||
static_cast<u64>(SourceKeyType::Titlekek));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetMasterKeySource(),
|
||||
static_cast<u64>(SourceKeyType::Master));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyblobMACKeySource(),
|
||||
static_cast<u64>(SourceKeyType::KeyblobMAC));
|
||||
|
||||
for (size_t i = 0; i < PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH; ++i) {
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyblobKeySource(i),
|
||||
static_cast<u64>(SourceKeyType::Keyblob), i);
|
||||
}
|
||||
|
||||
if (data.HasFuses())
|
||||
SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey());
|
||||
|
||||
DeriveBase();
|
||||
|
||||
Key128 latest_master{};
|
||||
for (s8 i = 0x1F; i >= 0; --i) {
|
||||
if (GetKey(S128KeyType::Master, static_cast<u8>(i)) != Key128{}) {
|
||||
latest_master = GetKey(S128KeyType::Master, static_cast<u8>(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto masters = data.GetTZMasterKeys(latest_master);
|
||||
for (size_t i = 0; i < masters.size(); ++i) {
|
||||
if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i))
|
||||
SetKey(S128KeyType::Master, masters[i], i);
|
||||
}
|
||||
|
||||
DeriveBase();
|
||||
|
||||
if (!data.HasPackage2())
|
||||
return;
|
||||
|
||||
std::array<Key128, 0x20> package2_keys{};
|
||||
for (size_t i = 0; i < package2_keys.size(); ++i) {
|
||||
if (HasKey(S128KeyType::Package2, i))
|
||||
package2_keys[i] = GetKey(S128KeyType::Package2, i);
|
||||
}
|
||||
data.DecryptPackage2(package2_keys, Package2Type::NormalMain);
|
||||
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyApplicationSource(),
|
||||
static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::Application));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyOceanSource(),
|
||||
static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::Ocean));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeySystemSource(),
|
||||
static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::System));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetSDKekSource(),
|
||||
static_cast<u64>(SourceKeyType::SDKek));
|
||||
SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDSaveKeySource(),
|
||||
static_cast<u64>(SDKeyType::Save));
|
||||
SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDNCAKeySource(),
|
||||
static_cast<u64>(SDKeyType::NCA));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetHeaderKekSource(),
|
||||
static_cast<u64>(SourceKeyType::HeaderKek));
|
||||
SetKeyWrapped(S256KeyType::HeaderSource, data.GetHeaderKeySource());
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetAESKeyGenerationSource(),
|
||||
static_cast<u64>(SourceKeyType::AESKeyGeneration));
|
||||
|
||||
DeriveBase();
|
||||
}
|
||||
|
||||
const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
|
||||
{"master_key_00", {S128KeyType::Master, 0, 0}},
|
||||
{"master_key_01", {S128KeyType::Master, 1, 0}},
|
||||
{"master_key_02", {S128KeyType::Master, 2, 0}},
|
||||
{"master_key_03", {S128KeyType::Master, 3, 0}},
|
||||
{"master_key_04", {S128KeyType::Master, 4, 0}},
|
||||
{"package1_key_00", {S128KeyType::Package1, 0, 0}},
|
||||
{"package1_key_01", {S128KeyType::Package1, 1, 0}},
|
||||
{"package1_key_02", {S128KeyType::Package1, 2, 0}},
|
||||
{"package1_key_03", {S128KeyType::Package1, 3, 0}},
|
||||
{"package1_key_04", {S128KeyType::Package1, 4, 0}},
|
||||
{"package2_key_00", {S128KeyType::Package2, 0, 0}},
|
||||
{"package2_key_01", {S128KeyType::Package2, 1, 0}},
|
||||
{"package2_key_02", {S128KeyType::Package2, 2, 0}},
|
||||
{"package2_key_03", {S128KeyType::Package2, 3, 0}},
|
||||
{"package2_key_04", {S128KeyType::Package2, 4, 0}},
|
||||
{"titlekek_00", {S128KeyType::Titlekek, 0, 0}},
|
||||
{"titlekek_01", {S128KeyType::Titlekek, 1, 0}},
|
||||
{"titlekek_02", {S128KeyType::Titlekek, 2, 0}},
|
||||
{"titlekek_03", {S128KeyType::Titlekek, 3, 0}},
|
||||
{"titlekek_04", {S128KeyType::Titlekek, 4, 0}},
|
||||
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
|
||||
{"key_area_key_application_00",
|
||||
{S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_01",
|
||||
{S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_02",
|
||||
{S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_03",
|
||||
{S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_04",
|
||||
{S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK), 0}},
|
||||
{"eticket_rsa_kek_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
|
||||
{"eticket_rsa_kekek_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
|
||||
{"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
|
||||
{"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
|
||||
{"rsa_oaep_kek_generation_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
|
||||
{"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
|
||||
{"aes_kek_generation_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}},
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
|
||||
{"aes_key_generation_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
|
||||
{"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
|
||||
{"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
|
||||
{"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
|
||||
{"key_area_key_application_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_ocean_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_system_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
|
||||
{"keyblob_mac_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)}},
|
||||
{"tsec_key", {S128KeyType::TSEC, 0, 0}},
|
||||
{"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
|
||||
{"sd_seed", {S128KeyType::SDSeed, 0, 0}},
|
||||
{"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
|
||||
{"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
|
||||
{"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
|
||||
{"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
|
||||
{"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
|
||||
{"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
|
||||
{"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
|
||||
{"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
|
||||
{"header_kek", {S128KeyType::HeaderKek, 0, 0}},
|
||||
{"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
|
||||
};
|
||||
|
||||
const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
|
||||
{"header_key", {S256KeyType::Header, 0, 0}},
|
||||
{"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
|
||||
{"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
|
||||
{"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
|
||||
{"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
|
||||
{"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
|
||||
};
|
||||
} // namespace Core::Crypto
|
||||
|
||||
@@ -5,11 +5,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace FileUtil {
|
||||
class IOFile;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
@@ -22,13 +29,30 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
|
||||
using Key128 = std::array<u8, 0x10>;
|
||||
using Key256 = std::array<u8, 0x20>;
|
||||
using SHA256Hash = std::array<u8, 0x20>;
|
||||
using TicketRaw = std::array<u8, 0x400>;
|
||||
|
||||
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
|
||||
static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
|
||||
static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
|
||||
|
||||
template <size_t bit_size, size_t byte_size = (bit_size >> 3)>
|
||||
struct RSAKeyPair {
|
||||
std::array<u8, byte_size> encryption_key;
|
||||
std::array<u8, byte_size> decryption_key;
|
||||
std::array<u8, byte_size> modulus;
|
||||
std::array<u8, 4> exponent;
|
||||
};
|
||||
|
||||
enum class KeyCategory : u8 {
|
||||
Standard,
|
||||
Title,
|
||||
Console,
|
||||
};
|
||||
|
||||
enum class S256KeyType : u64 {
|
||||
Header, //
|
||||
SDKeySource, // f1=SDKeyType
|
||||
SDKey, // f1=SDKeyType
|
||||
Header, //
|
||||
SDKeySource, // f1=SDKeyType
|
||||
HeaderSource, //
|
||||
};
|
||||
|
||||
enum class S128KeyType : u64 {
|
||||
@@ -41,6 +65,14 @@ enum class S128KeyType : u64 {
|
||||
SDSeed, //
|
||||
Titlekey, // f1=rights id LSB f2=rights id MSB
|
||||
Source, // f1=source type, f2= sub id
|
||||
Keyblob, // f1=crypto revision
|
||||
KeyblobMAC, // f1=crypto revision
|
||||
TSEC, //
|
||||
SecureBoot, //
|
||||
BIS, // f1=partition (0-3), f2=type {crypt, tweak}
|
||||
HeaderKek, //
|
||||
SDKek, //
|
||||
RSAKek, //
|
||||
};
|
||||
|
||||
enum class KeyAreaKeyType : u8 {
|
||||
@@ -50,9 +82,19 @@ enum class KeyAreaKeyType : u8 {
|
||||
};
|
||||
|
||||
enum class SourceKeyType : u8 {
|
||||
SDKEK,
|
||||
AESKEKGeneration,
|
||||
AESKeyGeneration,
|
||||
SDKek, //
|
||||
AESKekGeneration, //
|
||||
AESKeyGeneration, //
|
||||
RSAOaepKekGeneration, //
|
||||
Master, //
|
||||
Keyblob, // f2=crypto revision
|
||||
KeyAreaKey, // f2=KeyAreaKeyType
|
||||
Titlekek, //
|
||||
Package2, //
|
||||
HeaderKek, //
|
||||
KeyblobMAC, //
|
||||
ETicketKek, //
|
||||
ETicketKekek, //
|
||||
};
|
||||
|
||||
enum class SDKeyType : u8 {
|
||||
@@ -60,6 +102,16 @@ enum class SDKeyType : u8 {
|
||||
NCA,
|
||||
};
|
||||
|
||||
enum class BISKeyType : u8 {
|
||||
Crypto,
|
||||
Tweak,
|
||||
};
|
||||
|
||||
enum class RSAKekType : u8 {
|
||||
Mask0,
|
||||
Seed3,
|
||||
};
|
||||
|
||||
template <typename KeyType>
|
||||
struct KeyIndex {
|
||||
KeyType type;
|
||||
@@ -91,6 +143,8 @@ public:
|
||||
Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
|
||||
Key256 GetBISKey(u8 partition_id) const;
|
||||
|
||||
void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
|
||||
void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
|
||||
|
||||
@@ -100,23 +154,51 @@ public:
|
||||
// 8*43 and the private file to exist.
|
||||
void DeriveSDSeedLazy();
|
||||
|
||||
bool BaseDeriveNecessary() const;
|
||||
void DeriveBase();
|
||||
void DeriveETicket(PartitionDataManager& data);
|
||||
|
||||
void PopulateFromPartitionData(PartitionDataManager& data);
|
||||
|
||||
private:
|
||||
boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
|
||||
std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
|
||||
std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
|
||||
|
||||
bool dev_mode;
|
||||
void LoadFromFile(const std::string& filename, bool is_title_keys);
|
||||
void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
|
||||
const std::string& filename, bool title);
|
||||
template <std::size_t Size>
|
||||
void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key);
|
||||
template <size_t Size>
|
||||
void WriteKeyToFile(KeyCategory category, std::string_view keyname,
|
||||
const std::array<u8, Size>& key);
|
||||
|
||||
void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
|
||||
|
||||
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
|
||||
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
|
||||
|
||||
static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
|
||||
static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
|
||||
};
|
||||
|
||||
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
|
||||
Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source);
|
||||
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source);
|
||||
Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source);
|
||||
std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblob,
|
||||
const Key128& key);
|
||||
|
||||
boost::optional<Key128> DeriveSDSeed();
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys);
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
|
||||
|
||||
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
|
||||
|
||||
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
|
||||
// 0x140-0x144 is zero)
|
||||
boost::optional<std::pair<Key128, Key128>> ParseTicket(
|
||||
const TicketRaw& ticket, const RSAKeyPair<2048>& eticket_extended_key);
|
||||
|
||||
} // namespace Core::Crypto
|
||||
|
||||
594
src/core/crypto/partition_data_manager.cpp
Normal file
594
src/core/crypto/partition_data_manager.cpp
Normal file
@@ -0,0 +1,594 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// NOTE TO FUTURE MAINTAINERS:
|
||||
// When a new version of switch cryptography is released,
|
||||
// hash the new keyblob source and master key and add the hashes to
|
||||
// the arrays below.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
#include "core/crypto/xts_encryption_layer.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
|
||||
using namespace Common;
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
struct Package2Header {
|
||||
std::array<u8, 0x100> signature;
|
||||
Key128 header_ctr;
|
||||
std::array<Key128, 4> section_ctr;
|
||||
u32_le magic;
|
||||
u32_le base_offset;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u8 version_max;
|
||||
u8 version_min;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
std::array<u32_le, 4> section_size;
|
||||
std::array<u32_le, 4> section_offset;
|
||||
std::array<SHA256Hash, 4> section_hash;
|
||||
};
|
||||
static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
|
||||
|
||||
struct INIHeader {
|
||||
u32_le magic;
|
||||
u32_le size;
|
||||
u32_le process_count;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
};
|
||||
static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
|
||||
|
||||
struct SectionHeader {
|
||||
u32_le offset;
|
||||
u32_le size_decompressed;
|
||||
u32_le size_compressed;
|
||||
u32_le attribute;
|
||||
};
|
||||
static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size.");
|
||||
|
||||
struct KIPHeader {
|
||||
u32_le magic;
|
||||
std::array<char, 12> name;
|
||||
u64_le title_id;
|
||||
u32_le category;
|
||||
u8 priority;
|
||||
u8 core;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u8 flags;
|
||||
std::array<SectionHeader, 6> sections;
|
||||
std::array<u32, 0x20> capabilities;
|
||||
};
|
||||
static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
|
||||
|
||||
const std::array<SHA256Hash, 0x10> source_hashes{
|
||||
"B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
|
||||
"7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
|
||||
"21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source
|
||||
"FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source
|
||||
"FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source
|
||||
"C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source
|
||||
"04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source
|
||||
"FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source
|
||||
"1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source
|
||||
"6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source
|
||||
"D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source
|
||||
"2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source
|
||||
"1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source
|
||||
"8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source
|
||||
"D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3
|
||||
"FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0
|
||||
};
|
||||
|
||||
const std::array<SHA256Hash, 0x20> keyblob_source_hashes{
|
||||
"8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00
|
||||
"2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01
|
||||
"61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02
|
||||
"8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03
|
||||
"95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04
|
||||
"3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F
|
||||
};
|
||||
|
||||
const std::array<SHA256Hash, 0x20> master_key_hashes{
|
||||
"0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00
|
||||
"4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01
|
||||
"79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02
|
||||
"4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03
|
||||
"75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04
|
||||
"EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
|
||||
};
|
||||
|
||||
static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) {
|
||||
const auto data_size = in.size() - 0xC;
|
||||
|
||||
u32 compressed_size{};
|
||||
u32 init_index{};
|
||||
u32 additional_size{};
|
||||
std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32));
|
||||
std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32));
|
||||
std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32));
|
||||
|
||||
std::vector<u8> out(in.size() + additional_size);
|
||||
|
||||
if (compressed_size == in.size())
|
||||
std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size);
|
||||
else
|
||||
std::memcpy(out.data(), in.data(), compressed_size);
|
||||
|
||||
auto index = in.size() - init_index;
|
||||
auto out_index = out.size();
|
||||
|
||||
while (out_index > 0) {
|
||||
--index;
|
||||
auto control = in[index];
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
if ((control & 0x80) > 0) {
|
||||
ASSERT(index >= 2);
|
||||
index -= 2;
|
||||
u64 segment_offset = in[index] | in[index + 1] << 8;
|
||||
u64 segment_size = ((segment_offset >> 12) & 0xF) + 3;
|
||||
segment_offset &= 0xFFF;
|
||||
segment_offset += 3;
|
||||
|
||||
if (out_index < segment_size)
|
||||
segment_size = out_index;
|
||||
|
||||
ASSERT(out_index >= segment_size);
|
||||
|
||||
out_index -= segment_size;
|
||||
|
||||
for (size_t j = 0; j < segment_size; ++j) {
|
||||
ASSERT(out_index + j + segment_offset < out.size());
|
||||
out[out_index + j] = out[out_index + j + segment_offset];
|
||||
}
|
||||
} else {
|
||||
ASSERT(out_index >= 1);
|
||||
--out_index;
|
||||
--index;
|
||||
out[out_index] = in[index];
|
||||
}
|
||||
|
||||
control <<= 1;
|
||||
if (out_index == 0)
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static u8 CalculateMaxKeyblobSourceHash() {
|
||||
for (s8 i = 0x1F; i >= 0; --i) {
|
||||
if (keyblob_source_hashes[i] != SHA256Hash{})
|
||||
return static_cast<u8>(i + 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const u8 PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH = CalculateMaxKeyblobSourceHash();
|
||||
|
||||
template <size_t key_size = 0x10>
|
||||
std::array<u8, key_size> FindKeyFromHex(const std::vector<u8>& binary,
|
||||
const std::array<u8, 0x20>& hash) {
|
||||
if (binary.size() < key_size)
|
||||
return {};
|
||||
|
||||
std::array<u8, 0x20> temp{};
|
||||
for (size_t i = 0; i < binary.size() - key_size; ++i) {
|
||||
mbedtls_sha256(binary.data() + i, key_size, temp.data(), 0);
|
||||
|
||||
if (temp != hash)
|
||||
continue;
|
||||
|
||||
std::array<u8, key_size> out{};
|
||||
std::memcpy(out.data(), binary.data() + i, key_size);
|
||||
return out;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::array<u8, 16> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 32> hash) {
|
||||
return FindKeyFromHex<0x10>(binary, hash);
|
||||
}
|
||||
|
||||
static std::array<Key128, 0x20> FindEncryptedMasterKeyFromHex(const std::vector<u8>& binary,
|
||||
const Key128& key) {
|
||||
if (binary.size() < 0x10)
|
||||
return {};
|
||||
|
||||
SHA256Hash temp{};
|
||||
Key128 dec_temp{};
|
||||
std::array<Key128, 0x20> out{};
|
||||
AESCipher<Key128> cipher(key, Mode::ECB);
|
||||
for (size_t i = 0; i < binary.size() - 0x10; ++i) {
|
||||
cipher.Transcode(binary.data() + i, dec_temp.size(), dec_temp.data(), Op::Decrypt);
|
||||
mbedtls_sha256(dec_temp.data(), dec_temp.size(), temp.data(), 0);
|
||||
|
||||
for (size_t k = 0; k < out.size(); ++k) {
|
||||
if (temp == master_key_hashes[k]) {
|
||||
out[k] = dec_temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir,
|
||||
const std::string& name) {
|
||||
auto upper = name;
|
||||
std::transform(upper.begin(), upper.end(), upper.begin(), [](u8 c) { return std::toupper(c); });
|
||||
for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) {
|
||||
if (dir->GetFile(fname) != nullptr)
|
||||
return dir->GetFile(fname);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PartitionDataManager::PartitionDataManager(const FileSys::VirtualDir& sysdata_dir)
|
||||
: boot0(FindFileInDirWithNames(sysdata_dir, "BOOT0")),
|
||||
fuses(FindFileInDirWithNames(sysdata_dir, "fuses")),
|
||||
kfuses(FindFileInDirWithNames(sysdata_dir, "kfuses")),
|
||||
package2({
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-1-Normal-Main"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-2-Normal-Sub"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-3-SafeMode-Main"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-4-SafeMode-Sub"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-5-Repair-Main"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-6-Repair-Sub"),
|
||||
}),
|
||||
prodinfo(FindFileInDirWithNames(sysdata_dir, "PRODINFO")),
|
||||
secure_monitor(FindFileInDirWithNames(sysdata_dir, "secmon")),
|
||||
package1_decrypted(FindFileInDirWithNames(sysdata_dir, "pkg1_decr")),
|
||||
secure_monitor_bytes(secure_monitor == nullptr ? std::vector<u8>{}
|
||||
: secure_monitor->ReadAllBytes()),
|
||||
package1_decrypted_bytes(package1_decrypted == nullptr ? std::vector<u8>{}
|
||||
: package1_decrypted->ReadAllBytes()) {
|
||||
}
|
||||
|
||||
PartitionDataManager::~PartitionDataManager() = default;
|
||||
|
||||
bool PartitionDataManager::HasBoot0() const {
|
||||
return boot0 != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetBoot0Raw() const {
|
||||
return boot0;
|
||||
}
|
||||
|
||||
PartitionDataManager::EncryptedKeyBlob PartitionDataManager::GetEncryptedKeyblob(
|
||||
std::size_t index) const {
|
||||
if (HasBoot0() && index < NUM_ENCRYPTED_KEYBLOBS)
|
||||
return GetEncryptedKeyblobs()[index];
|
||||
return {};
|
||||
}
|
||||
|
||||
PartitionDataManager::EncryptedKeyBlobs PartitionDataManager::GetEncryptedKeyblobs() const {
|
||||
if (!HasBoot0())
|
||||
return {};
|
||||
|
||||
EncryptedKeyBlobs out{};
|
||||
for (size_t i = 0; i < out.size(); ++i)
|
||||
boot0->Read(out[i].data(), out[i].size(), 0x180000 + i * 0x200);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<u8> PartitionDataManager::GetSecureMonitor() const {
|
||||
return secure_monitor_bytes;
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetPackage2KeySource() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[2]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetAESKekGenerationSource() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[3]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetTitlekekSource() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[5]);
|
||||
}
|
||||
|
||||
std::array<std::array<u8, 16>, 32> PartitionDataManager::GetTZMasterKeys(
|
||||
std::array<u8, 0x10> master_key) const {
|
||||
return FindEncryptedMasterKeyFromHex(secure_monitor_bytes, master_key);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetRSAKekSeed3() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[14]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetRSAKekMask0() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[15]);
|
||||
}
|
||||
|
||||
std::vector<u8> PartitionDataManager::GetPackage1Decrypted() const {
|
||||
return package1_decrypted_bytes;
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetMasterKeySource() const {
|
||||
return FindKeyFromHex(package1_decrypted_bytes, source_hashes[1]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyblobMACKeySource() const {
|
||||
return FindKeyFromHex(package1_decrypted_bytes, source_hashes[0]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyblobKeySource(std::size_t revision) const {
|
||||
if (keyblob_source_hashes[revision] == SHA256Hash{}) {
|
||||
LOG_WARNING(Crypto,
|
||||
"No keyblob source hash for crypto revision {:02X}! Cannot derive keys...",
|
||||
revision);
|
||||
}
|
||||
return FindKeyFromHex(package1_decrypted_bytes, keyblob_source_hashes[revision]);
|
||||
}
|
||||
|
||||
bool PartitionDataManager::HasFuses() const {
|
||||
return fuses != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetFusesRaw() const {
|
||||
return fuses;
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetSecureBootKey() const {
|
||||
if (!HasFuses())
|
||||
return {};
|
||||
Key128 out{};
|
||||
fuses->Read(out.data(), out.size(), 0xA4);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool PartitionDataManager::HasKFuses() const {
|
||||
return kfuses != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetKFusesRaw() const {
|
||||
return kfuses;
|
||||
}
|
||||
|
||||
bool PartitionDataManager::HasPackage2(Package2Type type) const {
|
||||
return package2.at(static_cast<size_t>(type)) != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) const {
|
||||
return package2.at(static_cast<size_t>(type));
|
||||
}
|
||||
|
||||
bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) {
|
||||
|
||||
const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end());
|
||||
Package2Header temp = header;
|
||||
AESCipher<Key128> cipher(key, Mode::CTR);
|
||||
cipher.SetIV(iv);
|
||||
cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr,
|
||||
Op::Decrypt);
|
||||
if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) {
|
||||
header = temp;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& package2_keys,
|
||||
Package2Type type) {
|
||||
FileSys::VirtualFile file = std::make_shared<FileSys::OffsetVfsFile>(
|
||||
package2[static_cast<size_t>(type)],
|
||||
package2[static_cast<size_t>(type)]->GetSize() - 0x4000, 0x4000);
|
||||
|
||||
Package2Header header{};
|
||||
if (file->ReadObject(&header) != sizeof(Package2Header))
|
||||
return;
|
||||
|
||||
std::size_t revision = 0xFF;
|
||||
if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) {
|
||||
for (std::size_t i = 0; i < package2_keys.size(); ++i) {
|
||||
if (AttemptDecrypt(package2_keys[i], header)) {
|
||||
revision = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (header.magic != Common::MakeMagic('P', 'K', '2', '1'))
|
||||
return;
|
||||
|
||||
const auto a = std::make_shared<FileSys::OffsetVfsFile>(
|
||||
file, header.section_size[1], header.section_size[0] + sizeof(Package2Header));
|
||||
|
||||
auto c = a->ReadAllBytes();
|
||||
|
||||
AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR);
|
||||
cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()});
|
||||
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
|
||||
|
||||
INIHeader ini;
|
||||
std::memcpy(&ini, c.data(), sizeof(INIHeader));
|
||||
if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1'))
|
||||
return;
|
||||
|
||||
u64 offset = sizeof(INIHeader);
|
||||
for (size_t i = 0; i < ini.process_count; ++i) {
|
||||
KIPHeader kip;
|
||||
std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader));
|
||||
if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1'))
|
||||
return;
|
||||
|
||||
const auto name =
|
||||
Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size());
|
||||
|
||||
if (name != "FS" && name != "spl") {
|
||||
offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
|
||||
kip.sections[1].size_compressed + kip.sections[2].size_compressed;
|
||||
continue;
|
||||
}
|
||||
|
||||
const u64 initial_offset = sizeof(KIPHeader) + offset;
|
||||
const auto text_begin = c.cbegin() + initial_offset;
|
||||
const auto text_end = text_begin + kip.sections[0].size_compressed;
|
||||
const std::vector<u8> text = DecompressBLZ({text_begin, text_end});
|
||||
|
||||
const auto rodata_end = text_end + kip.sections[1].size_compressed;
|
||||
const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end});
|
||||
|
||||
const auto data_end = rodata_end + kip.sections[2].size_compressed;
|
||||
const std::vector<u8> data = DecompressBLZ({rodata_end, data_end});
|
||||
|
||||
std::vector<u8> out;
|
||||
out.reserve(text.size() + rodata.size() + data.size());
|
||||
out.insert(out.end(), text.begin(), text.end());
|
||||
out.insert(out.end(), rodata.begin(), rodata.end());
|
||||
out.insert(out.end(), data.begin(), data.end());
|
||||
|
||||
offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
|
||||
kip.sections[1].size_compressed + kip.sections[2].size_compressed;
|
||||
|
||||
if (name == "FS")
|
||||
package2_fs[static_cast<size_t>(type)] = std::move(out);
|
||||
else if (name == "spl")
|
||||
package2_spl[static_cast<size_t>(type)] = std::move(out);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<u8>& PartitionDataManager::GetPackage2FSDecompressed(Package2Type type) const {
|
||||
return package2_fs.at(static_cast<size_t>(type));
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyApplicationSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[6]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyOceanSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[7]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeySystemSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[8]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetSDKekSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[9]);
|
||||
}
|
||||
|
||||
std::array<u8, 32> PartitionDataManager::GetSDSaveKeySource(Package2Type type) const {
|
||||
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[10]);
|
||||
}
|
||||
|
||||
std::array<u8, 32> PartitionDataManager::GetSDNCAKeySource(Package2Type type) const {
|
||||
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[11]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetHeaderKekSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[12]);
|
||||
}
|
||||
|
||||
std::array<u8, 32> PartitionDataManager::GetHeaderKeySource(Package2Type type) const {
|
||||
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[13]);
|
||||
}
|
||||
|
||||
const std::vector<u8>& PartitionDataManager::GetPackage2SPLDecompressed(Package2Type type) const {
|
||||
return package2_spl.at(static_cast<size_t>(type));
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetAESKeyGenerationSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_spl.at(static_cast<size_t>(type)), source_hashes[4]);
|
||||
}
|
||||
|
||||
bool PartitionDataManager::HasProdInfo() const {
|
||||
return prodinfo != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetProdInfoRaw() const {
|
||||
return prodinfo;
|
||||
}
|
||||
|
||||
void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) {
|
||||
if (prodinfo == nullptr)
|
||||
return;
|
||||
|
||||
prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key);
|
||||
}
|
||||
|
||||
std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const {
|
||||
std::array<u8, 0x240> out{};
|
||||
if (prodinfo_decrypted != nullptr)
|
||||
prodinfo_decrypted->Read(out.data(), out.size(), 0x3890);
|
||||
return out;
|
||||
}
|
||||
} // namespace Core::Crypto
|
||||
109
src/core/crypto/partition_data_manager.h
Normal file
109
src/core/crypto/partition_data_manager.h
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
enum class Package2Type {
|
||||
NormalMain,
|
||||
NormalSub,
|
||||
SafeModeMain,
|
||||
SafeModeSub,
|
||||
RepairMain,
|
||||
RepairSub,
|
||||
};
|
||||
|
||||
class PartitionDataManager {
|
||||
public:
|
||||
static const u8 MAX_KEYBLOB_SOURCE_HASH;
|
||||
static constexpr std::size_t NUM_ENCRYPTED_KEYBLOBS = 32;
|
||||
static constexpr std::size_t ENCRYPTED_KEYBLOB_SIZE = 0xB0;
|
||||
|
||||
using EncryptedKeyBlob = std::array<u8, ENCRYPTED_KEYBLOB_SIZE>;
|
||||
using EncryptedKeyBlobs = std::array<EncryptedKeyBlob, NUM_ENCRYPTED_KEYBLOBS>;
|
||||
|
||||
explicit PartitionDataManager(const FileSys::VirtualDir& sysdata_dir);
|
||||
~PartitionDataManager();
|
||||
|
||||
// BOOT0
|
||||
bool HasBoot0() const;
|
||||
FileSys::VirtualFile GetBoot0Raw() const;
|
||||
EncryptedKeyBlob GetEncryptedKeyblob(std::size_t index) const;
|
||||
EncryptedKeyBlobs GetEncryptedKeyblobs() const;
|
||||
std::vector<u8> GetSecureMonitor() const;
|
||||
std::array<u8, 0x10> GetPackage2KeySource() const;
|
||||
std::array<u8, 0x10> GetAESKekGenerationSource() const;
|
||||
std::array<u8, 0x10> GetTitlekekSource() const;
|
||||
std::array<std::array<u8, 0x10>, 0x20> GetTZMasterKeys(std::array<u8, 0x10> master_key) const;
|
||||
std::array<u8, 0x10> GetRSAKekSeed3() const;
|
||||
std::array<u8, 0x10> GetRSAKekMask0() const;
|
||||
std::vector<u8> GetPackage1Decrypted() const;
|
||||
std::array<u8, 0x10> GetMasterKeySource() const;
|
||||
std::array<u8, 0x10> GetKeyblobMACKeySource() const;
|
||||
std::array<u8, 0x10> GetKeyblobKeySource(std::size_t revision) const;
|
||||
|
||||
// Fuses
|
||||
bool HasFuses() const;
|
||||
FileSys::VirtualFile GetFusesRaw() const;
|
||||
std::array<u8, 0x10> GetSecureBootKey() const;
|
||||
|
||||
// K-Fuses
|
||||
bool HasKFuses() const;
|
||||
FileSys::VirtualFile GetKFusesRaw() const;
|
||||
|
||||
// Package2
|
||||
bool HasPackage2(Package2Type type = Package2Type::NormalMain) const;
|
||||
FileSys::VirtualFile GetPackage2Raw(Package2Type type = Package2Type::NormalMain) const;
|
||||
void DecryptPackage2(const std::array<std::array<u8, 16>, 0x20>& package2_keys,
|
||||
Package2Type type);
|
||||
const std::vector<u8>& GetPackage2FSDecompressed(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetKeyAreaKeyApplicationSource(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetKeyAreaKeyOceanSource(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetKeyAreaKeySystemSource(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetSDKekSource(Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x20> GetSDSaveKeySource(Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x20> GetSDNCAKeySource(Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetHeaderKekSource(Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x20> GetHeaderKeySource(Package2Type type = Package2Type::NormalMain) const;
|
||||
const std::vector<u8>& GetPackage2SPLDecompressed(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetAESKeyGenerationSource(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
|
||||
// PRODINFO
|
||||
bool HasProdInfo() const;
|
||||
FileSys::VirtualFile GetProdInfoRaw() const;
|
||||
void DecryptProdInfo(std::array<u8, 0x20> bis_key);
|
||||
std::array<u8, 0x240> GetETicketExtendedKek() const;
|
||||
|
||||
private:
|
||||
FileSys::VirtualFile boot0;
|
||||
FileSys::VirtualFile fuses;
|
||||
FileSys::VirtualFile kfuses;
|
||||
std::array<FileSys::VirtualFile, 6> package2;
|
||||
FileSys::VirtualFile prodinfo;
|
||||
FileSys::VirtualFile secure_monitor;
|
||||
FileSys::VirtualFile package1_decrypted;
|
||||
|
||||
// Processed
|
||||
std::array<FileSys::VirtualFile, 6> package2_decrypted;
|
||||
FileSys::VirtualFile prodinfo_decrypted;
|
||||
std::vector<u8> secure_monitor_bytes;
|
||||
std::vector<u8> package1_decrypted_bytes;
|
||||
std::array<std::vector<u8>, 6> package2_fs;
|
||||
std::array<std::vector<u8>, 6> package2_spl;
|
||||
};
|
||||
|
||||
std::array<u8, 0x10> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 0x20> hash);
|
||||
|
||||
} // namespace Core::Crypto
|
||||
@@ -10,19 +10,19 @@ namespace FileSys {
|
||||
|
||||
BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
|
||||
: nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
|
||||
sysnand_cache(std::make_shared<RegisteredCache>(
|
||||
sysnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
|
||||
usrnand_cache(std::make_shared<RegisteredCache>(
|
||||
usrnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
|
||||
|
||||
BISFactory::~BISFactory() = default;
|
||||
|
||||
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache;
|
||||
RegisteredCache* BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache.get();
|
||||
}
|
||||
|
||||
std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache;
|
||||
RegisteredCache* BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache.get();
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
|
||||
|
||||
@@ -20,8 +20,8 @@ public:
|
||||
explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
|
||||
~BISFactory();
|
||||
|
||||
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
|
||||
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
|
||||
RegisteredCache* GetSystemNANDContents() const;
|
||||
RegisteredCache* GetUserNANDContents() const;
|
||||
|
||||
VirtualDir GetModificationLoadRoot(u64 title_id) const;
|
||||
|
||||
@@ -29,8 +29,8 @@ private:
|
||||
VirtualDir nand_root;
|
||||
VirtualDir load_root;
|
||||
|
||||
std::shared_ptr<RegisteredCache> sysnand_cache;
|
||||
std::shared_ptr<RegisteredCache> usrnand_cache;
|
||||
std::unique_ptr<RegisteredCache> sysnand_cache;
|
||||
std::unique_ptr<RegisteredCache> usrnand_cache;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -20,7 +20,9 @@ namespace FileSys {
|
||||
|
||||
constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"};
|
||||
|
||||
XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
|
||||
XCI::XCI(VirtualFile file_)
|
||||
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
|
||||
partitions(0x4) {
|
||||
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
|
||||
status = Loader::ResultStatus::ErrorBadXCIHeader;
|
||||
return;
|
||||
@@ -120,14 +122,16 @@ u64 XCI::GetProgramTitleID() const {
|
||||
return secure_partition->GetProgramTitleID();
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> XCI::GetProgramNCA() const {
|
||||
return program;
|
||||
bool XCI::HasProgramNCA() const {
|
||||
return program != nullptr;
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetProgramNCAFile() const {
|
||||
if (GetProgramNCA() == nullptr)
|
||||
if (!HasProgramNCA()) {
|
||||
return nullptr;
|
||||
return GetProgramNCA()->GetBaseFile();
|
||||
}
|
||||
|
||||
return program->GetBaseFile();
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
|
||||
u64 GetProgramTitleID() const;
|
||||
|
||||
std::shared_ptr<NCA> GetProgramNCA() const;
|
||||
bool HasProgramNCA() const;
|
||||
VirtualFile GetProgramNCAFile() const;
|
||||
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
|
||||
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
|
||||
|
||||
@@ -97,11 +97,288 @@ union NCASectionHeader {
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
|
||||
|
||||
bool IsValidNCA(const NCAHeader& header) {
|
||||
static bool IsValidNCA(const NCAHeader& header) {
|
||||
// TODO(DarkLordZach): Add NCA2/NCA0 support.
|
||||
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
|
||||
: file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
|
||||
if (file == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorNullFile;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sizeof(NCAHeader) != file->ReadObject(&header)) {
|
||||
LOG_ERROR(Loader, "File reader errored out during header read.");
|
||||
status = Loader::ResultStatus::ErrorBadNCAHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HandlePotentialHeaderDecryption()) {
|
||||
return;
|
||||
}
|
||||
|
||||
has_rights_id = std::any_of(header.rights_id.begin(), header.rights_id.end(),
|
||||
[](char c) { return c != '\0'; });
|
||||
|
||||
const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
|
||||
is_update = std::any_of(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
|
||||
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
|
||||
});
|
||||
|
||||
if (!ReadSections(sections, bktr_base_ivfc_offset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
NCA::~NCA() = default;
|
||||
|
||||
bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) {
|
||||
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
|
||||
status = Loader::ResultStatus::ErrorNCA2;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
|
||||
status = Loader::ResultStatus::ErrorNCA0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NCA::HandlePotentialHeaderDecryption() {
|
||||
if (IsValidNCA(header)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CheckSupportedNCA(header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NCAHeader dec_header{};
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
if (IsValidNCA(dec_header)) {
|
||||
header = dec_header;
|
||||
encrypted = true;
|
||||
} else {
|
||||
if (!CheckSupportedNCA(dec_header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keys.HasKey(Core::Crypto::S256KeyType::Header)) {
|
||||
status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
|
||||
} else {
|
||||
status = Loader::ResultStatus::ErrorMissingHeaderKey;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const {
|
||||
const std::ptrdiff_t number_sections =
|
||||
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
|
||||
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
|
||||
|
||||
std::vector<NCASectionHeader> sections(number_sections);
|
||||
const auto length_sections = SECTION_HEADER_SIZE * number_sections;
|
||||
|
||||
if (encrypted) {
|
||||
auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
} else {
|
||||
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
|
||||
for (std::size_t i = 0; i < sections.size(); ++i) {
|
||||
const auto& section = sections[i];
|
||||
|
||||
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
|
||||
return false;
|
||||
}
|
||||
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
if (!ReadPFS0Section(section, header.section_tables[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
|
||||
u64 bktr_base_ivfc_offset) {
|
||||
const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
|
||||
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const std::size_t romfs_offset = base_offset + ivfc_offset;
|
||||
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
|
||||
auto dec = Decrypt(section, raw, romfs_offset);
|
||||
|
||||
if (dec == nullptr) {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return false;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
|
||||
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
|
||||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
|
||||
status = Loader::ResultStatus::ErrorBadBKTRHeader;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (section.bktr.relocation.offset + section.bktr.relocation.size !=
|
||||
section.bktr.subsection.offset) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
|
||||
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
RelocationBlock relocation_block{};
|
||||
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBlock;
|
||||
return false;
|
||||
}
|
||||
SubsectionBlock subsection_block{};
|
||||
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBlock;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucketRaw> relocation_buckets_raw(
|
||||
(section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
|
||||
if (dec->ReadBytes(relocation_buckets_raw.data(),
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock),
|
||||
section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBuckets;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<SubsectionBucketRaw> subsection_buckets_raw(
|
||||
(section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw));
|
||||
if (dec->ReadBytes(subsection_buckets_raw.data(),
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock),
|
||||
section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
|
||||
std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
|
||||
relocation_buckets.begin(), &ConvertRelocationBucketRaw);
|
||||
std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
|
||||
std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
|
||||
subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
|
||||
|
||||
u32 ctr_low;
|
||||
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
|
||||
subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
|
||||
subsection_buckets.back().entries.push_back({size, {0}, 0});
|
||||
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
if (encrypted) {
|
||||
if (has_rights_id) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
key = GetTitlekey();
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bktr_base_romfs == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto bktr = std::make_shared<BKTR>(
|
||||
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
|
||||
relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
|
||||
encrypted ? key.get() : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
|
||||
section.raw.section_ctr);
|
||||
|
||||
// BKTR applies to entire IVFC, so make an offset version to level 6
|
||||
files.push_back(std::make_shared<OffsetVfsFile>(
|
||||
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
|
||||
} else {
|
||||
files.push_back(std::move(dec));
|
||||
}
|
||||
|
||||
romfs = files.back();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
|
||||
const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
|
||||
section.pfs0.pfs0_header_offset;
|
||||
const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
|
||||
|
||||
auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
|
||||
if (dec != nullptr) {
|
||||
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
|
||||
|
||||
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
|
||||
dirs.push_back(std::move(npfs));
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
} else {
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return false;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u8 NCA::GetCryptoRevision() const {
|
||||
u8 master_key_id = header.crypto_type;
|
||||
if (header.crypto_type_2 > master_key_id)
|
||||
@@ -133,7 +410,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
|
||||
static_cast<u8>(type));
|
||||
u128 out_128{};
|
||||
memcpy(out_128.data(), out.data(), 16);
|
||||
LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
|
||||
LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
|
||||
master_key_id, header.key_index, out_128[1], out_128[0]);
|
||||
|
||||
return out;
|
||||
@@ -167,7 +444,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
|
||||
return titlekey;
|
||||
}
|
||||
|
||||
VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) {
|
||||
VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
|
||||
if (!encrypted)
|
||||
return in;
|
||||
|
||||
@@ -215,256 +492,6 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
|
||||
}
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
|
||||
: file(std::move(file_)),
|
||||
bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
|
||||
if (file == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorNullFile;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sizeof(NCAHeader) != file->ReadObject(&header)) {
|
||||
LOG_ERROR(Loader, "File reader errored out during header read.");
|
||||
status = Loader::ResultStatus::ErrorBadNCAHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
encrypted = false;
|
||||
|
||||
if (!IsValidNCA(header)) {
|
||||
if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
|
||||
status = Loader::ResultStatus::ErrorNCA2;
|
||||
return;
|
||||
}
|
||||
if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
|
||||
status = Loader::ResultStatus::ErrorNCA0;
|
||||
return;
|
||||
}
|
||||
|
||||
NCAHeader dec_header{};
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
if (IsValidNCA(dec_header)) {
|
||||
header = dec_header;
|
||||
encrypted = true;
|
||||
} else {
|
||||
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
|
||||
status = Loader::ResultStatus::ErrorNCA2;
|
||||
return;
|
||||
}
|
||||
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
|
||||
status = Loader::ResultStatus::ErrorNCA0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keys.HasKey(Core::Crypto::S256KeyType::Header))
|
||||
status = Loader::ResultStatus::ErrorMissingHeaderKey;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(),
|
||||
[](char c) { return c == '\0'; }) != header.rights_id.end();
|
||||
|
||||
const std::ptrdiff_t number_sections =
|
||||
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
|
||||
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
|
||||
|
||||
std::vector<NCASectionHeader> sections(number_sections);
|
||||
const auto length_sections = SECTION_HEADER_SIZE * number_sections;
|
||||
|
||||
if (encrypted) {
|
||||
auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
} else {
|
||||
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
|
||||
}
|
||||
|
||||
is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
|
||||
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
|
||||
}) != sections.end();
|
||||
ivfc_offset = 0;
|
||||
|
||||
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
|
||||
auto section = sections[i];
|
||||
|
||||
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
const std::size_t base_offset =
|
||||
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
|
||||
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const std::size_t romfs_offset = base_offset + ivfc_offset;
|
||||
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
|
||||
auto dec = Decrypt(section, raw, romfs_offset);
|
||||
|
||||
if (dec == nullptr) {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
|
||||
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
|
||||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
|
||||
status = Loader::ResultStatus::ErrorBadBKTRHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.bktr.relocation.offset + section.bktr.relocation.size !=
|
||||
section.bktr.subsection.offset) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 size =
|
||||
MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
|
||||
header.section_tables[i].media_offset);
|
||||
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
RelocationBlock relocation_block{};
|
||||
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBlock;
|
||||
return;
|
||||
}
|
||||
SubsectionBlock subsection_block{};
|
||||
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBlock;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucketRaw> relocation_buckets_raw(
|
||||
(section.bktr.relocation.size - sizeof(RelocationBlock)) /
|
||||
sizeof(RelocationBucketRaw));
|
||||
if (dec->ReadBytes(relocation_buckets_raw.data(),
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock),
|
||||
section.bktr.relocation.offset + sizeof(RelocationBlock) -
|
||||
offset) !=
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBuckets;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<SubsectionBucketRaw> subsection_buckets_raw(
|
||||
(section.bktr.subsection.size - sizeof(SubsectionBlock)) /
|
||||
sizeof(SubsectionBucketRaw));
|
||||
if (dec->ReadBytes(subsection_buckets_raw.data(),
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock),
|
||||
section.bktr.subsection.offset + sizeof(SubsectionBlock) -
|
||||
offset) !=
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
|
||||
std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
|
||||
relocation_buckets.begin(), &ConvertRelocationBucketRaw);
|
||||
std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
|
||||
std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
|
||||
subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
|
||||
|
||||
u32 ctr_low;
|
||||
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
|
||||
subsection_buckets.back().entries.push_back(
|
||||
{section.bktr.relocation.offset, {0}, ctr_low});
|
||||
subsection_buckets.back().entries.push_back({size, {0}, 0});
|
||||
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
if (encrypted) {
|
||||
if (has_rights_id) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
key = GetTitlekey();
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bktr_base_romfs == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
|
||||
return;
|
||||
}
|
||||
|
||||
auto bktr = std::make_shared<BKTR>(
|
||||
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
|
||||
relocation_block, relocation_buckets, subsection_block, subsection_buckets,
|
||||
encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset,
|
||||
bktr_base_ivfc_offset, section.raw.section_ctr);
|
||||
|
||||
// BKTR applies to entire IVFC, so make an offset version to level 6
|
||||
|
||||
files.push_back(std::make_shared<OffsetVfsFile>(
|
||||
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
|
||||
romfs = files.back();
|
||||
} else {
|
||||
files.push_back(std::move(dec));
|
||||
romfs = files.back();
|
||||
}
|
||||
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
|
||||
MEDIA_OFFSET_MULTIPLIER) +
|
||||
section.pfs0.pfs0_header_offset;
|
||||
u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
|
||||
header.section_tables[i].media_offset);
|
||||
auto dec =
|
||||
Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
|
||||
if (dec != nullptr) {
|
||||
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
|
||||
|
||||
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
|
||||
dirs.push_back(std::move(npfs));
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
} else {
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
NCA::~NCA() = default;
|
||||
|
||||
Loader::ResultStatus NCA::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -73,8 +73,6 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
|
||||
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
|
||||
}
|
||||
|
||||
bool IsValidNCA(const NCAHeader& header);
|
||||
|
||||
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
|
||||
// After construction, use GetStatus to determine if the file is valid and ready to be used.
|
||||
class NCA : public ReadOnlyVfsDirectory {
|
||||
@@ -106,10 +104,19 @@ protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
bool CheckSupportedNCA(const NCAHeader& header);
|
||||
bool HandlePotentialHeaderDecryption();
|
||||
|
||||
std::vector<NCASectionHeader> ReadSectionHeaders() const;
|
||||
bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
|
||||
bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
|
||||
u64 bktr_base_ivfc_offset);
|
||||
bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
|
||||
|
||||
u8 GetCryptoRevision() const;
|
||||
boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
|
||||
boost::optional<Core::Crypto::Key128> GetTitlekey();
|
||||
VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
|
||||
VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
|
||||
|
||||
std::vector<VirtualDir> dirs;
|
||||
std::vector<VirtualFile> files;
|
||||
@@ -118,15 +125,15 @@ private:
|
||||
VirtualDir exefs = nullptr;
|
||||
VirtualFile file;
|
||||
VirtualFile bktr_base_romfs;
|
||||
u64 ivfc_offset;
|
||||
u64 ivfc_offset = 0;
|
||||
|
||||
NCAHeader header{};
|
||||
bool has_rights_id{};
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
bool encrypted;
|
||||
bool is_update;
|
||||
bool encrypted = false;
|
||||
bool is_update = false;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
|
||||
@@ -17,11 +17,13 @@ const std::array<const char*, 15> LANGUAGE_NAMES = {
|
||||
};
|
||||
|
||||
std::string LanguageEntry::GetApplicationName() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), 0x200);
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(),
|
||||
application_name.size());
|
||||
}
|
||||
|
||||
std::string LanguageEntry::GetDeveloperName() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(),
|
||||
developer_name.size());
|
||||
}
|
||||
|
||||
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
|
||||
@@ -56,7 +58,12 @@ u64 NACP::GetTitleId() const {
|
||||
return raw->title_id;
|
||||
}
|
||||
|
||||
u64 NACP::GetDLCBaseTitleId() const {
|
||||
return raw->dlc_base_title_id;
|
||||
}
|
||||
|
||||
std::string NACP::GetVersionString() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), 0x10);
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(),
|
||||
raw->version_string.size());
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
std::string GetApplicationName(Language language = Language::Default) const;
|
||||
std::string GetDeveloperName(Language language = Language::Default) const;
|
||||
u64 GetTitleId() const;
|
||||
u64 GetDLCBaseTitleId() const;
|
||||
std::string GetVersionString() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/fsmitm_romfsbuild.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
@@ -123,7 +124,7 @@ static u64 romfs_get_hash_table_count(u64 num_entries) {
|
||||
return count;
|
||||
}
|
||||
|
||||
void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
|
||||
void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent) {
|
||||
std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
|
||||
|
||||
@@ -144,6 +145,9 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
|
||||
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
|
||||
child->path = parent->path + "/" + kv.first;
|
||||
|
||||
if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
|
||||
continue;
|
||||
|
||||
// Sanity check on path_len
|
||||
ASSERT(child->path_len < FS_MAX_PATH);
|
||||
|
||||
@@ -157,11 +161,24 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
|
||||
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
|
||||
child->path = parent->path + "/" + kv.first;
|
||||
|
||||
if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
|
||||
continue;
|
||||
|
||||
// Sanity check on path_len
|
||||
ASSERT(child->path_len < FS_MAX_PATH);
|
||||
|
||||
child->source = root_romfs->GetFileRelative(child->path);
|
||||
|
||||
if (ext != nullptr) {
|
||||
const auto ips = ext->GetFileRelative(child->path + ".ips");
|
||||
|
||||
if (ips != nullptr) {
|
||||
auto patched = PatchIPS(child->source, ips);
|
||||
if (patched != nullptr)
|
||||
child->source = std::move(patched);
|
||||
}
|
||||
}
|
||||
|
||||
child->size = child->source->GetSize();
|
||||
|
||||
AddFile(parent, child);
|
||||
@@ -169,7 +186,7 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
|
||||
}
|
||||
|
||||
for (auto& child : child_dirs) {
|
||||
this->VisitDirectory(root_romfs, child);
|
||||
this->VisitDirectory(root_romfs, ext, child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,14 +225,15 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare
|
||||
return true;
|
||||
}
|
||||
|
||||
RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) {
|
||||
RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_)
|
||||
: base(std::move(base_)), ext(std::move(ext_)) {
|
||||
root = std::make_shared<RomFSBuildDirectoryContext>();
|
||||
root->path = "\0";
|
||||
directories.emplace(root->path, root);
|
||||
num_dirs = 1;
|
||||
dir_table_size = 0x18;
|
||||
|
||||
VisitDirectory(base, root);
|
||||
VisitDirectory(base, ext, root);
|
||||
}
|
||||
|
||||
RomFSBuildContext::~RomFSBuildContext() = default;
|
||||
|
||||
@@ -40,7 +40,7 @@ struct RomFSFileEntry;
|
||||
|
||||
class RomFSBuildContext {
|
||||
public:
|
||||
explicit RomFSBuildContext(VirtualDir base);
|
||||
explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr);
|
||||
~RomFSBuildContext();
|
||||
|
||||
// This finalizes the context.
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
|
||||
private:
|
||||
VirtualDir base;
|
||||
VirtualDir ext;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> root;
|
||||
std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories;
|
||||
std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files;
|
||||
@@ -59,7 +60,8 @@ private:
|
||||
u64 file_hash_table_size = 0;
|
||||
u64 file_partition_size = 0;
|
||||
|
||||
void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent);
|
||||
void VisitDirectory(VirtualDir filesys, VirtualDir ext,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent);
|
||||
|
||||
bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
@@ -15,16 +23,48 @@ enum class IPSFileType {
|
||||
Error,
|
||||
};
|
||||
|
||||
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{
|
||||
{"\\a", "\a"},
|
||||
{"\\b", "\b"},
|
||||
{"\\f", "\f"},
|
||||
{"\\n", "\n"},
|
||||
{"\\r", "\r"},
|
||||
{"\\t", "\t"},
|
||||
{"\\v", "\v"},
|
||||
{"\\\\", "\\"},
|
||||
{"\\\'", "\'"},
|
||||
{"\\\"", "\""},
|
||||
{"\\\?", "\?"},
|
||||
}};
|
||||
|
||||
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
|
||||
if (magic.size() != 5)
|
||||
if (magic.size() != 5) {
|
||||
return IPSFileType::Error;
|
||||
if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'})
|
||||
}
|
||||
|
||||
constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}};
|
||||
if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) {
|
||||
return IPSFileType::IPS;
|
||||
if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'})
|
||||
}
|
||||
|
||||
constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}};
|
||||
if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) {
|
||||
return IPSFileType::IPS32;
|
||||
}
|
||||
|
||||
return IPSFileType::Error;
|
||||
}
|
||||
|
||||
static bool IsEOF(IPSFileType type, const std::vector<u8>& data) {
|
||||
constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}};
|
||||
if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
|
||||
return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
|
||||
}
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
if (in == nullptr || ips == nullptr)
|
||||
return nullptr;
|
||||
@@ -39,8 +79,7 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
u64 offset = 5; // After header
|
||||
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
|
||||
offset += temp.size();
|
||||
if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} ||
|
||||
type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) {
|
||||
if (IsEOF(type, temp)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -68,21 +107,232 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
return nullptr;
|
||||
|
||||
if (real_offset + rle_size > in_data.size())
|
||||
rle_size = in_data.size() - real_offset;
|
||||
rle_size = static_cast<u16>(in_data.size() - real_offset);
|
||||
std::memset(in_data.data() + real_offset, data.get(), rle_size);
|
||||
} else { // Standard Patch
|
||||
auto read = data_size;
|
||||
if (real_offset + read > in_data.size())
|
||||
read = in_data.size() - real_offset;
|
||||
read = static_cast<u16>(in_data.size() - real_offset);
|
||||
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
|
||||
return nullptr;
|
||||
offset += data_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'})
|
||||
if (!IsEOF(type, temp)) {
|
||||
return nullptr;
|
||||
return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
|
||||
in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
struct IPSwitchCompiler::IPSwitchPatch {
|
||||
std::string name;
|
||||
bool enabled;
|
||||
std::map<u32, std::vector<u8>> records;
|
||||
};
|
||||
|
||||
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
|
||||
Parse();
|
||||
}
|
||||
|
||||
IPSwitchCompiler::~IPSwitchCompiler() = default;
|
||||
|
||||
std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
|
||||
return nso_build_id;
|
||||
}
|
||||
|
||||
bool IPSwitchCompiler::IsValid() const {
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool StartsWith(std::string_view base, std::string_view check) {
|
||||
return base.size() >= check.size() && base.substr(0, check.size()) == check;
|
||||
}
|
||||
|
||||
static std::string EscapeStringSequences(std::string in) {
|
||||
for (const auto& seq : ESCAPE_CHARACTER_MAP) {
|
||||
for (auto index = in.find(seq.first); index != std::string::npos;
|
||||
index = in.find(seq.first, index)) {
|
||||
in.replace(index, std::strlen(seq.first), seq.second);
|
||||
index += std::strlen(seq.second);
|
||||
}
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
void IPSwitchCompiler::ParseFlag(const std::string& line) {
|
||||
if (StartsWith(line, "@flag offset_shift ")) {
|
||||
// Offset Shift Flag
|
||||
offset_shift = std::stoll(line.substr(19), nullptr, 0);
|
||||
} else if (StartsWith(line, "@little-endian")) {
|
||||
// Set values to read as little endian
|
||||
is_little_endian = true;
|
||||
} else if (StartsWith(line, "@big-endian")) {
|
||||
// Set values to read as big endian
|
||||
is_little_endian = false;
|
||||
} else if (StartsWith(line, "@flag print_values")) {
|
||||
// Force printing of applied values
|
||||
print_values = true;
|
||||
}
|
||||
}
|
||||
|
||||
void IPSwitchCompiler::Parse() {
|
||||
const auto bytes = patch_text->ReadAllBytes();
|
||||
std::stringstream s;
|
||||
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string stream_line;
|
||||
while (std::getline(s, 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));
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
auto line = lines[i];
|
||||
|
||||
// Remove midline comments
|
||||
std::size_t comment_index = std::string::npos;
|
||||
bool within_string = false;
|
||||
for (std::size_t k = 0; k < line.size(); ++k) {
|
||||
if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
|
||||
within_string = !within_string;
|
||||
} else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
|
||||
comment_index = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!StartsWith(line, "//") && comment_index != std::string::npos) {
|
||||
last_comment = line.substr(comment_index + 2);
|
||||
line = line.substr(0, comment_index);
|
||||
}
|
||||
|
||||
if (StartsWith(line, "@stop")) {
|
||||
// Force stop
|
||||
break;
|
||||
} else if (StartsWith(line, "@nsobid-")) {
|
||||
// NSO Build ID Specifier
|
||||
auto raw_build_id = line.substr(8);
|
||||
if (raw_build_id.size() != 0x40)
|
||||
raw_build_id.resize(0x40, '0');
|
||||
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
|
||||
} else if (StartsWith(line, "#")) {
|
||||
// Mandatory Comment
|
||||
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
|
||||
patch_text->GetName(), line.substr(1));
|
||||
} else if (StartsWith(line, "//")) {
|
||||
// Normal Comment
|
||||
last_comment = line.substr(2);
|
||||
if (last_comment.find_first_not_of(' ') == std::string::npos)
|
||||
continue;
|
||||
if (last_comment.find_first_not_of(' ') != 0)
|
||||
last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
|
||||
} else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
|
||||
// Start of patch
|
||||
const auto enabled = StartsWith(line, "@enabled");
|
||||
if (i == 0)
|
||||
return;
|
||||
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
|
||||
patch_text->GetName(), last_comment, line.substr(1));
|
||||
|
||||
IPSwitchPatch patch{last_comment, enabled, {}};
|
||||
|
||||
// Read rest of patch
|
||||
while (true) {
|
||||
if (i + 1 >= lines.size())
|
||||
break;
|
||||
const auto patch_line = lines[++i];
|
||||
|
||||
// Start of new patch
|
||||
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for a flag
|
||||
if (StartsWith(patch_line, "@")) {
|
||||
ParseFlag(patch_line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
|
||||
if (patch_line.length() < 11)
|
||||
break;
|
||||
auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
|
||||
offset += static_cast<unsigned long>(offset_shift);
|
||||
|
||||
std::vector<u8> replace;
|
||||
// 9 - first char of replacement val
|
||||
if (patch_line[9] == '\"') {
|
||||
// string replacement
|
||||
auto end_index = patch_line.find('\"', 10);
|
||||
if (end_index == std::string::npos || end_index < 10)
|
||||
return;
|
||||
while (patch_line[end_index - 1] == '\\') {
|
||||
end_index = patch_line.find('\"', end_index + 1);
|
||||
if (end_index == std::string::npos || end_index < 10)
|
||||
return;
|
||||
}
|
||||
|
||||
auto value = patch_line.substr(10, end_index - 10);
|
||||
value = EscapeStringSequences(value);
|
||||
replace.reserve(value.size());
|
||||
std::copy(value.begin(), value.end(), std::back_inserter(replace));
|
||||
} else {
|
||||
// hex replacement
|
||||
const auto value = patch_line.substr(9);
|
||||
replace.reserve(value.size() / 2);
|
||||
replace = Common::HexStringToVector(value, is_little_endian);
|
||||
}
|
||||
|
||||
if (print_values) {
|
||||
LOG_INFO(Loader,
|
||||
"[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
|
||||
"with byte string '{}'",
|
||||
patch_text->GetName(), offset, Common::HexVectorToString(replace));
|
||||
}
|
||||
|
||||
patch.records.insert_or_assign(offset, std::move(replace));
|
||||
}
|
||||
|
||||
patches.push_back(std::move(patch));
|
||||
} else if (StartsWith(line, "@")) {
|
||||
ParseFlag(line);
|
||||
}
|
||||
}
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
||||
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
|
||||
if (in == nullptr || !valid)
|
||||
return nullptr;
|
||||
|
||||
auto in_data = in->ReadAllBytes();
|
||||
|
||||
for (const auto& patch : patches) {
|
||||
if (!patch.enabled)
|
||||
continue;
|
||||
|
||||
for (const auto& record : patch.records) {
|
||||
if (record.first >= in_data.size())
|
||||
continue;
|
||||
auto replace_size = record.second.size();
|
||||
if (record.first + replace_size > in_data.size())
|
||||
replace_size = in_data.size() - record.first;
|
||||
for (std::size_t i = 0; i < replace_size; ++i)
|
||||
in_data[i + record.first] = record.second[i];
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
|
||||
in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -4,12 +4,41 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips);
|
||||
|
||||
class IPSwitchCompiler {
|
||||
public:
|
||||
explicit IPSwitchCompiler(VirtualFile patch_text);
|
||||
~IPSwitchCompiler();
|
||||
|
||||
std::array<u8, 0x20> GetBuildID() const;
|
||||
bool IsValid() const;
|
||||
VirtualFile Apply(const VirtualFile& in) const;
|
||||
|
||||
private:
|
||||
struct IPSwitchPatch;
|
||||
|
||||
void ParseFlag(const std::string& flag);
|
||||
void Parse();
|
||||
|
||||
bool valid = false;
|
||||
|
||||
VirtualFile patch_text;
|
||||
std::vector<IPSwitchPatch> patches;
|
||||
std::array<u8, 0x20> nso_build_id{};
|
||||
bool is_little_endian = false;
|
||||
s64 offset_shift = 0;
|
||||
bool print_values = false;
|
||||
std::string last_comment = "";
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -73,27 +73,38 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) {
|
||||
std::vector<VirtualFile> ips;
|
||||
ips.reserve(patch_dirs.size());
|
||||
static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) {
|
||||
std::vector<VirtualFile> out;
|
||||
out.reserve(patch_dirs.size());
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto exefs_dir = subdir->GetSubdirectory("exefs");
|
||||
if (exefs_dir != nullptr) {
|
||||
for (const auto& file : exefs_dir->GetFiles()) {
|
||||
if (file->GetExtension() != "ips")
|
||||
continue;
|
||||
auto name = file->GetName();
|
||||
const auto p1 = name.substr(0, name.find('.'));
|
||||
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
|
||||
if (file->GetExtension() == "ips") {
|
||||
auto name = file->GetName();
|
||||
const auto p1 = name.substr(0, name.find('.'));
|
||||
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
|
||||
|
||||
if (build_id == this_build_id)
|
||||
ips.push_back(file);
|
||||
if (build_id == this_build_id)
|
||||
out.push_back(file);
|
||||
} else if (file->GetExtension() == "pchtxt") {
|
||||
IPSwitchCompiler compiler{file};
|
||||
if (!compiler.IsValid())
|
||||
continue;
|
||||
|
||||
auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
|
||||
this_build_id =
|
||||
this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
|
||||
|
||||
if (build_id == this_build_id)
|
||||
out.push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ips;
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
@@ -115,15 +126,24 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
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(); });
|
||||
const auto ips = CollectIPSPatches(patch_dirs, build_id);
|
||||
const auto patches = CollectPatches(patch_dirs, build_id);
|
||||
|
||||
auto out = nso;
|
||||
for (const auto& ips_file : ips) {
|
||||
LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"",
|
||||
ips_file->GetContainingDirectory()->GetParentDirectory()->GetName());
|
||||
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file);
|
||||
if (patched != nullptr)
|
||||
out = patched->ReadAllBytes();
|
||||
for (const auto& patch_file : patches) {
|
||||
if (patch_file->GetExtension() == "ips") {
|
||||
LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"",
|
||||
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
|
||||
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file);
|
||||
if (patched != nullptr)
|
||||
out = patched->ReadAllBytes();
|
||||
} else if (patch_file->GetExtension() == "pchtxt") {
|
||||
LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"",
|
||||
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
|
||||
const IPSwitchCompiler compiler{patch_file};
|
||||
const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out));
|
||||
if (patched != nullptr)
|
||||
out = patched->ReadAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
if (out.size() < 0x100)
|
||||
@@ -143,7 +163,7 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
return !CollectIPSPatches(patch_dirs, build_id).empty();
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||
@@ -162,11 +182,17 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<VirtualDir> layers;
|
||||
std::vector<VirtualDir> layers_ext;
|
||||
layers.reserve(patch_dirs.size() + 1);
|
||||
layers_ext.reserve(patch_dirs.size() + 1);
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto romfs_dir = subdir->GetSubdirectory("romfs");
|
||||
if (romfs_dir != nullptr)
|
||||
layers.push_back(std::move(romfs_dir));
|
||||
|
||||
auto ext_dir = subdir->GetSubdirectory("romfs_ext");
|
||||
if (ext_dir != nullptr)
|
||||
layers_ext.push_back(std::move(ext_dir));
|
||||
}
|
||||
layers.push_back(std::move(extracted));
|
||||
|
||||
@@ -175,7 +201,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
||||
return;
|
||||
}
|
||||
|
||||
auto packed = CreateRomFS(std::move(layered));
|
||||
auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
|
||||
|
||||
auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
|
||||
if (packed == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -184,10 +212,16 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
||||
romfs = std::move(packed);
|
||||
}
|
||||
|
||||
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
|
||||
ContentRecordType type) const {
|
||||
LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
|
||||
static_cast<u8>(type));
|
||||
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
|
||||
VirtualFile update_raw) const {
|
||||
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
|
||||
title_id, static_cast<u8>(type))
|
||||
.c_str();
|
||||
|
||||
if (type == ContentRecordType::Program)
|
||||
LOG_INFO(Loader, log_string);
|
||||
else
|
||||
LOG_DEBUG(Loader, log_string);
|
||||
|
||||
if (romfs == nullptr)
|
||||
return romfs;
|
||||
@@ -205,6 +239,13 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
|
||||
romfs = new_nca->GetRomFS();
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
|
||||
romfs = new_nca->GetRomFS();
|
||||
}
|
||||
}
|
||||
|
||||
// LayeredFS
|
||||
@@ -224,7 +265,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
||||
}
|
||||
|
||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const {
|
||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
|
||||
VirtualFile update_raw) const {
|
||||
std::map<std::string, std::string, std::less<>> out;
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
@@ -245,6 +287,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
"Update",
|
||||
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
out.insert_or_assign("Update", "PACKED");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,8 +297,24 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs")))
|
||||
AppendCommaIfNotEmpty(types, "IPS");
|
||||
|
||||
const auto exefs_dir = mod->GetSubdirectory("exefs");
|
||||
if (IsDirValidAndNonEmpty(exefs_dir)) {
|
||||
bool ips = false;
|
||||
bool ipswitch = false;
|
||||
|
||||
for (const auto& file : exefs_dir->GetFiles()) {
|
||||
if (file->GetExtension() == "ips")
|
||||
ips = true;
|
||||
else if (file->GetExtension() == "pchtxt")
|
||||
ipswitch = true;
|
||||
}
|
||||
|
||||
if (ips)
|
||||
AppendCommaIfNotEmpty(types, "IPS");
|
||||
if (ipswitch)
|
||||
AppendCommaIfNotEmpty(types, "IPSwitch");
|
||||
}
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
|
||||
@@ -291,23 +351,22 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
|
||||
const auto& installed{Service::FileSystem::GetUnionContents()};
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
|
||||
const auto installed{Service::FileSystem::GetUnionContents()};
|
||||
|
||||
const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
|
||||
if (base_control_nca == nullptr)
|
||||
return {};
|
||||
|
||||
return ParseControlNCA(base_control_nca);
|
||||
return ParseControlNCA(*base_control_nca);
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
|
||||
const std::shared_ptr<NCA>& nca) const {
|
||||
const auto base_romfs = nca->GetRomFS();
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
|
||||
const auto base_romfs = nca.GetRomFS();
|
||||
if (base_romfs == nullptr)
|
||||
return {};
|
||||
|
||||
const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control);
|
||||
const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
|
||||
if (romfs == nullptr)
|
||||
return {};
|
||||
|
||||
@@ -319,7 +378,7 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
|
||||
if (nacp_file == nullptr)
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
|
||||
const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file);
|
||||
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
|
||||
|
||||
VirtualFile icon_file;
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
@@ -328,6 +387,6 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
|
||||
break;
|
||||
}
|
||||
|
||||
return {nacp, icon_file};
|
||||
return {std::move(nacp), icon_file};
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
|
||||
// Currently tracked NSO patches:
|
||||
// - IPS
|
||||
// - IPSwitch
|
||||
std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
|
||||
|
||||
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
||||
@@ -46,19 +47,20 @@ public:
|
||||
// - Game Updates
|
||||
// - LayeredFS
|
||||
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
|
||||
ContentRecordType type = ContentRecordType::Program) const;
|
||||
ContentRecordType type = ContentRecordType::Program,
|
||||
VirtualFile update_raw = nullptr) const;
|
||||
|
||||
// Returns a vector of pairs between patch names and patch versions.
|
||||
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
|
||||
std::map<std::string, std::string, std::less<>> GetPatchVersionNames() const;
|
||||
std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
|
||||
VirtualFile update_raw = nullptr) const;
|
||||
|
||||
// Given title_id of the program, attempts to get the control data of the update and parse it,
|
||||
// falling back to the base control data.
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const;
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
|
||||
|
||||
// Version of GetControlMetadata that takes an arbitrary NCA
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA(
|
||||
const std::shared_ptr<NCA>& nca) const;
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
|
||||
|
||||
private:
|
||||
u64 title_id;
|
||||
|
||||
@@ -308,14 +308,14 @@ VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<NCA>(raw);
|
||||
return std::make_unique<NCA>(raw);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
|
||||
std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
@@ -516,7 +516,7 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
|
||||
}) != yuzu_meta.end();
|
||||
}
|
||||
|
||||
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches)
|
||||
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches)
|
||||
: caches(std::move(caches)) {}
|
||||
|
||||
void RegisteredCacheUnion::Refresh() {
|
||||
@@ -572,14 +572,14 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<NCA>(raw);
|
||||
return std::make_unique<NCA>(raw);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
|
||||
std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,8 +88,8 @@ public:
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
@@ -142,7 +142,7 @@ private:
|
||||
// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
class RegisteredCacheUnion {
|
||||
public:
|
||||
explicit RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches);
|
||||
explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);
|
||||
|
||||
void Refresh();
|
||||
|
||||
@@ -157,8 +157,8 @@ public:
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
@@ -168,7 +168,7 @@ public:
|
||||
boost::optional<u64> title_id = boost::none) const;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<RegisteredCache>> caches;
|
||||
std::vector<RegisteredCache*> caches;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -129,11 +129,11 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualFile CreateRomFS(VirtualDir dir) {
|
||||
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
|
||||
if (dir == nullptr)
|
||||
return nullptr;
|
||||
|
||||
RomFSBuildContext ctx{dir};
|
||||
RomFSBuildContext ctx{dir, ext};
|
||||
return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName());
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,6 @@ VirtualDir ExtractRomFS(VirtualFile file,
|
||||
|
||||
// Converts a VFS filesystem into a RomFS binary
|
||||
// Returns nullptr on failure
|
||||
VirtualFile CreateRomFS(VirtualDir dir);
|
||||
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr);
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -30,12 +30,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
|
||||
|
||||
RomFSFactory::~RomFSFactory() = default;
|
||||
|
||||
void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
|
||||
this->update_raw = std::move(update_raw);
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
if (!updatable)
|
||||
return MakeResult<VirtualFile>(file);
|
||||
|
||||
const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID());
|
||||
return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset));
|
||||
return MakeResult<VirtualFile>(
|
||||
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
|
||||
|
||||
@@ -32,11 +32,13 @@ public:
|
||||
explicit RomFSFactory(Loader::AppLoader& app_loader);
|
||||
~RomFSFactory();
|
||||
|
||||
void SetPackedUpdate(VirtualFile update_raw);
|
||||
ResultVal<VirtualFile> OpenCurrentProcess();
|
||||
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
VirtualFile update_raw;
|
||||
bool updatable;
|
||||
u64 ivfc_offset;
|
||||
};
|
||||
|
||||
@@ -51,6 +51,13 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
|
||||
meta.title_id);
|
||||
}
|
||||
|
||||
if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is "
|
||||
"non-zero ({:016X}{:016X})",
|
||||
meta.user_id[1], meta.user_id[0]);
|
||||
}
|
||||
|
||||
std::string save_directory =
|
||||
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
|
||||
|
||||
@@ -92,6 +99,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
|
||||
case SaveDataSpaceId::NandUser:
|
||||
out = "/user/";
|
||||
break;
|
||||
case SaveDataSpaceId::TemporaryStorage:
|
||||
out = "/temp/";
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
}
|
||||
@@ -100,10 +110,11 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
|
||||
case SaveDataType::SystemSaveData:
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
|
||||
case SaveDataType::SaveData:
|
||||
case SaveDataType::DeviceSaveData:
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
case SaveDataType::TemporaryStorage:
|
||||
return fmt::format("{}temp/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
namespace FileSys {
|
||||
|
||||
SDMCFactory::SDMCFactory(VirtualDir dir_)
|
||||
: dir(std::move(dir_)), contents(std::make_shared<RegisteredCache>(
|
||||
: dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
|
||||
[](const VirtualFile& file, const NcaID& id) {
|
||||
return std::make_shared<NAX>(file, id)->GetDecrypted();
|
||||
return NAX{file, id}.GetDecrypted();
|
||||
})) {}
|
||||
|
||||
SDMCFactory::~SDMCFactory() = default;
|
||||
@@ -22,8 +22,8 @@ ResultVal<VirtualDir> SDMCFactory::Open() {
|
||||
return MakeResult<VirtualDir>(dir);
|
||||
}
|
||||
|
||||
std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const {
|
||||
return contents;
|
||||
RegisteredCache* SDMCFactory::GetSDMCContents() const {
|
||||
return contents.get();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -19,12 +19,12 @@ public:
|
||||
~SDMCFactory();
|
||||
|
||||
ResultVal<VirtualDir> Open();
|
||||
std::shared_ptr<RegisteredCache> GetSDMCContents() const;
|
||||
RegisteredCache* GetSDMCContents() const;
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
|
||||
std::shared_ptr<RegisteredCache> contents;
|
||||
std::unique_ptr<RegisteredCache> contents;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -18,6 +18,39 @@
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
namespace {
|
||||
void SetTicketKeys(const std::vector<VirtualFile>& files) {
|
||||
Core::Crypto::KeyManager keys;
|
||||
|
||||
for (const auto& ticket_file : files) {
|
||||
if (ticket_file == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ticket_file->GetExtension() != "tik") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ticket_file->GetSize() <
|
||||
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Core::Crypto::Key128 key{};
|
||||
ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
|
||||
|
||||
// We get the name without the extension in order to create the rights ID.
|
||||
std::string name_only(ticket_file->GetName());
|
||||
name_only.erase(name_only.size() - 4);
|
||||
|
||||
const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
|
||||
keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
NSP::NSP(VirtualFile file_)
|
||||
: file(std::move(file_)), status{Loader::ResultStatus::Success},
|
||||
pfs(std::make_shared<PartitionFilesystem>(file)) {
|
||||
@@ -26,83 +59,16 @@ NSP::NSP(VirtualFile file_)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto files = pfs->GetFiles();
|
||||
|
||||
if (IsDirectoryExeFS(pfs)) {
|
||||
extracted = true;
|
||||
exefs = pfs;
|
||||
|
||||
const auto& files = pfs->GetFiles();
|
||||
const auto romfs_iter =
|
||||
std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
|
||||
return file->GetName().find(".romfs") != std::string::npos;
|
||||
});
|
||||
if (romfs_iter != files.end())
|
||||
romfs = *romfs_iter;
|
||||
InitializeExeFSAndRomFS(files);
|
||||
return;
|
||||
}
|
||||
|
||||
extracted = false;
|
||||
const auto files = pfs->GetFiles();
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
for (const auto& ticket_file : files) {
|
||||
if (ticket_file->GetExtension() == "tik") {
|
||||
if (ticket_file == nullptr ||
|
||||
ticket_file->GetSize() <
|
||||
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Core::Crypto::Key128 key{};
|
||||
ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
|
||||
std::string_view name_only(ticket_file->GetName());
|
||||
name_only.remove_suffix(4);
|
||||
const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
|
||||
keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& outer_file : files) {
|
||||
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") {
|
||||
const auto nca = std::make_shared<NCA>(outer_file);
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success) {
|
||||
program_status[nca->GetTitleId()] = nca->GetStatus();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
|
||||
for (const auto& inner_file : section0->GetFiles()) {
|
||||
if (inner_file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
const CNMT cnmt(inner_file);
|
||||
auto& ncas_title = ncas[cnmt.GetTitleID()];
|
||||
|
||||
ncas_title[ContentRecordType::Meta] = nca;
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
|
||||
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
|
||||
if (next_file == nullptr) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"NCA with ID {}.nca is listed in content metadata, but cannot "
|
||||
"be found in PFS. NSP appears to be corrupted.",
|
||||
id_string);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto next_nca = std::make_shared<NCA>(next_file);
|
||||
if (next_nca->GetType() == NCAContentType::Program)
|
||||
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
|
||||
if (next_nca->GetStatus() == Loader::ResultStatus::Success)
|
||||
ncas_title[rec.type] = std::move(next_nca);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SetTicketKeys(files);
|
||||
ReadNCAs(files);
|
||||
}
|
||||
|
||||
NSP::~NSP() = default;
|
||||
@@ -242,4 +208,66 @@ VirtualDir NSP::GetParentDirectory() const {
|
||||
bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
|
||||
exefs = pfs;
|
||||
|
||||
const auto romfs_iter = std::find_if(files.begin(), files.end(), [](const VirtualFile& file) {
|
||||
return file->GetName().rfind(".romfs") != std::string::npos;
|
||||
});
|
||||
|
||||
if (romfs_iter == files.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
romfs = *romfs_iter;
|
||||
}
|
||||
|
||||
void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
for (const auto& outer_file : files) {
|
||||
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto nca = std::make_shared<NCA>(outer_file);
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success) {
|
||||
program_status[nca->GetTitleId()] = nca->GetStatus();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
|
||||
for (const auto& inner_file : section0->GetFiles()) {
|
||||
if (inner_file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
const CNMT cnmt(inner_file);
|
||||
auto& ncas_title = ncas[cnmt.GetTitleID()];
|
||||
|
||||
ncas_title[ContentRecordType::Meta] = nca;
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
|
||||
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
|
||||
if (next_file == nullptr) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"NCA with ID {}.nca is listed in content metadata, but cannot "
|
||||
"be found in PFS. NSP appears to be corrupted.",
|
||||
id_string);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto next_nca = std::make_shared<NCA>(next_file);
|
||||
if (next_nca->GetType() == NCAContentType::Program)
|
||||
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
|
||||
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
|
||||
(next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
|
||||
(cnmt.GetTitleID() & 0x800) != 0)) {
|
||||
ncas_title[rec.type] = std::move(next_nca);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -59,9 +59,12 @@ protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
|
||||
void ReadNCAs(const std::vector<VirtualFile>& files);
|
||||
|
||||
VirtualFile file;
|
||||
|
||||
bool extracted;
|
||||
bool extracted = false;
|
||||
Loader::ResultStatus status;
|
||||
std::map<u64, Loader::ResultStatus> program_status;
|
||||
|
||||
|
||||
@@ -12,20 +12,12 @@
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class VfsDirectory;
|
||||
class VfsFile;
|
||||
class VfsFilesystem;
|
||||
|
||||
enum class Mode : u32;
|
||||
|
||||
// Convenience typedefs to use Vfs* interfaces
|
||||
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
|
||||
using VirtualDir = std::shared_ptr<VfsDirectory>;
|
||||
using VirtualFile = std::shared_ptr<VfsFile>;
|
||||
|
||||
// An enumeration representing what can be at the end of a path in a VfsFilesystem
|
||||
enum class VfsEntryType {
|
||||
None,
|
||||
|
||||
21
src/core/file_sys/vfs_types.h
Normal file
21
src/core/file_sys/vfs_types.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class VfsDirectory;
|
||||
class VfsFile;
|
||||
class VfsFilesystem;
|
||||
|
||||
// Declarations for Vfs* pointer types
|
||||
|
||||
using VirtualDir = std::shared_ptr<VfsDirectory>;
|
||||
using VirtualFile = std::shared_ptr<VfsFile>;
|
||||
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -207,9 +207,9 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
|
||||
|
||||
static Kernel::Thread* FindThreadById(int id) {
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
|
||||
for (auto& thread : threads) {
|
||||
if (thread->GetThreadId() == static_cast<u32>(id)) {
|
||||
if (thread->GetThreadID() == static_cast<u32>(id)) {
|
||||
current_core = core;
|
||||
return thread.get();
|
||||
}
|
||||
@@ -223,16 +223,18 @@ static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& thread_context = thread->GetContext();
|
||||
|
||||
if (id < SP_REGISTER) {
|
||||
return thread->context.cpu_registers[id];
|
||||
return thread_context.cpu_registers[id];
|
||||
} else if (id == SP_REGISTER) {
|
||||
return thread->context.sp;
|
||||
return thread_context.sp;
|
||||
} else if (id == PC_REGISTER) {
|
||||
return thread->context.pc;
|
||||
return thread_context.pc;
|
||||
} else if (id == PSTATE_REGISTER) {
|
||||
return thread->context.pstate;
|
||||
return thread_context.pstate;
|
||||
} else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
|
||||
return thread->context.vector_registers[id - UC_ARM64_REG_Q0][0];
|
||||
return thread_context.vector_registers[id - UC_ARM64_REG_Q0][0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -243,16 +245,18 @@ static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
auto& thread_context = thread->GetContext();
|
||||
|
||||
if (id < SP_REGISTER) {
|
||||
thread->context.cpu_registers[id] = val;
|
||||
thread_context.cpu_registers[id] = val;
|
||||
} else if (id == SP_REGISTER) {
|
||||
thread->context.sp = val;
|
||||
thread_context.sp = val;
|
||||
} else if (id == PC_REGISTER) {
|
||||
thread->context.pc = val;
|
||||
thread_context.pc = val;
|
||||
} else if (id == PSTATE_REGISTER) {
|
||||
thread->context.pstate = static_cast<u32>(val);
|
||||
thread_context.pstate = static_cast<u32>(val);
|
||||
} else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
|
||||
thread->context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
|
||||
thread_context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,9 +597,9 @@ static void HandleQuery() {
|
||||
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
|
||||
std::string val = "m";
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
val += fmt::format("{:x}", thread->GetThreadId());
|
||||
val += fmt::format("{:x}", thread->GetThreadID());
|
||||
val += ",";
|
||||
}
|
||||
}
|
||||
@@ -608,11 +612,11 @@ static void HandleQuery() {
|
||||
buffer += "l<?xml version=\"1.0\"?>";
|
||||
buffer += "<threads>";
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
buffer +=
|
||||
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
|
||||
thread->GetThreadId(), core, thread->GetThreadId());
|
||||
thread->GetThreadID(), core, thread->GetThreadID());
|
||||
}
|
||||
}
|
||||
buffer += "</threads>";
|
||||
@@ -693,7 +697,7 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
|
||||
}
|
||||
|
||||
if (thread) {
|
||||
buffer += fmt::format(";thread:{:x};", thread->GetThreadId());
|
||||
buffer += fmt::format(";thread:{:x};", thread->GetThreadID());
|
||||
}
|
||||
|
||||
SendReply(buffer.c_str());
|
||||
@@ -857,7 +861,9 @@ static void WriteRegister() {
|
||||
}
|
||||
|
||||
// Update Unicorn context skipping scheduler, no running threads at this point
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
Core::System::GetInstance()
|
||||
.ArmInterface(current_core)
|
||||
.LoadContext(current_thread->GetContext());
|
||||
|
||||
SendReply("OK");
|
||||
}
|
||||
@@ -886,7 +892,9 @@ static void WriteRegisters() {
|
||||
}
|
||||
|
||||
// Update Unicorn context skipping scheduler, no running threads at this point
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
Core::System::GetInstance()
|
||||
.ArmInterface(current_core)
|
||||
.LoadContext(current_thread->GetContext());
|
||||
|
||||
SendReply("OK");
|
||||
}
|
||||
@@ -960,7 +968,9 @@ static void Step() {
|
||||
if (command_length > 1) {
|
||||
RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread);
|
||||
// Update Unicorn context skipping scheduler, no running threads at this point
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
Core::System::GetInstance()
|
||||
.ArmInterface(current_core)
|
||||
.LoadContext(current_thread->GetContext());
|
||||
}
|
||||
step_loop = true;
|
||||
halt_loop = true;
|
||||
|
||||
@@ -23,13 +23,13 @@ namespace AddressArbiter {
|
||||
// Performs actual address waiting logic.
|
||||
static ResultCode WaitForAddress(VAddr address, s64 timeout) {
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
current_thread->arb_wait_address = address;
|
||||
current_thread->status = ThreadStatus::WaitArb;
|
||||
current_thread->wakeup_callback = nullptr;
|
||||
current_thread->SetArbiterWaitAddress(address);
|
||||
current_thread->SetStatus(ThreadStatus::WaitArb);
|
||||
current_thread->InvalidateWakeupCallback();
|
||||
|
||||
current_thread->WakeAfterDelay(timeout);
|
||||
|
||||
Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr arb_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
auto& thread_list = scheduler->GetThreadList();
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->arb_wait_address == arb_addr)
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetArbiterWaitAddress() == arb_addr)
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
};
|
||||
@@ -57,7 +57,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
|
||||
// Sort them by priority, such that the highest priority ones come first.
|
||||
std::sort(threads.begin(), threads.end(),
|
||||
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
|
||||
return lhs->current_priority < rhs->current_priority;
|
||||
return lhs->GetPriority() < rhs->GetPriority();
|
||||
});
|
||||
|
||||
return threads;
|
||||
@@ -73,9 +73,9 @@ static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num
|
||||
|
||||
// Signal the waiting threads.
|
||||
for (std::size_t i = 0; i < last; i++) {
|
||||
ASSERT(waiting_threads[i]->status == ThreadStatus::WaitArb);
|
||||
ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
|
||||
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
waiting_threads[i]->arb_wait_address = 0;
|
||||
waiting_threads[i]->SetArbiterWaitAddress(0);
|
||||
waiting_threads[i]->ResumeFromWait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ namespace Kernel {
|
||||
ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {}
|
||||
ClientPort::~ClientPort() = default;
|
||||
|
||||
SharedPtr<ServerPort> ClientPort::GetServerPort() const {
|
||||
return server_port;
|
||||
}
|
||||
|
||||
ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
|
||||
// Note: Threads do not wait for the server endpoint to call
|
||||
// AcceptSession before returning from this call.
|
||||
|
||||
@@ -30,6 +30,8 @@ public:
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
SharedPtr<ServerPort> GetServerPort() const;
|
||||
|
||||
/**
|
||||
* Creates a new Session pair, adds the created ServerSession to the associated ServerPort's
|
||||
* list of pending sessions, and signals the ServerPort, causing any threads
|
||||
|
||||
@@ -22,6 +22,7 @@ enum {
|
||||
HandleTableFull = 105,
|
||||
InvalidMemoryState = 106,
|
||||
InvalidMemoryPermissions = 108,
|
||||
InvalidMemoryRange = 110,
|
||||
InvalidThreadPriority = 112,
|
||||
InvalidProcessorId = 113,
|
||||
InvalidHandle = 114,
|
||||
@@ -56,6 +57,7 @@ constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidA
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
|
||||
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
|
||||
ErrCodes::InvalidMemoryPermissions);
|
||||
constexpr ResultCode ERR_INVALID_MEMORY_RANGE(ErrorModule::Kernel, ErrCodes::InvalidMemoryRange);
|
||||
constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle);
|
||||
constexpr ResultCode ERR_INVALID_PROCESSOR_ID(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
|
||||
constexpr ResultCode ERR_INVALID_SIZE(ErrorModule::Kernel, ErrCodes::InvalidSize);
|
||||
|
||||
@@ -42,14 +42,14 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
|
||||
Kernel::SharedPtr<Kernel::Event> event) {
|
||||
|
||||
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
|
||||
thread->wakeup_callback = [context = *this, callback](
|
||||
thread->SetWakeupCallback([context = *this, callback](
|
||||
ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index) mutable -> bool {
|
||||
ASSERT(thread->status == ThreadStatus::WaitHLEEvent);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent);
|
||||
callback(thread, context, reason);
|
||||
context.WriteToOutgoingCommandBuffer(*thread);
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
if (!event) {
|
||||
// Create event if not provided
|
||||
@@ -59,8 +59,8 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
|
||||
}
|
||||
|
||||
event->Clear();
|
||||
thread->status = ThreadStatus::WaitHLEEvent;
|
||||
thread->wait_objects = {event};
|
||||
thread->SetStatus(ThreadStatus::WaitHLEEvent);
|
||||
thread->SetWaitObjects({event});
|
||||
event->AddWaitingThread(thread);
|
||||
|
||||
if (timeout > 0) {
|
||||
@@ -209,7 +209,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
|
||||
|
||||
ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) {
|
||||
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
|
||||
Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
|
||||
Memory::ReadBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
|
||||
dst_cmdbuf.size() * sizeof(u32));
|
||||
|
||||
// The header was already built in the internal command buffer. Attempt to parse it to verify
|
||||
@@ -268,7 +268,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread)
|
||||
}
|
||||
|
||||
// Copy the translated command buffer back into the thread's command buffer area.
|
||||
Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
|
||||
Memory::WriteBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
|
||||
dst_cmdbuf.size() * sizeof(u32));
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
@@ -46,40 +46,40 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
|
||||
|
||||
bool resume = true;
|
||||
|
||||
if (thread->status == ThreadStatus::WaitSynchAny ||
|
||||
thread->status == ThreadStatus::WaitSynchAll ||
|
||||
thread->status == ThreadStatus::WaitHLEEvent) {
|
||||
if (thread->GetStatus() == ThreadStatus::WaitSynchAny ||
|
||||
thread->GetStatus() == ThreadStatus::WaitSynchAll ||
|
||||
thread->GetStatus() == ThreadStatus::WaitHLEEvent) {
|
||||
// Remove the thread from each of its waiting objects' waitlists
|
||||
for (auto& object : thread->wait_objects) {
|
||||
for (const auto& object : thread->GetWaitObjects()) {
|
||||
object->RemoveWaitingThread(thread.get());
|
||||
}
|
||||
thread->wait_objects.clear();
|
||||
thread->ClearWaitObjects();
|
||||
|
||||
// Invoke the wakeup callback before clearing the wait objects
|
||||
if (thread->wakeup_callback) {
|
||||
resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
|
||||
if (thread->HasWakeupCallback()) {
|
||||
resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 ||
|
||||
thread->wait_handle) {
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
|
||||
thread->GetWaitHandle() != 0) {
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetCondVarWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
|
||||
auto lock_owner = thread->lock_owner;
|
||||
auto* const lock_owner = thread->GetLockOwner();
|
||||
// Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
|
||||
// and don't have a lock owner unless SignalProcessWideKey was called first and the thread
|
||||
// wasn't awakened due to the mutex already being acquired.
|
||||
if (lock_owner) {
|
||||
if (lock_owner != nullptr) {
|
||||
lock_owner->RemoveMutexWaiter(thread);
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->arb_wait_address != 0) {
|
||||
ASSERT(thread->status == ThreadStatus::WaitArb);
|
||||
thread->arb_wait_address = 0;
|
||||
if (thread->GetArbiterWaitAddress() != 0) {
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitArb);
|
||||
thread->SetArbiterWaitAddress(0);
|
||||
}
|
||||
|
||||
if (resume) {
|
||||
@@ -116,7 +116,7 @@ struct KernelCore::Impl {
|
||||
next_thread_id = 1;
|
||||
|
||||
process_list.clear();
|
||||
current_process.reset();
|
||||
current_process = nullptr;
|
||||
|
||||
handle_table.Clear();
|
||||
resource_limits.fill(nullptr);
|
||||
@@ -207,7 +207,7 @@ struct KernelCore::Impl {
|
||||
|
||||
// Lists all processes that exist in the current session.
|
||||
std::vector<SharedPtr<Process>> process_list;
|
||||
SharedPtr<Process> current_process;
|
||||
Process* current_process = nullptr;
|
||||
|
||||
Kernel::HandleTable handle_table;
|
||||
std::array<SharedPtr<ResourceLimit>, 4> resource_limits;
|
||||
@@ -266,15 +266,15 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
|
||||
impl->process_list.push_back(std::move(process));
|
||||
}
|
||||
|
||||
void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) {
|
||||
impl->current_process = std::move(process);
|
||||
void KernelCore::MakeCurrentProcess(Process* process) {
|
||||
impl->current_process = process;
|
||||
}
|
||||
|
||||
SharedPtr<Process>& KernelCore::CurrentProcess() {
|
||||
Process* KernelCore::CurrentProcess() {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
const SharedPtr<Process>& KernelCore::CurrentProcess() const {
|
||||
const Process* KernelCore::CurrentProcess() const {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,13 +66,13 @@ public:
|
||||
void AppendNewProcess(SharedPtr<Process> process);
|
||||
|
||||
/// Makes the given process the new current process.
|
||||
void MakeCurrentProcess(SharedPtr<Process> process);
|
||||
void MakeCurrentProcess(Process* process);
|
||||
|
||||
/// Retrieves a reference to the current process.
|
||||
SharedPtr<Process>& CurrentProcess();
|
||||
/// Retrieves a pointer to the current process.
|
||||
Process* CurrentProcess();
|
||||
|
||||
/// Retrieves a const reference to the current process.
|
||||
const SharedPtr<Process>& CurrentProcess() const;
|
||||
/// Retrieves a const pointer to the current process.
|
||||
const Process* CurrentProcess() const;
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
|
||||
|
||||
@@ -28,11 +28,11 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
|
||||
SharedPtr<Thread> highest_priority_thread;
|
||||
u32 num_waiters = 0;
|
||||
|
||||
for (auto& thread : current_thread->wait_mutex_threads) {
|
||||
if (thread->mutex_wait_address != mutex_addr)
|
||||
for (const auto& thread : current_thread->GetMutexWaitingThreads()) {
|
||||
if (thread->GetMutexWaitAddress() != mutex_addr)
|
||||
continue;
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
|
||||
++num_waiters;
|
||||
if (highest_priority_thread == nullptr ||
|
||||
@@ -47,12 +47,12 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
|
||||
/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
|
||||
static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
|
||||
SharedPtr<Thread> new_owner) {
|
||||
auto threads = current_thread->wait_mutex_threads;
|
||||
for (auto& thread : threads) {
|
||||
if (thread->mutex_wait_address != mutex_addr)
|
||||
const auto threads = current_thread->GetMutexWaitingThreads();
|
||||
for (const auto& thread : threads) {
|
||||
if (thread->GetMutexWaitAddress() != mutex_addr)
|
||||
continue;
|
||||
|
||||
ASSERT(thread->lock_owner == current_thread);
|
||||
ASSERT(thread->GetLockOwner() == current_thread);
|
||||
current_thread->RemoveMutexWaiter(thread);
|
||||
if (new_owner != thread)
|
||||
new_owner->AddMutexWaiter(thread);
|
||||
@@ -84,11 +84,11 @@ ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle ho
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
// Wait until the mutex is released
|
||||
GetCurrentThread()->mutex_wait_address = address;
|
||||
GetCurrentThread()->wait_handle = requesting_thread_handle;
|
||||
GetCurrentThread()->SetMutexWaitAddress(address);
|
||||
GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
|
||||
|
||||
GetCurrentThread()->status = ThreadStatus::WaitMutex;
|
||||
GetCurrentThread()->wakeup_callback = nullptr;
|
||||
GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
|
||||
GetCurrentThread()->InvalidateWakeupCallback();
|
||||
|
||||
// Update the lock holder thread's priority to prevent priority inversion.
|
||||
holding_thread->AddMutexWaiter(GetCurrentThread());
|
||||
@@ -115,7 +115,7 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
// Transfer the ownership of the mutex from the previous owner to the new one.
|
||||
TransferMutexOwnership(address, GetCurrentThread(), thread);
|
||||
|
||||
u32 mutex_value = thread->wait_handle;
|
||||
u32 mutex_value = thread->GetWaitHandle();
|
||||
|
||||
if (num_waiters >= 2) {
|
||||
// Notify the guest that there are still some threads waiting for the mutex
|
||||
@@ -125,13 +125,13 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
// Grant the mutex to the next waiting thread and resume it.
|
||||
Memory::Write32(address, mutex_value);
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
thread->ResumeFromWait();
|
||||
|
||||
thread->lock_owner = nullptr;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
thread->SetLockOwner(nullptr);
|
||||
thread->SetCondVarWaitAddress(0);
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ bool Object::IsWaitable() const {
|
||||
case HandleType::Process:
|
||||
case HandleType::AddressArbiter:
|
||||
case HandleType::ResourceLimit:
|
||||
case HandleType::CodeSet:
|
||||
case HandleType::ClientPort:
|
||||
case HandleType::ClientSession:
|
||||
return false;
|
||||
|
||||
@@ -26,7 +26,6 @@ enum class HandleType : u32 {
|
||||
AddressArbiter,
|
||||
Timer,
|
||||
ResourceLimit,
|
||||
CodeSet,
|
||||
ClientPort,
|
||||
ServerPort,
|
||||
ClientSession,
|
||||
|
||||
@@ -20,13 +20,7 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
SharedPtr<CodeSet> CodeSet::Create(KernelCore& kernel, std::string name) {
|
||||
SharedPtr<CodeSet> codeset(new CodeSet(kernel));
|
||||
codeset->name = std::move(name);
|
||||
return codeset;
|
||||
}
|
||||
|
||||
CodeSet::CodeSet(KernelCore& kernel) : Object{kernel} {}
|
||||
CodeSet::CodeSet() = default;
|
||||
CodeSet::~CodeSet() = default;
|
||||
|
||||
SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
|
||||
@@ -144,26 +138,26 @@ void Process::PrepareForTermination() {
|
||||
|
||||
const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) {
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->owner_process != this)
|
||||
if (thread->GetOwnerProcess() != this)
|
||||
continue;
|
||||
|
||||
if (thread == GetCurrentThread())
|
||||
continue;
|
||||
|
||||
// TODO(Subv): When are the other running/ready threads terminated?
|
||||
ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny ||
|
||||
thread->status == ThreadStatus::WaitSynchAll,
|
||||
ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynchAny ||
|
||||
thread->GetStatus() == ThreadStatus::WaitSynchAll,
|
||||
"Exiting processes with non-waiting threads is currently unimplemented");
|
||||
|
||||
thread->Stop();
|
||||
}
|
||||
};
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
stop_threads(system.Scheduler(0)->GetThreadList());
|
||||
stop_threads(system.Scheduler(1)->GetThreadList());
|
||||
stop_threads(system.Scheduler(2)->GetThreadList());
|
||||
stop_threads(system.Scheduler(3)->GetThreadList());
|
||||
const auto& system = Core::System::GetInstance();
|
||||
stop_threads(system.Scheduler(0).GetThreadList());
|
||||
stop_threads(system.Scheduler(1).GetThreadList());
|
||||
stop_threads(system.Scheduler(2).GetThreadList());
|
||||
stop_threads(system.Scheduler(3).GetThreadList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,20 +218,20 @@ void Process::FreeTLSSlot(VAddr tls_address) {
|
||||
tls_slots[tls_page].reset(tls_slot);
|
||||
}
|
||||
|
||||
void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) {
|
||||
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
|
||||
MemoryState memory_state) {
|
||||
auto vma = vm_manager
|
||||
.MapMemoryBlock(segment.addr + base_addr, module_->memory, segment.offset,
|
||||
segment.size, memory_state)
|
||||
.Unwrap();
|
||||
const auto vma = vm_manager
|
||||
.MapMemoryBlock(segment.addr + base_addr, module_.memory,
|
||||
segment.offset, segment.size, memory_state)
|
||||
.Unwrap();
|
||||
vm_manager.Reprotect(vma, permissions);
|
||||
};
|
||||
|
||||
// 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::CodeStatic);
|
||||
MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
|
||||
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
|
||||
}
|
||||
|
||||
ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {
|
||||
|
||||
@@ -24,6 +24,7 @@ class ProgramMetadata;
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class ResourceLimit;
|
||||
|
||||
struct AddressMapping {
|
||||
// Address and size must be page-aligned
|
||||
@@ -57,30 +58,33 @@ union ProcessFlags {
|
||||
BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000).
|
||||
};
|
||||
|
||||
enum class ProcessStatus { Created, Running, Exited };
|
||||
/**
|
||||
* Indicates the status of a Process instance.
|
||||
*
|
||||
* @note These match the values as used by kernel,
|
||||
* so new entries should only be added if RE
|
||||
* shows that a new value has been introduced.
|
||||
*/
|
||||
enum class ProcessStatus {
|
||||
Created,
|
||||
CreatedWithDebuggerAttached,
|
||||
Running,
|
||||
WaitingForDebuggerToAttach,
|
||||
DebuggerAttached,
|
||||
Exiting,
|
||||
Exited,
|
||||
DebugBreak,
|
||||
};
|
||||
|
||||
class ResourceLimit;
|
||||
|
||||
struct CodeSet final : public Object {
|
||||
struct CodeSet final {
|
||||
struct Segment {
|
||||
std::size_t offset = 0;
|
||||
VAddr addr = 0;
|
||||
u32 size = 0;
|
||||
};
|
||||
|
||||
static SharedPtr<CodeSet> Create(KernelCore& kernel, std::string name);
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "CodeSet";
|
||||
}
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
}
|
||||
|
||||
static const HandleType HANDLE_TYPE = HandleType::CodeSet;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
explicit CodeSet();
|
||||
~CodeSet();
|
||||
|
||||
Segment& CodeSegment() {
|
||||
return segments[0];
|
||||
@@ -109,14 +113,7 @@ struct CodeSet final : public Object {
|
||||
std::shared_ptr<std::vector<u8>> memory;
|
||||
|
||||
std::array<Segment, 3> segments;
|
||||
VAddr entrypoint;
|
||||
|
||||
/// Name of the process
|
||||
std::string name;
|
||||
|
||||
private:
|
||||
explicit CodeSet(KernelCore& kernel);
|
||||
~CodeSet() override;
|
||||
VAddr entrypoint = 0;
|
||||
};
|
||||
|
||||
class Process final : public Object {
|
||||
@@ -219,7 +216,7 @@ public:
|
||||
*/
|
||||
void PrepareForTermination();
|
||||
|
||||
void LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr);
|
||||
void LoadModule(CodeSet module_, VAddr base_addr);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Memory Management
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
|
||||
@@ -38,10 +38,10 @@ Thread* Scheduler::PopNextReadyThread() {
|
||||
Thread* next = nullptr;
|
||||
Thread* thread = GetCurrentThread();
|
||||
|
||||
if (thread && thread->status == ThreadStatus::Running) {
|
||||
if (thread && thread->GetStatus() == ThreadStatus::Running) {
|
||||
// We have to do better than the current thread.
|
||||
// This call returns null when that's not possible.
|
||||
next = ready_queue.pop_first_better(thread->current_priority);
|
||||
next = ready_queue.pop_first_better(thread->GetPriority());
|
||||
if (!next) {
|
||||
// Otherwise just keep going with the current thread
|
||||
next = thread;
|
||||
@@ -58,40 +58,40 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
// Save context for previous thread
|
||||
if (previous_thread) {
|
||||
previous_thread->last_running_ticks = CoreTiming::GetTicks();
|
||||
cpu_core.SaveContext(previous_thread->context);
|
||||
cpu_core.SaveContext(previous_thread->GetContext());
|
||||
// Save the TPIDR_EL0 system register in case it was modified.
|
||||
previous_thread->tpidr_el0 = cpu_core.GetTPIDR_EL0();
|
||||
previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
|
||||
|
||||
if (previous_thread->status == ThreadStatus::Running) {
|
||||
if (previous_thread->GetStatus() == ThreadStatus::Running) {
|
||||
// This is only the case when a reschedule is triggered without the current thread
|
||||
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
|
||||
ready_queue.push_front(previous_thread->current_priority, previous_thread);
|
||||
previous_thread->status = ThreadStatus::Ready;
|
||||
ready_queue.push_front(previous_thread->GetPriority(), previous_thread);
|
||||
previous_thread->SetStatus(ThreadStatus::Ready);
|
||||
}
|
||||
}
|
||||
|
||||
// Load context of new thread
|
||||
if (new_thread) {
|
||||
ASSERT_MSG(new_thread->status == ThreadStatus::Ready,
|
||||
ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready,
|
||||
"Thread must be ready to become running.");
|
||||
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
new_thread->CancelWakeupTimer();
|
||||
|
||||
auto previous_process = Core::CurrentProcess();
|
||||
auto* const previous_process = Core::CurrentProcess();
|
||||
|
||||
current_thread = new_thread;
|
||||
|
||||
ready_queue.remove(new_thread->current_priority, new_thread);
|
||||
new_thread->status = ThreadStatus::Running;
|
||||
ready_queue.remove(new_thread->GetPriority(), new_thread);
|
||||
new_thread->SetStatus(ThreadStatus::Running);
|
||||
|
||||
if (previous_process != current_thread->owner_process) {
|
||||
Core::CurrentProcess() = current_thread->owner_process;
|
||||
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);
|
||||
}
|
||||
|
||||
cpu_core.LoadContext(new_thread->context);
|
||||
cpu_core.LoadContext(new_thread->GetContext());
|
||||
cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
|
||||
cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
@@ -136,14 +136,14 @@ void Scheduler::RemoveThread(Thread* thread) {
|
||||
void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::Ready);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
|
||||
ready_queue.push_back(priority, thread);
|
||||
}
|
||||
|
||||
void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::Ready);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
|
||||
ready_queue.remove(priority, thread);
|
||||
}
|
||||
|
||||
@@ -151,8 +151,8 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
// If thread was ready, adjust queues
|
||||
if (thread->status == ThreadStatus::Ready)
|
||||
ready_queue.move(thread, thread->current_priority, priority);
|
||||
if (thread->GetStatus() == ThreadStatus::Ready)
|
||||
ready_queue.move(thread, thread->GetPriority(), priority);
|
||||
else
|
||||
ready_queue.prepare(priority);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
|
||||
@@ -120,10 +120,10 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
|
||||
result = hle_handler->HandleSyncRequest(context);
|
||||
}
|
||||
|
||||
if (thread->status == ThreadStatus::Running) {
|
||||
if (thread->GetStatus() == ThreadStatus::Running) {
|
||||
// Put the thread to sleep until the server replies, it will be awoken in
|
||||
// svcReplyAndReceive for LLE servers.
|
||||
thread->status = ThreadStatus::WaitIPC;
|
||||
thread->SetStatus(ThreadStatus::WaitIPC);
|
||||
|
||||
if (hle_handler != nullptr) {
|
||||
// For HLE services, we put the request threads to sleep for a short duration to
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
@@ -36,8 +37,72 @@
|
||||
|
||||
namespace Kernel {
|
||||
namespace {
|
||||
constexpr bool Is4KBAligned(VAddr address) {
|
||||
return (address & 0xFFF) == 0;
|
||||
|
||||
// Checks if address + size is greater than the given address
|
||||
// This can return false if the size causes an overflow of a 64-bit type
|
||||
// or if the given size is zero.
|
||||
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());
|
||||
}
|
||||
|
||||
// Helper function that performs the common sanity checks for svcMapMemory
|
||||
// and svcUnmapMemory. This is doable, as both functions perform their sanitizing
|
||||
// in the same order.
|
||||
ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_addr, VAddr src_addr,
|
||||
u64 size) {
|
||||
if (!Common::Is4KBAligned(dst_addr) || !Common::Is4KBAligned(src_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (size == 0 || !Common::Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(dst_addr, size)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(src_addr, size)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!IsInsideAddressSpace(vm_manager, src_addr, size)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!IsInsideNewMapRegion(vm_manager, dst_addr, size)) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
const VAddr dst_end_address = dst_addr + size;
|
||||
if (dst_end_address > vm_manager.GetHeapRegionBaseAddress() &&
|
||||
vm_manager.GetHeapRegionEndAddress() > dst_addr) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
if (dst_end_address > vm_manager.GetMapRegionBaseAddress() &&
|
||||
vm_manager.GetMapRegionEndAddress() > dst_addr) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
@@ -69,15 +134,15 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
|
||||
if (result != RESULT_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (size == 0 || !Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size);
|
||||
return current_process->MirrorMemory(dst_addr, src_addr, size);
|
||||
}
|
||||
|
||||
/// Unmaps a region that was previously mapped with svcMapMemory
|
||||
@@ -85,15 +150,15 @@ static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
|
||||
if (result != RESULT_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (size == 0 || !Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size);
|
||||
return current_process->UnmapMemory(dst_addr, src_addr, size);
|
||||
}
|
||||
|
||||
/// Connect to an OS service given the port name, returns the handle to the port to out
|
||||
@@ -156,7 +221,7 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
*thread_id = thread->GetThreadId();
|
||||
*thread_id = thread->GetThreadID();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -177,7 +242,7 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
|
||||
/// Default thread wakeup callback for WaitSynchronization
|
||||
static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index) {
|
||||
ASSERT(thread->status == ThreadStatus::WaitSynchAny);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
|
||||
|
||||
if (reason == ThreadWakeupReason::Timeout) {
|
||||
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
|
||||
@@ -204,10 +269,10 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
|
||||
if (handle_count > MaxHandles)
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::TooLarge);
|
||||
|
||||
auto thread = GetCurrentThread();
|
||||
auto* const thread = GetCurrentThread();
|
||||
|
||||
using ObjectPtr = SharedPtr<WaitObject>;
|
||||
std::vector<ObjectPtr> objects(handle_count);
|
||||
using ObjectPtr = Thread::ThreadWaitObjects::value_type;
|
||||
Thread::ThreadWaitObjects objects(handle_count);
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
|
||||
for (u64 i = 0; i < handle_count; ++i) {
|
||||
@@ -244,14 +309,14 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
|
||||
for (auto& object : objects)
|
||||
object->AddWaitingThread(thread);
|
||||
|
||||
thread->wait_objects = std::move(objects);
|
||||
thread->status = ThreadStatus::WaitSynchAny;
|
||||
thread->SetWaitObjects(std::move(objects));
|
||||
thread->SetStatus(ThreadStatus::WaitSynchAny);
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
thread->WakeAfterDelay(nano_seconds);
|
||||
thread->wakeup_callback = DefaultThreadWakeupCallback;
|
||||
thread->SetWakeupCallback(DefaultThreadWakeupCallback);
|
||||
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
@@ -266,7 +331,7 @@ static ResultCode CancelSynchronization(Handle thread_handle) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::WaitSynchAny);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
|
||||
thread->SetWaitSynchronizationResult(
|
||||
ResultCode(ErrorModule::Kernel, ErrCodes::SynchronizationCanceled));
|
||||
thread->ResumeFromWait();
|
||||
@@ -285,6 +350,10 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!Common::IsWordAligned(mutex_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
|
||||
return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
|
||||
requesting_thread_handle);
|
||||
@@ -298,16 +367,41 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!Common::IsWordAligned(mutex_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
return Mutex::Release(mutex_addr);
|
||||
}
|
||||
|
||||
struct BreakReason {
|
||||
union {
|
||||
u32 raw;
|
||||
BitField<31, 1, u32> signal_debugger;
|
||||
};
|
||||
};
|
||||
|
||||
/// Break program execution
|
||||
static void Break(u64 reason, u64 info1, u64 info2) {
|
||||
LOG_CRITICAL(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
ASSERT(false);
|
||||
static void Break(u32 reason, u64 info1, u64 info2) {
|
||||
BreakReason break_reason{reason};
|
||||
if (break_reason.signal_debugger) {
|
||||
LOG_ERROR(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
} else {
|
||||
LOG_CRITICAL(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
ASSERT(false);
|
||||
|
||||
Core::CurrentProcess()->PrepareForTermination();
|
||||
|
||||
// Kill the current thread
|
||||
GetCurrentThread()->Stop();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
|
||||
@@ -326,7 +420,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
|
||||
info_sub_id, handle);
|
||||
|
||||
const auto& current_process = Core::CurrentProcess();
|
||||
const auto* current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
switch (static_cast<GetInfoType>(info_id)) {
|
||||
@@ -360,25 +454,12 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
case GetInfoType::RandomEntropy:
|
||||
*result = 0;
|
||||
break;
|
||||
case GetInfoType::AddressSpaceBaseAddr:
|
||||
*result = vm_manager.GetCodeRegionBaseAddress();
|
||||
case GetInfoType::ASLRRegionBaseAddr:
|
||||
*result = vm_manager.GetASLRRegionBaseAddress();
|
||||
break;
|
||||
case GetInfoType::AddressSpaceSize: {
|
||||
const u64 width = vm_manager.GetAddressSpaceWidth();
|
||||
|
||||
switch (width) {
|
||||
case 32:
|
||||
*result = 0xFFE00000;
|
||||
break;
|
||||
case 36:
|
||||
*result = 0xFF8000000;
|
||||
break;
|
||||
case 39:
|
||||
*result = 0x7FF8000000;
|
||||
break;
|
||||
}
|
||||
case GetInfoType::ASLRRegionSize:
|
||||
*result = vm_manager.GetASLRRegionSize();
|
||||
break;
|
||||
}
|
||||
case GetInfoType::NewMapRegionBaseAddr:
|
||||
*result = vm_manager.GetNewMapRegionBaseAddress();
|
||||
break;
|
||||
@@ -424,8 +505,8 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const auto current_process = Core::CurrentProcess();
|
||||
if (thread->owner_process != current_process) {
|
||||
const auto* current_process = Core::CurrentProcess();
|
||||
if (thread->GetOwnerProcess() != current_process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
@@ -433,7 +514,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
|
||||
return ERR_ALREADY_REGISTERED;
|
||||
}
|
||||
|
||||
Core::ARM_Interface::ThreadContext ctx = thread->context;
|
||||
Core::ARM_Interface::ThreadContext ctx = thread->GetContext();
|
||||
// Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
|
||||
ctx.pstate &= 0xFF0FFE20;
|
||||
|
||||
@@ -479,14 +560,14 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||
|
||||
thread->SetPriority(priority);
|
||||
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Get which CPU core is executing the current thread
|
||||
static u32 GetCurrentProcessorNumber() {
|
||||
LOG_TRACE(Kernel_SVC, "called");
|
||||
return GetCurrentThread()->processor_id;
|
||||
return GetCurrentThread()->GetProcessorID();
|
||||
}
|
||||
|
||||
static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size,
|
||||
@@ -495,14 +576,18 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
|
||||
"called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
|
||||
shared_memory_handle, addr, size, permissions);
|
||||
|
||||
if (!Is4KBAligned(addr)) {
|
||||
if (!Common::Is4KBAligned(addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (size == 0 || !Is4KBAligned(size)) {
|
||||
if (size == 0 || !Common::Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(addr, size)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
const auto permissions_type = static_cast<MemoryPermission>(permissions);
|
||||
if (permissions_type != MemoryPermission::Read &&
|
||||
permissions_type != MemoryPermission::ReadWrite) {
|
||||
@@ -516,26 +601,46 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type,
|
||||
MemoryPermission::DontCare);
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
if (!vm_manager.IsWithinASLRRegion(addr, size)) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
return shared_memory->Map(current_process, addr, permissions_type, MemoryPermission::DontCare);
|
||||
}
|
||||
|
||||
static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) {
|
||||
LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}",
|
||||
shared_memory_handle, addr, size);
|
||||
|
||||
if (!Is4KBAligned(addr)) {
|
||||
if (!Common::Is4KBAligned(addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (size == 0 || !Is4KBAligned(size)) {
|
||||
if (size == 0 || !Common::Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(addr, size)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle);
|
||||
if (!shared_memory) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
return shared_memory->Unmap(Core::CurrentProcess().get(), addr);
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
if (!vm_manager.IsWithinASLRRegion(addr, size)) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
return shared_memory->Unmap(current_process, addr);
|
||||
}
|
||||
|
||||
/// Query process memory
|
||||
@@ -549,7 +654,7 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i
|
||||
}
|
||||
auto vma = process->VMManager().FindVMA(addr);
|
||||
memory_info->attributes = 0;
|
||||
if (vma == Core::CurrentProcess()->VMManager().vma_map.end()) {
|
||||
if (vma == process->VMManager().vma_map.end()) {
|
||||
memory_info->base_address = 0;
|
||||
memory_info->permission = static_cast<u32>(VMAPermission::None);
|
||||
memory_info->size = 0;
|
||||
@@ -573,7 +678,7 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd
|
||||
|
||||
/// Exits the current process
|
||||
static void ExitProcess() {
|
||||
auto& current_process = Core::CurrentProcess();
|
||||
auto* current_process = Core::CurrentProcess();
|
||||
|
||||
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID());
|
||||
ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running,
|
||||
@@ -621,11 +726,15 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
CASCADE_RESULT(SharedPtr<Thread> thread,
|
||||
Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
|
||||
Core::CurrentProcess()));
|
||||
CASCADE_RESULT(thread->guest_handle, kernel.HandleTable().Create(thread));
|
||||
*out_handle = thread->guest_handle;
|
||||
*Core::CurrentProcess()));
|
||||
const auto new_guest_handle = kernel.HandleTable().Create(thread);
|
||||
if (new_guest_handle.Failed()) {
|
||||
return new_guest_handle.Code();
|
||||
}
|
||||
thread->SetGuestHandle(*new_guest_handle);
|
||||
*out_handle = *new_guest_handle;
|
||||
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
|
||||
@@ -645,10 +754,10 @@ static ResultCode StartThread(Handle thread_handle) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::Dormant);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Dormant);
|
||||
|
||||
thread->ResumeFromWait();
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -694,17 +803,17 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
|
||||
CASCADE_CODE(Mutex::Release(mutex_addr));
|
||||
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
current_thread->condvar_wait_address = condition_variable_addr;
|
||||
current_thread->mutex_wait_address = mutex_addr;
|
||||
current_thread->wait_handle = thread_handle;
|
||||
current_thread->status = ThreadStatus::WaitMutex;
|
||||
current_thread->wakeup_callback = nullptr;
|
||||
current_thread->SetCondVarWaitAddress(condition_variable_addr);
|
||||
current_thread->SetMutexWaitAddress(mutex_addr);
|
||||
current_thread->SetWaitHandle(thread_handle);
|
||||
current_thread->SetStatus(ThreadStatus::WaitMutex);
|
||||
current_thread->InvalidateWakeupCallback();
|
||||
|
||||
current_thread->WakeAfterDelay(nano_seconds);
|
||||
|
||||
// Note: Deliberately don't attempt to inherit the lock owner's priority.
|
||||
|
||||
Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -713,14 +822,14 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
|
||||
condition_variable_addr, target);
|
||||
|
||||
auto RetrieveWaitingThreads = [](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr condvar_addr) {
|
||||
const auto RetrieveWaitingThreads = [](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr condvar_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
auto& thread_list = scheduler->GetThreadList();
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->condvar_wait_address == condvar_addr)
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetCondVarWaitAddress() == condvar_addr)
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
};
|
||||
@@ -734,7 +843,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
// Sort them by priority, such that the highest priority ones come first.
|
||||
std::sort(waiting_threads.begin(), waiting_threads.end(),
|
||||
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
|
||||
return lhs->current_priority < rhs->current_priority;
|
||||
return lhs->GetPriority() < rhs->GetPriority();
|
||||
});
|
||||
|
||||
// Only process up to 'target' threads, unless 'target' is -1, in which case process
|
||||
@@ -750,7 +859,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
for (std::size_t index = 0; index < last; ++index) {
|
||||
auto& thread = waiting_threads[index];
|
||||
|
||||
ASSERT(thread->condvar_wait_address == condition_variable_addr);
|
||||
ASSERT(thread->GetCondVarWaitAddress() == condition_variable_addr);
|
||||
|
||||
std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex();
|
||||
|
||||
@@ -759,42 +868,43 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
// Atomically read the value of the mutex.
|
||||
u32 mutex_val = 0;
|
||||
do {
|
||||
monitor.SetExclusive(current_core, thread->mutex_wait_address);
|
||||
monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
|
||||
|
||||
// If the mutex is not yet acquired, acquire it.
|
||||
mutex_val = Memory::Read32(thread->mutex_wait_address);
|
||||
mutex_val = Memory::Read32(thread->GetMutexWaitAddress());
|
||||
|
||||
if (mutex_val != 0) {
|
||||
monitor.ClearExclusive();
|
||||
break;
|
||||
}
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address,
|
||||
thread->wait_handle));
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
|
||||
thread->GetWaitHandle()));
|
||||
|
||||
if (mutex_val == 0) {
|
||||
// We were able to acquire the mutex, resume this thread.
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
thread->ResumeFromWait();
|
||||
|
||||
auto lock_owner = thread->lock_owner;
|
||||
if (lock_owner)
|
||||
auto* const lock_owner = thread->GetLockOwner();
|
||||
if (lock_owner != nullptr) {
|
||||
lock_owner->RemoveMutexWaiter(thread);
|
||||
}
|
||||
|
||||
thread->lock_owner = nullptr;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
thread->SetLockOwner(nullptr);
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetCondVarWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
} else {
|
||||
// Atomically signal that the mutex now has a waiting thread.
|
||||
do {
|
||||
monitor.SetExclusive(current_core, thread->mutex_wait_address);
|
||||
monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
|
||||
|
||||
// Ensure that the mutex value is still what we expect.
|
||||
u32 value = Memory::Read32(thread->mutex_wait_address);
|
||||
u32 value = Memory::Read32(thread->GetMutexWaitAddress());
|
||||
// TODO(Subv): When this happens, the kernel just clears the exclusive state and
|
||||
// retries the initial read for this thread.
|
||||
ASSERT_MSG(mutex_val == value, "Unhandled synchronization primitive case");
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address,
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
|
||||
mutex_val | Mutex::MutexHasWaitersFlag));
|
||||
|
||||
// The mutex is already owned by some other thread, make this thread wait on it.
|
||||
@@ -802,12 +912,12 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
|
||||
auto owner = kernel.HandleTable().Get<Thread>(owner_handle);
|
||||
ASSERT(owner);
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
thread->wakeup_callback = nullptr;
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
thread->InvalidateWakeupCallback();
|
||||
|
||||
owner->AddMutexWaiter(thread);
|
||||
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,8 +1023,8 @@ static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask)
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
*core = thread->ideal_core;
|
||||
*mask = thread->affinity_mask;
|
||||
*core = thread->GetIdealCore();
|
||||
*mask = thread->GetAffinityMask();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -930,11 +1040,13 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
|
||||
}
|
||||
|
||||
if (core == static_cast<u32>(THREADPROCESSORID_DEFAULT)) {
|
||||
ASSERT(thread->owner_process->GetDefaultProcessorID() !=
|
||||
static_cast<u8>(THREADPROCESSORID_DEFAULT));
|
||||
const u8 default_processor_id = thread->GetOwnerProcess()->GetDefaultProcessorID();
|
||||
|
||||
ASSERT(default_processor_id != static_cast<u8>(THREADPROCESSORID_DEFAULT));
|
||||
|
||||
// Set the target CPU to the one specified in the process' exheader.
|
||||
core = thread->owner_process->GetDefaultProcessorID();
|
||||
mask = 1ull << core;
|
||||
core = default_processor_id;
|
||||
mask = 1ULL << core;
|
||||
}
|
||||
|
||||
if (mask == 0) {
|
||||
@@ -945,7 +1057,7 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
|
||||
static constexpr u32 OnlyChangeMask = static_cast<u32>(-3);
|
||||
|
||||
if (core == OnlyChangeMask) {
|
||||
core = thread->ideal_core;
|
||||
core = thread->GetIdealCore();
|
||||
} else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
|
||||
}
|
||||
@@ -1003,6 +1115,29 @@ static ResultCode ClearEvent(Handle handle) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
|
||||
|
||||
// This function currently only allows retrieving a process' status.
|
||||
enum class InfoType {
|
||||
Status,
|
||||
};
|
||||
|
||||
const auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const auto process = kernel.HandleTable().Get<Process>(process_handle);
|
||||
if (!process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const auto info_type = static_cast<InfoType>(type);
|
||||
if (info_type != InfoType::Status) {
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
|
||||
*out = static_cast<u64>(process->GetStatus());
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct FunctionDef {
|
||||
using Func = void();
|
||||
@@ -1138,7 +1273,7 @@ static const FunctionDef SVC_Table[] = {
|
||||
{0x79, nullptr, "CreateProcess"},
|
||||
{0x7A, nullptr, "StartProcess"},
|
||||
{0x7B, nullptr, "TerminateProcess"},
|
||||
{0x7C, nullptr, "GetProcessInfo"},
|
||||
{0x7C, SvcWrap<GetProcessInfo>, "GetProcessInfo"},
|
||||
{0x7D, nullptr, "CreateResourceLimit"},
|
||||
{0x7E, nullptr, "SetResourceLimitLimitValue"},
|
||||
{0x7F, nullptr, "CallSecureMonitor"},
|
||||
|
||||
@@ -41,8 +41,8 @@ enum class GetInfoType : u64 {
|
||||
RandomEntropy = 11,
|
||||
PerformanceCounter = 0xF0000002,
|
||||
// 2.0.0+
|
||||
AddressSpaceBaseAddr = 12,
|
||||
AddressSpaceSize = 13,
|
||||
ASLRRegionBaseAddr = 12,
|
||||
ASLRRegionSize = 13,
|
||||
NewMapRegionBaseAddr = 14,
|
||||
NewMapRegionSize = 15,
|
||||
// 3.0.0+
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user