Compare commits
221 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be97fc884d | ||
|
|
30ff42b8cc | ||
|
|
561d79e034 | ||
|
|
6b48ba5271 | ||
|
|
fd891ee9c0 | ||
|
|
3f1f82a8c4 | ||
|
|
ae982a9bdf | ||
|
|
c5c184246d | ||
|
|
f945e9767c | ||
|
|
081f5c1dbf | ||
|
|
8bbc12b9c2 | ||
|
|
95dff555a4 | ||
|
|
e09505ff61 | ||
|
|
fa3f3cd07f | ||
|
|
6e4d2e672d | ||
|
|
2c0b0ad50d | ||
|
|
1cc5e6e9bc | ||
|
|
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 | ||
|
|
215b65fe75 | ||
|
|
fdb35760a7 | ||
|
|
bfe84f06f2 | ||
|
|
a4595bb939 | ||
|
|
e4daf4bee5 | ||
|
|
ac06105dfe | ||
|
|
aa48468862 | ||
|
|
62f9409ba3 | ||
|
|
120d8f3bf7 | ||
|
|
b4ace6ec6f | ||
|
|
4d139943f2 | ||
|
|
5f30f95e94 | ||
|
|
6d441828e6 | ||
|
|
003b44822a | ||
|
|
42fb4e82d3 | ||
|
|
4c2a94fa94 | ||
|
|
21b2411c44 | ||
|
|
3c9e70fefa | ||
|
|
bd14f397ce | ||
|
|
393042c09c | ||
|
|
1835911425 | ||
|
|
bc679c9b8c | ||
|
|
7c61f0c322 | ||
|
|
8391048a83 | ||
|
|
decc319634 | ||
|
|
f72046099a | ||
|
|
7d86a008e2 | ||
|
|
62225ae050 | ||
|
|
aa0c82e405 | ||
|
|
32fc31fb13 | ||
|
|
8f2ad3a66d | ||
|
|
5e2f23e2b1 | ||
|
|
df3799a008 | ||
|
|
3031aa9d27 | ||
|
|
29782273ec | ||
|
|
f543b43fd0 | ||
|
|
15cc729ebd | ||
|
|
ed2e0e85c9 | ||
|
|
871580dcd8 | ||
|
|
a9aa1db552 | ||
|
|
2e1cdde994 | ||
|
|
fefb003b23 | ||
|
|
ce452049d3 | ||
|
|
b92b4bbeaf | ||
|
|
f8e46d335f | ||
|
|
56901912cb | ||
|
|
541c550753 | ||
|
|
dccfe193a9 | ||
|
|
cf9d6c6f52 | ||
|
|
16145e2f21 | ||
|
|
4092907687 | ||
|
|
334090fe6f | ||
|
|
a63e6f9dfd | ||
|
|
0921558a9f | ||
|
|
97c0ac3545 | ||
|
|
fe5962e073 | ||
|
|
8d8366b602 | ||
|
|
f7b69d61f2 | ||
|
|
f4c24d0832 | ||
|
|
e3e51d3ddb | ||
|
|
b8f1506aa5 | ||
|
|
f7da74d18e | ||
|
|
fc2419e441 | ||
|
|
29ff84ea99 | ||
|
|
a75740b298 | ||
|
|
62048edc15 | ||
|
|
8731ac87fa | ||
|
|
70e86248fd | ||
|
|
861580f6d2 | ||
|
|
7ecdaaf189 | ||
|
|
c0445006af | ||
|
|
efcb83fb41 | ||
|
|
cc866d1384 | ||
|
|
92dd496fb9 | ||
|
|
9d163c00cc | ||
|
|
11104b4883 | ||
|
|
ab65fde9f4 | ||
|
|
e3b2ef9170 | ||
|
|
4654f89618 | ||
|
|
91b56c4928 | ||
|
|
1f92cbc059 | ||
|
|
fa9e0f9c8b | ||
|
|
f646ca874d | ||
|
|
cbb146069a | ||
|
|
57616f9758 | ||
|
|
28bef31ea8 | ||
|
|
14e2df5610 | ||
|
|
e42bb5e003 | ||
|
|
7b81e1e525 | ||
|
|
598e4d2f6c | ||
|
|
a58eefa7e4 | ||
|
|
5873b14efd | ||
|
|
1008be9b67 | ||
|
|
b67c1fdf38 | ||
|
|
83377113bf | ||
|
|
6c6f95d071 | ||
|
|
7fd598636e | ||
|
|
75603b005b | ||
|
|
367c52ff0d | ||
|
|
2513e086ab | ||
|
|
f2c1fd08f9 | ||
|
|
b3c2ec362b | ||
|
|
d42424ace0 | ||
|
|
fb6bc2c495 | ||
|
|
05db48b3f8 | ||
|
|
ba0873d33c | ||
|
|
050547b801 | ||
|
|
6eada3c57d | ||
|
|
940a711caf | ||
|
|
50a470eab8 | ||
|
|
16188acb50 | ||
|
|
44fdac334c | ||
|
|
3e5c3d0f16 | ||
|
|
b52343a428 | ||
|
|
c65d4d119f | ||
|
|
f68e324672 | ||
|
|
d6cbb3a3e0 | ||
|
|
bd8db3f7f8 | ||
|
|
44228ee3ed | ||
|
|
57b44200a2 | ||
|
|
d2dd1289bd | ||
|
|
41c6c4593a | ||
|
|
f020319a45 | ||
|
|
68aaa83836 | ||
|
|
bf2f2a715f | ||
|
|
c8f9bbbf85 | ||
|
|
d06f4cfc63 | ||
|
|
b91f7d5d67 |
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
dist/languages/* linguist-vendored
|
||||
dist/qt_themes/* linguist-vendored
|
||||
externals/* linguist-vendored
|
||||
*.h linguist-language=cpp
|
||||
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
|
||||
|
||||
@@ -24,7 +24,7 @@ matrix:
|
||||
- os: osx
|
||||
env: NAME="macos build"
|
||||
sudo: false
|
||||
osx_image: xcode9.3
|
||||
osx_image: xcode10
|
||||
install: "./.travis/macos/deps.sh"
|
||||
script: "./.travis/macos/build.sh"
|
||||
after_success: "./.travis/macos/upload.sh"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
set -o pipefail
|
||||
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.12
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.13
|
||||
export Qt5_DIR=$(brew --prefix)/opt/qt5
|
||||
export UNICORNDIR=$(pwd)/externals/unicorn
|
||||
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
|
||||
@@ -123,8 +127,6 @@ else()
|
||||
# Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
|
||||
add_definitions(/DWIN32_LEAN_AND_MEAN)
|
||||
|
||||
# set up output paths for executable binaries (.exe-files, and .dll-files on DLL-capable platforms)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
||||
|
||||
# Tweak optimization settings
|
||||
@@ -346,14 +348,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/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 171d11659d...4e6848d1c9
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++.
|
||||
@@ -13,3 +13,6 @@ endif()
|
||||
if (ENABLE_QT)
|
||||
add_subdirectory(yuzu)
|
||||
endif()
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
add_subdirectory(web_service)
|
||||
endif()
|
||||
|
||||
@@ -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
|
||||
@@ -87,6 +89,7 @@ add_library(common STATIC
|
||||
timer.cpp
|
||||
timer.h
|
||||
vector_math.h
|
||||
web_result.h
|
||||
)
|
||||
|
||||
if(ARCHITECTURE_x86_64)
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#define NAND_DIR "nand"
|
||||
#define SYSDATA_DIR "sysdata"
|
||||
#define KEYS_DIR "keys"
|
||||
#define LOAD_DIR "load"
|
||||
#define DUMP_DIR "dump"
|
||||
#define LOG_DIR "log"
|
||||
|
||||
// Filenames
|
||||
|
||||
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
|
||||
@@ -705,6 +705,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
|
||||
#endif
|
||||
paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
|
||||
// TODO: Put the logs in a better location for each OS
|
||||
|
||||
@@ -29,6 +29,8 @@ enum class UserPath {
|
||||
NANDDir,
|
||||
RootDir,
|
||||
SDMCDir,
|
||||
LoadDir,
|
||||
DumpDir,
|
||||
SysDataDir,
|
||||
UserDir,
|
||||
};
|
||||
@@ -283,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;
|
||||
|
||||
@@ -183,6 +183,7 @@ void FileBackend::Write(const Entry& entry) {
|
||||
SUB(Service, FS) \
|
||||
SUB(Service, GRC) \
|
||||
SUB(Service, HID) \
|
||||
SUB(Service, IRS) \
|
||||
SUB(Service, LBL) \
|
||||
SUB(Service, LDN) \
|
||||
SUB(Service, LDR) \
|
||||
|
||||
@@ -70,6 +70,7 @@ enum class Class : ClassType {
|
||||
Service_FS, ///< The FS (Filesystem) service
|
||||
Service_GRC, ///< The game recording service
|
||||
Service_HID, ///< The HID (Human interface device) service
|
||||
Service_IRS, ///< The IRS service
|
||||
Service_LBL, ///< The LBL (LCD backlight) service
|
||||
Service_LDN, ///< The LDN (Local domain network) service
|
||||
Service_LDR, ///< The loader 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
24
src/common/web_result.h
Normal file
24
src/common/web_result.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
||||
@@ -32,6 +32,10 @@ add_library(core STATIC
|
||||
file_sys/control_metadata.h
|
||||
file_sys/directory.h
|
||||
file_sys/errors.h
|
||||
file_sys/fsmitm_romfsbuild.cpp
|
||||
file_sys/fsmitm_romfsbuild.h
|
||||
file_sys/ips_layer.cpp
|
||||
file_sys/ips_layer.h
|
||||
file_sys/mode.h
|
||||
file_sys/nca_metadata.cpp
|
||||
file_sys/nca_metadata.h
|
||||
@@ -59,10 +63,13 @@ add_library(core STATIC
|
||||
file_sys/vfs.h
|
||||
file_sys/vfs_concat.cpp
|
||||
file_sys/vfs_concat.h
|
||||
file_sys/vfs_layered.cpp
|
||||
file_sys/vfs_layered.h
|
||||
file_sys/vfs_offset.cpp
|
||||
file_sys/vfs_offset.h
|
||||
file_sys/vfs_real.cpp
|
||||
file_sys/vfs_real.h
|
||||
file_sys/vfs_static.h
|
||||
file_sys/vfs_vector.cpp
|
||||
file_sys/vfs_vector.h
|
||||
file_sys/xts_archive.cpp
|
||||
@@ -389,6 +396,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)
|
||||
add_definitions(-DENABLE_WEB_SERVICE)
|
||||
target_link_libraries(core PUBLIC json-headers web_service)
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
target_sources(core PRIVATE
|
||||
|
||||
@@ -22,10 +22,16 @@ public:
|
||||
std::array<u64, 31> cpu_registers;
|
||||
u64 sp;
|
||||
u64 pc;
|
||||
u64 pstate;
|
||||
u32 pstate;
|
||||
std::array<u8, 4> padding;
|
||||
std::array<u128, 32> vector_registers;
|
||||
u64 fpcr;
|
||||
u32 fpcr;
|
||||
u32 fpsr;
|
||||
u64 tpidr;
|
||||
};
|
||||
// Internally within the kernel, it expects the AArch64 version of the
|
||||
// thread context to be 800 bytes in size.
|
||||
static_assert(sizeof(ThreadContext) == 0x320);
|
||||
|
||||
/// Runs the CPU until an event happens
|
||||
virtual void Run() = 0;
|
||||
|
||||
@@ -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,8 @@ public:
|
||||
};
|
||||
|
||||
std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
auto** const page_table = Core::CurrentProcess()->vm_manager.page_table.pointers.data();
|
||||
auto& current_process = Core::CurrentProcess();
|
||||
auto** const page_table = current_process->VMManager().page_table.pointers.data();
|
||||
|
||||
Dynarmic::A64::UserConfig config;
|
||||
|
||||
@@ -138,7 +139,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
|
||||
// Memory
|
||||
config.page_table = reinterpret_cast<void**>(page_table);
|
||||
config.page_table_address_space_bits = Memory::ADDRESS_SPACE_BITS;
|
||||
config.page_table_address_space_bits = current_process->VMManager().GetAddressSpaceWidth();
|
||||
config.silently_mirror_page_table = false;
|
||||
|
||||
// Multi-process state
|
||||
@@ -174,7 +175,7 @@ ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<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)} {
|
||||
ThreadContext ctx;
|
||||
ThreadContext ctx{};
|
||||
inner_unicorn.SaveContext(ctx);
|
||||
PageTableChanged();
|
||||
LoadContext(ctx);
|
||||
@@ -246,15 +247,19 @@ void ARM_Dynarmic::SaveContext(ThreadContext& ctx) {
|
||||
ctx.pstate = jit->GetPstate();
|
||||
ctx.vector_registers = jit->GetVectors();
|
||||
ctx.fpcr = jit->GetFpcr();
|
||||
ctx.fpsr = jit->GetFpsr();
|
||||
ctx.tpidr = cb->tpidr_el0;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::LoadContext(const ThreadContext& ctx) {
|
||||
jit->SetRegisters(ctx.cpu_registers);
|
||||
jit->SetSP(ctx.sp);
|
||||
jit->SetPC(ctx.pc);
|
||||
jit->SetPstate(static_cast<u32>(ctx.pstate));
|
||||
jit->SetPstate(ctx.pstate);
|
||||
jit->SetVectors(ctx.vector_registers);
|
||||
jit->SetFpcr(static_cast<u32>(ctx.fpcr));
|
||||
jit->SetFpcr(ctx.fpcr);
|
||||
jit->SetFpsr(ctx.fpsr);
|
||||
SetTPIDR_EL0(ctx.tpidr);
|
||||
}
|
||||
|
||||
void ARM_Dynarmic::PrepareReschedule() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -64,7 +64,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
if (concat.empty())
|
||||
return nullptr;
|
||||
|
||||
return FileSys::ConcatenateFiles(concat, dir->GetName());
|
||||
return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
|
||||
}
|
||||
|
||||
return vfs->OpenFile(path, FileSys::Mode::Read);
|
||||
@@ -202,7 +202,7 @@ struct System::Impl {
|
||||
return init_result;
|
||||
}
|
||||
|
||||
const Loader::ResultStatus load_result{app_loader->Load(kernel.CurrentProcess())};
|
||||
const Loader::ResultStatus load_result{app_loader->Load(*kernel.CurrentProcess())};
|
||||
if (load_result != Loader::ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
|
||||
Shutdown();
|
||||
|
||||
@@ -55,16 +55,16 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_shared<ARM_Dynarmic>(exclusive_monitor, core_index);
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index);
|
||||
#else
|
||||
arm_interface = std::make_shared<ARM_Unicorn>();
|
||||
arm_interface = std::make_unique<ARM_Unicorn>();
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
arm_interface = std::make_shared<ARM_Unicorn>();
|
||||
arm_interface = std::make_unique<ARM_Unicorn>();
|
||||
}
|
||||
|
||||
scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get());
|
||||
scheduler = std::make_shared<Kernel::Scheduler>(*arm_interface);
|
||||
}
|
||||
|
||||
Cpu::~Cpu() = default;
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
private:
|
||||
void Reschedule();
|
||||
|
||||
std::shared_ptr<ARM_Interface> arm_interface;
|
||||
std::unique_ptr<ARM_Interface> arm_interface;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::shared_ptr<Kernel::Scheduler> scheduler;
|
||||
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "core/file_sys/bis_factory.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
BISFactory::BISFactory(VirtualDir nand_root_)
|
||||
: nand_root(std::move(nand_root_)),
|
||||
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>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
|
||||
usrnand_cache(std::make_shared<RegisteredCache>(
|
||||
@@ -24,4 +25,11 @@ std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache;
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
|
||||
// LayeredFS doesn't work on updates and title id-less homebrew
|
||||
if (title_id == 0 || (title_id & 0x800) > 0)
|
||||
return nullptr;
|
||||
return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -17,14 +17,17 @@ class RegisteredCache;
|
||||
/// registered caches.
|
||||
class BISFactory {
|
||||
public:
|
||||
explicit BISFactory(VirtualDir nand_root);
|
||||
explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
|
||||
~BISFactory();
|
||||
|
||||
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
|
||||
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
|
||||
|
||||
VirtualDir GetModificationLoadRoot(u64 title_id) const;
|
||||
|
||||
private:
|
||||
VirtualDir nand_root;
|
||||
VirtualDir load_root;
|
||||
|
||||
std::shared_ptr<RegisteredCache> sysnand_cache;
|
||||
std::shared_ptr<RegisteredCache> usrnand_cache;
|
||||
|
||||
@@ -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;
|
||||
|
||||
384
src/core/file_sys/fsmitm_romfsbuild.cpp
Normal file
384
src/core/file_sys/fsmitm_romfsbuild.cpp
Normal file
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Adapted by DarkLordZach for use/interaction with yuzu
|
||||
*
|
||||
* Modifications Copyright 2018 yuzu emulator team
|
||||
* Licensed under GPLv2 or any later version
|
||||
* Refer to the license.txt file included.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#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"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 FS_MAX_PATH = 0x301;
|
||||
|
||||
constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
|
||||
constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200;
|
||||
|
||||
// Types for building a RomFS.
|
||||
struct RomFSHeader {
|
||||
u64 header_size;
|
||||
u64 dir_hash_table_ofs;
|
||||
u64 dir_hash_table_size;
|
||||
u64 dir_table_ofs;
|
||||
u64 dir_table_size;
|
||||
u64 file_hash_table_ofs;
|
||||
u64 file_hash_table_size;
|
||||
u64 file_table_ofs;
|
||||
u64 file_table_size;
|
||||
u64 file_partition_ofs;
|
||||
};
|
||||
static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
|
||||
|
||||
struct RomFSDirectoryEntry {
|
||||
u32 parent;
|
||||
u32 sibling;
|
||||
u32 child;
|
||||
u32 file;
|
||||
u32 hash;
|
||||
u32 name_size;
|
||||
};
|
||||
static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size.");
|
||||
|
||||
struct RomFSFileEntry {
|
||||
u32 parent;
|
||||
u32 sibling;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u32 hash;
|
||||
u32 name_size;
|
||||
};
|
||||
static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size.");
|
||||
|
||||
struct RomFSBuildFileContext;
|
||||
|
||||
struct RomFSBuildDirectoryContext {
|
||||
std::string path;
|
||||
u32 cur_path_ofs = 0;
|
||||
u32 path_len = 0;
|
||||
u32 entry_offset = 0;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> child;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> sibling;
|
||||
std::shared_ptr<RomFSBuildFileContext> file;
|
||||
};
|
||||
|
||||
struct RomFSBuildFileContext {
|
||||
std::string path;
|
||||
u32 cur_path_ofs = 0;
|
||||
u32 path_len = 0;
|
||||
u32 entry_offset = 0;
|
||||
u64 offset = 0;
|
||||
u64 size = 0;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent;
|
||||
std::shared_ptr<RomFSBuildFileContext> sibling;
|
||||
VirtualFile source;
|
||||
};
|
||||
|
||||
static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, std::size_t path_len) {
|
||||
u32 hash = parent ^ 123456789;
|
||||
for (u32 i = 0; i < path_len; i++) {
|
||||
hash = (hash >> 5) | (hash << 27);
|
||||
hash ^= path[start + i];
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static u64 romfs_get_hash_table_count(u64 num_entries) {
|
||||
if (num_entries < 3) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (num_entries < 19) {
|
||||
return num_entries | 1;
|
||||
}
|
||||
|
||||
u64 count = num_entries;
|
||||
while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
|
||||
count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent) {
|
||||
std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
|
||||
|
||||
VirtualDir dir;
|
||||
|
||||
if (parent->path_len == 0)
|
||||
dir = root_romfs;
|
||||
else
|
||||
dir = root_romfs->GetDirectoryRelative(parent->path);
|
||||
|
||||
const auto entries = dir->GetEntries();
|
||||
|
||||
for (const auto& kv : entries) {
|
||||
if (kv.second == VfsEntryType::Directory) {
|
||||
const auto child = std::make_shared<RomFSBuildDirectoryContext>();
|
||||
// Set child's path.
|
||||
child->cur_path_ofs = parent->path_len + 1;
|
||||
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);
|
||||
|
||||
if (AddDirectory(parent, child)) {
|
||||
child_dirs.push_back(child);
|
||||
}
|
||||
} else {
|
||||
const auto child = std::make_shared<RomFSBuildFileContext>();
|
||||
// Set child's path.
|
||||
child->cur_path_ofs = parent->path_len + 1;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& child : child_dirs) {
|
||||
this->VisitDirectory(root_romfs, ext, child);
|
||||
}
|
||||
}
|
||||
|
||||
bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) {
|
||||
// Check whether it's already in the known directories.
|
||||
const auto existing = directories.find(dir_ctx->path);
|
||||
if (existing != directories.end())
|
||||
return false;
|
||||
|
||||
// Add a new directory.
|
||||
num_dirs++;
|
||||
dir_table_size +=
|
||||
sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4);
|
||||
dir_ctx->parent = parent_dir_ctx;
|
||||
directories.emplace(dir_ctx->path, dir_ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
|
||||
std::shared_ptr<RomFSBuildFileContext> file_ctx) {
|
||||
// Check whether it's already in the known files.
|
||||
const auto existing = files.find(file_ctx->path);
|
||||
if (existing != files.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add a new file.
|
||||
num_files++;
|
||||
file_table_size +=
|
||||
sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4);
|
||||
file_ctx->parent = parent_dir_ctx;
|
||||
files.emplace(file_ctx->path, file_ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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, ext, root);
|
||||
}
|
||||
|
||||
RomFSBuildContext::~RomFSBuildContext() = default;
|
||||
|
||||
std::map<u64, VirtualFile> RomFSBuildContext::Build() {
|
||||
const u64 dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs);
|
||||
const u64 file_hash_table_entry_count = romfs_get_hash_table_count(num_files);
|
||||
dir_hash_table_size = 4 * dir_hash_table_entry_count;
|
||||
file_hash_table_size = 4 * file_hash_table_entry_count;
|
||||
|
||||
// Assign metadata pointers
|
||||
RomFSHeader header{};
|
||||
|
||||
std::vector<u32> dir_hash_table(dir_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
|
||||
std::vector<u32> file_hash_table(file_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
|
||||
|
||||
std::vector<u8> dir_table(dir_table_size);
|
||||
std::vector<u8> file_table(file_table_size);
|
||||
|
||||
std::shared_ptr<RomFSBuildFileContext> cur_file;
|
||||
|
||||
// Determine file offsets.
|
||||
u32 entry_offset = 0;
|
||||
std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr;
|
||||
for (const auto& it : files) {
|
||||
cur_file = it.second;
|
||||
file_partition_size = Common::AlignUp(file_partition_size, 16);
|
||||
cur_file->offset = file_partition_size;
|
||||
file_partition_size += cur_file->size;
|
||||
cur_file->entry_offset = entry_offset;
|
||||
entry_offset += sizeof(RomFSFileEntry) +
|
||||
Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4);
|
||||
prev_file = cur_file;
|
||||
}
|
||||
// Assign deferred parent/sibling ownership.
|
||||
for (auto it = files.rbegin(); it != files.rend(); ++it) {
|
||||
cur_file = it->second;
|
||||
cur_file->sibling = cur_file->parent->file;
|
||||
cur_file->parent->file = cur_file;
|
||||
}
|
||||
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> cur_dir;
|
||||
|
||||
// Determine directory offsets.
|
||||
entry_offset = 0;
|
||||
for (const auto& it : directories) {
|
||||
cur_dir = it.second;
|
||||
cur_dir->entry_offset = entry_offset;
|
||||
entry_offset += sizeof(RomFSDirectoryEntry) +
|
||||
Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4);
|
||||
}
|
||||
// Assign deferred parent/sibling ownership.
|
||||
for (auto it = directories.rbegin(); it->second != root; ++it) {
|
||||
cur_dir = it->second;
|
||||
cur_dir->sibling = cur_dir->parent->child;
|
||||
cur_dir->parent->child = cur_dir;
|
||||
}
|
||||
|
||||
std::map<u64, VirtualFile> out;
|
||||
|
||||
// Populate file tables.
|
||||
for (const auto& it : files) {
|
||||
cur_file = it.second;
|
||||
RomFSFileEntry cur_entry{};
|
||||
|
||||
cur_entry.parent = cur_file->parent->entry_offset;
|
||||
cur_entry.sibling =
|
||||
cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
|
||||
cur_entry.offset = cur_file->offset;
|
||||
cur_entry.size = cur_file->size;
|
||||
|
||||
const auto name_size = cur_file->path_len - cur_file->cur_path_ofs;
|
||||
const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path,
|
||||
cur_file->cur_path_ofs, name_size);
|
||||
cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count];
|
||||
file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
|
||||
|
||||
cur_entry.name_size = name_size;
|
||||
|
||||
out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->source);
|
||||
std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry));
|
||||
std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0,
|
||||
Common::AlignUp(cur_entry.name_size, 4));
|
||||
std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry),
|
||||
cur_file->path.data() + cur_file->cur_path_ofs, name_size);
|
||||
}
|
||||
|
||||
// Populate dir tables.
|
||||
for (const auto& it : directories) {
|
||||
cur_dir = it.second;
|
||||
RomFSDirectoryEntry cur_entry{};
|
||||
|
||||
cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset;
|
||||
cur_entry.sibling =
|
||||
cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
|
||||
cur_entry.child =
|
||||
cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
|
||||
cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
|
||||
|
||||
const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
|
||||
const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset,
|
||||
cur_dir->path, cur_dir->cur_path_ofs, name_size);
|
||||
cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count];
|
||||
dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
|
||||
|
||||
cur_entry.name_size = name_size;
|
||||
|
||||
std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
|
||||
sizeof(RomFSDirectoryEntry));
|
||||
std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0,
|
||||
Common::AlignUp(cur_entry.name_size, 4));
|
||||
std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry),
|
||||
cur_dir->path.data() + cur_dir->cur_path_ofs, name_size);
|
||||
}
|
||||
|
||||
// Set header fields.
|
||||
header.header_size = sizeof(RomFSHeader);
|
||||
header.file_hash_table_size = file_hash_table_size;
|
||||
header.file_table_size = file_table_size;
|
||||
header.dir_hash_table_size = dir_hash_table_size;
|
||||
header.dir_table_size = dir_table_size;
|
||||
header.file_partition_ofs = ROMFS_FILEPARTITION_OFS;
|
||||
header.dir_hash_table_ofs = Common::AlignUp(header.file_partition_ofs + file_partition_size, 4);
|
||||
header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size;
|
||||
header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size;
|
||||
header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size;
|
||||
|
||||
std::vector<u8> header_data(sizeof(RomFSHeader));
|
||||
std::memcpy(header_data.data(), &header, header_data.size());
|
||||
out.emplace(0, std::make_shared<VectorVfsFile>(std::move(header_data)));
|
||||
|
||||
std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size +
|
||||
dir_table_size);
|
||||
std::size_t index = 0;
|
||||
std::memcpy(metadata.data(), dir_hash_table.data(), dir_hash_table.size() * sizeof(u32));
|
||||
index += dir_hash_table.size() * sizeof(u32);
|
||||
std::memcpy(metadata.data() + index, dir_table.data(), dir_table.size());
|
||||
index += dir_table.size();
|
||||
std::memcpy(metadata.data() + index, file_hash_table.data(),
|
||||
file_hash_table.size() * sizeof(u32));
|
||||
index += file_hash_table.size() * sizeof(u32);
|
||||
std::memcpy(metadata.data() + index, file_table.data(), file_table.size());
|
||||
out.emplace(header.dir_hash_table_ofs, std::make_shared<VectorVfsFile>(std::move(metadata)));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
72
src/core/file_sys/fsmitm_romfsbuild.h
Normal file
72
src/core/file_sys/fsmitm_romfsbuild.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Adapted by DarkLordZach for use/interaction with yuzu
|
||||
*
|
||||
* Modifications Copyright 2018 yuzu emulator team
|
||||
* Licensed under GPLv2 or any later version
|
||||
* Refer to the license.txt file included.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <boost/detail/container_fwd.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct RomFSBuildDirectoryContext;
|
||||
struct RomFSBuildFileContext;
|
||||
struct RomFSDirectoryEntry;
|
||||
struct RomFSFileEntry;
|
||||
|
||||
class RomFSBuildContext {
|
||||
public:
|
||||
explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr);
|
||||
~RomFSBuildContext();
|
||||
|
||||
// This finalizes the context.
|
||||
std::map<u64, VirtualFile> Build();
|
||||
|
||||
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;
|
||||
u64 num_dirs = 0;
|
||||
u64 num_files = 0;
|
||||
u64 dir_table_size = 0;
|
||||
u64 file_table_size = 0;
|
||||
u64 dir_hash_table_size = 0;
|
||||
u64 file_hash_table_size = 0;
|
||||
u64 file_partition_size = 0;
|
||||
|
||||
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);
|
||||
bool AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
|
||||
std::shared_ptr<RomFSBuildFileContext> file_ctx);
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
297
src/core/file_sys/ips_layer.cpp
Normal file
297
src/core/file_sys/ips_layer.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <sstream>
|
||||
#include "common/assert.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class IPSFileType {
|
||||
IPS,
|
||||
IPS32,
|
||||
Error,
|
||||
};
|
||||
|
||||
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{
|
||||
std::pair{"\\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)
|
||||
return IPSFileType::Error;
|
||||
if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'})
|
||||
return IPSFileType::IPS;
|
||||
if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'})
|
||||
return IPSFileType::IPS32;
|
||||
return IPSFileType::Error;
|
||||
}
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
if (in == nullptr || ips == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const auto type = IdentifyMagic(ips->ReadBytes(0x5));
|
||||
if (type == IPSFileType::Error)
|
||||
return nullptr;
|
||||
|
||||
auto in_data = in->ReadAllBytes();
|
||||
|
||||
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
|
||||
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'}) {
|
||||
break;
|
||||
}
|
||||
|
||||
u32 real_offset{};
|
||||
if (type == IPSFileType::IPS32)
|
||||
real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
|
||||
else
|
||||
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
|
||||
|
||||
u16 data_size{};
|
||||
if (ips->ReadObject(&data_size, offset) != sizeof(u16))
|
||||
return nullptr;
|
||||
data_size = Common::swap16(data_size);
|
||||
offset += sizeof(u16);
|
||||
|
||||
if (data_size == 0) { // RLE
|
||||
u16 rle_size{};
|
||||
if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
|
||||
return nullptr;
|
||||
rle_size = Common::swap16(data_size);
|
||||
offset += sizeof(u16);
|
||||
|
||||
const auto data = ips->ReadByte(offset++);
|
||||
if (data == boost::none)
|
||||
return nullptr;
|
||||
|
||||
if (real_offset + rle_size > in_data.size())
|
||||
rle_size = 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;
|
||||
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'})
|
||||
return nullptr;
|
||||
return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
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 += 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>(in_data, in->GetName(), in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
45
src/core/file_sys/ips_layer.h
Normal file
45
src/core/file_sys/ips_layer.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#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:
|
||||
void ParseFlag(const std::string& flag);
|
||||
void Parse();
|
||||
|
||||
bool valid = false;
|
||||
|
||||
struct IPSwitchPatch {
|
||||
std::string name;
|
||||
bool enabled;
|
||||
std::map<u32, std::vector<u8>> records;
|
||||
};
|
||||
|
||||
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
|
||||
@@ -2,21 +2,36 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_layered.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
|
||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
|
||||
struct NSOBuildHeader {
|
||||
u32_le magic;
|
||||
INSERT_PADDING_BYTES(0x3C);
|
||||
std::array<u8, 0x20> build_id;
|
||||
INSERT_PADDING_BYTES(0xA0);
|
||||
};
|
||||
static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
|
||||
|
||||
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
||||
std::array<u8, sizeof(u32)> bytes{};
|
||||
@@ -31,14 +46,6 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
||||
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
||||
}
|
||||
|
||||
constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{
|
||||
"Update",
|
||||
};
|
||||
|
||||
std::string FormatPatchTypeName(PatchType type) {
|
||||
return PATCH_TYPE_NAMES.at(static_cast<std::size_t>(type));
|
||||
}
|
||||
|
||||
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
|
||||
|
||||
PatchManager::~PatchManager() = default;
|
||||
@@ -66,8 +73,147 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
|
||||
ContentRecordType type) const {
|
||||
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") {
|
||||
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)
|
||||
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 out;
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
if (nso.size() < 0x100)
|
||||
return nso;
|
||||
|
||||
NSOBuildHeader header;
|
||||
std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
|
||||
|
||||
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
||||
return nso;
|
||||
|
||||
const auto build_id_raw = Common::HexArrayToString(header.build_id);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
const auto patches = CollectPatches(patch_dirs, build_id);
|
||||
|
||||
auto out = nso;
|
||||
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)
|
||||
return nso;
|
||||
std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
|
||||
return out;
|
||||
}
|
||||
|
||||
bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
const auto build_id_raw = Common::HexArrayToString(build_id_);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<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));
|
||||
|
||||
auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
|
||||
if (layered == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
|
||||
|
||||
auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
|
||||
if (packed == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Loader, " RomFS: LayeredFS patches applied successfully");
|
||||
romfs = std::move(packed);
|
||||
}
|
||||
|
||||
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
|
||||
VirtualFile update_raw) const {
|
||||
LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
|
||||
static_cast<u8>(type));
|
||||
|
||||
@@ -87,33 +233,115 @@ 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
|
||||
ApplyLayeredFS(romfs, title_id, type);
|
||||
|
||||
return romfs;
|
||||
}
|
||||
|
||||
std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
|
||||
std::map<PatchType, std::string> out;
|
||||
static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
|
||||
if (to.empty())
|
||||
to += with;
|
||||
else
|
||||
to += ", " + with;
|
||||
}
|
||||
|
||||
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(
|
||||
VirtualFile update_raw) const {
|
||||
std::map<std::string, std::string, std::less<>> out;
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
PatchManager update{update_tid};
|
||||
auto [nacp, discard_icon_file] = update.GetControlMetadata();
|
||||
|
||||
if (nacp != nullptr) {
|
||||
out[PatchType::Update] = nacp->GetVersionString();
|
||||
out.insert_or_assign("Update", nacp->GetVersionString());
|
||||
} else {
|
||||
if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
const auto meta_ver = installed->GetEntryVersion(update_tid);
|
||||
if (meta_ver == boost::none || meta_ver.get() == 0) {
|
||||
out[PatchType::Update] = "";
|
||||
out.insert_or_assign("Update", "");
|
||||
} else {
|
||||
out[PatchType::Update] =
|
||||
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements);
|
||||
out.insert_or_assign(
|
||||
"Update",
|
||||
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
out.insert_or_assign("Update", "PACKED");
|
||||
}
|
||||
}
|
||||
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
|
||||
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");
|
||||
|
||||
if (types.empty())
|
||||
continue;
|
||||
|
||||
out.insert_or_assign(mod->GetName(), types);
|
||||
}
|
||||
}
|
||||
|
||||
// DLC
|
||||
const auto dlc_entries = installed->ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
|
||||
std::vector<RegisteredCacheEntry> dlc_match;
|
||||
dlc_match.reserve(dlc_entries.size());
|
||||
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||
[this, &installed](const RegisteredCacheEntry& entry) {
|
||||
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
|
||||
installed->GetEntry(entry)->GetStatus() ==
|
||||
Loader::ResultStatus::Success;
|
||||
});
|
||||
if (!dlc_match.empty()) {
|
||||
// Ensure sorted so DLC IDs show in order.
|
||||
std::sort(dlc_match.begin(), dlc_match.end());
|
||||
|
||||
std::string list;
|
||||
for (size_t i = 0; i < dlc_match.size() - 1; ++i)
|
||||
list += fmt::format("{}, ", dlc_match[i].title_id & 0x7FF);
|
||||
|
||||
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
|
||||
|
||||
out.insert_or_assign("DLC", std::move(list));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,6 @@ enum class TitleVersionFormat : u8 {
|
||||
std::string FormatTitleVersion(u32 version,
|
||||
TitleVersionFormat format = TitleVersionFormat::ThreeElements);
|
||||
|
||||
enum class PatchType {
|
||||
Update,
|
||||
};
|
||||
|
||||
std::string FormatPatchTypeName(PatchType type);
|
||||
|
||||
// A centralized class to manage patches to games.
|
||||
class PatchManager {
|
||||
public:
|
||||
@@ -40,14 +34,26 @@ public:
|
||||
// - Game Updates
|
||||
VirtualDir PatchExeFS(VirtualDir exefs) const;
|
||||
|
||||
// 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.
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - 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 v80 will return {Update, 80}
|
||||
std::map<PatchType, std::string> GetPatchVersionNames() const;
|
||||
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
|
||||
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.
|
||||
|
||||
@@ -83,10 +83,12 @@ void ProgramMetadata::Print() const {
|
||||
|
||||
auto address_space = "Unknown";
|
||||
switch (npdm_header.address_space_type) {
|
||||
case ProgramAddressSpaceType::Is64Bit:
|
||||
case ProgramAddressSpaceType::Is36Bit:
|
||||
case ProgramAddressSpaceType::Is39Bit:
|
||||
address_space = "64-bit";
|
||||
break;
|
||||
case ProgramAddressSpaceType::Is32Bit:
|
||||
case ProgramAddressSpaceType::Is32BitNoMap:
|
||||
address_space = "32-bit";
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ enum class ResultStatus : u16;
|
||||
namespace FileSys {
|
||||
|
||||
enum class ProgramAddressSpaceType : u8 {
|
||||
Is64Bit = 1,
|
||||
Is32Bit = 2,
|
||||
Is32Bit = 0,
|
||||
Is36Bit = 1,
|
||||
Is32BitNoMap = 2,
|
||||
Is39Bit = 3,
|
||||
};
|
||||
|
||||
enum class ProgramFilePermission : u64 {
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// The size of blocks to use when vfs raw copying into nand.
|
||||
constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
|
||||
|
||||
std::string RegisteredCacheEntry::DebugInfo() const {
|
||||
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
|
||||
}
|
||||
@@ -121,7 +125,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
|
||||
if (concat.empty())
|
||||
return nullptr;
|
||||
|
||||
file = FileSys::ConcatenateFiles(concat);
|
||||
file = ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName());
|
||||
}
|
||||
|
||||
return file;
|
||||
@@ -480,7 +484,8 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
|
||||
auto out = dir->CreateFileRelative(path);
|
||||
if (out == nullptr)
|
||||
return InstallResult::ErrorCopyFailed;
|
||||
return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
|
||||
return copy(in, out, VFS_RC_LARGE_COPY_BLOCK) ? InstallResult::Success
|
||||
: InstallResult::ErrorCopyFailed;
|
||||
}
|
||||
|
||||
bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
|
||||
|
||||
@@ -27,7 +27,7 @@ struct ContentRecord;
|
||||
|
||||
using NcaID = std::array<u8, 0x10>;
|
||||
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
|
||||
using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
|
||||
|
||||
enum class InstallResult {
|
||||
Success,
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/fsmitm_romfsbuild.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
@@ -98,7 +100,7 @@ void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file
|
||||
}
|
||||
}
|
||||
|
||||
VirtualDir ExtractRomFS(VirtualFile file) {
|
||||
VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
|
||||
RomFSHeader header{};
|
||||
if (file->ReadObject(&header) != sizeof(RomFSHeader))
|
||||
return nullptr;
|
||||
@@ -117,9 +119,22 @@ VirtualDir ExtractRomFS(VirtualFile file) {
|
||||
|
||||
VirtualDir out = std::move(root);
|
||||
|
||||
while (out->GetSubdirectory("") != nullptr)
|
||||
out = out->GetSubdirectory("");
|
||||
while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
|
||||
if (out->GetSubdirectories().front()->GetName() == "data" &&
|
||||
type == RomFSExtractionType::Truncated)
|
||||
break;
|
||||
out = out->GetSubdirectories().front();
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
|
||||
if (dir == nullptr)
|
||||
return nullptr;
|
||||
|
||||
RomFSBuildContext ctx{dir, ext};
|
||||
return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
@@ -12,6 +13,8 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct RomFSHeader;
|
||||
|
||||
struct IVFCLevel {
|
||||
u64_le offset;
|
||||
u64_le size;
|
||||
@@ -29,8 +32,18 @@ struct IVFCHeader {
|
||||
};
|
||||
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
|
||||
|
||||
enum class RomFSExtractionType {
|
||||
Full, // Includes data directory
|
||||
Truncated, // Traverses into data directory
|
||||
};
|
||||
|
||||
// Converts a RomFS binary blob to VFS Filesystem
|
||||
// Returns nullptr on failure
|
||||
VirtualDir ExtractRomFS(VirtualFile file);
|
||||
VirtualDir ExtractRomFS(VirtualFile file,
|
||||
RomFSExtractionType type = RomFSExtractionType::Truncated);
|
||||
|
||||
// Converts a VFS filesystem into a RomFS binary
|
||||
// Returns nullptr on failure
|
||||
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr);
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -30,45 +30,49 @@ 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()->program_id);
|
||||
return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset));
|
||||
const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID());
|
||||
return MakeResult<VirtualFile>(
|
||||
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
|
||||
std::shared_ptr<NCA> res;
|
||||
|
||||
switch (storage) {
|
||||
case StorageId::NandSystem: {
|
||||
const auto res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
if (res == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
const auto romfs = res->GetRomFS();
|
||||
if (romfs == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
return MakeResult<VirtualFile>(romfs);
|
||||
}
|
||||
case StorageId::NandUser: {
|
||||
const auto res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
|
||||
if (res == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
const auto romfs = res->GetRomFS();
|
||||
if (romfs == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
return MakeResult<VirtualFile>(romfs);
|
||||
}
|
||||
case StorageId::None:
|
||||
res = Service::FileSystem::GetUnionContents()->GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::NandSystem:
|
||||
res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::NandUser:
|
||||
res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::SdCard:
|
||||
res = Service::FileSystem::GetSDMCContents()->GetEntry(title_id, type);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
|
||||
}
|
||||
|
||||
if (res == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
const auto romfs = res->GetRomFS();
|
||||
if (romfs == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
return MakeResult<VirtualFile>(romfs);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
|
||||
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
|
||||
// be interpreted as the title id of the current process.
|
||||
if (type == SaveDataType::SaveData && title_id == 0)
|
||||
title_id = Core::CurrentProcess()->program_id;
|
||||
title_id = Core::CurrentProcess()->GetTitleID();
|
||||
|
||||
std::string out;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -399,6 +399,15 @@ bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
|
||||
return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
|
||||
}
|
||||
|
||||
std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const {
|
||||
std::map<std::string, VfsEntryType, std::less<>> out;
|
||||
for (const auto& dir : GetSubdirectories())
|
||||
out.emplace(dir->GetName(), VfsEntryType::Directory);
|
||||
for (const auto& file : GetFiles())
|
||||
out.emplace(file->GetName(), VfsEntryType::File);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string VfsDirectory::GetFullPath() const {
|
||||
if (IsRoot())
|
||||
return GetName();
|
||||
@@ -454,13 +463,41 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
|
||||
if (src == nullptr || dest == nullptr)
|
||||
bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size) {
|
||||
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
|
||||
return false;
|
||||
if (!dest->Resize(src->GetSize()))
|
||||
return false;
|
||||
std::vector<u8> data = src->ReadAllBytes();
|
||||
return dest->WriteBytes(data, 0) == data.size();
|
||||
|
||||
std::vector<u8> temp(std::min(block_size, src->GetSize()));
|
||||
for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
|
||||
const auto read = std::min(block_size, src->GetSize() - i);
|
||||
const auto block = src->Read(temp.data(), read, i);
|
||||
|
||||
if (dest->Write(temp.data(), read, i) != read)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size) {
|
||||
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
|
||||
return false;
|
||||
|
||||
for (const auto& file : src->GetFiles()) {
|
||||
const auto out = dest->CreateFile(file->GetName());
|
||||
if (!VfsRawCopy(file, out, block_size))
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& dir : src->GetSubdirectories()) {
|
||||
const auto out = dest->CreateSubdirectory(dir->GetName());
|
||||
if (!VfsRawCopyD(dir, out, block_size))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -265,6 +266,10 @@ public:
|
||||
// dest.
|
||||
virtual bool Copy(std::string_view src, std::string_view dest);
|
||||
|
||||
// Gets all of the entries directly in the directory (files and dirs), returning a map between
|
||||
// item name -> type.
|
||||
virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
|
||||
|
||||
// Interprets the file with name file instead as a directory of type directory.
|
||||
// The directory must have a constructor that takes a single argument of type
|
||||
// std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
|
||||
@@ -310,13 +315,19 @@ public:
|
||||
bool Rename(std::string_view name) override;
|
||||
};
|
||||
|
||||
// Compare the two files, byte-for-byte, in increments specificed by block_size
|
||||
bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200);
|
||||
// Compare the two files, byte-for-byte, in increments specified by block_size
|
||||
bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2,
|
||||
std::size_t block_size = 0x1000);
|
||||
|
||||
// A method that copies the raw data between two different implementations of VirtualFile. If you
|
||||
// are using the same implementation, it is probably better to use the Copy method in the parent
|
||||
// directory of src/dest.
|
||||
bool VfsRawCopy(VirtualFile src, VirtualFile dest);
|
||||
bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size = 0x1000);
|
||||
|
||||
// A method that performs a similar function to VfsRawCopy above, but instead copies entire
|
||||
// directories. It suffers the same performance penalties as above and an implementation-specific
|
||||
// Copy should always be preferred.
|
||||
bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size = 0x1000);
|
||||
|
||||
// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
|
||||
// it attempts to create it and returns the new dir or nullptr on failure.
|
||||
|
||||
@@ -5,17 +5,22 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_static.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
|
||||
if (files.empty())
|
||||
return nullptr;
|
||||
if (files.size() == 1)
|
||||
return files[0];
|
||||
static bool VerifyConcatenationMapContinuity(const std::map<u64, VirtualFile>& map) {
|
||||
const auto last_valid = --map.end();
|
||||
for (auto iter = map.begin(); iter != last_valid;) {
|
||||
const auto old = iter++;
|
||||
if (old->first + old->second->GetSize() != iter->first) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
|
||||
return map.begin()->first == 0;
|
||||
}
|
||||
|
||||
ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
|
||||
@@ -27,8 +32,48 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s
|
||||
}
|
||||
}
|
||||
|
||||
ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name)
|
||||
: files(std::move(files_)), name(std::move(name)) {
|
||||
ASSERT(VerifyConcatenationMapContinuity(files));
|
||||
}
|
||||
|
||||
ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
|
||||
|
||||
VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> files,
|
||||
std::string name) {
|
||||
if (files.empty())
|
||||
return nullptr;
|
||||
if (files.size() == 1)
|
||||
return files[0];
|
||||
|
||||
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
|
||||
}
|
||||
|
||||
VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
|
||||
std::map<u64, VirtualFile> files,
|
||||
std::string name) {
|
||||
if (files.empty())
|
||||
return nullptr;
|
||||
if (files.size() == 1)
|
||||
return files.begin()->second;
|
||||
|
||||
const auto last_valid = --files.end();
|
||||
for (auto iter = files.begin(); iter != last_valid;) {
|
||||
const auto old = iter++;
|
||||
if (old->first + old->second->GetSize() != iter->first) {
|
||||
files.emplace(old->first + old->second->GetSize(),
|
||||
std::make_shared<StaticVfsFile>(filler_byte, iter->first - old->first -
|
||||
old->second->GetSize()));
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the map starts at offset 0 (start of file), otherwise pad to fill.
|
||||
if (files.begin()->first != 0)
|
||||
files.emplace(0, std::make_shared<StaticVfsFile>(filler_byte, files.begin()->first));
|
||||
|
||||
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
|
||||
}
|
||||
|
||||
std::string ConcatenatedVfsFile::GetName() const {
|
||||
if (files.empty())
|
||||
return "";
|
||||
@@ -62,7 +107,7 @@ bool ConcatenatedVfsFile::IsReadable() const {
|
||||
}
|
||||
|
||||
std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
|
||||
auto entry = files.end();
|
||||
auto entry = --files.end();
|
||||
for (auto iter = files.begin(); iter != files.end(); ++iter) {
|
||||
if (iter->first > offset) {
|
||||
entry = --iter;
|
||||
@@ -70,20 +115,17 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the entry should be the last one. The loop above will make it end().
|
||||
if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
|
||||
--entry;
|
||||
|
||||
if (entry == files.end())
|
||||
if (entry->first + entry->second->GetSize() <= offset)
|
||||
return 0;
|
||||
|
||||
const auto remaining = entry->second->GetSize() + offset - entry->first;
|
||||
if (length > remaining) {
|
||||
return entry->second->Read(data, remaining, offset - entry->first) +
|
||||
Read(data + remaining, length - remaining, offset + remaining);
|
||||
const auto read_in =
|
||||
std::min<u64>(entry->first + entry->second->GetSize() - offset, entry->second->GetSize());
|
||||
if (length > read_in) {
|
||||
return entry->second->Read(data, read_in, offset - entry->first) +
|
||||
Read(data + read_in, length - read_in, offset + read_in);
|
||||
}
|
||||
|
||||
return entry->second->Read(data, length, offset - entry->first);
|
||||
return entry->second->Read(data, std::min<u64>(read_in, length), offset - entry->first);
|
||||
}
|
||||
|
||||
std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
|
||||
@@ -93,4 +135,5 @@ std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::
|
||||
bool ConcatenatedVfsFile::Rename(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -4,26 +4,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
|
||||
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
|
||||
|
||||
// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
|
||||
// read-only.
|
||||
class ConcatenatedVfsFile : public VfsFile {
|
||||
friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
|
||||
|
||||
ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
|
||||
ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name);
|
||||
|
||||
public:
|
||||
~ConcatenatedVfsFile() override;
|
||||
|
||||
/// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
|
||||
static VirtualFile MakeConcatenatedFile(std::vector<VirtualFile> files, std::string name);
|
||||
|
||||
/// Convenience function that turns a map of offsets to files into a concatenated file, filling
|
||||
/// gaps with a given filler byte.
|
||||
static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::map<u64, VirtualFile> files,
|
||||
std::string name);
|
||||
|
||||
std::string GetName() const override;
|
||||
std::size_t GetSize() const override;
|
||||
bool Resize(std::size_t new_size) override;
|
||||
@@ -36,7 +40,7 @@ public:
|
||||
|
||||
private:
|
||||
// Maps starting offset to file -- more efficient.
|
||||
boost::container::flat_map<u64, VirtualFile> files;
|
||||
std::map<u64, VirtualFile> files;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
|
||||
132
src/core/file_sys/vfs_layered.cpp
Normal file
132
src/core/file_sys/vfs_layered.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include "core/file_sys/vfs_layered.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name)
|
||||
: dirs(std::move(dirs)), name(std::move(name)) {}
|
||||
|
||||
LayeredVfsDirectory::~LayeredVfsDirectory() = default;
|
||||
|
||||
VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dirs,
|
||||
std::string name) {
|
||||
if (dirs.empty())
|
||||
return nullptr;
|
||||
if (dirs.size() == 1)
|
||||
return dirs[0];
|
||||
|
||||
return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
for (const auto& layer : dirs) {
|
||||
const auto file = layer->GetFileRelative(path);
|
||||
if (file != nullptr)
|
||||
return file;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative(
|
||||
std::string_view path) const {
|
||||
std::vector<VirtualDir> out;
|
||||
for (const auto& layer : dirs) {
|
||||
auto dir = layer->GetDirectoryRelative(path);
|
||||
if (dir != nullptr)
|
||||
out.push_back(std::move(dir));
|
||||
}
|
||||
|
||||
return MakeLayeredDirectory(std::move(out));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const {
|
||||
return GetFileRelative(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
|
||||
return GetDirectoryRelative(name);
|
||||
}
|
||||
|
||||
std::string LayeredVfsDirectory::GetFullPath() const {
|
||||
return dirs[0]->GetFullPath();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
|
||||
std::vector<VirtualFile> out;
|
||||
for (const auto& layer : dirs) {
|
||||
for (const auto& file : layer->GetFiles()) {
|
||||
if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) {
|
||||
return comp->GetName() == file->GetName();
|
||||
}) == out.end()) {
|
||||
out.push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const {
|
||||
std::vector<std::string> names;
|
||||
for (const auto& layer : dirs) {
|
||||
for (const auto& sd : layer->GetSubdirectories()) {
|
||||
if (std::find(names.begin(), names.end(), sd->GetName()) == names.end())
|
||||
names.push_back(sd->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<VirtualDir> out;
|
||||
out.reserve(names.size());
|
||||
for (const auto& subdir : names)
|
||||
out.push_back(GetSubdirectory(subdir));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool LayeredVfsDirectory::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayeredVfsDirectory::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string LayeredVfsDirectory::GetName() const {
|
||||
return name.empty() ? dirs[0]->GetName() : name;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const {
|
||||
return dirs[0]->GetParentDirectory();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayeredVfsDirectory::DeleteFile(std::string_view name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayeredVfsDirectory::Rename(std::string_view name_) {
|
||||
name = name_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
50
src/core/file_sys/vfs_layered.h
Normal file
50
src/core/file_sys/vfs_layered.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
|
||||
// one and falling back to the one after. The highest priority directory (overwrites all others)
|
||||
// should be element 0 in the dirs vector.
|
||||
class LayeredVfsDirectory : public VfsDirectory {
|
||||
LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name);
|
||||
|
||||
public:
|
||||
~LayeredVfsDirectory() override;
|
||||
|
||||
/// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
|
||||
static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
|
||||
|
||||
std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
|
||||
std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
|
||||
std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
|
||||
std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
|
||||
std::string GetFullPath() const override;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
std::string GetName() const override;
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
|
||||
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
|
||||
bool DeleteSubdirectory(std::string_view name) override;
|
||||
bool DeleteFile(std::string_view name) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
std::vector<VirtualDir> dirs;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -413,6 +413,23 @@ std::string RealVfsDirectory::GetFullPath() const {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
|
||||
if (perms == Mode::Append)
|
||||
return {};
|
||||
|
||||
std::map<std::string, VfsEntryType, std::less<>> out;
|
||||
FileUtil::ForeachDirectoryEntry(
|
||||
nullptr, path,
|
||||
[&out](u64* entries_out, const std::string& directory, const std::string& filename) {
|
||||
const std::string full_path = directory + DIR_SEP + filename;
|
||||
out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory
|
||||
: VfsEntryType::File);
|
||||
return true;
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ public:
|
||||
bool DeleteFile(std::string_view name) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
std::string GetFullPath() const override;
|
||||
std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
79
src/core/file_sys/vfs_static.h
Normal file
79
src/core/file_sys/vfs_static.h
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class StaticVfsFile : public VfsFile {
|
||||
public:
|
||||
explicit StaticVfsFile(u8 value, std::size_t size = 0, std::string name = "",
|
||||
VirtualDir parent = nullptr)
|
||||
: value{value}, size{size}, name{std::move(name)}, parent{std::move(parent)} {}
|
||||
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
}
|
||||
|
||||
std::size_t GetSize() const override {
|
||||
return size;
|
||||
}
|
||||
|
||||
bool Resize(std::size_t new_size) override {
|
||||
size = new_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
|
||||
return parent;
|
||||
}
|
||||
|
||||
bool IsWritable() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsReadable() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override {
|
||||
const auto read = std::min(length, size - offset);
|
||||
std::fill(data, data + read, value);
|
||||
return read;
|
||||
}
|
||||
|
||||
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
boost::optional<u8> ReadByte(std::size_t offset) const override {
|
||||
if (offset < size)
|
||||
return value;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
|
||||
const auto read = std::min(length, size - offset);
|
||||
return std::vector<u8>(read, value);
|
||||
}
|
||||
|
||||
bool Rename(std::string_view new_name) override {
|
||||
name = new_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
u8 value;
|
||||
std::size_t size;
|
||||
std::string name;
|
||||
VirtualDir parent;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -3,10 +3,64 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
namespace FileSys {
|
||||
VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name, VirtualDir parent)
|
||||
: data(std::move(initial_data)), parent(std::move(parent)), name(std::move(name)) {}
|
||||
|
||||
VectorVfsFile::~VectorVfsFile() = default;
|
||||
|
||||
std::string VectorVfsFile::GetName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
size_t VectorVfsFile::GetSize() const {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
bool VectorVfsFile::Resize(size_t new_size) {
|
||||
data.resize(new_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
bool VectorVfsFile::IsWritable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VectorVfsFile::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::size_t offset) const {
|
||||
const auto read = std::min(length, data.size() - offset);
|
||||
std::memcpy(data_, data.data() + offset, read);
|
||||
return read;
|
||||
}
|
||||
|
||||
std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_t offset) {
|
||||
if (offset + length > data.size())
|
||||
data.resize(offset + length);
|
||||
const auto write = std::min(length, data.size() - offset);
|
||||
std::memcpy(data.data(), data_, write);
|
||||
return write;
|
||||
}
|
||||
|
||||
bool VectorVfsFile::Rename(std::string_view name_) {
|
||||
name = name_;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VectorVfsFile::Assign(std::vector<u8> new_data) {
|
||||
data = std::move(new_data);
|
||||
}
|
||||
|
||||
VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
|
||||
std::vector<VirtualDir> dirs_, std::string name_,
|
||||
VirtualDir parent_)
|
||||
|
||||
@@ -8,6 +8,31 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
|
||||
class VectorVfsFile : public VfsFile {
|
||||
public:
|
||||
explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name = "",
|
||||
VirtualDir parent = nullptr);
|
||||
~VectorVfsFile() override;
|
||||
|
||||
std::string GetName() const override;
|
||||
std::size_t GetSize() const override;
|
||||
bool Resize(std::size_t new_size) override;
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
|
||||
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
virtual void Assign(std::vector<u8> new_data);
|
||||
|
||||
private:
|
||||
std::vector<u8> data;
|
||||
VirtualDir parent;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
|
||||
// Vector data is supplied upon construction.
|
||||
class VectorVfsDirectory : public VfsDirectory {
|
||||
|
||||
@@ -37,7 +37,9 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -207,7 +209,7 @@ 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();
|
||||
for (auto& thread : threads) {
|
||||
if (thread->GetThreadId() == static_cast<u32>(id)) {
|
||||
if (thread->GetThreadID() == static_cast<u32>(id)) {
|
||||
current_core = core;
|
||||
return thread.get();
|
||||
}
|
||||
@@ -221,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;
|
||||
}
|
||||
@@ -241,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 = 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,14 +591,15 @@ static void HandleQuery() {
|
||||
strlen("Xfer:features:read:target.xml:")) == 0) {
|
||||
SendReply(target_xml);
|
||||
} else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) {
|
||||
std::string buffer = fmt::format("TextSeg={:0x}", Memory::PROCESS_IMAGE_VADDR);
|
||||
const VAddr base_address = Core::CurrentProcess()->VMManager().GetCodeRegionBaseAddress();
|
||||
std::string buffer = fmt::format("TextSeg={:0x}", base_address);
|
||||
SendReply(buffer.c_str());
|
||||
} 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();
|
||||
for (const auto& thread : threads) {
|
||||
val += fmt::format("{:x}", thread->GetThreadId());
|
||||
val += fmt::format("{:x}", thread->GetThreadID());
|
||||
val += ",";
|
||||
}
|
||||
}
|
||||
@@ -609,7 +616,7 @@ static void HandleQuery() {
|
||||
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>";
|
||||
@@ -690,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());
|
||||
@@ -854,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");
|
||||
}
|
||||
@@ -883,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");
|
||||
}
|
||||
@@ -893,11 +904,11 @@ static void ReadMemory() {
|
||||
static u8 reply[GDB_BUFFER_SIZE - 4];
|
||||
|
||||
auto start_offset = command_buffer + 1;
|
||||
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
|
||||
VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
|
||||
const auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
|
||||
const VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
|
||||
|
||||
start_offset = addr_pos + 1;
|
||||
u64 len =
|
||||
const u64 len =
|
||||
HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset));
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: addr: {:016X} len: {:016X}", addr, len);
|
||||
@@ -906,7 +917,9 @@ static void ReadMemory() {
|
||||
SendReply("E01");
|
||||
}
|
||||
|
||||
if (addr < Memory::PROCESS_IMAGE_VADDR || addr >= Memory::MAP_REGION_VADDR_END) {
|
||||
const auto& vm_manager = Core::CurrentProcess()->VMManager();
|
||||
if (addr < vm_manager.GetCodeRegionBaseAddress() ||
|
||||
addr >= vm_manager.GetMapRegionEndAddress()) {
|
||||
return SendReply("E00");
|
||||
}
|
||||
|
||||
@@ -955,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
|
||||
|
||||
@@ -31,6 +31,7 @@ enum {
|
||||
TooLarge = 119,
|
||||
InvalidEnumValue = 120,
|
||||
NoSuchEntry = 121,
|
||||
AlreadyRegistered = 122,
|
||||
InvalidState = 125,
|
||||
ResourceLimitExceeded = 132,
|
||||
};
|
||||
@@ -58,6 +59,7 @@ constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
|
||||
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);
|
||||
constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::Kernel, ErrCodes::AlreadyRegistered);
|
||||
constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);
|
||||
constexpr ResultCode ERR_INVALID_THREAD_PRIORITY(ErrorModule::Kernel,
|
||||
ErrCodes::InvalidThreadPriority);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/smart_ptr/intrusive_ptr.hpp>
|
||||
|
||||
@@ -97,7 +96,7 @@ using SharedPtr = boost::intrusive_ptr<T>;
|
||||
template <typename T>
|
||||
inline SharedPtr<T> DynamicObjectCast(SharedPtr<Object> object) {
|
||||
if (object != nullptr && object->GetHandleType() == T::HANDLE_TYPE) {
|
||||
return boost::static_pointer_cast<T>(std::move(object));
|
||||
return boost::static_pointer_cast<T>(object);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -34,14 +35,22 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
|
||||
process->name = std::move(name);
|
||||
process->flags.raw = 0;
|
||||
process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
|
||||
process->resource_limit = kernel.ResourceLimitForCategory(ResourceLimitCategory::APPLICATION);
|
||||
process->status = ProcessStatus::Created;
|
||||
process->program_id = 0;
|
||||
process->process_id = kernel.CreateNewProcessID();
|
||||
process->svc_access_mask.set();
|
||||
|
||||
kernel.AppendNewProcess(process);
|
||||
return process;
|
||||
}
|
||||
|
||||
void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
|
||||
program_id = metadata.GetTitleID();
|
||||
is_64bit_process = metadata.Is64BitProgram();
|
||||
vm_manager.Reset(metadata.GetAddressSpaceType());
|
||||
}
|
||||
|
||||
void Process::ParseKernelCaps(const u32* kernel_caps, std::size_t len) {
|
||||
for (std::size_t i = 0; i < len; ++i) {
|
||||
u32 descriptor = kernel_caps[i];
|
||||
@@ -119,7 +128,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
|
||||
// TODO(bunnei): This is heap area that should be allocated by the kernel and not mapped as part
|
||||
// of the user address space.
|
||||
vm_manager
|
||||
.MapMemoryBlock(Memory::STACK_AREA_VADDR_END - stack_size,
|
||||
.MapMemoryBlock(vm_manager.GetTLSIORegionEndAddress() - stack_size,
|
||||
std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size,
|
||||
MemoryState::Mapped)
|
||||
.Unwrap();
|
||||
@@ -135,15 +144,15 @@ 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();
|
||||
@@ -185,6 +194,7 @@ static std::tuple<std::size_t, std::size_t, bool> FindFreeThreadLocalSlot(
|
||||
|
||||
VAddr Process::MarkNextAvailableTLSSlotAsUsed(Thread& thread) {
|
||||
auto [available_page, available_slot, needs_allocation] = FindFreeThreadLocalSlot(tls_slots);
|
||||
const VAddr tls_begin = vm_manager.GetTLSIORegionBaseAddress();
|
||||
|
||||
if (needs_allocation) {
|
||||
tls_slots.emplace_back(0); // The page is completely available at the start
|
||||
@@ -197,18 +207,17 @@ VAddr Process::MarkNextAvailableTLSSlotAsUsed(Thread& thread) {
|
||||
|
||||
vm_manager.RefreshMemoryBlockMappings(tls_memory.get());
|
||||
|
||||
vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE,
|
||||
tls_memory, 0, Memory::PAGE_SIZE, MemoryState::ThreadLocal);
|
||||
vm_manager.MapMemoryBlock(tls_begin + available_page * Memory::PAGE_SIZE, tls_memory, 0,
|
||||
Memory::PAGE_SIZE, MemoryState::ThreadLocal);
|
||||
}
|
||||
|
||||
tls_slots[available_page].set(available_slot);
|
||||
|
||||
return Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE +
|
||||
available_slot * Memory::TLS_ENTRY_SIZE;
|
||||
return tls_begin + available_page * Memory::PAGE_SIZE + available_slot * Memory::TLS_ENTRY_SIZE;
|
||||
}
|
||||
|
||||
void Process::FreeTLSSlot(VAddr tls_address) {
|
||||
const VAddr tls_base = tls_address - Memory::TLS_AREA_VADDR;
|
||||
const VAddr tls_base = tls_address - vm_manager.GetTLSIORegionBaseAddress();
|
||||
const VAddr tls_page = tls_base / Memory::PAGE_SIZE;
|
||||
const VAddr tls_slot = (tls_base % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE;
|
||||
|
||||
@@ -232,8 +241,8 @@ void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) {
|
||||
}
|
||||
|
||||
ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {
|
||||
if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END ||
|
||||
target + size < target) {
|
||||
if (target < vm_manager.GetHeapRegionBaseAddress() ||
|
||||
target + size > vm_manager.GetHeapRegionEndAddress() || target + size < target) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
@@ -268,8 +277,8 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission per
|
||||
}
|
||||
|
||||
ResultCode Process::HeapFree(VAddr target, u32 size) {
|
||||
if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END ||
|
||||
target + size < target) {
|
||||
if (target < vm_manager.GetHeapRegionBaseAddress() ||
|
||||
target + size > vm_manager.GetHeapRegionEndAddress() || target + size < target) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
|
||||
namespace FileSys {
|
||||
class ProgramMetadata;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
@@ -131,6 +135,16 @@ public:
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
/// Gets a reference to the process' memory manager.
|
||||
Kernel::VMManager& VMManager() {
|
||||
return vm_manager;
|
||||
}
|
||||
|
||||
/// Gets a const reference to the process' memory manager.
|
||||
const Kernel::VMManager& VMManager() const {
|
||||
return vm_manager;
|
||||
}
|
||||
|
||||
/// Gets the current status of the process
|
||||
ProcessStatus GetStatus() const {
|
||||
return status;
|
||||
@@ -141,29 +155,52 @@ public:
|
||||
return process_id;
|
||||
}
|
||||
|
||||
/// Title ID corresponding to the process
|
||||
u64 program_id;
|
||||
/// Gets the title ID corresponding to this process.
|
||||
u64 GetTitleID() const {
|
||||
return program_id;
|
||||
}
|
||||
|
||||
/// Resource limit descriptor for this process
|
||||
SharedPtr<ResourceLimit> resource_limit;
|
||||
/// Gets the resource limit descriptor for this process
|
||||
ResourceLimit& GetResourceLimit() {
|
||||
return *resource_limit;
|
||||
}
|
||||
|
||||
/// The process may only call SVCs which have the corresponding bit set.
|
||||
std::bitset<0x80> svc_access_mask;
|
||||
/// Maximum size of the handle table for the process.
|
||||
unsigned int handle_table_size = 0x200;
|
||||
/// Special memory ranges mapped into this processes address space. This is used to give
|
||||
/// processes access to specific I/O regions and device memory.
|
||||
boost::container::static_vector<AddressMapping, 8> address_mappings;
|
||||
ProcessFlags flags;
|
||||
/// Kernel compatibility version for this process
|
||||
u16 kernel_version = 0;
|
||||
/// The default CPU for this process, threads are scheduled on this cpu by default.
|
||||
u8 ideal_processor = 0;
|
||||
/// Bitmask of allowed CPUs that this process' threads can run on. TODO(Subv): Actually parse
|
||||
/// this value from the process header.
|
||||
u32 allowed_processor_mask = THREADPROCESSORID_DEFAULT_MASK;
|
||||
u32 allowed_thread_priority_mask = 0xFFFFFFFF;
|
||||
u32 is_virtual_address_memory_enabled = 0;
|
||||
/// Gets the resource limit descriptor for this process
|
||||
const ResourceLimit& GetResourceLimit() const {
|
||||
return *resource_limit;
|
||||
}
|
||||
|
||||
/// Gets the default CPU ID for this process
|
||||
u8 GetDefaultProcessorID() const {
|
||||
return ideal_processor;
|
||||
}
|
||||
|
||||
/// Gets the bitmask of allowed CPUs that this process' threads can run on.
|
||||
u32 GetAllowedProcessorMask() const {
|
||||
return allowed_processor_mask;
|
||||
}
|
||||
|
||||
/// Gets the bitmask of allowed thread priorities.
|
||||
u32 GetAllowedThreadPriorityMask() const {
|
||||
return allowed_thread_priority_mask;
|
||||
}
|
||||
|
||||
u32 IsVirtualMemoryEnabled() const {
|
||||
return is_virtual_address_memory_enabled;
|
||||
}
|
||||
|
||||
/// Whether this process is an AArch64 or AArch32 process.
|
||||
bool Is64BitProcess() const {
|
||||
return is_64bit_process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads process-specifics configuration info with metadata provided
|
||||
* by an executable.
|
||||
*
|
||||
* @param metadata The provided metadata to load process specific info.
|
||||
*/
|
||||
void LoadFromMetadata(const FileSys::ProgramMetadata& metadata);
|
||||
|
||||
/**
|
||||
* Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them
|
||||
@@ -200,18 +237,43 @@ public:
|
||||
|
||||
ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size);
|
||||
|
||||
VMManager vm_manager;
|
||||
|
||||
private:
|
||||
explicit Process(KernelCore& kernel);
|
||||
~Process() override;
|
||||
|
||||
/// Memory manager for this process.
|
||||
Kernel::VMManager vm_manager;
|
||||
|
||||
/// Current status of the process
|
||||
ProcessStatus status;
|
||||
|
||||
/// The ID of this process
|
||||
u32 process_id = 0;
|
||||
|
||||
/// Title ID corresponding to the process
|
||||
u64 program_id;
|
||||
|
||||
/// Resource limit descriptor for this process
|
||||
SharedPtr<ResourceLimit> resource_limit;
|
||||
|
||||
/// The process may only call SVCs which have the corresponding bit set.
|
||||
std::bitset<0x80> svc_access_mask;
|
||||
/// Maximum size of the handle table for the process.
|
||||
u32 handle_table_size = 0x200;
|
||||
/// Special memory ranges mapped into this processes address space. This is used to give
|
||||
/// processes access to specific I/O regions and device memory.
|
||||
boost::container::static_vector<AddressMapping, 8> address_mappings;
|
||||
ProcessFlags flags;
|
||||
/// Kernel compatibility version for this process
|
||||
u16 kernel_version = 0;
|
||||
/// The default CPU for this process, threads are scheduled on this cpu by default.
|
||||
u8 ideal_processor = 0;
|
||||
/// Bitmask of allowed CPUs that this process' threads can run on. TODO(Subv): Actually parse
|
||||
/// this value from the process header.
|
||||
u32 allowed_processor_mask = THREADPROCESSORID_DEFAULT_MASK;
|
||||
u32 allowed_thread_priority_mask = 0xFFFFFFFF;
|
||||
u32 is_virtual_address_memory_enabled = 0;
|
||||
|
||||
// Memory used to back the allocations in the regular heap. A single vector is used to cover
|
||||
// the entire virtual address space extents that bound the allocations, including any holes.
|
||||
// This makes deallocation and reallocation of holes fast and keeps process memory contiguous
|
||||
@@ -230,6 +292,11 @@ private:
|
||||
/// This vector will grow as more pages are allocated for new threads.
|
||||
std::vector<std::bitset<8>> tls_slots;
|
||||
|
||||
/// Whether or not this process is AArch64, or AArch32.
|
||||
/// By default, we currently assume this is true, unless otherwise
|
||||
/// specified by metadata provided to the process during loading.
|
||||
bool is_64bit_process = true;
|
||||
|
||||
std::string name;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Kernel {
|
||||
|
||||
std::mutex Scheduler::scheduler_mutex;
|
||||
|
||||
Scheduler::Scheduler(Core::ARM_Interface* cpu_core) : cpu_core(cpu_core) {}
|
||||
Scheduler::Scheduler(Core::ARM_Interface& cpu_core) : cpu_core(cpu_core) {}
|
||||
|
||||
Scheduler::~Scheduler() {
|
||||
for (auto& thread : thread_list) {
|
||||
@@ -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,22 +58,21 @@ 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
|
||||
@@ -83,18 +82,19 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
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;
|
||||
SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table);
|
||||
const auto thread_owner_process = current_thread->GetOwnerProcess();
|
||||
if (previous_process != thread_owner_process) {
|
||||
Core::CurrentProcess() = thread_owner_process;
|
||||
SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
|
||||
}
|
||||
|
||||
cpu_core->LoadContext(new_thread->context);
|
||||
cpu_core->SetTlsAddress(new_thread->GetTLSAddress());
|
||||
cpu_core->SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
|
||||
cpu_core->ClearExclusiveState();
|
||||
cpu_core.LoadContext(new_thread->GetContext());
|
||||
cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
|
||||
cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
} else {
|
||||
current_thread = nullptr;
|
||||
// Note: We do not reset the current process and current page table when idling because
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Kernel {
|
||||
|
||||
class Scheduler final {
|
||||
public:
|
||||
explicit Scheduler(Core::ARM_Interface* cpu_core);
|
||||
explicit Scheduler(Core::ARM_Interface& cpu_core);
|
||||
~Scheduler();
|
||||
|
||||
/// Returns whether there are any threads that are ready to run.
|
||||
@@ -72,7 +72,7 @@ private:
|
||||
|
||||
SharedPtr<Thread> current_thread = nullptr;
|
||||
|
||||
Core::ARM_Interface* cpu_core;
|
||||
Core::ARM_Interface& cpu_core;
|
||||
|
||||
static std::mutex scheduler_mutex;
|
||||
};
|
||||
|
||||
@@ -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 "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -34,11 +35,11 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Proce
|
||||
|
||||
// Refresh the address mappings for the current process.
|
||||
if (Core::CurrentProcess() != nullptr) {
|
||||
Core::CurrentProcess()->vm_manager.RefreshMemoryBlockMappings(
|
||||
Core::CurrentProcess()->VMManager().RefreshMemoryBlockMappings(
|
||||
shared_memory->backing_block.get());
|
||||
}
|
||||
} else {
|
||||
auto& vm_manager = shared_memory->owner_process->vm_manager;
|
||||
auto& vm_manager = shared_memory->owner_process->VMManager();
|
||||
|
||||
// The memory is already available and mapped in the owner process.
|
||||
auto vma = vm_manager.FindVMA(address);
|
||||
@@ -71,7 +72,8 @@ SharedPtr<SharedMemory> SharedMemory::CreateForApplet(
|
||||
shared_memory->other_permissions = other_permissions;
|
||||
shared_memory->backing_block = std::move(heap_block);
|
||||
shared_memory->backing_block_offset = offset;
|
||||
shared_memory->base_address = Memory::HEAP_VADDR + offset;
|
||||
shared_memory->base_address =
|
||||
kernel.CurrentProcess()->VMManager().GetHeapRegionBaseAddress() + offset;
|
||||
|
||||
return shared_memory;
|
||||
}
|
||||
@@ -105,7 +107,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
|
||||
VAddr target_address = address;
|
||||
|
||||
// Map the memory block into the target process
|
||||
auto result = target_process->vm_manager.MapMemoryBlock(
|
||||
auto result = target_process->VMManager().MapMemoryBlock(
|
||||
target_address, backing_block, backing_block_offset, size, MemoryState::Shared);
|
||||
if (result.Failed()) {
|
||||
LOG_ERROR(
|
||||
@@ -115,14 +117,14 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
|
||||
return result.Code();
|
||||
}
|
||||
|
||||
return target_process->vm_manager.ReprotectRange(target_address, size,
|
||||
ConvertPermissions(permissions));
|
||||
return target_process->VMManager().ReprotectRange(target_address, size,
|
||||
ConvertPermissions(permissions));
|
||||
}
|
||||
|
||||
ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) {
|
||||
// TODO(Subv): Verify what happens if the application tries to unmap an address that is not
|
||||
// mapped to a SharedMemory.
|
||||
return target_process->vm_manager.UnmapRange(address, size);
|
||||
return target_process->VMManager().UnmapRange(address, size);
|
||||
}
|
||||
|
||||
VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) {
|
||||
|
||||
@@ -51,8 +51,9 @@ static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) {
|
||||
}
|
||||
|
||||
auto& process = *Core::CurrentProcess();
|
||||
const VAddr heap_base = process.VMManager().GetHeapRegionBaseAddress();
|
||||
CASCADE_RESULT(*heap_addr,
|
||||
process.HeapAllocate(Memory::HEAP_VADDR, heap_size, VMAPermission::ReadWrite));
|
||||
process.HeapAllocate(heap_base, heap_size, VMAPermission::ReadWrite));
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -155,7 +156,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;
|
||||
}
|
||||
|
||||
@@ -176,7 +177,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);
|
||||
@@ -203,10 +204,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) {
|
||||
@@ -243,14 +244,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;
|
||||
}
|
||||
@@ -265,7 +266,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();
|
||||
@@ -325,26 +326,27 @@ 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& vm_manager = Core::CurrentProcess()->vm_manager;
|
||||
const auto& current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
switch (static_cast<GetInfoType>(info_id)) {
|
||||
case GetInfoType::AllowedCpuIdBitmask:
|
||||
*result = Core::CurrentProcess()->allowed_processor_mask;
|
||||
*result = current_process->GetAllowedProcessorMask();
|
||||
break;
|
||||
case GetInfoType::AllowedThreadPrioBitmask:
|
||||
*result = Core::CurrentProcess()->allowed_thread_priority_mask;
|
||||
*result = current_process->GetAllowedThreadPriorityMask();
|
||||
break;
|
||||
case GetInfoType::MapRegionBaseAddr:
|
||||
*result = Memory::MAP_REGION_VADDR;
|
||||
*result = vm_manager.GetMapRegionBaseAddress();
|
||||
break;
|
||||
case GetInfoType::MapRegionSize:
|
||||
*result = Memory::MAP_REGION_SIZE;
|
||||
*result = vm_manager.GetMapRegionSize();
|
||||
break;
|
||||
case GetInfoType::HeapRegionBaseAddr:
|
||||
*result = Memory::HEAP_VADDR;
|
||||
*result = vm_manager.GetHeapRegionBaseAddress();
|
||||
break;
|
||||
case GetInfoType::HeapRegionSize:
|
||||
*result = Memory::HEAP_SIZE;
|
||||
*result = vm_manager.GetHeapRegionSize();
|
||||
break;
|
||||
case GetInfoType::TotalMemoryUsage:
|
||||
*result = vm_manager.GetTotalMemoryUsage();
|
||||
@@ -359,22 +361,35 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
*result = 0;
|
||||
break;
|
||||
case GetInfoType::AddressSpaceBaseAddr:
|
||||
*result = vm_manager.GetAddressSpaceBaseAddr();
|
||||
*result = vm_manager.GetCodeRegionBaseAddress();
|
||||
break;
|
||||
case GetInfoType::AddressSpaceSize:
|
||||
*result = vm_manager.GetAddressSpaceSize();
|
||||
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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GetInfoType::NewMapRegionBaseAddr:
|
||||
*result = Memory::NEW_MAP_REGION_VADDR;
|
||||
*result = vm_manager.GetNewMapRegionBaseAddress();
|
||||
break;
|
||||
case GetInfoType::NewMapRegionSize:
|
||||
*result = Memory::NEW_MAP_REGION_SIZE;
|
||||
*result = vm_manager.GetNewMapRegionSize();
|
||||
break;
|
||||
case GetInfoType::IsVirtualAddressMemoryEnabled:
|
||||
*result = Core::CurrentProcess()->is_virtual_address_memory_enabled;
|
||||
*result = current_process->IsVirtualMemoryEnabled();
|
||||
break;
|
||||
case GetInfoType::TitleId:
|
||||
*result = Core::CurrentProcess()->program_id;
|
||||
*result = current_process->GetTitleID();
|
||||
break;
|
||||
case GetInfoType::PrivilegedProcessId:
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
@@ -400,8 +415,36 @@ static ResultCode SetThreadActivity(Handle handle, u32 unknown) {
|
||||
}
|
||||
|
||||
/// Gets the thread context
|
||||
static ResultCode GetThreadContext(Handle handle, VAddr addr) {
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) called, handle=0x{:08X}, addr=0x{:X}", handle, addr);
|
||||
static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, context=0x{:08X}, thread=0x{:X}", thread_context, handle);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle);
|
||||
if (!thread) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const auto current_process = Core::CurrentProcess();
|
||||
if (thread->GetOwnerProcess() != current_process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (thread == GetCurrentThread()) {
|
||||
return ERR_ALREADY_REGISTERED;
|
||||
}
|
||||
|
||||
Core::ARM_Interface::ThreadContext ctx = thread->GetContext();
|
||||
// Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
|
||||
ctx.pstate &= 0xFF0FFE20;
|
||||
|
||||
// If 64-bit, we can just write the context registers directly and we're good.
|
||||
// However, if 32-bit, we have to ensure some registers are zeroed out.
|
||||
if (!current_process->Is64BitProcess()) {
|
||||
std::fill(ctx.cpu_registers.begin() + 15, ctx.cpu_registers.end(), 0);
|
||||
std::fill(ctx.vector_registers.begin() + 16, ctx.vector_registers.end(), u128{});
|
||||
}
|
||||
|
||||
Memory::WriteBlock(thread_context, &ctx, sizeof(ctx));
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -429,21 +472,21 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||
|
||||
// Note: The kernel uses the current process's resource limit instead of
|
||||
// the one from the thread owner's resource limit.
|
||||
SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
|
||||
if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
const ResourceLimit& resource_limit = Core::CurrentProcess()->GetResourceLimit();
|
||||
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -504,9 +547,9 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i
|
||||
if (!process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
auto vma = process->vm_manager.FindVMA(addr);
|
||||
auto vma = process->VMManager().FindVMA(addr);
|
||||
memory_info->attributes = 0;
|
||||
if (vma == Core::CurrentProcess()->vm_manager.vma_map.end()) {
|
||||
if (vma == Core::CurrentProcess()->VMManager().vma_map.end()) {
|
||||
memory_info->base_address = 0;
|
||||
memory_info->permission = static_cast<u32>(VMAPermission::None);
|
||||
memory_info->size = 0;
|
||||
@@ -553,14 +596,14 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
return ERR_INVALID_THREAD_PRIORITY;
|
||||
}
|
||||
|
||||
SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
|
||||
if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
const ResourceLimit& resource_limit = Core::CurrentProcess()->GetResourceLimit();
|
||||
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
if (processor_id == THREADPROCESSORID_DEFAULT) {
|
||||
// Set the target CPU to the one specified in the process' exheader.
|
||||
processor_id = Core::CurrentProcess()->ideal_processor;
|
||||
processor_id = Core::CurrentProcess()->GetDefaultProcessorID();
|
||||
ASSERT(processor_id != THREADPROCESSORID_DEFAULT);
|
||||
}
|
||||
|
||||
@@ -579,10 +622,14 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
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;
|
||||
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}, "
|
||||
@@ -602,10 +649,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;
|
||||
}
|
||||
@@ -651,17 +698,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;
|
||||
}
|
||||
|
||||
@@ -670,14 +717,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);
|
||||
}
|
||||
};
|
||||
@@ -691,7 +738,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
|
||||
@@ -707,7 +754,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();
|
||||
|
||||
@@ -716,42 +763,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.
|
||||
@@ -759,12 +807,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,8 +918,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;
|
||||
}
|
||||
@@ -887,11 +935,13 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
|
||||
}
|
||||
|
||||
if (core == static_cast<u32>(THREADPROCESSORID_DEFAULT)) {
|
||||
ASSERT(thread->owner_process->ideal_processor !=
|
||||
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->ideal_processor;
|
||||
mask = 1ull << core;
|
||||
core = default_processor_id;
|
||||
mask = 1ULL << core;
|
||||
}
|
||||
|
||||
if (mask == 0) {
|
||||
@@ -902,7 +952,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);
|
||||
}
|
||||
@@ -1017,7 +1067,7 @@ static const FunctionDef SVC_Table[] = {
|
||||
{0x2B, nullptr, "FlushDataCache"},
|
||||
{0x2C, nullptr, "MapPhysicalMemory"},
|
||||
{0x2D, nullptr, "UnmapPhysicalMemory"},
|
||||
{0x2E, nullptr, "GetNextThreadInfo"},
|
||||
{0x2E, nullptr, "GetFutureThreadInfo"},
|
||||
{0x2F, nullptr, "GetLastThreadInfo"},
|
||||
{0x30, nullptr, "GetResourceLimitLimitValue"},
|
||||
{0x31, nullptr, "GetResourceLimitCurrentValue"},
|
||||
@@ -1043,11 +1093,11 @@ static const FunctionDef SVC_Table[] = {
|
||||
{0x45, nullptr, "CreateEvent"},
|
||||
{0x46, nullptr, "Unknown"},
|
||||
{0x47, nullptr, "Unknown"},
|
||||
{0x48, nullptr, "AllocateUnsafeMemory"},
|
||||
{0x49, nullptr, "FreeUnsafeMemory"},
|
||||
{0x4A, nullptr, "SetUnsafeAllocationLimit"},
|
||||
{0x4B, nullptr, "CreateJitMemory"},
|
||||
{0x4C, nullptr, "MapJitMemory"},
|
||||
{0x48, nullptr, "MapPhysicalMemoryUnsafe"},
|
||||
{0x49, nullptr, "UnmapPhysicalMemoryUnsafe"},
|
||||
{0x4A, nullptr, "SetUnsafeLimit"},
|
||||
{0x4B, nullptr, "CreateCodeMemory"},
|
||||
{0x4C, nullptr, "ControlCodeMemory"},
|
||||
{0x4D, nullptr, "SleepSystem"},
|
||||
{0x4E, nullptr, "ReadWriteRegister"},
|
||||
{0x4F, nullptr, "SetProcessActivity"},
|
||||
@@ -1082,7 +1132,7 @@ static const FunctionDef SVC_Table[] = {
|
||||
{0x6C, nullptr, "SetHardwareBreakPoint"},
|
||||
{0x6D, nullptr, "GetDebugThreadParam"},
|
||||
{0x6E, nullptr, "Unknown"},
|
||||
{0x6F, nullptr, "GetMemoryInfo"},
|
||||
{0x6F, nullptr, "GetSystemInfo"},
|
||||
{0x70, nullptr, "CreatePort"},
|
||||
{0x71, nullptr, "ManageNamedPort"},
|
||||
{0x72, nullptr, "ConnectToPort"},
|
||||
|
||||
@@ -64,6 +64,11 @@ void SvcWrap() {
|
||||
FuncReturn(func(Param(0), (s32)Param(1)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), static_cast<u32>(Param(1))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64*, u64)>
|
||||
void SvcWrap() {
|
||||
u64 param_1 = 0;
|
||||
|
||||
@@ -70,7 +70,7 @@ void Thread::Stop() {
|
||||
|
||||
void WaitCurrentThread_Sleep() {
|
||||
Thread* thread = GetCurrentThread();
|
||||
thread->status = ThreadStatus::WaitSleep;
|
||||
thread->SetStatus(ThreadStatus::WaitSleep);
|
||||
}
|
||||
|
||||
void ExitCurrentThread() {
|
||||
@@ -169,7 +169,7 @@ void Thread::ResumeFromWait() {
|
||||
next_scheduler->ScheduleThread(this, current_priority);
|
||||
|
||||
// Change thread's scheduler
|
||||
scheduler = next_scheduler;
|
||||
scheduler = next_scheduler.get();
|
||||
|
||||
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
|
||||
}
|
||||
@@ -233,7 +233,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
thread->name = std::move(name);
|
||||
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
|
||||
thread->owner_process = owner_process;
|
||||
thread->scheduler = Core::System::GetInstance().Scheduler(processor_id);
|
||||
thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get();
|
||||
thread->scheduler->AddThread(thread, priority);
|
||||
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
|
||||
|
||||
@@ -259,18 +259,19 @@ void Thread::BoostPriority(u32 priority) {
|
||||
SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 priority,
|
||||
Process& owner_process) {
|
||||
// Setup page table so we can write to memory
|
||||
SetCurrentPageTable(&owner_process.vm_manager.page_table);
|
||||
SetCurrentPageTable(&owner_process.VMManager().page_table);
|
||||
|
||||
// Initialize new "main" thread
|
||||
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
|
||||
auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, THREADPROCESSORID_0,
|
||||
Memory::STACK_AREA_VADDR_END, &owner_process);
|
||||
stack_top, &owner_process);
|
||||
|
||||
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
|
||||
|
||||
// Register 1 must be a handle to the main thread
|
||||
thread->guest_handle = kernel.HandleTable().Create(thread).Unwrap();
|
||||
|
||||
thread->context.cpu_registers[1] = thread->guest_handle;
|
||||
const Handle guest_handle = kernel.HandleTable().Create(thread).Unwrap();
|
||||
thread->SetGuestHandle(guest_handle);
|
||||
thread->GetContext().cpu_registers[1] = guest_handle;
|
||||
|
||||
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
|
||||
thread->ResumeFromWait();
|
||||
@@ -298,6 +299,18 @@ VAddr Thread::GetCommandBufferAddress() const {
|
||||
return GetTLSAddress() + CommandHeaderOffset;
|
||||
}
|
||||
|
||||
void Thread::SetStatus(ThreadStatus new_status) {
|
||||
if (new_status == status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == ThreadStatus::Running) {
|
||||
last_running_ticks = CoreTiming::GetTicks();
|
||||
}
|
||||
|
||||
status = new_status;
|
||||
}
|
||||
|
||||
void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
if (thread->lock_owner == this) {
|
||||
// If the thread is already waiting for this thread to release the mutex, ensure that the
|
||||
@@ -387,11 +400,23 @@ void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
next_scheduler->ScheduleThread(this, current_priority);
|
||||
|
||||
// Change thread's scheduler
|
||||
scheduler = next_scheduler;
|
||||
scheduler = next_scheduler.get();
|
||||
|
||||
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
|
||||
}
|
||||
|
||||
bool Thread::AllWaitObjectsReady() {
|
||||
return std::none_of(
|
||||
wait_objects.begin(), wait_objects.end(),
|
||||
[this](const SharedPtr<WaitObject>& object) { return object->ShouldWait(this); });
|
||||
}
|
||||
|
||||
bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index) {
|
||||
ASSERT(wakeup_callback);
|
||||
return wakeup_callback(reason, std::move(thread), std::move(object), index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,6 +65,15 @@ public:
|
||||
using TLSMemory = std::vector<u8>;
|
||||
using TLSMemoryPtr = std::shared_ptr<TLSMemory>;
|
||||
|
||||
using MutexWaitingThreads = std::vector<SharedPtr<Thread>>;
|
||||
|
||||
using ThreadContext = Core::ARM_Interface::ThreadContext;
|
||||
|
||||
using ThreadWaitObjects = std::vector<SharedPtr<WaitObject>>;
|
||||
|
||||
using WakeupCallback = std::function<bool(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index)>;
|
||||
|
||||
/**
|
||||
* Creates and returns a new thread. The new thread is immediately scheduled
|
||||
* @param kernel The kernel instance this thread will be created under.
|
||||
@@ -105,6 +114,14 @@ public:
|
||||
return current_priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the thread's nominal priority.
|
||||
* @return The current thread's nominal priority.
|
||||
*/
|
||||
u32 GetNominalPriority() const {
|
||||
return nominal_priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the thread's current priority
|
||||
* @param priority The new priority
|
||||
@@ -133,7 +150,7 @@ public:
|
||||
* Gets the thread's thread ID
|
||||
* @return The thread's ID
|
||||
*/
|
||||
u32 GetThreadId() const {
|
||||
u32 GetThreadID() const {
|
||||
return thread_id;
|
||||
}
|
||||
|
||||
@@ -203,6 +220,11 @@ public:
|
||||
return tpidr_el0;
|
||||
}
|
||||
|
||||
/// Sets the value of the TPIDR_EL0 Read/Write system register for this thread.
|
||||
void SetTPIDR_EL0(u64 value) {
|
||||
tpidr_el0 = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the address of the current thread's command buffer, located in the TLS.
|
||||
* @returns VAddr of the thread's command buffer.
|
||||
@@ -218,69 +240,193 @@ public:
|
||||
return status == ThreadStatus::WaitSynchAll;
|
||||
}
|
||||
|
||||
Core::ARM_Interface::ThreadContext context;
|
||||
ThreadContext& GetContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
u32 thread_id;
|
||||
const ThreadContext& GetContext() const {
|
||||
return context;
|
||||
}
|
||||
|
||||
ThreadStatus status;
|
||||
VAddr entry_point;
|
||||
VAddr stack_top;
|
||||
ThreadStatus GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
u32 nominal_priority; ///< Nominal thread priority, as set by the emulated application
|
||||
u32 current_priority; ///< Current thread priority, can be temporarily changed
|
||||
void SetStatus(ThreadStatus new_status);
|
||||
|
||||
u64 last_running_ticks; ///< CPU tick when thread was last running
|
||||
u64 GetLastRunningTicks() const {
|
||||
return last_running_ticks;
|
||||
}
|
||||
|
||||
s32 processor_id;
|
||||
s32 GetProcessorID() const {
|
||||
return processor_id;
|
||||
}
|
||||
|
||||
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
|
||||
u64 tpidr_el0; ///< TPIDR_EL0 read/write system register.
|
||||
SharedPtr<Process>& GetOwnerProcess() {
|
||||
return owner_process;
|
||||
}
|
||||
|
||||
SharedPtr<Process> owner_process; ///< Process that owns this thread
|
||||
const SharedPtr<Process>& GetOwnerProcess() const {
|
||||
return owner_process;
|
||||
}
|
||||
|
||||
/// Objects that the thread is waiting on, in the same order as they were
|
||||
// passed to WaitSynchronization1/N.
|
||||
std::vector<SharedPtr<WaitObject>> wait_objects;
|
||||
const ThreadWaitObjects& GetWaitObjects() const {
|
||||
return wait_objects;
|
||||
}
|
||||
|
||||
/// List of threads that are waiting for a mutex that is held by this thread.
|
||||
std::vector<SharedPtr<Thread>> wait_mutex_threads;
|
||||
void SetWaitObjects(ThreadWaitObjects objects) {
|
||||
wait_objects = std::move(objects);
|
||||
}
|
||||
|
||||
/// Thread that owns the lock that this thread is waiting for.
|
||||
SharedPtr<Thread> lock_owner;
|
||||
void ClearWaitObjects() {
|
||||
wait_objects.clear();
|
||||
}
|
||||
|
||||
// If waiting on a ConditionVariable, this is the ConditionVariable address
|
||||
VAddr condvar_wait_address;
|
||||
VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
|
||||
Handle wait_handle; ///< The handle used to wait for the mutex.
|
||||
/// Determines whether all the objects this thread is waiting on are ready.
|
||||
bool AllWaitObjectsReady();
|
||||
|
||||
// If waiting for an AddressArbiter, this is the address being waited on.
|
||||
VAddr arb_wait_address{0};
|
||||
const MutexWaitingThreads& GetMutexWaitingThreads() const {
|
||||
return wait_mutex_threads;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
Thread* GetLockOwner() const {
|
||||
return lock_owner.get();
|
||||
}
|
||||
|
||||
/// Handle used by guest emulated application to access this thread
|
||||
Handle guest_handle;
|
||||
void SetLockOwner(SharedPtr<Thread> owner) {
|
||||
lock_owner = std::move(owner);
|
||||
}
|
||||
|
||||
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
|
||||
Handle callback_handle;
|
||||
VAddr GetCondVarWaitAddress() const {
|
||||
return condvar_wait_address;
|
||||
}
|
||||
|
||||
using WakeupCallback = bool(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index);
|
||||
// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
|
||||
// was waiting via WaitSynchronizationN then the object will be the last object that became
|
||||
// available. In case of a timeout, the object will be nullptr.
|
||||
std::function<WakeupCallback> wakeup_callback;
|
||||
void SetCondVarWaitAddress(VAddr address) {
|
||||
condvar_wait_address = address;
|
||||
}
|
||||
|
||||
std::shared_ptr<Scheduler> scheduler;
|
||||
VAddr GetMutexWaitAddress() const {
|
||||
return mutex_wait_address;
|
||||
}
|
||||
|
||||
u32 ideal_core{0xFFFFFFFF};
|
||||
u64 affinity_mask{0x1};
|
||||
void SetMutexWaitAddress(VAddr address) {
|
||||
mutex_wait_address = address;
|
||||
}
|
||||
|
||||
Handle GetWaitHandle() const {
|
||||
return wait_handle;
|
||||
}
|
||||
|
||||
void SetWaitHandle(Handle handle) {
|
||||
wait_handle = handle;
|
||||
}
|
||||
|
||||
VAddr GetArbiterWaitAddress() const {
|
||||
return arb_wait_address;
|
||||
}
|
||||
|
||||
void SetArbiterWaitAddress(VAddr address) {
|
||||
arb_wait_address = address;
|
||||
}
|
||||
|
||||
void SetGuestHandle(Handle handle) {
|
||||
guest_handle = handle;
|
||||
}
|
||||
|
||||
bool HasWakeupCallback() const {
|
||||
return wakeup_callback != nullptr;
|
||||
}
|
||||
|
||||
void SetWakeupCallback(WakeupCallback callback) {
|
||||
wakeup_callback = std::move(callback);
|
||||
}
|
||||
|
||||
void InvalidateWakeupCallback() {
|
||||
SetWakeupCallback(nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the thread's wakeup callback.
|
||||
*
|
||||
* @pre A valid wakeup callback has been set. Violating this precondition
|
||||
* will cause an assertion to trigger.
|
||||
*/
|
||||
bool InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index);
|
||||
|
||||
u32 GetIdealCore() const {
|
||||
return ideal_core;
|
||||
}
|
||||
|
||||
u64 GetAffinityMask() const {
|
||||
return affinity_mask;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Thread(KernelCore& kernel);
|
||||
~Thread() override;
|
||||
|
||||
Core::ARM_Interface::ThreadContext context{};
|
||||
|
||||
u32 thread_id = 0;
|
||||
|
||||
ThreadStatus status = ThreadStatus::Dormant;
|
||||
|
||||
VAddr entry_point = 0;
|
||||
VAddr stack_top = 0;
|
||||
|
||||
u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
|
||||
u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
|
||||
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
|
||||
s32 processor_id = 0;
|
||||
|
||||
VAddr tls_address = 0; ///< Virtual address of the Thread Local Storage of the thread
|
||||
u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register.
|
||||
|
||||
/// Process that owns this thread
|
||||
SharedPtr<Process> owner_process;
|
||||
|
||||
/// Objects that the thread is waiting on, in the same order as they were
|
||||
/// passed to WaitSynchronization1/N.
|
||||
ThreadWaitObjects wait_objects;
|
||||
|
||||
/// List of threads that are waiting for a mutex that is held by this thread.
|
||||
MutexWaitingThreads wait_mutex_threads;
|
||||
|
||||
/// Thread that owns the lock that this thread is waiting for.
|
||||
SharedPtr<Thread> lock_owner;
|
||||
|
||||
/// If waiting on a ConditionVariable, this is the ConditionVariable address
|
||||
VAddr condvar_wait_address = 0;
|
||||
/// If waiting on a Mutex, this is the mutex address
|
||||
VAddr mutex_wait_address = 0;
|
||||
/// The handle used to wait for the mutex.
|
||||
Handle wait_handle = 0;
|
||||
|
||||
/// If waiting for an AddressArbiter, this is the address being waited on.
|
||||
VAddr arb_wait_address{0};
|
||||
|
||||
/// Handle used by guest emulated application to access this thread
|
||||
Handle guest_handle = 0;
|
||||
|
||||
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
|
||||
Handle callback_handle = 0;
|
||||
|
||||
/// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
|
||||
/// was waiting via WaitSynchronizationN then the object will be the last object that became
|
||||
/// available. In case of a timeout, the object will be nullptr.
|
||||
WakeupCallback wakeup_callback;
|
||||
|
||||
Scheduler* scheduler = nullptr;
|
||||
|
||||
u32 ideal_core{0xFFFFFFFF};
|
||||
u64 affinity_mask{0x1};
|
||||
|
||||
TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>();
|
||||
|
||||
std::string name;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/memory.h"
|
||||
@@ -54,30 +55,32 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
|
||||
}
|
||||
|
||||
VMManager::VMManager() {
|
||||
Reset();
|
||||
// Default to assuming a 39-bit address space. This way we have a sane
|
||||
// starting point with executables that don't provide metadata.
|
||||
Reset(FileSys::ProgramAddressSpaceType::Is39Bit);
|
||||
}
|
||||
|
||||
VMManager::~VMManager() {
|
||||
Reset();
|
||||
Reset(FileSys::ProgramAddressSpaceType::Is39Bit);
|
||||
}
|
||||
|
||||
void VMManager::Reset() {
|
||||
vma_map.clear();
|
||||
void VMManager::Reset(FileSys::ProgramAddressSpaceType type) {
|
||||
Clear();
|
||||
|
||||
InitializeMemoryRegionRanges(type);
|
||||
|
||||
page_table.Resize(address_space_width);
|
||||
|
||||
// Initialize the map with a single free region covering the entire managed space.
|
||||
VirtualMemoryArea initial_vma;
|
||||
initial_vma.size = MAX_ADDRESS;
|
||||
initial_vma.size = address_space_end;
|
||||
vma_map.emplace(initial_vma.base, initial_vma);
|
||||
|
||||
page_table.pointers.fill(nullptr);
|
||||
page_table.special_regions.clear();
|
||||
page_table.attributes.fill(Memory::PageType::Unmapped);
|
||||
|
||||
UpdatePageTableForVMA(initial_vma);
|
||||
}
|
||||
|
||||
VMManager::VMAHandle VMManager::FindVMA(VAddr target) const {
|
||||
if (target >= MAX_ADDRESS) {
|
||||
if (target >= address_space_end) {
|
||||
return vma_map.end();
|
||||
} else {
|
||||
return std::prev(vma_map.upper_bound(target));
|
||||
@@ -291,7 +294,7 @@ ResultVal<VMManager::VMAIter> VMManager::CarveVMARange(VAddr target, u64 size) {
|
||||
|
||||
const VAddr target_end = target + size;
|
||||
ASSERT(target_end >= target);
|
||||
ASSERT(target_end <= MAX_ADDRESS);
|
||||
ASSERT(target_end <= address_space_end);
|
||||
ASSERT(size > 0);
|
||||
|
||||
VMAIter begin_vma = StripIterConstness(FindVMA(target));
|
||||
@@ -382,6 +385,85 @@ void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
|
||||
}
|
||||
}
|
||||
|
||||
void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType type) {
|
||||
u64 map_region_size = 0;
|
||||
u64 heap_region_size = 0;
|
||||
u64 new_map_region_size = 0;
|
||||
u64 tls_io_region_size = 0;
|
||||
|
||||
switch (type) {
|
||||
case FileSys::ProgramAddressSpaceType::Is32Bit:
|
||||
address_space_width = 32;
|
||||
code_region_base = 0x200000;
|
||||
code_region_end = code_region_base + 0x3FE00000;
|
||||
map_region_size = 0x40000000;
|
||||
heap_region_size = 0x40000000;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is36Bit:
|
||||
address_space_width = 36;
|
||||
code_region_base = 0x8000000;
|
||||
code_region_end = code_region_base + 0x78000000;
|
||||
map_region_size = 0x180000000;
|
||||
heap_region_size = 0x180000000;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
|
||||
address_space_width = 32;
|
||||
code_region_base = 0x200000;
|
||||
code_region_end = code_region_base + 0x3FE00000;
|
||||
map_region_size = 0;
|
||||
heap_region_size = 0x80000000;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is39Bit:
|
||||
address_space_width = 39;
|
||||
code_region_base = 0x8000000;
|
||||
code_region_end = code_region_base + 0x80000000;
|
||||
map_region_size = 0x1000000000;
|
||||
heap_region_size = 0x180000000;
|
||||
new_map_region_size = 0x80000000;
|
||||
tls_io_region_size = 0x1000000000;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid address space type specified: {}", static_cast<u32>(type));
|
||||
return;
|
||||
}
|
||||
|
||||
address_space_base = 0;
|
||||
address_space_end = 1ULL << address_space_width;
|
||||
|
||||
map_region_base = code_region_end;
|
||||
map_region_end = map_region_base + map_region_size;
|
||||
|
||||
heap_region_base = map_region_end;
|
||||
heap_region_end = heap_region_base + heap_region_size;
|
||||
|
||||
new_map_region_base = heap_region_end;
|
||||
new_map_region_end = new_map_region_base + new_map_region_size;
|
||||
|
||||
tls_io_region_base = new_map_region_end;
|
||||
tls_io_region_end = tls_io_region_base + tls_io_region_size;
|
||||
|
||||
if (new_map_region_size == 0) {
|
||||
new_map_region_base = address_space_base;
|
||||
new_map_region_end = address_space_end;
|
||||
}
|
||||
}
|
||||
|
||||
void VMManager::Clear() {
|
||||
ClearVMAMap();
|
||||
ClearPageTable();
|
||||
}
|
||||
|
||||
void VMManager::ClearVMAMap() {
|
||||
vma_map.clear();
|
||||
}
|
||||
|
||||
void VMManager::ClearPageTable() {
|
||||
std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
|
||||
page_table.special_regions.clear();
|
||||
std::fill(page_table.attributes.begin(), page_table.attributes.end(),
|
||||
Memory::PageType::Unmapped);
|
||||
}
|
||||
|
||||
u64 VMManager::GetTotalMemoryUsage() const {
|
||||
LOG_WARNING(Kernel, "(STUBBED) called");
|
||||
return 0xF8000000;
|
||||
@@ -392,14 +474,80 @@ u64 VMManager::GetTotalHeapUsage() const {
|
||||
return 0x0;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetAddressSpaceBaseAddr() const {
|
||||
LOG_WARNING(Kernel, "(STUBBED) called");
|
||||
return 0x8000000;
|
||||
VAddr VMManager::GetAddressSpaceBaseAddress() const {
|
||||
return address_space_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetAddressSpaceEndAddress() const {
|
||||
return address_space_end;
|
||||
}
|
||||
|
||||
u64 VMManager::GetAddressSpaceSize() const {
|
||||
LOG_WARNING(Kernel, "(STUBBED) called");
|
||||
return MAX_ADDRESS;
|
||||
return address_space_end - address_space_base;
|
||||
}
|
||||
|
||||
u64 VMManager::GetAddressSpaceWidth() const {
|
||||
return address_space_width;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetCodeRegionBaseAddress() const {
|
||||
return code_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetCodeRegionEndAddress() const {
|
||||
return code_region_end;
|
||||
}
|
||||
|
||||
u64 VMManager::GetCodeRegionSize() const {
|
||||
return code_region_end - code_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetHeapRegionBaseAddress() const {
|
||||
return heap_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetHeapRegionEndAddress() const {
|
||||
return heap_region_end;
|
||||
}
|
||||
|
||||
u64 VMManager::GetHeapRegionSize() const {
|
||||
return heap_region_end - heap_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetMapRegionBaseAddress() const {
|
||||
return map_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetMapRegionEndAddress() const {
|
||||
return map_region_end;
|
||||
}
|
||||
|
||||
u64 VMManager::GetMapRegionSize() const {
|
||||
return map_region_end - map_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetNewMapRegionBaseAddress() const {
|
||||
return new_map_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetNewMapRegionEndAddress() const {
|
||||
return new_map_region_end;
|
||||
}
|
||||
|
||||
u64 VMManager::GetNewMapRegionSize() const {
|
||||
return new_map_region_end - new_map_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetTLSIORegionBaseAddress() const {
|
||||
return tls_io_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetTLSIORegionEndAddress() const {
|
||||
return tls_io_region_end;
|
||||
}
|
||||
|
||||
u64 VMManager::GetTLSIORegionSize() const {
|
||||
return tls_io_region_end - tls_io_region_base;
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user