Compare commits
130 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0d9ab0717 | ||
|
|
da07977db0 | ||
|
|
d5fe722a30 | ||
|
|
9764c13d6d | ||
|
|
ac2e2ebe97 | ||
|
|
157fc2d785 | ||
|
|
9106ac1e6b | ||
|
|
21b18057f7 | ||
|
|
87ff58b1d7 | ||
|
|
ae5725b709 | ||
|
|
64fbf319f1 | ||
|
|
82b7daed9c | ||
|
|
dc81a90640 | ||
|
|
5169ce9fcd | ||
|
|
59c46f9de9 | ||
|
|
12d16248dd | ||
|
|
f20e18f60d | ||
|
|
95d156a150 | ||
|
|
85cfd96f62 | ||
|
|
b02464f685 | ||
|
|
8d55c8c855 | ||
|
|
3f048c8646 | ||
|
|
388cf58b31 | ||
|
|
b36896b90e | ||
|
|
aa87278bf0 | ||
|
|
0383363a8f | ||
|
|
22ba437aa4 | ||
|
|
7dbdda908c | ||
|
|
1defd0847a | ||
|
|
80fece4e08 | ||
|
|
0dc4ab42cc | ||
|
|
453560fb3a | ||
|
|
c8a4967c9d | ||
|
|
1b9e08ab78 | ||
|
|
1e191cc837 | ||
|
|
5dbda22659 | ||
|
|
5836530a87 | ||
|
|
868c397cb6 | ||
|
|
17badbc442 | ||
|
|
d7f5e55f8e | ||
|
|
64fad8cfe9 | ||
|
|
29ccc7673f | ||
|
|
c243932b41 | ||
|
|
1279c7ce7a | ||
|
|
c3e201a829 | ||
|
|
d5984284ed | ||
|
|
10b0ab7926 | ||
|
|
82fa9f8d56 | ||
|
|
51cddcb8b8 | ||
|
|
2ddd83cdfe | ||
|
|
8b95bf041d | ||
|
|
93cb783853 | ||
|
|
d5e0923e3d | ||
|
|
d46ca5a015 | ||
|
|
46183294b2 | ||
|
|
f9653a4417 | ||
|
|
54ea3c47c8 | ||
|
|
5836786246 | ||
|
|
51a7681957 | ||
|
|
d6d1a8e02c | ||
|
|
89df483567 | ||
|
|
a5750f437d | ||
|
|
ccb439efb0 | ||
|
|
0b47f7a46b | ||
|
|
79316be18c | ||
|
|
ec100ca4db | ||
|
|
873ad1272e | ||
|
|
8cb683f3b9 | ||
|
|
5d29d2111c | ||
|
|
ac3b4f918f | ||
|
|
9b023a56a3 | ||
|
|
f3db273753 | ||
|
|
2e1b998d5e | ||
|
|
37bec068c2 | ||
|
|
df6427d30b | ||
|
|
c96930fd9d | ||
|
|
292dd642ce | ||
|
|
761206cf81 | ||
|
|
1c773c0869 | ||
|
|
69b46dd607 | ||
|
|
c918c6480f | ||
|
|
37194dd4e9 | ||
|
|
4de079b256 | ||
|
|
8941cdb7d2 | ||
|
|
dfee6321cd | ||
|
|
0195038c07 | ||
|
|
ac3ec5ed13 | ||
|
|
cdb36aef9e | ||
|
|
5e9b77129f | ||
|
|
2d47a5fd41 | ||
|
|
3802474483 | ||
|
|
d1a2b3fb18 | ||
|
|
b1657b8c6b | ||
|
|
2de124e223 | ||
|
|
deff708cbe | ||
|
|
a9cfe06aaf | ||
|
|
009bdb3558 | ||
|
|
e15039372e | ||
|
|
0eb6c6cd83 | ||
|
|
edcbd47800 | ||
|
|
9e7a1f1351 | ||
|
|
ce0712bf95 | ||
|
|
bcc5c4403a | ||
|
|
1bdb756d28 | ||
|
|
d4ae0ae0e9 | ||
|
|
9b492430bb | ||
|
|
ed4d1e2ade | ||
|
|
b1b4f2337e | ||
|
|
165d8485f0 | ||
|
|
960500cfd2 | ||
|
|
8fd921557f | ||
|
|
4d3be1816c | ||
|
|
357d79fb6e | ||
|
|
d2c0c94f0b | ||
|
|
b1326d9230 | ||
|
|
bc59ca92b6 | ||
|
|
b9b7e4f915 | ||
|
|
ccce6cb3be | ||
|
|
4756cb203e | ||
|
|
8d3e06349e | ||
|
|
9e29e36a78 | ||
|
|
c10a37e5b6 | ||
|
|
7e5d0f1fe3 | ||
|
|
39d356782e | ||
|
|
d58a609ae4 | ||
|
|
493263f415 | ||
|
|
a3ccac3eb7 | ||
|
|
8dbfa4e1a4 | ||
|
|
e18ee8d681 | ||
|
|
c2f83c04cb |
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
mkdir -p "$HOME/.ccache"
|
||||
docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache yuzuemu/build-environments:linux-fresh /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":/home/yuzu/.ccache yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.travis/linux/docker.sh
|
||||
|
||||
@@ -24,8 +24,6 @@ option(YUZU_ENABLE_BOXCAT "Enable the Boxcat service, a yuzu high-level implemen
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(ENABLE_VULKAN "Enables Vulkan backend" ON)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
# Default to a Release build
|
||||
@@ -160,7 +158,6 @@ macro(yuzu_find_packages)
|
||||
# Capitalization matters here. We need the naming to match the generated paths from Conan
|
||||
set(REQUIRED_LIBS
|
||||
# Cmake Pkg Prefix Version Conan Pkg
|
||||
"Boost 1.73 boost/1.73.0"
|
||||
"Catch2 2.13 catch2/2.13.0"
|
||||
"fmt 7.1 fmt/7.1.2"
|
||||
# can't use until https://github.com/bincrafters/community/issues/1173
|
||||
@@ -195,6 +192,22 @@ macro(yuzu_find_packages)
|
||||
unset(FN_FORCE_REQUIRED)
|
||||
endmacro()
|
||||
|
||||
find_package(Boost 1.73.0 COMPONENTS context headers QUIET)
|
||||
if (Boost_FOUND)
|
||||
set(Boost_LIBRARIES Boost::boost)
|
||||
# Conditionally add Boost::context only if the active version of the Conan or system Boost package provides it
|
||||
# The old version is missing Boost::context, so we want to avoid adding in that case
|
||||
# The new version requires adding Boost::context to prevent linking issues
|
||||
#
|
||||
# This one is used by Conan on subsequent CMake configures, not the first configure.
|
||||
if (TARGET Boost::context)
|
||||
list(APPEND Boost_LIBRARIES Boost::context)
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "Boost 1.73.0 or newer not found, falling back to Conan")
|
||||
list(APPEND CONAN_REQUIRED_LIBS "boost/1.73.0")
|
||||
endif()
|
||||
|
||||
# Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS
|
||||
yuzu_find_packages()
|
||||
|
||||
@@ -299,6 +312,17 @@ if (CONAN_REQUIRED_LIBS)
|
||||
# this time with required, so we bail if its not found.
|
||||
yuzu_find_packages(FORCE_REQUIRED)
|
||||
|
||||
if (NOT Boost_FOUND)
|
||||
find_package(Boost 1.73.0 REQUIRED COMPONENTS context headers)
|
||||
set(Boost_LIBRARIES Boost::boost)
|
||||
# Conditionally add Boost::context only if the active version of the Conan Boost package provides it
|
||||
# The old version is missing Boost::context, so we want to avoid adding in that case
|
||||
# The new version requires adding Boost::context to prevent linking issues
|
||||
if (TARGET Boost::context)
|
||||
list(APPEND Boost_LIBRARIES Boost::context)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Due to issues with variable scopes in functions, we need to also find_package(qt5) outside of the function
|
||||
if(ENABLE_QT)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}")
|
||||
|
||||
4
externals/CMakeLists.txt
vendored
4
externals/CMakeLists.txt
vendored
@@ -61,9 +61,7 @@ if (USE_DISCORD_PRESENCE)
|
||||
endif()
|
||||
|
||||
# Sirit
|
||||
if (ENABLE_VULKAN)
|
||||
add_subdirectory(sirit)
|
||||
endif()
|
||||
add_subdirectory(sirit)
|
||||
|
||||
# libzip
|
||||
find_package(Libzip 1.5)
|
||||
|
||||
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 0e1112b7df...4a9a0d07f7
@@ -62,6 +62,7 @@ else()
|
||||
-Werror=implicit-fallthrough
|
||||
-Werror=missing-declarations
|
||||
-Werror=reorder
|
||||
-Werror=uninitialized
|
||||
-Werror=unused-result
|
||||
-Wextra
|
||||
-Wmissing-declarations
|
||||
|
||||
@@ -104,6 +104,7 @@ add_library(common STATIC
|
||||
detached_tasks.h
|
||||
bit_cast.h
|
||||
bit_field.h
|
||||
bit_set.h
|
||||
bit_util.h
|
||||
cityhash.cpp
|
||||
cityhash.h
|
||||
@@ -140,7 +141,6 @@ add_library(common STATIC
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
misc.cpp
|
||||
multi_level_queue.h
|
||||
page_table.cpp
|
||||
page_table.h
|
||||
param_package.cpp
|
||||
@@ -209,7 +209,6 @@ else()
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(common)
|
||||
find_package(Boost 1.71 COMPONENTS context headers REQUIRED)
|
||||
|
||||
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile)
|
||||
target_link_libraries(common PRIVATE lz4::lz4 xbyak)
|
||||
|
||||
99
src/common/bit_set.h
Normal file
99
src/common/bit_set.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2020 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/bit_util.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace impl {
|
||||
|
||||
template <typename Storage, size_t N>
|
||||
class BitSet {
|
||||
|
||||
public:
|
||||
constexpr BitSet() = default;
|
||||
|
||||
constexpr void SetBit(size_t i) {
|
||||
this->words[i / FlagsPerWord] |= GetBitMask(i % FlagsPerWord);
|
||||
}
|
||||
|
||||
constexpr void ClearBit(size_t i) {
|
||||
this->words[i / FlagsPerWord] &= ~GetBitMask(i % FlagsPerWord);
|
||||
}
|
||||
|
||||
constexpr size_t CountLeadingZero() const {
|
||||
for (size_t i = 0; i < NumWords; i++) {
|
||||
if (this->words[i]) {
|
||||
return FlagsPerWord * i + CountLeadingZeroImpl(this->words[i]);
|
||||
}
|
||||
}
|
||||
return FlagsPerWord * NumWords;
|
||||
}
|
||||
|
||||
constexpr size_t GetNextSet(size_t n) const {
|
||||
for (size_t i = (n + 1) / FlagsPerWord; i < NumWords; i++) {
|
||||
Storage word = this->words[i];
|
||||
if (!IsAligned(n + 1, FlagsPerWord)) {
|
||||
word &= GetBitMask(n % FlagsPerWord) - 1;
|
||||
}
|
||||
if (word) {
|
||||
return FlagsPerWord * i + CountLeadingZeroImpl(word);
|
||||
}
|
||||
}
|
||||
return FlagsPerWord * NumWords;
|
||||
}
|
||||
|
||||
private:
|
||||
static_assert(std::is_unsigned_v<Storage>);
|
||||
static_assert(sizeof(Storage) <= sizeof(u64));
|
||||
|
||||
static constexpr size_t FlagsPerWord = BitSize<Storage>();
|
||||
static constexpr size_t NumWords = AlignUp(N, FlagsPerWord) / FlagsPerWord;
|
||||
|
||||
static constexpr auto CountLeadingZeroImpl(Storage word) {
|
||||
return std::countl_zero(static_cast<unsigned long long>(word)) -
|
||||
(BitSize<unsigned long long>() - FlagsPerWord);
|
||||
}
|
||||
|
||||
static constexpr Storage GetBitMask(size_t bit) {
|
||||
return Storage(1) << (FlagsPerWord - 1 - bit);
|
||||
}
|
||||
|
||||
std::array<Storage, NumWords> words{};
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
template <size_t N>
|
||||
using BitSet8 = impl::BitSet<u8, N>;
|
||||
|
||||
template <size_t N>
|
||||
using BitSet16 = impl::BitSet<u16, N>;
|
||||
|
||||
template <size_t N>
|
||||
using BitSet32 = impl::BitSet<u32, N>;
|
||||
|
||||
template <size_t N>
|
||||
using BitSet64 = impl::BitSet<u64, N>;
|
||||
|
||||
} // namespace Common
|
||||
@@ -31,4 +31,8 @@ concept DerivedFrom = requires {
|
||||
std::is_convertible_v<const volatile Derived*, const volatile Base*>;
|
||||
};
|
||||
|
||||
// TODO: Replace with std::convertible_to when libc++ implements it.
|
||||
template <typename From, typename To>
|
||||
concept ConvertibleTo = std::is_convertible_v<From, To>;
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
@@ -68,122 +67,290 @@
|
||||
#include <algorithm>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef S_ISDIR
|
||||
#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
|
||||
#endif
|
||||
|
||||
// This namespace has various generic functions related to files and paths.
|
||||
// The code still needs a ton of cleanup.
|
||||
// REMEMBER: strdup considered harmful!
|
||||
namespace Common::FS {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
bool Exists(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
return fs::exists(path, ec);
|
||||
// Remove any ending forward slashes from directory paths
|
||||
// Modifies argument.
|
||||
static void StripTailDirSlashes(std::string& fname) {
|
||||
if (fname.length() <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t i = fname.length();
|
||||
while (i > 0 && fname[i - 1] == DIR_SEP_CHR) {
|
||||
--i;
|
||||
}
|
||||
fname.resize(i);
|
||||
}
|
||||
|
||||
bool IsDirectory(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
return fs::is_directory(path, ec);
|
||||
bool Exists(const std::string& filename) {
|
||||
struct stat file_info;
|
||||
|
||||
std::string copy(filename);
|
||||
StripTailDirSlashes(copy);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows needs a slash to identify a driver root
|
||||
if (copy.size() != 0 && copy.back() == ':')
|
||||
copy += DIR_SEP_CHR;
|
||||
|
||||
int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
|
||||
#else
|
||||
int result = stat(copy.c_str(), &file_info);
|
||||
#endif
|
||||
|
||||
return (result == 0);
|
||||
}
|
||||
|
||||
bool Delete(const fs::path& path) {
|
||||
LOG_TRACE(Common_Filesystem, "file {}", path.string());
|
||||
bool IsDirectory(const std::string& filename) {
|
||||
struct stat file_info;
|
||||
|
||||
std::string copy(filename);
|
||||
StripTailDirSlashes(copy);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows needs a slash to identify a driver root
|
||||
if (copy.size() != 0 && copy.back() == ':')
|
||||
copy += DIR_SEP_CHR;
|
||||
|
||||
int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
|
||||
#else
|
||||
int result = stat(copy.c_str(), &file_info);
|
||||
#endif
|
||||
|
||||
if (result < 0) {
|
||||
LOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
return S_ISDIR(file_info.st_mode);
|
||||
}
|
||||
|
||||
bool Delete(const std::string& filename) {
|
||||
LOG_TRACE(Common_Filesystem, "file {}", filename);
|
||||
|
||||
// Return true because we care about the file no
|
||||
// being there, not the actual delete.
|
||||
if (!Exists(path)) {
|
||||
LOG_DEBUG(Common_Filesystem, "{} does not exist", path.string());
|
||||
if (!Exists(filename)) {
|
||||
LOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
return fs::remove(path, ec);
|
||||
// We can't delete a directory
|
||||
if (IsDirectory(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
|
||||
LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (unlink(filename.c_str()) == -1) {
|
||||
LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreateDir(const fs::path& path) {
|
||||
LOG_TRACE(Common_Filesystem, "directory {}", path.string());
|
||||
bool CreateDir(const std::string& path) {
|
||||
LOG_TRACE(Common_Filesystem, "directory {}", path);
|
||||
#ifdef _WIN32
|
||||
if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
|
||||
return true;
|
||||
DWORD error = GetLastError();
|
||||
if (error == ERROR_ALREADY_EXISTS) {
|
||||
LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
|
||||
return true;
|
||||
}
|
||||
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
|
||||
return false;
|
||||
#else
|
||||
if (mkdir(path.c_str(), 0755) == 0)
|
||||
return true;
|
||||
|
||||
if (Exists(path)) {
|
||||
LOG_DEBUG(Common_Filesystem, "path exists {}", path.string());
|
||||
int err = errno;
|
||||
|
||||
if (err == EEXIST) {
|
||||
LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
const bool success = fs::create_directory(path, ec);
|
||||
|
||||
if (!success) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to create directory: {}", ec.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CreateDirs(const fs::path& path) {
|
||||
LOG_TRACE(Common_Filesystem, "path {}", path.string());
|
||||
bool CreateFullPath(const std::string& fullPath) {
|
||||
int panicCounter = 100;
|
||||
LOG_TRACE(Common_Filesystem, "path {}", fullPath);
|
||||
|
||||
if (Exists(path)) {
|
||||
LOG_DEBUG(Common_Filesystem, "path exists {}", path.string());
|
||||
if (Exists(fullPath)) {
|
||||
LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
const bool success = fs::create_directories(path, ec);
|
||||
std::size_t position = 0;
|
||||
while (true) {
|
||||
// Find next sub path
|
||||
position = fullPath.find(DIR_SEP_CHR, position);
|
||||
|
||||
if (!success) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to create directories: {}", ec.message());
|
||||
// we're done, yay!
|
||||
if (position == fullPath.npos)
|
||||
return true;
|
||||
|
||||
// Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
|
||||
std::string const subPath(fullPath.substr(0, position + 1));
|
||||
if (!IsDirectory(subPath) && !CreateDir(subPath)) {
|
||||
LOG_ERROR(Common, "CreateFullPath: directory creation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// A safety check
|
||||
panicCounter--;
|
||||
if (panicCounter <= 0) {
|
||||
LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
|
||||
return false;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
}
|
||||
|
||||
bool DeleteDir(const std::string& filename) {
|
||||
LOG_TRACE(Common_Filesystem, "directory {}", filename);
|
||||
|
||||
// check if a directory
|
||||
if (!IsDirectory(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#ifdef _WIN32
|
||||
if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str()))
|
||||
return true;
|
||||
#else
|
||||
if (rmdir(filename.c_str()) == 0)
|
||||
return true;
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CreateFullPath(const fs::path& path) {
|
||||
LOG_TRACE(Common_Filesystem, "path {}", path);
|
||||
|
||||
if (path.has_extension()) {
|
||||
return CreateDirs(path.parent_path());
|
||||
} else {
|
||||
return CreateDirs(path);
|
||||
}
|
||||
bool Rename(const std::string& srcFilename, const std::string& destFilename) {
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
|
||||
#ifdef _WIN32
|
||||
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||
Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
|
||||
return true;
|
||||
#else
|
||||
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
|
||||
return true;
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
|
||||
GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Rename(const fs::path& src, const fs::path& dst) {
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string());
|
||||
bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
|
||||
#ifdef _WIN32
|
||||
if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||
Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
|
||||
return true;
|
||||
|
||||
std::error_code ec;
|
||||
fs::rename(src, dst, ec);
|
||||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
|
||||
GetLastErrorMsg());
|
||||
return false;
|
||||
#else
|
||||
using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to rename file from {} to {}: {}", src.string(),
|
||||
dst.string(), ec.message());
|
||||
// Open input file
|
||||
CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose};
|
||||
if (!input) {
|
||||
LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Copy(const fs::path& src, const fs::path& dst) {
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string());
|
||||
|
||||
std::error_code ec;
|
||||
const bool success = fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
|
||||
|
||||
if (!success) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to copy file {} to {}: {}", src.string(), dst.string(),
|
||||
ec.message());
|
||||
// open output file
|
||||
CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose};
|
||||
if (!output) {
|
||||
LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy loop
|
||||
std::array<char, 1024> buffer;
|
||||
while (!feof(input.get())) {
|
||||
// read input
|
||||
std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get());
|
||||
if (rnum != buffer.size()) {
|
||||
if (ferror(input.get()) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
|
||||
srcFilename, destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// write output
|
||||
std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get());
|
||||
if (wnum != rnum) {
|
||||
LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
|
||||
destFilename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 GetSize(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
const auto size = fs::file_size(path, ec);
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to retrieve file size ({}): {}", path.string(),
|
||||
ec.message());
|
||||
u64 GetSize(const std::string& filename) {
|
||||
if (!Exists(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return size;
|
||||
if (IsDirectory(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat buf;
|
||||
#ifdef _WIN32
|
||||
if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0)
|
||||
#else
|
||||
if (stat(filename.c_str(), &buf) == 0)
|
||||
#endif
|
||||
{
|
||||
LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size);
|
||||
return buf.st_size;
|
||||
}
|
||||
|
||||
LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 GetSize(const int fd) {
|
||||
struct stat buf;
|
||||
if (fstat(fd, &buf) != 0) {
|
||||
LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg());
|
||||
return 0;
|
||||
}
|
||||
return buf.st_size;
|
||||
}
|
||||
|
||||
u64 GetSize(FILE* f) {
|
||||
@@ -233,7 +400,7 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
|
||||
}
|
||||
// windows loop
|
||||
do {
|
||||
const std::string virtual_name = std::filesystem::path(ffd.cFileName).string();
|
||||
const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName));
|
||||
#else
|
||||
DIR* dirp = opendir(directory.c_str());
|
||||
if (!dirp)
|
||||
@@ -271,58 +438,132 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeleteDirRecursively(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
fs::remove_all(path, ec);
|
||||
u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
unsigned int recursion) {
|
||||
const auto callback = [recursion, &parent_entry](u64* num_entries_out,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
FSTEntry entry;
|
||||
entry.virtualName = virtual_name;
|
||||
entry.physicalName = directory + DIR_SEP + virtual_name;
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to completely delete directory {}: {}", path.string(),
|
||||
ec.message());
|
||||
if (IsDirectory(entry.physicalName)) {
|
||||
entry.isDirectory = true;
|
||||
// is a directory, lets go inside if we didn't recurse to often
|
||||
if (recursion > 0) {
|
||||
entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
|
||||
*num_entries_out += entry.size;
|
||||
} else {
|
||||
entry.size = 0;
|
||||
}
|
||||
} else { // is a file
|
||||
entry.isDirectory = false;
|
||||
entry.size = GetSize(entry.physicalName);
|
||||
}
|
||||
(*num_entries_out)++;
|
||||
|
||||
// Push into the tree
|
||||
parent_entry.children.push_back(std::move(entry));
|
||||
return true;
|
||||
};
|
||||
|
||||
u64 num_entries;
|
||||
return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
|
||||
}
|
||||
|
||||
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
|
||||
const auto callback = [recursion](u64*, const std::string& directory,
|
||||
const std::string& virtual_name) {
|
||||
const std::string new_path = directory + DIR_SEP_CHR + virtual_name;
|
||||
|
||||
if (IsDirectory(new_path)) {
|
||||
if (recursion == 0) {
|
||||
return false;
|
||||
}
|
||||
return DeleteDirRecursively(new_path, recursion - 1);
|
||||
}
|
||||
return Delete(new_path);
|
||||
};
|
||||
|
||||
if (!ForeachDirectoryEntry(nullptr, directory, callback))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the outermost directory
|
||||
DeleteDir(directory);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CopyDir(const fs::path& src, const fs::path& dst) {
|
||||
constexpr auto copy_flags = fs::copy_options::skip_existing | fs::copy_options::recursive;
|
||||
void CopyDir([[maybe_unused]] const std::string& source_path,
|
||||
[[maybe_unused]] const std::string& dest_path) {
|
||||
#ifndef _WIN32
|
||||
if (source_path == dest_path) {
|
||||
return;
|
||||
}
|
||||
if (!Exists(source_path)) {
|
||||
return;
|
||||
}
|
||||
if (!Exists(dest_path)) {
|
||||
CreateFullPath(dest_path);
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
fs::copy(src, dst, copy_flags, ec);
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Error copying directory {} to {}: {}", src.string(),
|
||||
dst.string(), ec.message());
|
||||
DIR* dirp = opendir(source_path.c_str());
|
||||
if (!dirp) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_TRACE(Common_Filesystem, "Successfully copied directory.");
|
||||
while (struct dirent* result = readdir(dirp)) {
|
||||
const std::string virtualName(result->d_name);
|
||||
// check for "." and ".."
|
||||
if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
|
||||
((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string source, dest;
|
||||
source = source_path + virtualName;
|
||||
dest = dest_path + virtualName;
|
||||
if (IsDirectory(source)) {
|
||||
source += '/';
|
||||
dest += '/';
|
||||
if (!Exists(dest)) {
|
||||
CreateFullPath(dest);
|
||||
}
|
||||
CopyDir(source, dest);
|
||||
} else if (!Exists(dest)) {
|
||||
Copy(source, dest);
|
||||
}
|
||||
}
|
||||
closedir(dirp);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<fs::path> GetCurrentDir() {
|
||||
std::error_code ec;
|
||||
auto path = fs::current_path(ec);
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to retrieve current working directory: {}",
|
||||
ec.message());
|
||||
std::optional<std::string> GetCurrentDir() {
|
||||
// Get the current working directory (getcwd uses malloc)
|
||||
#ifdef _WIN32
|
||||
wchar_t* dir = _wgetcwd(nullptr, 0);
|
||||
if (!dir) {
|
||||
#else
|
||||
char* dir = getcwd(nullptr, 0);
|
||||
if (!dir) {
|
||||
#endif
|
||||
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return {std::move(path)};
|
||||
#ifdef _WIN32
|
||||
std::string strDir = Common::UTF16ToUTF8(dir);
|
||||
#else
|
||||
std::string strDir = dir;
|
||||
#endif
|
||||
free(dir);
|
||||
return strDir;
|
||||
}
|
||||
|
||||
bool SetCurrentDir(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
fs::current_path(path, ec);
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to set {} as working directory: {}", path.string(),
|
||||
ec.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
bool SetCurrentDir(const std::string& directory) {
|
||||
#ifdef _WIN32
|
||||
return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
|
||||
#else
|
||||
return chdir(directory.c_str()) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
@@ -39,40 +38,48 @@ enum class UserPath {
|
||||
UserDir,
|
||||
};
|
||||
|
||||
// Returns true if the path exists
|
||||
[[nodiscard]] bool Exists(const std::filesystem::path& path);
|
||||
// FileSystem tree node/
|
||||
struct FSTEntry {
|
||||
bool isDirectory;
|
||||
u64 size; // file length or number of entries from children
|
||||
std::string physicalName; // name on disk
|
||||
std::string virtualName; // name in FST names table
|
||||
std::vector<FSTEntry> children;
|
||||
};
|
||||
|
||||
// Returns true if path is a directory
|
||||
[[nodiscard]] bool IsDirectory(const std::filesystem::path& path);
|
||||
// Returns true if file filename exists
|
||||
[[nodiscard]] bool Exists(const std::string& filename);
|
||||
|
||||
// Returns true if filename is a directory
|
||||
[[nodiscard]] bool IsDirectory(const std::string& filename);
|
||||
|
||||
// Returns the size of filename (64bit)
|
||||
[[nodiscard]] u64 GetSize(const std::filesystem::path& path);
|
||||
[[nodiscard]] u64 GetSize(const std::string& filename);
|
||||
|
||||
// Overloaded GetSize, accepts file descriptor
|
||||
[[nodiscard]] u64 GetSize(int fd);
|
||||
|
||||
// Overloaded GetSize, accepts FILE*
|
||||
[[nodiscard]] u64 GetSize(FILE* f);
|
||||
|
||||
// Returns true if successful, or path already exists.
|
||||
bool CreateDir(const std::filesystem::path& path);
|
||||
bool CreateDir(const std::string& filename);
|
||||
|
||||
// Create all directories in path
|
||||
// Returns true if successful, or path already exists.
|
||||
[[nodiscard("Directory creation can fail and must be tested")]] bool CreateDirs(
|
||||
const std::filesystem::path& path);
|
||||
// Creates the full path of fullPath returns true on success
|
||||
bool CreateFullPath(const std::string& fullPath);
|
||||
|
||||
// Creates directories in path. Returns true on success.
|
||||
[[deprecated("This function is deprecated, use CreateDirs")]] bool CreateFullPath(
|
||||
const std::filesystem::path& path);
|
||||
// Deletes a given filename, return true on success
|
||||
// Doesn't supports deleting a directory
|
||||
bool Delete(const std::string& filename);
|
||||
|
||||
// Deletes a given file at the path.
|
||||
// This will also delete empty directories.
|
||||
// Return true on success
|
||||
bool Delete(const std::filesystem::path& path);
|
||||
// Deletes a directory filename, returns true on success
|
||||
bool DeleteDir(const std::string& filename);
|
||||
|
||||
// Renames file src to dst, returns true on success
|
||||
bool Rename(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
// renames file srcFilename to destFilename, returns true on success
|
||||
bool Rename(const std::string& srcFilename, const std::string& destFilename);
|
||||
|
||||
// copies file src to dst, returns true on success
|
||||
bool Copy(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
// copies file srcFilename to destFilename, returns true on success
|
||||
bool Copy(const std::string& srcFilename, const std::string& destFilename);
|
||||
|
||||
// creates an empty file filename, returns true on success
|
||||
bool CreateEmptyFile(const std::string& filename);
|
||||
@@ -99,17 +106,27 @@ using DirectoryEntryCallable = std::function<bool(
|
||||
bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
|
||||
DirectoryEntryCallable callback);
|
||||
|
||||
// Deletes the given path and anything under it. Returns true on success.
|
||||
bool DeleteDirRecursively(const std::filesystem::path& path);
|
||||
/**
|
||||
* Scans the directory tree, storing the results.
|
||||
* @param directory the parent directory to start scanning from
|
||||
* @param parent_entry FSTEntry where the filesystem tree results will be stored.
|
||||
* @param recursion Number of children directories to read before giving up.
|
||||
* @return the total number of files/directories found
|
||||
*/
|
||||
u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
unsigned int recursion = 0);
|
||||
|
||||
// deletes the given directory and anything under it. Returns true on success.
|
||||
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
|
||||
|
||||
// Returns the current directory
|
||||
[[nodiscard]] std::optional<std::filesystem::path> GetCurrentDir();
|
||||
[[nodiscard]] std::optional<std::string> GetCurrentDir();
|
||||
|
||||
// Create directory and copy contents (does not overwrite existing files)
|
||||
void CopyDir(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
void CopyDir(const std::string& source_path, const std::string& dest_path);
|
||||
|
||||
// Set the current directory to given path
|
||||
bool SetCurrentDir(const std::filesystem::path& path);
|
||||
// Set the current directory to given directory
|
||||
bool SetCurrentDir(const std::string& directory);
|
||||
|
||||
// Returns a pointer to a string with a yuzu data dir in the user's home
|
||||
// directory. To be used in "multi-user" mode (that is, installed).
|
||||
|
||||
@@ -1,345 +0,0 @@
|
||||
// Copyright 2019 TuxSH
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <utility>
|
||||
|
||||
#include "common/bit_util.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* A MultiLevelQueue is a type of priority queue which has the following characteristics:
|
||||
* - iteratable through each of its elements.
|
||||
* - back can be obtained.
|
||||
* - O(1) add, lookup (both front and back)
|
||||
* - discrete priorities and a max of 64 priorities (limited domain)
|
||||
* This type of priority queue is normaly used for managing threads within an scheduler
|
||||
*/
|
||||
template <typename T, std::size_t Depth>
|
||||
class MultiLevelQueue {
|
||||
public:
|
||||
using value_type = T;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using pointer = value_type*;
|
||||
using const_pointer = const value_type*;
|
||||
|
||||
using difference_type = typename std::pointer_traits<pointer>::difference_type;
|
||||
using size_type = std::size_t;
|
||||
|
||||
template <bool is_constant>
|
||||
class iterator_impl {
|
||||
public:
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using value_type = T;
|
||||
using pointer = std::conditional_t<is_constant, T*, const T*>;
|
||||
using reference = std::conditional_t<is_constant, const T&, T&>;
|
||||
using difference_type = typename std::pointer_traits<pointer>::difference_type;
|
||||
|
||||
friend bool operator==(const iterator_impl& lhs, const iterator_impl& rhs) {
|
||||
if (lhs.IsEnd() && rhs.IsEnd())
|
||||
return true;
|
||||
return std::tie(lhs.current_priority, lhs.it) == std::tie(rhs.current_priority, rhs.it);
|
||||
}
|
||||
|
||||
friend bool operator!=(const iterator_impl& lhs, const iterator_impl& rhs) {
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
reference operator*() const {
|
||||
return *it;
|
||||
}
|
||||
|
||||
pointer operator->() const {
|
||||
return it.operator->();
|
||||
}
|
||||
|
||||
iterator_impl& operator++() {
|
||||
if (IsEnd()) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
++it;
|
||||
|
||||
if (it == GetEndItForPrio()) {
|
||||
u64 prios = mlq.used_priorities;
|
||||
prios &= ~((1ULL << (current_priority + 1)) - 1);
|
||||
if (prios == 0) {
|
||||
current_priority = static_cast<u32>(mlq.depth());
|
||||
} else {
|
||||
current_priority = CountTrailingZeroes64(prios);
|
||||
it = GetBeginItForPrio();
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator_impl& operator--() {
|
||||
if (IsEnd()) {
|
||||
if (mlq.used_priorities != 0) {
|
||||
current_priority = 63 - CountLeadingZeroes64(mlq.used_priorities);
|
||||
it = GetEndItForPrio();
|
||||
--it;
|
||||
}
|
||||
} else if (it == GetBeginItForPrio()) {
|
||||
u64 prios = mlq.used_priorities;
|
||||
prios &= (1ULL << current_priority) - 1;
|
||||
if (prios != 0) {
|
||||
current_priority = CountTrailingZeroes64(prios);
|
||||
it = GetEndItForPrio();
|
||||
--it;
|
||||
}
|
||||
} else {
|
||||
--it;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator_impl operator++(int) {
|
||||
const iterator_impl v{*this};
|
||||
++(*this);
|
||||
return v;
|
||||
}
|
||||
|
||||
iterator_impl operator--(int) {
|
||||
const iterator_impl v{*this};
|
||||
--(*this);
|
||||
return v;
|
||||
}
|
||||
|
||||
// allow implicit const->non-const
|
||||
iterator_impl(const iterator_impl<false>& other)
|
||||
: mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
|
||||
|
||||
iterator_impl(const iterator_impl<true>& other)
|
||||
: mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
|
||||
|
||||
iterator_impl& operator=(const iterator_impl<false>& other) {
|
||||
mlq = other.mlq;
|
||||
it = other.it;
|
||||
current_priority = other.current_priority;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend class iterator_impl<true>;
|
||||
iterator_impl() = default;
|
||||
|
||||
private:
|
||||
friend class MultiLevelQueue;
|
||||
using container_ref =
|
||||
std::conditional_t<is_constant, const MultiLevelQueue&, MultiLevelQueue&>;
|
||||
using list_iterator = std::conditional_t<is_constant, typename std::list<T>::const_iterator,
|
||||
typename std::list<T>::iterator>;
|
||||
|
||||
explicit iterator_impl(container_ref mlq, list_iterator it, u32 current_priority)
|
||||
: mlq(mlq), it(it), current_priority(current_priority) {}
|
||||
explicit iterator_impl(container_ref mlq, u32 current_priority)
|
||||
: mlq(mlq), it(), current_priority(current_priority) {}
|
||||
|
||||
bool IsEnd() const {
|
||||
return current_priority == mlq.depth();
|
||||
}
|
||||
|
||||
list_iterator GetBeginItForPrio() const {
|
||||
return mlq.levels[current_priority].begin();
|
||||
}
|
||||
|
||||
list_iterator GetEndItForPrio() const {
|
||||
return mlq.levels[current_priority].end();
|
||||
}
|
||||
|
||||
container_ref mlq;
|
||||
list_iterator it;
|
||||
u32 current_priority;
|
||||
};
|
||||
|
||||
using iterator = iterator_impl<false>;
|
||||
using const_iterator = iterator_impl<true>;
|
||||
|
||||
void add(const T& element, u32 priority, bool send_back = true) {
|
||||
if (send_back)
|
||||
levels[priority].push_back(element);
|
||||
else
|
||||
levels[priority].push_front(element);
|
||||
used_priorities |= 1ULL << priority;
|
||||
}
|
||||
|
||||
void remove(const T& element, u32 priority) {
|
||||
auto it = ListIterateTo(levels[priority], element);
|
||||
if (it == levels[priority].end())
|
||||
return;
|
||||
levels[priority].erase(it);
|
||||
if (levels[priority].empty()) {
|
||||
used_priorities &= ~(1ULL << priority);
|
||||
}
|
||||
}
|
||||
|
||||
void adjust(const T& element, u32 old_priority, u32 new_priority, bool adjust_front = false) {
|
||||
remove(element, old_priority);
|
||||
add(element, new_priority, !adjust_front);
|
||||
}
|
||||
void adjust(const_iterator it, u32 old_priority, u32 new_priority, bool adjust_front = false) {
|
||||
adjust(*it, old_priority, new_priority, adjust_front);
|
||||
}
|
||||
|
||||
void transfer_to_front(const T& element, u32 priority, MultiLevelQueue& other) {
|
||||
ListSplice(other.levels[priority], other.levels[priority].begin(), levels[priority],
|
||||
ListIterateTo(levels[priority], element));
|
||||
|
||||
other.used_priorities |= 1ULL << priority;
|
||||
|
||||
if (levels[priority].empty()) {
|
||||
used_priorities &= ~(1ULL << priority);
|
||||
}
|
||||
}
|
||||
|
||||
void transfer_to_front(const_iterator it, u32 priority, MultiLevelQueue& other) {
|
||||
transfer_to_front(*it, priority, other);
|
||||
}
|
||||
|
||||
void transfer_to_back(const T& element, u32 priority, MultiLevelQueue& other) {
|
||||
ListSplice(other.levels[priority], other.levels[priority].end(), levels[priority],
|
||||
ListIterateTo(levels[priority], element));
|
||||
|
||||
other.used_priorities |= 1ULL << priority;
|
||||
|
||||
if (levels[priority].empty()) {
|
||||
used_priorities &= ~(1ULL << priority);
|
||||
}
|
||||
}
|
||||
|
||||
void transfer_to_back(const_iterator it, u32 priority, MultiLevelQueue& other) {
|
||||
transfer_to_back(*it, priority, other);
|
||||
}
|
||||
|
||||
void yield(u32 priority, std::size_t n = 1) {
|
||||
ListShiftForward(levels[priority], n);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t depth() const {
|
||||
return Depth;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t size(u32 priority) const {
|
||||
return levels[priority].size();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t size() const {
|
||||
u64 priorities = used_priorities;
|
||||
std::size_t size = 0;
|
||||
while (priorities != 0) {
|
||||
const u64 current_priority = CountTrailingZeroes64(priorities);
|
||||
size += levels[current_priority].size();
|
||||
priorities &= ~(1ULL << current_priority);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return used_priorities == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool empty(u32 priority) const {
|
||||
return (used_priorities & (1ULL << priority)) == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 highest_priority_set(u32 max_priority = 0) const {
|
||||
const u64 priorities =
|
||||
max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1));
|
||||
return priorities == 0 ? Depth : static_cast<u32>(CountTrailingZeroes64(priorities));
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
|
||||
const u64 priorities = min_priority >= Depth - 1
|
||||
? used_priorities
|
||||
: (used_priorities & ((1ULL << (min_priority + 1)) - 1));
|
||||
return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities);
|
||||
}
|
||||
|
||||
[[nodiscard]] const_iterator cbegin(u32 max_prio = 0) const {
|
||||
const u32 priority = highest_priority_set(max_prio);
|
||||
return priority == Depth ? cend()
|
||||
: const_iterator{*this, levels[priority].cbegin(), priority};
|
||||
}
|
||||
[[nodiscard]] const_iterator begin(u32 max_prio = 0) const {
|
||||
return cbegin(max_prio);
|
||||
}
|
||||
[[nodiscard]] iterator begin(u32 max_prio = 0) {
|
||||
const u32 priority = highest_priority_set(max_prio);
|
||||
return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority};
|
||||
}
|
||||
|
||||
[[nodiscard]] const_iterator cend(u32 min_prio = Depth - 1) const {
|
||||
return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1);
|
||||
}
|
||||
[[nodiscard]] const_iterator end(u32 min_prio = Depth - 1) const {
|
||||
return cend(min_prio);
|
||||
}
|
||||
[[nodiscard]] iterator end(u32 min_prio = Depth - 1) {
|
||||
return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] T& front(u32 max_priority = 0) {
|
||||
const u32 priority = highest_priority_set(max_priority);
|
||||
return levels[priority == Depth ? 0 : priority].front();
|
||||
}
|
||||
[[nodiscard]] const T& front(u32 max_priority = 0) const {
|
||||
const u32 priority = highest_priority_set(max_priority);
|
||||
return levels[priority == Depth ? 0 : priority].front();
|
||||
}
|
||||
|
||||
[[nodiscard]] T& back(u32 min_priority = Depth - 1) {
|
||||
const u32 priority = lowest_priority_set(min_priority); // intended
|
||||
return levels[priority == Depth ? 63 : priority].back();
|
||||
}
|
||||
[[nodiscard]] const T& back(u32 min_priority = Depth - 1) const {
|
||||
const u32 priority = lowest_priority_set(min_priority); // intended
|
||||
return levels[priority == Depth ? 63 : priority].back();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
used_priorities = 0;
|
||||
for (std::size_t i = 0; i < Depth; i++) {
|
||||
levels[i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
using const_list_iterator = typename std::list<T>::const_iterator;
|
||||
|
||||
static void ListShiftForward(std::list<T>& list, const std::size_t shift = 1) {
|
||||
if (shift >= list.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto begin_range = list.begin();
|
||||
const auto end_range = std::next(begin_range, shift);
|
||||
list.splice(list.end(), list, begin_range, end_range);
|
||||
}
|
||||
|
||||
static void ListSplice(std::list<T>& in_list, const_list_iterator position,
|
||||
std::list<T>& out_list, const_list_iterator element) {
|
||||
in_list.splice(position, out_list, element);
|
||||
}
|
||||
|
||||
[[nodiscard]] static const_list_iterator ListIterateTo(const std::list<T>& list,
|
||||
const T& element) {
|
||||
auto it = list.cbegin();
|
||||
while (it != list.cend() && *it != element) {
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
std::array<std::list<T>, Depth> levels;
|
||||
u64 used_priorities = 0;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -41,6 +41,7 @@ add_library(core STATIC
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/common_funcs.h
|
||||
file_sys/content_archive.cpp
|
||||
file_sys/content_archive.h
|
||||
file_sys/control_metadata.cpp
|
||||
@@ -134,6 +135,8 @@ add_library(core STATIC
|
||||
frontend/emu_window.h
|
||||
frontend/framebuffer_layout.cpp
|
||||
frontend/framebuffer_layout.h
|
||||
frontend/input_interpreter.cpp
|
||||
frontend/input_interpreter.h
|
||||
frontend/input.h
|
||||
hardware_interrupt_manager.cpp
|
||||
hardware_interrupt_manager.h
|
||||
@@ -148,10 +151,19 @@ add_library(core STATIC
|
||||
hle/kernel/code_set.cpp
|
||||
hle/kernel/code_set.h
|
||||
hle/kernel/errors.h
|
||||
hle/kernel/global_scheduler_context.cpp
|
||||
hle/kernel/global_scheduler_context.h
|
||||
hle/kernel/handle_table.cpp
|
||||
hle/kernel/handle_table.h
|
||||
hle/kernel/hle_ipc.cpp
|
||||
hle/kernel/hle_ipc.h
|
||||
hle/kernel/k_affinity_mask.h
|
||||
hle/kernel/k_priority_queue.h
|
||||
hle/kernel/k_scheduler.cpp
|
||||
hle/kernel/k_scheduler.h
|
||||
hle/kernel/k_scheduler_lock.h
|
||||
hle/kernel/k_scoped_lock.h
|
||||
hle/kernel/k_scoped_scheduler_lock_and_sleep.h
|
||||
hle/kernel/kernel.cpp
|
||||
hle/kernel/kernel.h
|
||||
hle/kernel/memory/address_space_info.cpp
|
||||
@@ -186,8 +198,6 @@ add_library(core STATIC
|
||||
hle/kernel/readable_event.h
|
||||
hle/kernel/resource_limit.cpp
|
||||
hle/kernel/resource_limit.h
|
||||
hle/kernel/scheduler.cpp
|
||||
hle/kernel/scheduler.h
|
||||
hle/kernel/server_port.cpp
|
||||
hle/kernel/server_port.h
|
||||
hle/kernel/server_session.cpp
|
||||
|
||||
@@ -294,6 +294,9 @@ void ARM_Dynarmic_32::InvalidateCacheRange(VAddr addr, std::size_t size) {
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::ClearExclusiveState() {
|
||||
if (!jit) {
|
||||
return;
|
||||
}
|
||||
jit->ClearExclusiveState();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hardware_properties.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
@@ -330,6 +330,9 @@ void ARM_Dynarmic_64::InvalidateCacheRange(VAddr addr, std::size_t size) {
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_64::ClearExclusiveState() {
|
||||
if (!jit) {
|
||||
return;
|
||||
}
|
||||
jit->ClearExclusiveState();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hardware_interrupt_manager.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
#include "core/hle/service/apm/controller.h"
|
||||
@@ -507,14 +507,6 @@ std::size_t System::CurrentCoreIndex() const {
|
||||
return core;
|
||||
}
|
||||
|
||||
Kernel::Scheduler& System::CurrentScheduler() {
|
||||
return impl->kernel.CurrentScheduler();
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& System::CurrentScheduler() const {
|
||||
return impl->kernel.CurrentScheduler();
|
||||
}
|
||||
|
||||
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
|
||||
return impl->kernel.CurrentPhysicalCore();
|
||||
}
|
||||
@@ -523,22 +515,14 @@ const Kernel::PhysicalCore& System::CurrentPhysicalCore() const {
|
||||
return impl->kernel.CurrentPhysicalCore();
|
||||
}
|
||||
|
||||
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
|
||||
return impl->kernel.Scheduler(core_index);
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
|
||||
return impl->kernel.Scheduler(core_index);
|
||||
/// Gets the global scheduler
|
||||
Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() {
|
||||
return impl->kernel.GlobalSchedulerContext();
|
||||
}
|
||||
|
||||
/// Gets the global scheduler
|
||||
Kernel::GlobalScheduler& System::GlobalScheduler() {
|
||||
return impl->kernel.GlobalScheduler();
|
||||
}
|
||||
|
||||
/// Gets the global scheduler
|
||||
const Kernel::GlobalScheduler& System::GlobalScheduler() const {
|
||||
return impl->kernel.GlobalScheduler();
|
||||
const Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() const {
|
||||
return impl->kernel.GlobalSchedulerContext();
|
||||
}
|
||||
|
||||
Kernel::Process* System::CurrentProcess() {
|
||||
|
||||
@@ -26,11 +26,11 @@ class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
namespace Kernel {
|
||||
class GlobalScheduler;
|
||||
class GlobalSchedulerContext;
|
||||
class KernelCore;
|
||||
class PhysicalCore;
|
||||
class Process;
|
||||
class Scheduler;
|
||||
class KScheduler;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Loader {
|
||||
@@ -213,12 +213,6 @@ public:
|
||||
/// Gets the index of the currently running CPU core
|
||||
[[nodiscard]] std::size_t CurrentCoreIndex() const;
|
||||
|
||||
/// Gets the scheduler for the CPU core that is currently running
|
||||
[[nodiscard]] Kernel::Scheduler& CurrentScheduler();
|
||||
|
||||
/// Gets the scheduler for the CPU core that is currently running
|
||||
[[nodiscard]] const Kernel::Scheduler& CurrentScheduler() const;
|
||||
|
||||
/// Gets the physical core for the CPU core that is currently running
|
||||
[[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore();
|
||||
|
||||
@@ -261,17 +255,11 @@ public:
|
||||
/// Gets an immutable reference to the renderer.
|
||||
[[nodiscard]] const VideoCore::RendererBase& Renderer() const;
|
||||
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
[[nodiscard]] Kernel::Scheduler& Scheduler(std::size_t core_index);
|
||||
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
[[nodiscard]] const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
|
||||
/// Gets the global scheduler
|
||||
[[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
|
||||
|
||||
/// Gets the global scheduler
|
||||
[[nodiscard]] Kernel::GlobalScheduler& GlobalScheduler();
|
||||
|
||||
/// Gets the global scheduler
|
||||
[[nodiscard]] const Kernel::GlobalScheduler& GlobalScheduler() const;
|
||||
[[nodiscard]] const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const;
|
||||
|
||||
/// Gets the manager for the guest device memory
|
||||
[[nodiscard]] Core::DeviceMemory& DeviceMemory();
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
@@ -109,11 +109,8 @@ void* CpuManager::GetStartFuncParamater() {
|
||||
|
||||
void CpuManager::MultiCoreRunGuestThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
{
|
||||
auto& sched = kernel.CurrentScheduler();
|
||||
sched.OnThreadStart();
|
||||
}
|
||||
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
|
||||
kernel.CurrentScheduler()->OnThreadStart();
|
||||
auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||
auto& host_context = thread->GetHostContext();
|
||||
host_context->SetRewindPoint(GuestRewindFunction, this);
|
||||
MultiCoreRunGuestLoop();
|
||||
@@ -130,8 +127,8 @@ void CpuManager::MultiCoreRunGuestLoop() {
|
||||
physical_core = &kernel.CurrentPhysicalCore();
|
||||
}
|
||||
system.ExitDynarmicProfile();
|
||||
auto& scheduler = kernel.CurrentScheduler();
|
||||
scheduler.TryDoContextSwitch();
|
||||
physical_core->ArmInterface().ClearExclusiveState();
|
||||
kernel.CurrentScheduler()->RescheduleCurrentCore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,25 +137,21 @@ void CpuManager::MultiCoreRunIdleThread() {
|
||||
while (true) {
|
||||
auto& physical_core = kernel.CurrentPhysicalCore();
|
||||
physical_core.Idle();
|
||||
auto& scheduler = kernel.CurrentScheduler();
|
||||
scheduler.TryDoContextSwitch();
|
||||
kernel.CurrentScheduler()->RescheduleCurrentCore();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::MultiCoreRunSuspendThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
{
|
||||
auto& sched = kernel.CurrentScheduler();
|
||||
sched.OnThreadStart();
|
||||
}
|
||||
kernel.CurrentScheduler()->OnThreadStart();
|
||||
while (true) {
|
||||
auto core = kernel.GetCurrentHostThreadID();
|
||||
auto& scheduler = kernel.CurrentScheduler();
|
||||
auto& scheduler = *kernel.CurrentScheduler();
|
||||
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
|
||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[core].host_context);
|
||||
ASSERT(scheduler.ContextSwitchPending());
|
||||
ASSERT(core == kernel.GetCurrentHostThreadID());
|
||||
scheduler.TryDoContextSwitch();
|
||||
scheduler.RescheduleCurrentCore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,11 +199,8 @@ void CpuManager::MultiCorePause(bool paused) {
|
||||
|
||||
void CpuManager::SingleCoreRunGuestThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
{
|
||||
auto& sched = kernel.CurrentScheduler();
|
||||
sched.OnThreadStart();
|
||||
}
|
||||
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
|
||||
kernel.CurrentScheduler()->OnThreadStart();
|
||||
auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||
auto& host_context = thread->GetHostContext();
|
||||
host_context->SetRewindPoint(GuestRewindFunction, this);
|
||||
SingleCoreRunGuestLoop();
|
||||
@@ -218,7 +208,7 @@ void CpuManager::SingleCoreRunGuestThread() {
|
||||
|
||||
void CpuManager::SingleCoreRunGuestLoop() {
|
||||
auto& kernel = system.Kernel();
|
||||
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
|
||||
auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||
while (true) {
|
||||
auto* physical_core = &kernel.CurrentPhysicalCore();
|
||||
system.EnterDynarmicProfile();
|
||||
@@ -230,9 +220,10 @@ void CpuManager::SingleCoreRunGuestLoop() {
|
||||
thread->SetPhantomMode(true);
|
||||
system.CoreTiming().Advance();
|
||||
thread->SetPhantomMode(false);
|
||||
physical_core->ArmInterface().ClearExclusiveState();
|
||||
PreemptSingleCore();
|
||||
auto& scheduler = kernel.Scheduler(current_core);
|
||||
scheduler.TryDoContextSwitch();
|
||||
scheduler.RescheduleCurrentCore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,51 +235,53 @@ void CpuManager::SingleCoreRunIdleThread() {
|
||||
system.CoreTiming().AddTicks(1000U);
|
||||
idle_count++;
|
||||
auto& scheduler = physical_core.Scheduler();
|
||||
scheduler.TryDoContextSwitch();
|
||||
scheduler.RescheduleCurrentCore();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::SingleCoreRunSuspendThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
{
|
||||
auto& sched = kernel.CurrentScheduler();
|
||||
sched.OnThreadStart();
|
||||
}
|
||||
kernel.CurrentScheduler()->OnThreadStart();
|
||||
while (true) {
|
||||
auto core = kernel.GetCurrentHostThreadID();
|
||||
auto& scheduler = kernel.CurrentScheduler();
|
||||
auto& scheduler = *kernel.CurrentScheduler();
|
||||
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
|
||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[0].host_context);
|
||||
ASSERT(scheduler.ContextSwitchPending());
|
||||
ASSERT(core == kernel.GetCurrentHostThreadID());
|
||||
scheduler.TryDoContextSwitch();
|
||||
scheduler.RescheduleCurrentCore();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
|
||||
std::size_t old_core = current_core;
|
||||
auto& scheduler = system.Kernel().Scheduler(old_core);
|
||||
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
|
||||
if (idle_count >= 4 || from_running_enviroment) {
|
||||
if (!from_running_enviroment) {
|
||||
system.CoreTiming().Idle();
|
||||
{
|
||||
auto& scheduler = system.Kernel().Scheduler(current_core);
|
||||
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
|
||||
if (idle_count >= 4 || from_running_enviroment) {
|
||||
if (!from_running_enviroment) {
|
||||
system.CoreTiming().Idle();
|
||||
idle_count = 0;
|
||||
}
|
||||
current_thread->SetPhantomMode(true);
|
||||
system.CoreTiming().Advance();
|
||||
current_thread->SetPhantomMode(false);
|
||||
}
|
||||
current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
|
||||
system.CoreTiming().ResetTicks();
|
||||
scheduler.Unload(scheduler.GetCurrentThread());
|
||||
|
||||
auto& next_scheduler = system.Kernel().Scheduler(current_core);
|
||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext());
|
||||
}
|
||||
|
||||
// May have changed scheduler
|
||||
{
|
||||
auto& scheduler = system.Kernel().Scheduler(current_core);
|
||||
scheduler.Reload(scheduler.GetCurrentThread());
|
||||
auto* currrent_thread2 = scheduler.GetCurrentThread();
|
||||
if (!currrent_thread2->IsIdleThread()) {
|
||||
idle_count = 0;
|
||||
}
|
||||
current_thread->SetPhantomMode(true);
|
||||
system.CoreTiming().Advance();
|
||||
current_thread->SetPhantomMode(false);
|
||||
}
|
||||
current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
|
||||
system.CoreTiming().ResetTicks();
|
||||
scheduler.Unload();
|
||||
auto& next_scheduler = system.Kernel().Scheduler(current_core);
|
||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext());
|
||||
/// May have changed scheduler
|
||||
auto& current_scheduler = system.Kernel().Scheduler(current_core);
|
||||
current_scheduler.Reload();
|
||||
auto* currrent_thread2 = current_scheduler.GetCurrentThread();
|
||||
if (!currrent_thread2->IsIdleThread()) {
|
||||
idle_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,8 +362,7 @@ void CpuManager::RunThread(std::size_t core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& scheduler = system.Kernel().CurrentScheduler();
|
||||
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
|
||||
auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
|
||||
data.is_running = true;
|
||||
Common::Fiber::YieldTo(data.host_context, current_thread->GetHostContext());
|
||||
data.is_running = false;
|
||||
|
||||
56
src/core/file_sys/common_funcs.h
Normal file
56
src/core/file_sys/common_funcs.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 AOC_TITLE_ID_MASK = 0x7FF;
|
||||
constexpr u64 AOC_TITLE_ID_OFFSET = 0x1000;
|
||||
constexpr u64 BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
|
||||
/**
|
||||
* Gets the base title ID from a given title ID.
|
||||
*
|
||||
* @param title_id The title ID.
|
||||
* @returns The base title ID.
|
||||
*/
|
||||
[[nodiscard]] constexpr u64 GetBaseTitleID(u64 title_id) {
|
||||
return title_id & BASE_TITLE_ID_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base title ID with a program index offset from a given title ID.
|
||||
*
|
||||
* @param title_id The title ID.
|
||||
* @param program_index The program index.
|
||||
* @returns The base title ID with a program index offset.
|
||||
*/
|
||||
[[nodiscard]] constexpr u64 GetBaseTitleIDWithProgramIndex(u64 title_id, u64 program_index) {
|
||||
return GetBaseTitleID(title_id) + program_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AOC (Add-On Content) base title ID from a given title ID.
|
||||
*
|
||||
* @param title_id The title ID.
|
||||
* @returns The AOC base title ID.
|
||||
*/
|
||||
[[nodiscard]] constexpr u64 GetAOCBaseTitleID(u64 title_id) {
|
||||
return GetBaseTitleID(title_id) + AOC_TITLE_ID_OFFSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AOC (Add-On Content) ID from a given AOC title ID.
|
||||
*
|
||||
* @param aoc_title_id The AOC title ID.
|
||||
* @returns The AOC ID.
|
||||
*/
|
||||
[[nodiscard]] constexpr u64 GetAOCID(u64 aoc_title_id) {
|
||||
return aoc_title_id & AOC_TITLE_ID_MASK;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -519,15 +519,17 @@ Loader::ResultStatus NCA::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> NCA::GetFiles() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
std::vector<VirtualFile> NCA::GetFiles() const {
|
||||
if (status != Loader::ResultStatus::Success) {
|
||||
return {};
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> NCA::GetSubdirectories() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
std::vector<VirtualDir> NCA::GetSubdirectories() const {
|
||||
if (status != Loader::ResultStatus::Success) {
|
||||
return {};
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
@@ -535,7 +537,7 @@ std::string NCA::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> NCA::GetParentDirectory() const {
|
||||
VirtualDir NCA::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ struct NCAHeader {
|
||||
};
|
||||
static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
|
||||
|
||||
inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
|
||||
inline bool IsDirectoryExeFS(const VirtualDir& pfs) {
|
||||
// According to switchbrew, an exefs must only contain these two files:
|
||||
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
|
||||
}
|
||||
@@ -104,10 +104,10 @@ public:
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
std::vector<VirtualDir> GetSubdirectories() const override;
|
||||
std::string GetName() const override;
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
NCAContentType GetType() const;
|
||||
u64 GetTitleId() const;
|
||||
|
||||
@@ -191,7 +191,7 @@ bool BKTR::Resize(std::size_t new_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const {
|
||||
VirtualDir BKTR::GetContainingDirectory() const {
|
||||
return base_romfs->GetContainingDirectory();
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
|
||||
bool Resize(std::size_t new_size) override;
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
|
||||
VirtualDir GetContainingDirectory() const override;
|
||||
|
||||
bool IsWritable() const override;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
@@ -30,7 +31,6 @@ namespace FileSys {
|
||||
namespace {
|
||||
|
||||
constexpr u32 SINGLE_BYTE_MODULUS = 0x100;
|
||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
|
||||
constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
|
||||
"main", "main.npdm", "rtld", "sdk", "subsdk0", "subsdk1", "subsdk2",
|
||||
@@ -532,7 +532,7 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u
|
||||
dlc_match.reserve(dlc_entries.size());
|
||||
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||
[this](const ContentProviderEntry& entry) {
|
||||
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
|
||||
return GetBaseTitleID(entry.title_id) == title_id &&
|
||||
content_provider.GetEntry(entry)->GetStatus() ==
|
||||
Loader::ResultStatus::Success;
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -47,6 +48,27 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_titl
|
||||
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const {
|
||||
auto nca = content_provider.GetEntry(title_id, type);
|
||||
|
||||
if (nca == nullptr) {
|
||||
// TODO: Find the right error code to use here
|
||||
return RESULT_UNKNOWN;
|
||||
}
|
||||
|
||||
const PatchManager patch_manager{title_id, filesystem_controller, content_provider};
|
||||
|
||||
return MakeResult<VirtualFile>(
|
||||
patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type));
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFSWithProgramIndex(
|
||||
u64 title_id, u8 program_index, ContentRecordType type) const {
|
||||
const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index);
|
||||
|
||||
return OpenPatchedRomFS(res_title_id, type);
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
|
||||
ContentRecordType type) const {
|
||||
const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type);
|
||||
|
||||
@@ -42,6 +42,10 @@ public:
|
||||
|
||||
void SetPackedUpdate(VirtualFile update_raw);
|
||||
[[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
|
||||
[[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFS(u64 title_id,
|
||||
ContentRecordType type) const;
|
||||
[[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFSWithProgramIndex(
|
||||
u64 title_id, u8 program_index, ContentRecordType type) const;
|
||||
[[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage,
|
||||
ContentRecordType type) const;
|
||||
|
||||
|
||||
@@ -6,191 +6,384 @@
|
||||
|
||||
namespace FileSys::SystemArchive::SharedFontData {
|
||||
|
||||
const std::array<unsigned char, 2932> FONT_NINTENDO_EXTENDED{{
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x80, 0x00, 0x03, 0x00, 0x70, 0x44, 0x53, 0x49, 0x47,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0b, 0x6c, 0x00, 0x00, 0x00, 0x08, 0x4f, 0x53, 0x2f, 0x32,
|
||||
0x33, 0x86, 0x1d, 0x9b, 0x00, 0x00, 0x01, 0x78, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6d, 0x61, 0x70,
|
||||
0xc2, 0x06, 0x20, 0xde, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x63, 0x76, 0x74, 0x20,
|
||||
0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x04, 0x2c, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6d,
|
||||
0x06, 0x59, 0x9c, 0x37, 0x00, 0x00, 0x02, 0xa0, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70,
|
||||
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x0b, 0x64, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6c, 0x79, 0x66,
|
||||
0x10, 0x31, 0x88, 0x00, 0x00, 0x00, 0x04, 0x34, 0x00, 0x00, 0x04, 0x64, 0x68, 0x65, 0x61, 0x64,
|
||||
0x15, 0x9d, 0xef, 0x91, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61,
|
||||
0x09, 0x60, 0x03, 0x71, 0x00, 0x00, 0x01, 0x34, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6d, 0x74, 0x78,
|
||||
0x0d, 0x2e, 0x03, 0xa7, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x26, 0x6c, 0x6f, 0x63, 0x61,
|
||||
0x05, 0xc0, 0x04, 0x6c, 0x00, 0x00, 0x08, 0x98, 0x00, 0x00, 0x00, 0x1e, 0x6d, 0x61, 0x78, 0x70,
|
||||
0x02, 0x1c, 0x00, 0x5f, 0x00, 0x00, 0x01, 0x58, 0x00, 0x00, 0x00, 0x20, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x7c, 0xe0, 0x84, 0x5c, 0x00, 0x00, 0x08, 0xb8, 0x00, 0x00, 0x02, 0x09, 0x70, 0x6f, 0x73, 0x74,
|
||||
0x47, 0x4e, 0x74, 0x19, 0x00, 0x00, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x9e, 0x70, 0x72, 0x65, 0x70,
|
||||
0x1c, 0xfc, 0x7d, 0x9c, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x7c, 0xc7, 0xb1, 0x63, 0x5f, 0x0f, 0x3c, 0xf5, 0x00, 0x1b, 0x03, 0xe8,
|
||||
0x00, 0x00, 0x00, 0x00, 0xd9, 0x44, 0x2f, 0x5d, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x45, 0x7b, 0x69,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x84, 0xff, 0x83, 0x01, 0xf4, 0x03, 0xe8,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x5e,
|
||||
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x74, 0x01, 0x90, 0x00, 0x05,
|
||||
0x00, 0x04, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00, 0x01, 0x1f, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00,
|
||||
0x03, 0xc3, 0x00, 0x66, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
const std::array<unsigned char, 6024> FONT_NINTENDO_EXTENDED{{
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x80, 0x00, 0x03, 0x00, 0x60, 0x4F, 0x53, 0x2F, 0x32,
|
||||
0x34, 0x00, 0x1E, 0x26, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6D, 0x61, 0x70,
|
||||
0xC1, 0xE7, 0xC8, 0xF3, 0x00, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x01, 0x72, 0x63, 0x76, 0x74, 0x20,
|
||||
0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6D,
|
||||
0x06, 0x59, 0x9C, 0x37, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70,
|
||||
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x80, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6C, 0x79, 0x66,
|
||||
0x50, 0x0B, 0xEA, 0xFA, 0x00, 0x00, 0x05, 0x50, 0x00, 0x00, 0x0F, 0x04, 0x68, 0x65, 0x61, 0x64,
|
||||
0x18, 0x65, 0x81, 0x09, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61,
|
||||
0x09, 0x88, 0x03, 0x86, 0x00, 0x00, 0x01, 0x24, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6D, 0x74, 0x78,
|
||||
0x0A, 0xF0, 0x01, 0x94, 0x00, 0x00, 0x01, 0xC8, 0x00, 0x00, 0x00, 0x42, 0x6C, 0x6F, 0x63, 0x61,
|
||||
0x34, 0x80, 0x30, 0x6E, 0x00, 0x00, 0x05, 0x14, 0x00, 0x00, 0x00, 0x3A, 0x6D, 0x61, 0x78, 0x70,
|
||||
0x02, 0x2C, 0x00, 0x72, 0x00, 0x00, 0x01, 0x48, 0x00, 0x00, 0x00, 0x20, 0x6E, 0x61, 0x6D, 0x65,
|
||||
0xDB, 0xC5, 0x42, 0x4D, 0x00, 0x00, 0x14, 0x54, 0x00, 0x00, 0x01, 0xFE, 0x70, 0x6F, 0x73, 0x74,
|
||||
0xF4, 0xB4, 0xAC, 0xAB, 0x00, 0x00, 0x16, 0x54, 0x00, 0x00, 0x01, 0x2A, 0x70, 0x72, 0x65, 0x70,
|
||||
0x1C, 0xFC, 0x7D, 0x9C, 0x00, 0x00, 0x04, 0xF4, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0xC9, 0x16, 0x5B, 0x71, 0x5F, 0x0F, 0x3C, 0xF5, 0x00, 0x0B, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xD9, 0x44, 0x2F, 0x5D, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x02, 0x0D, 0xA7,
|
||||
0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x9A, 0xFF, 0x80, 0x02, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xEC, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x71,
|
||||
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0xC4, 0x01, 0x90, 0x00, 0x05,
|
||||
0x00, 0x04, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00, 0x01, 0x26, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00,
|
||||
0x03, 0xDA, 0x00, 0x68, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xc0, 0x00, 0x00, 0xe0, 0xe9, 0x03, 0x84, 0xff, 0x83,
|
||||
0x01, 0xf4, 0x02, 0xee, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8,
|
||||
0x02, 0xbc, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xfa, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x03, 0xe8, 0x00, 0xeb, 0x01, 0x21, 0x00, 0xff,
|
||||
0x00, 0xff, 0x01, 0x3d, 0x01, 0x17, 0x00, 0x42, 0x00, 0x1c, 0x00, 0x3e, 0x00, 0x17, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x68, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x1c, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x06, 0x00, 0x4c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xC0, 0x00, 0x0D, 0xE0, 0xF0, 0x03, 0x9A, 0xFF, 0x80,
|
||||
0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x02, 0xCD, 0x00, 0x00, 0x00, 0x20, 0x00, 0x01, 0x04, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14,
|
||||
0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14,
|
||||
0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14,
|
||||
0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C,
|
||||
0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x04, 0x00, 0x50, 0x00, 0x00, 0x00, 0x10,
|
||||
0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA9, 0xE0, 0xB4,
|
||||
0xE0, 0xE9, 0xE0, 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA0,
|
||||
0xE0, 0xB3, 0xE0, 0xE0, 0xE0, 0xEF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xF5, 0xFF, 0xE3, 0x1F, 0x64,
|
||||
0x1F, 0x5B, 0x1F, 0x30, 0x1F, 0x2B, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0a,
|
||||
0x00, 0x08, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe9, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe0, 0xff, 0xff, 0x00, 0x01, 0xff, 0xf5,
|
||||
0xff, 0xe3, 0x1f, 0x24, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xb8, 0x00, 0x00, 0x2c, 0x4b, 0xb8, 0x00, 0x09, 0x50, 0x58, 0xb1, 0x01, 0x01, 0x8e, 0x59, 0xb8,
|
||||
0x01, 0xff, 0x85, 0xb8, 0x00, 0x44, 0x1d, 0xb9, 0x00, 0x09, 0x00, 0x03, 0x5f, 0x5e, 0x2d, 0xb8,
|
||||
0x00, 0x01, 0x2c, 0x20, 0x20, 0x45, 0x69, 0x44, 0xb0, 0x01, 0x60, 0x2d, 0xb8, 0x00, 0x02, 0x2c,
|
||||
0xb8, 0x00, 0x01, 0x2a, 0x21, 0x2d, 0xb8, 0x00, 0x03, 0x2c, 0x20, 0x46, 0xb0, 0x03, 0x25, 0x46,
|
||||
0x52, 0x58, 0x23, 0x59, 0x20, 0x8a, 0x20, 0x8a, 0x49, 0x64, 0x8a, 0x20, 0x46, 0x20, 0x68, 0x61,
|
||||
0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8a, 0x59, 0x2f,
|
||||
0x20, 0xb0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x59, 0x1b,
|
||||
0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x65, 0x59, 0x59, 0x3a, 0x2d, 0xb8, 0x00,
|
||||
0x04, 0x2c, 0x20, 0x46, 0xb0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8a, 0x59, 0x20, 0x46, 0x20,
|
||||
0x6a, 0x61, 0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x6a, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8a, 0x59,
|
||||
0x2f, 0xfd, 0x2d, 0xb8, 0x00, 0x05, 0x2c, 0x4b, 0x20, 0xb0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58,
|
||||
0xb0, 0x80, 0x44, 0x1b, 0xb0, 0x40, 0x44, 0x59, 0x1b, 0x21, 0x21, 0x20, 0x45, 0xb0, 0xc0, 0x50,
|
||||
0x58, 0xb0, 0xc0, 0x44, 0x1b, 0x21, 0x59, 0x59, 0x2d, 0xb8, 0x00, 0x06, 0x2c, 0x20, 0x20, 0x45,
|
||||
0x69, 0x44, 0xb0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0xb0, 0x01, 0x60, 0x2d,
|
||||
0xb8, 0x00, 0x07, 0x2c, 0xb8, 0x00, 0x06, 0x2a, 0x2d, 0xb8, 0x00, 0x08, 0x2c, 0x4b, 0x20, 0xb0,
|
||||
0x03, 0x26, 0x53, 0x58, 0xb0, 0x40, 0x1b, 0xb0, 0x00, 0x59, 0x8a, 0x8a, 0x20, 0xb0, 0x03, 0x26,
|
||||
0x53, 0x58, 0x23, 0x21, 0xb0, 0x80, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03, 0x26,
|
||||
0x53, 0x58, 0x23, 0x21, 0xb8, 0x00, 0xc0, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03,
|
||||
0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x00, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0,
|
||||
0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x40, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20,
|
||||
0xb8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xb0, 0x03, 0x25, 0x45, 0xb8, 0x01, 0x80, 0x50, 0x58, 0x23,
|
||||
0x21, 0xb8, 0x01, 0x80, 0x23, 0x21, 0x1b, 0xb0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59,
|
||||
0x1b, 0x21, 0x59, 0x44, 0x2d, 0xb8, 0x00, 0x09, 0x2c, 0x4b, 0x53, 0x58, 0x45, 0x44, 0x1b, 0x21,
|
||||
0x21, 0x59, 0x2d, 0x00, 0xb8, 0x00, 0x00, 0x2b, 0x00, 0xba, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07,
|
||||
0x2b, 0xb8, 0x00, 0x00, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x06,
|
||||
0x00, 0x00, 0x35, 0x01, 0x33, 0x15, 0x01, 0x23, 0x35, 0x03, 0x52, 0x94, 0xfc, 0xa6, 0x8c, 0x90,
|
||||
0x03, 0x58, 0x86, 0xfc, 0xa0, 0x8e, 0x00, 0x00, 0x00, 0x02, 0x00, 0xeb, 0x00, 0xcc, 0x02, 0xfb,
|
||||
0x03, 0x1e, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x33, 0x13, 0x23, 0x27, 0x23, 0x07, 0x23,
|
||||
0x13, 0x17, 0x07, 0x06, 0x15, 0x33, 0x27, 0x07, 0x01, 0xbc, 0x6d, 0xd2, 0x7c, 0x26, 0xcc, 0x26,
|
||||
0x7c, 0xd1, 0x35, 0x40, 0x02, 0x89, 0x45, 0x02, 0x03, 0x1e, 0xfd, 0xae, 0x77, 0x77, 0x02, 0x52,
|
||||
0x9b, 0xcc, 0x08, 0x04, 0xda, 0x02, 0x00, 0x00, 0x00, 0x03, 0x01, 0x21, 0x00, 0xcc, 0x02, 0xc5,
|
||||
0x03, 0x1e, 0x00, 0x15, 0x00, 0x1f, 0x00, 0x2b, 0x00, 0x00, 0x25, 0x11, 0x33, 0x32, 0x1e, 0x02,
|
||||
0x15, 0x14, 0x0e, 0x02, 0x07, 0x1e, 0x01, 0x15, 0x14, 0x0e, 0x02, 0x2b, 0x01, 0x13, 0x33, 0x32,
|
||||
0x36, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x1d, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b,
|
||||
0x01, 0x15, 0x01, 0x21, 0xea, 0x25, 0x3f, 0x2e, 0x1a, 0x0e, 0x15, 0x1b, 0x0e, 0x2d, 0x2d, 0x1a,
|
||||
0x2e, 0x3f, 0x25, 0xf8, 0x76, 0x62, 0x20, 0x2a, 0x28, 0x22, 0x62, 0x76, 0x10, 0x18, 0x11, 0x09,
|
||||
0x22, 0x22, 0x74, 0xcc, 0x02, 0x52, 0x18, 0x2b, 0x3c, 0x24, 0x1d, 0x1f, 0x17, 0x17, 0x14, 0x0f,
|
||||
0x48, 0x2f, 0x24, 0x3f, 0x2e, 0x1a, 0x01, 0x5b, 0x29, 0x20, 0x20, 0x2b, 0x94, 0xf8, 0x0e, 0x16,
|
||||
0x1c, 0x0e, 0x1f, 0x31, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7,
|
||||
0x03, 0x1e, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x33, 0x17, 0x37, 0x33, 0x03, 0x13, 0x23, 0x27, 0x07,
|
||||
0x23, 0x13, 0x03, 0x01, 0x04, 0x86, 0x69, 0x69, 0x86, 0xa3, 0xa8, 0x88, 0x6c, 0x6c, 0x88, 0xa8,
|
||||
0xa3, 0x03, 0x1e, 0xcb, 0xcb, 0xfe, 0xda, 0xfe, 0xd4, 0xcf, 0xcf, 0x01, 0x2c, 0x01, 0x26, 0x00,
|
||||
0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7, 0x03, 0x1e, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x03,
|
||||
0x33, 0x17, 0x32, 0x15, 0x1e, 0x01, 0x15, 0x1b, 0x01, 0x33, 0x03, 0x15, 0x23, 0x35, 0x01, 0xb8,
|
||||
0xb9, 0x7e, 0x01, 0x01, 0x01, 0x03, 0x70, 0x75, 0x7f, 0xb9, 0x76, 0x01, 0xa3, 0x01, 0x7b, 0x01,
|
||||
0x01, 0x01, 0x05, 0x02, 0xff, 0x00, 0x01, 0x0a, 0xfe, 0x85, 0xd7, 0xd7, 0x00, 0x01, 0x01, 0x3d,
|
||||
0x00, 0xcc, 0x02, 0xa9, 0x03, 0x1e, 0x00, 0x06, 0x00, 0x00, 0x25, 0x11, 0x33, 0x11, 0x33, 0x15,
|
||||
0x21, 0x01, 0x3d, 0x75, 0xf7, 0xfe, 0x94, 0xcc, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x01, 0x17, 0x00, 0xbc, 0x02, 0xcf, 0x03, 0x0e, 0x00, 0x15, 0x00, 0x21, 0x00, 0x00,
|
||||
0x25, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x1d, 0x01, 0x0e, 0x03, 0x1d, 0x01, 0x17, 0x15, 0x23, 0x27,
|
||||
0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x15, 0x01, 0x17,
|
||||
0xf4, 0x27, 0x40, 0x2e, 0x19, 0x01, 0x1f, 0x24, 0x1e, 0x78, 0x7d, 0x6a, 0x5c, 0x75, 0x76, 0x72,
|
||||
0x12, 0x19, 0x11, 0x08, 0x26, 0x26, 0x6a, 0xbc, 0x02, 0x52, 0x1d, 0x31, 0x42, 0x25, 0x16, 0x18,
|
||||
0x32, 0x2a, 0x1b, 0x02, 0x01, 0xef, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x10, 0x1a, 0x1e, 0x0f, 0x23,
|
||||
0x36, 0xb0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x42, 0x00, 0xbc, 0x03, 0xa4, 0x03, 0x0e, 0x00, 0x0a,
|
||||
0x00, 0x11, 0x00, 0x00, 0x13, 0x35, 0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01,
|
||||
0x11, 0x33, 0x11, 0x33, 0x15, 0x21, 0x42, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b, 0xfe, 0x53, 0x01,
|
||||
0x15, 0xfe, 0xeb, 0x01, 0xf7, 0x75, 0xf6, 0xfe, 0x95, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62,
|
||||
0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00, 0x03, 0x00, 0x1c,
|
||||
0x00, 0xbc, 0x03, 0xca, 0x03, 0x0e, 0x00, 0x0a, 0x00, 0x21, 0x00, 0x2f, 0x00, 0x00, 0x13, 0x35,
|
||||
0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15,
|
||||
0x14, 0x06, 0x07, 0x0e, 0x03, 0x15, 0x17, 0x15, 0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32,
|
||||
0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01, 0x15, 0x1c, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b,
|
||||
0xfe, 0x53, 0x01, 0x15, 0xfe, 0xeb, 0x01, 0xf7, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x01,
|
||||
0x0d, 0x0e, 0x0a, 0x78, 0x7d, 0x69, 0x5c, 0x75, 0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14,
|
||||
0x1d, 0x13, 0x69, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62, 0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02,
|
||||
0x52, 0x1d, 0x31, 0x42, 0x25, 0x2b, 0x44, 0x1d, 0x01, 0x08, 0x09, 0x07, 0x01, 0xf1, 0x06, 0xd7,
|
||||
0xd7, 0x01, 0x3f, 0x11, 0x19, 0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x02, 0x00, 0x3e,
|
||||
0x00, 0xb3, 0x03, 0xa8, 0x03, 0x17, 0x00, 0x3a, 0x00, 0x41, 0x00, 0x00, 0x13, 0x34, 0x3e, 0x02,
|
||||
0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x27, 0x2e, 0x01, 0x23, 0x22, 0x0e, 0x02, 0x15,
|
||||
0x14, 0x16, 0x15, 0x1e, 0x05, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e, 0x02, 0x35, 0x33, 0x1e,
|
||||
0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x04, 0x35, 0x01, 0x11, 0x33, 0x11, 0x33, 0x15,
|
||||
0x21, 0x50, 0x24, 0x3b, 0x4a, 0x27, 0x28, 0x4b, 0x39, 0x22, 0x73, 0x01, 0x01, 0x08, 0x2b, 0x29,
|
||||
0x10, 0x20, 0x19, 0x0f, 0x01, 0x0b, 0x35, 0x41, 0x46, 0x3b, 0x25, 0x23, 0x3a, 0x4b, 0x27, 0x2b,
|
||||
0x50, 0x3f, 0x26, 0x74, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x11, 0x2c, 0x42, 0x4d, 0x42, 0x2c,
|
||||
0x01, 0xef, 0x73, 0xf6, 0xfe, 0x97, 0x02, 0x70, 0x2a, 0x3f, 0x2a, 0x14, 0x18, 0x2e, 0x44, 0x2c,
|
||||
0x02, 0x03, 0x01, 0x27, 0x27, 0x07, 0x10, 0x1a, 0x12, 0x02, 0x0b, 0x02, 0x1f, 0x22, 0x19, 0x17,
|
||||
0x27, 0x3f, 0x34, 0x2c, 0x3e, 0x28, 0x13, 0x1a, 0x32, 0x48, 0x2e, 0x30, 0x30, 0x06, 0x0f, 0x1a,
|
||||
0x13, 0x21, 0x27, 0x1e, 0x1b, 0x29, 0x3e, 0x31, 0xfe, 0x4c, 0x02, 0x53, 0xfe, 0x10, 0x63, 0x00,
|
||||
0x00, 0x03, 0x00, 0x17, 0x00, 0xb3, 0x03, 0xce, 0x03, 0x17, 0x00, 0x38, 0x00, 0x4f, 0x00, 0x5d,
|
||||
0x00, 0x00, 0x13, 0x34, 0x3e, 0x02, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x23, 0x2e,
|
||||
0x01, 0x23, 0x22, 0x0e, 0x02, 0x15, 0x14, 0x1e, 0x04, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e,
|
||||
0x02, 0x35, 0x33, 0x1e, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x27, 0x2e, 0x03, 0x35,
|
||||
0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x14, 0x06, 0x07, 0x30, 0x0e, 0x02, 0x31, 0x17, 0x15,
|
||||
0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01,
|
||||
0x15, 0x2a, 0x24, 0x3a, 0x4a, 0x26, 0x29, 0x4b, 0x39, 0x23, 0x73, 0x01, 0x01, 0x08, 0x2a, 0x2a,
|
||||
0x10, 0x1f, 0x1a, 0x10, 0x2c, 0x42, 0x4d, 0x42, 0x2c, 0x23, 0x39, 0x4b, 0x27, 0x2b, 0x51, 0x3f,
|
||||
0x27, 0x75, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x10, 0x1f, 0x1c, 0x25, 0x53, 0x47, 0x2e, 0x01,
|
||||
0xed, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x0c, 0x0e, 0x0c, 0x78, 0x7d, 0x68, 0x5d, 0x75,
|
||||
0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14, 0x1d, 0x13, 0x69, 0x02, 0x71, 0x2a, 0x3e, 0x2a,
|
||||
0x14, 0x18, 0x2e, 0x44, 0x2c, 0x02, 0x02, 0x27, 0x29, 0x07, 0x11, 0x1a, 0x12, 0x1d, 0x24, 0x1c,
|
||||
0x1d, 0x2b, 0x40, 0x32, 0x2c, 0x3f, 0x29, 0x13, 0x1a, 0x31, 0x49, 0x2e, 0x30, 0x30, 0x06, 0x0f,
|
||||
0x19, 0x13, 0x1e, 0x22, 0x0b, 0x0e, 0x20, 0x2f, 0x43, 0x30, 0xfe, 0x4b, 0x02, 0x52, 0x1d, 0x32,
|
||||
0x42, 0x25, 0x2c, 0x42, 0x1d, 0x08, 0x0a, 0x08, 0xf1, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x11, 0x19,
|
||||
0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12,
|
||||
0x00, 0x12, 0x00, 0x32, 0x00, 0x72, 0x00, 0x8e, 0x00, 0xac, 0x00, 0xbe, 0x00, 0xf0, 0x01, 0x14,
|
||||
0x01, 0x5c, 0x01, 0xb6, 0x02, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0xa2, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x00, 0x07, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x2f,
|
||||
0x00, 0x17, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x46, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x58, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x06, 0x00, 0x12, 0x00, 0x65, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x01, 0x00, 0x20,
|
||||
0x00, 0x77, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x97, 0x00, 0x03,
|
||||
0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x5e, 0x00, 0xa5, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
|
||||
0x00, 0x04, 0x00, 0x24, 0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x05, 0x00, 0x1a,
|
||||
0x01, 0x27, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x24, 0x01, 0x41, 0x00, 0x03,
|
||||
0x00, 0x01, 0x04, 0x09, 0x00, 0x11, 0x00, 0x02, 0x01, 0x65, 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53,
|
||||
0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61,
|
||||
0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x3b, 0x3b,
|
||||
0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x2d, 0x52, 0x3b, 0x32, 0x30, 0x31, 0x39, 0x3b, 0x46, 0x4c, 0x56, 0x49, 0x2d, 0x36, 0x31, 0x34,
|
||||
0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x20, 0x52, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x59,
|
||||
0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x2d,
|
||||
0x52, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00,
|
||||
0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00,
|
||||
0x6e, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x72, 0x00,
|
||||
0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
|
||||
0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x3b, 0x00, 0x3b, 0x00, 0x59, 0x00,
|
||||
0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00,
|
||||
0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00,
|
||||
0x52, 0x00, 0x3b, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x39, 0x00, 0x3b, 0x00, 0x46, 0x00,
|
||||
0x4c, 0x00, 0x56, 0x00, 0x49, 0x00, 0x2d, 0x00, 0x36, 0x00, 0x31, 0x00, 0x34, 0x00, 0x59, 0x00,
|
||||
0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00,
|
||||
0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00,
|
||||
0x52, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00,
|
||||
0x20, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x59, 0x00, 0x75, 0x00,
|
||||
0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00,
|
||||
0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x52, 0x00,
|
||||
0x52, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x9c, 0x00, 0x32,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04,
|
||||
0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0a, 0x01, 0x0b, 0x01, 0x0c,
|
||||
0x01, 0x0d, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30,
|
||||
0x30, 0x44, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
|
||||
0x45, 0x31, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
|
||||
0x45, 0x33, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
|
||||
0x45, 0x35, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
|
||||
0x45, 0x37, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30,
|
||||
0x45, 0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xB8, 0x00, 0x00, 0x2C, 0x4B, 0xB8, 0x00, 0x09, 0x50, 0x58, 0xB1, 0x01, 0x01, 0x8E, 0x59, 0xB8,
|
||||
0x01, 0xFF, 0x85, 0xB8, 0x00, 0x44, 0x1D, 0xB9, 0x00, 0x09, 0x00, 0x03, 0x5F, 0x5E, 0x2D, 0xB8,
|
||||
0x00, 0x01, 0x2C, 0x20, 0x20, 0x45, 0x69, 0x44, 0xB0, 0x01, 0x60, 0x2D, 0xB8, 0x00, 0x02, 0x2C,
|
||||
0xB8, 0x00, 0x01, 0x2A, 0x21, 0x2D, 0xB8, 0x00, 0x03, 0x2C, 0x20, 0x46, 0xB0, 0x03, 0x25, 0x46,
|
||||
0x52, 0x58, 0x23, 0x59, 0x20, 0x8A, 0x20, 0x8A, 0x49, 0x64, 0x8A, 0x20, 0x46, 0x20, 0x68, 0x61,
|
||||
0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8A, 0x59, 0x2F,
|
||||
0x20, 0xB0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x59, 0x1B,
|
||||
0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x65, 0x59, 0x59, 0x3A, 0x2D, 0xB8, 0x00,
|
||||
0x04, 0x2C, 0x20, 0x46, 0xB0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8A, 0x59, 0x20, 0x46, 0x20,
|
||||
0x6A, 0x61, 0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x6A, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8A, 0x59,
|
||||
0x2F, 0xFD, 0x2D, 0xB8, 0x00, 0x05, 0x2C, 0x4B, 0x20, 0xB0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58,
|
||||
0xB0, 0x80, 0x44, 0x1B, 0xB0, 0x40, 0x44, 0x59, 0x1B, 0x21, 0x21, 0x20, 0x45, 0xB0, 0xC0, 0x50,
|
||||
0x58, 0xB0, 0xC0, 0x44, 0x1B, 0x21, 0x59, 0x59, 0x2D, 0xB8, 0x00, 0x06, 0x2C, 0x20, 0x20, 0x45,
|
||||
0x69, 0x44, 0xB0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0xB0, 0x01, 0x60, 0x2D,
|
||||
0xB8, 0x00, 0x07, 0x2C, 0xB8, 0x00, 0x06, 0x2A, 0x2D, 0xB8, 0x00, 0x08, 0x2C, 0x4B, 0x20, 0xB0,
|
||||
0x03, 0x26, 0x53, 0x58, 0xB0, 0x40, 0x1B, 0xB0, 0x00, 0x59, 0x8A, 0x8A, 0x20, 0xB0, 0x03, 0x26,
|
||||
0x53, 0x58, 0x23, 0x21, 0xB0, 0x80, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03, 0x26,
|
||||
0x53, 0x58, 0x23, 0x21, 0xB8, 0x00, 0xC0, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03,
|
||||
0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x00, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0,
|
||||
0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x40, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20,
|
||||
0xB8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xB0, 0x03, 0x25, 0x45, 0xB8, 0x01, 0x80, 0x50, 0x58, 0x23,
|
||||
0x21, 0xB8, 0x01, 0x80, 0x23, 0x21, 0x1B, 0xB0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59,
|
||||
0x1B, 0x21, 0x59, 0x44, 0x2D, 0xB8, 0x00, 0x09, 0x2C, 0x4B, 0x53, 0x58, 0x45, 0x44, 0x1B, 0x21,
|
||||
0x21, 0x59, 0x2D, 0x00, 0xB8, 0x00, 0x00, 0x2B, 0x00, 0xBA, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07,
|
||||
0x2B, 0xB8, 0x00, 0x00, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x70,
|
||||
0x00, 0xDC, 0x01, 0x34, 0x01, 0x7C, 0x01, 0xA2, 0x01, 0xF4, 0x02, 0x3C, 0x02, 0xA8, 0x03, 0x4C,
|
||||
0x03, 0xE2, 0x04, 0x20, 0x04, 0x58, 0x04, 0x9A, 0x04, 0xEE, 0x05, 0x32, 0x05, 0x64, 0x05, 0x80,
|
||||
0x05, 0xC6, 0x05, 0xF6, 0x06, 0x54, 0x06, 0xB2, 0x07, 0x38, 0x07, 0x60, 0x07, 0x82, 0x00, 0x00,
|
||||
0x00, 0x02, 0x00, 0xA4, 0xFF, 0xFF, 0x03, 0x5C, 0x03, 0x09, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00,
|
||||
0x13, 0x11, 0x21, 0x11, 0x25, 0x21, 0x11, 0x21, 0xCD, 0x02, 0x66, 0xFD, 0x71, 0x02, 0xB8, 0xFD,
|
||||
0x48, 0x02, 0xE0, 0xFD, 0x48, 0x02, 0xB8, 0x29, 0xFC, 0xF6, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14,
|
||||
0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1F, 0x00, 0x2F, 0x00, 0x39, 0x00, 0x00,
|
||||
0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x32, 0x1E,
|
||||
0x02, 0x14, 0x0E, 0x02, 0x22, 0x2E, 0x02, 0x34, 0x3E, 0x01, 0x13, 0x12, 0x37, 0x33, 0x13, 0x12,
|
||||
0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30, 0x27, 0x26, 0x2F, 0x01,
|
||||
0x06, 0x07, 0x06, 0x32, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77,
|
||||
0x46, 0x46, 0x77, 0xFE, 0x9E, 0xC8, 0xB7, 0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0x4E,
|
||||
0x4E, 0x83, 0x23, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E, 0xD1, 0x2B, 0x37, 0x33,
|
||||
0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03, 0x3F, 0x46, 0x77, 0xA4,
|
||||
0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x4E, 0x83, 0xB7, 0xC8, 0xB7,
|
||||
0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0xFD, 0x64, 0x01, 0x1A, 0xEB, 0xFE, 0xFE, 0xFE,
|
||||
0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C, 0x02, 0xBE, 0x02, 0x00,
|
||||
0x00, 0x05, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2F,
|
||||
0x00, 0x3A, 0x00, 0x44, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x02,
|
||||
0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x01,
|
||||
0x16, 0x17, 0x14, 0x06, 0x07, 0x06, 0x2B, 0x01, 0x19, 0x01, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16,
|
||||
0x07, 0x06, 0x0F, 0x01, 0x36, 0x37, 0x34, 0x2E, 0x01, 0x27, 0x23, 0x15, 0x33, 0x32, 0x27, 0x32,
|
||||
0x37, 0x36, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x45, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46,
|
||||
0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE,
|
||||
0xF4, 0xE2, 0x01, 0xF7, 0x61, 0x01, 0x4E, 0x3E, 0x29, 0xAF, 0x4E, 0x81, 0x8B, 0x1D, 0x3C, 0x1F,
|
||||
0x19, 0x04, 0x06, 0x39, 0x57, 0x44, 0x01, 0x1B, 0x2D, 0x51, 0x46, 0x46, 0x47, 0x66, 0x70, 0x16,
|
||||
0x1F, 0x01, 0x2C, 0x08, 0x4B, 0x4C, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4,
|
||||
0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2,
|
||||
0x84, 0x84, 0x01, 0x6D, 0x21, 0x5B, 0x40, 0x50, 0x05, 0x03, 0x01, 0x03, 0x01, 0x05, 0x01, 0x05,
|
||||
0x09, 0x30, 0x25, 0x29, 0x40, 0x21, 0xC2, 0x06, 0x3E, 0x1A, 0x21, 0x0B, 0x01, 0x8C, 0xE1, 0x0A,
|
||||
0x0E, 0x54, 0x0B, 0x02, 0x79, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC,
|
||||
0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x38, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E,
|
||||
0x02, 0x34, 0x2E, 0x02, 0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E,
|
||||
0x01, 0x20, 0x26, 0x36, 0x34, 0x3F, 0x01, 0x27, 0x26, 0x27, 0x33, 0x17, 0x16, 0x33, 0x36, 0x3F,
|
||||
0x02, 0x32, 0x14, 0x06, 0x16, 0x12, 0x14, 0x2B, 0x01, 0x27, 0x26, 0x06, 0x0F, 0x01, 0x23, 0x45,
|
||||
0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2,
|
||||
0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x7B, 0x58, 0x58, 0x4D, 0x4F, 0x05, 0x7A,
|
||||
0x34, 0x34, 0x02, 0x01, 0x33, 0x32, 0x3C, 0x3C, 0xA1, 0x01, 0xB0, 0x3E, 0x3F, 0x39, 0x3B, 0x02,
|
||||
0x3A, 0x38, 0x3F, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46,
|
||||
0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x60,
|
||||
0x02, 0x87, 0x88, 0x79, 0x7A, 0x06, 0x54, 0x54, 0x01, 0x53, 0x53, 0x01, 0x01, 0xFB, 0x04, 0xFE,
|
||||
0xF8, 0x02, 0x5B, 0x5A, 0x03, 0x59, 0x59, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC,
|
||||
0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E,
|
||||
0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E,
|
||||
0x01, 0x10, 0x36, 0x01, 0x35, 0x27, 0x26, 0x34, 0x3B, 0x01, 0x17, 0x16, 0x36, 0x3F, 0x01, 0x33,
|
||||
0x03, 0x15, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46,
|
||||
0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01,
|
||||
0x36, 0x5E, 0x5F, 0x3C, 0x3D, 0x3D, 0x3D, 0x03, 0x3B, 0x3B, 0x77, 0xBE, 0x68, 0x03, 0x3F, 0x46,
|
||||
0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0xFE,
|
||||
0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6E, 0x96, 0x95, 0x01, 0x67, 0x67,
|
||||
0x03, 0x66, 0x65, 0xFE, 0xD3, 0xDA, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC,
|
||||
0x03, 0x4B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x12, 0x00, 0x00, 0x01, 0x21, 0x22, 0x15, 0x30, 0x11,
|
||||
0x21, 0x17, 0x21, 0x11, 0x10, 0x25, 0x21, 0x01, 0x11, 0x33, 0x11, 0x21, 0x15, 0x03, 0xBB, 0xFD,
|
||||
0x77, 0xED, 0x03, 0x76, 0x31, 0xFC, 0x28, 0x01, 0x1E, 0x02, 0xBA, 0xFD, 0x5C, 0x68, 0x01, 0x08,
|
||||
0x03, 0x1A, 0xEE, 0xFD, 0xC2, 0x31, 0x02, 0x6F, 0x01, 0x1E, 0x01, 0xFD, 0x36, 0x02, 0x07, 0xFE,
|
||||
0x50, 0x57, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC, 0x03, 0x4B, 0x00, 0x06,
|
||||
0x00, 0x0C, 0x00, 0x27, 0x00, 0x32, 0x00, 0x00, 0x05, 0x11, 0x34, 0x27, 0x30, 0x21, 0x11, 0x07,
|
||||
0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x07,
|
||||
0x06, 0x07, 0x06, 0x07, 0x1E, 0x02, 0x15, 0x07, 0x23, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x13,
|
||||
0x36, 0x35, 0x34, 0x27, 0x26, 0x27, 0x23, 0x15, 0x33, 0x36, 0x03, 0xBB, 0xED, 0xFD, 0x77, 0x31,
|
||||
0x02, 0xBA, 0x01, 0x1E, 0xFD, 0x2A, 0x77, 0x76, 0x15, 0x49, 0x20, 0x35, 0x08, 0x04, 0x06, 0x13,
|
||||
0x66, 0x0C, 0x01, 0x1F, 0x2E, 0x65, 0x3D, 0x3D, 0x2A, 0x56, 0x28, 0x2E, 0x19, 0x99, 0x3C, 0x20,
|
||||
0x10, 0x56, 0x4F, 0x46, 0x47, 0x12, 0x02, 0x3E, 0xED, 0x01, 0xFC, 0xD4, 0x31, 0x03, 0x8E, 0xFE,
|
||||
0xE1, 0xFD, 0x91, 0xC4, 0x02, 0x07, 0x01, 0x04, 0x13, 0x21, 0x44, 0x1D, 0x19, 0x58, 0x15, 0x02,
|
||||
0x01, 0x13, 0x2D, 0xA2, 0x01, 0x01, 0x3D, 0x81, 0x1A, 0x01, 0x01, 0xDA, 0x01, 0x2D, 0x08, 0x3A,
|
||||
0x29, 0x0F, 0x08, 0x01, 0x85, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC,
|
||||
0x03, 0x13, 0x00, 0x09, 0x00, 0x11, 0x00, 0x26, 0x00, 0x32, 0x00, 0x00, 0x37, 0x21, 0x34, 0x10,
|
||||
0x35, 0x34, 0x27, 0x21, 0x04, 0x11, 0x23, 0x10, 0x25, 0x21, 0x16, 0x15, 0x11, 0x21, 0x37, 0x35,
|
||||
0x37, 0x36, 0x22, 0x2B, 0x01, 0x3D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x0F, 0x01, 0x3B, 0x01, 0x1D,
|
||||
0x01, 0x2B, 0x01, 0x25, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x2B, 0x01, 0x45,
|
||||
0x03, 0x76, 0x45, 0xFE, 0x2D, 0xFE, 0xA2, 0x31, 0x01, 0x8F, 0x01, 0xD3, 0x76, 0xFC, 0x28, 0xA7,
|
||||
0x68, 0x68, 0x01, 0x5B, 0x5D, 0x90, 0x91, 0x6C, 0x6D, 0x71, 0x70, 0xA0, 0xA0, 0x01, 0x75, 0x27,
|
||||
0x28, 0x63, 0x63, 0x8B, 0x8A, 0x27, 0x69, 0x01, 0xA4, 0x69, 0x44, 0x01, 0x02, 0xFE, 0xA4, 0x01,
|
||||
0x8C, 0x03, 0x01, 0x75, 0xFD, 0x58, 0xBB, 0x24, 0x80, 0x80, 0x21, 0x21, 0x1F, 0x1E, 0x85, 0x86,
|
||||
0x20, 0x22, 0xC3, 0xC3, 0xA1, 0xA3, 0x20, 0x22, 0x00, 0x05, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC,
|
||||
0x03, 0x13, 0x00, 0x08, 0x00, 0x10, 0x00, 0x2B, 0x00, 0x37, 0x00, 0x44, 0x00, 0x00, 0x37, 0x21,
|
||||
0x11, 0x10, 0x25, 0x30, 0x21, 0x06, 0x15, 0x03, 0x11, 0x34, 0x37, 0x21, 0x04, 0x19, 0x01, 0x01,
|
||||
0x35, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17,
|
||||
0x16, 0x23, 0x2F, 0x01, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x23, 0x37, 0x32, 0x36, 0x37, 0x36, 0x35,
|
||||
0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x05, 0x35, 0x37, 0x36, 0x26, 0x2B, 0x01, 0x35, 0x21, 0x15,
|
||||
0x03, 0x17, 0x15, 0x45, 0x03, 0x76, 0xFE, 0xA2, 0xFE, 0x2D, 0x45, 0x31, 0x76, 0x01, 0xD3, 0x01,
|
||||
0x8F, 0xFE, 0x1E, 0x65, 0x6F, 0x15, 0x46, 0x10, 0x05, 0x04, 0x0D, 0x4F, 0x09, 0x09, 0x1F, 0x1D,
|
||||
0x3A, 0x06, 0x01, 0x30, 0x2F, 0x22, 0x37, 0x1E, 0x29, 0x14, 0x4E, 0x82, 0x34, 0x19, 0x0E, 0x13,
|
||||
0x0A, 0x22, 0x07, 0x38, 0x37, 0xFE, 0x3E, 0x68, 0x68, 0x01, 0x5C, 0x5C, 0x01, 0x20, 0xD8, 0xE1,
|
||||
0x27, 0x01, 0x5D, 0x01, 0x5B, 0x03, 0x01, 0x44, 0xFD, 0x58, 0x02, 0xA8, 0x75, 0x01, 0x03, 0xFE,
|
||||
0x74, 0xFE, 0x71, 0x01, 0x5C, 0xC5, 0x01, 0x04, 0x0C, 0x43, 0x15, 0x1D, 0x44, 0x10, 0x04, 0x06,
|
||||
0x14, 0x2B, 0x56, 0x10, 0x01, 0x01, 0x34, 0x52, 0x1C, 0x01, 0x01, 0xA5, 0xE3, 0x04, 0x06, 0x0A,
|
||||
0x20, 0x2C, 0x04, 0x01, 0x65, 0xE3, 0x47, 0x80, 0x80, 0x01, 0x42, 0x3D, 0xFE, 0xF5, 0x01, 0x41,
|
||||
0x00, 0x04, 0x00, 0x14, 0x00, 0x52, 0x03, 0xEC, 0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x64,
|
||||
0x00, 0x70, 0x00, 0x00, 0x25, 0x11, 0x21, 0x22, 0x15, 0x30, 0x15, 0x14, 0x33, 0x11, 0x21, 0x32,
|
||||
0x15, 0x11, 0x14, 0x27, 0x21, 0x22, 0x26, 0x3D, 0x01, 0x34, 0x36, 0x13, 0x26, 0x27, 0x26, 0x27,
|
||||
0x26, 0x37, 0x33, 0x36, 0x37, 0x36, 0x33, 0x16, 0x17, 0x16, 0x17, 0x16, 0x37, 0x36, 0x37, 0x36,
|
||||
0x35, 0x34, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x34, 0x37, 0x36, 0x37,
|
||||
0x36, 0x37, 0x36, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x0F, 0x01, 0x22, 0x06, 0x23,
|
||||
0x27, 0x26, 0x27, 0x26, 0x23, 0x22, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16,
|
||||
0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x27, 0x37, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B,
|
||||
0x01, 0x1D, 0x01, 0x2B, 0x01, 0x03, 0xBB, 0xFD, 0x2A, 0xA0, 0xA0, 0x02, 0xEE, 0x19, 0x19, 0xFD,
|
||||
0x12, 0x57, 0x7A, 0x7A, 0xCA, 0x38, 0x1D, 0x16, 0x08, 0x03, 0x01, 0x02, 0x0F, 0x0C, 0x1E, 0x01,
|
||||
0x02, 0x04, 0x0C, 0x2B, 0x0F, 0x0E, 0x18, 0x0C, 0x09, 0x04, 0x15, 0x32, 0x23, 0x12, 0x1C, 0x0E,
|
||||
0x09, 0x03, 0x01, 0x01, 0x09, 0x21, 0x0F, 0x14, 0x2E, 0x2A, 0x13, 0x0F, 0x0C, 0x08, 0x0B, 0x05,
|
||||
0x02, 0x01, 0x02, 0x03, 0x36, 0x03, 0x02, 0x03, 0x08, 0x0D, 0x23, 0x16, 0x0E, 0x10, 0x01, 0x01,
|
||||
0x07, 0x0B, 0x32, 0x25, 0x13, 0x26, 0x0F, 0x09, 0x01, 0x01, 0x0F, 0x11, 0x24, 0x21, 0x2A, 0xE3,
|
||||
0x20, 0x20, 0x52, 0x50, 0x71, 0x71, 0x84, 0x02, 0x00, 0xAF, 0xA2, 0xAF, 0x02, 0x32, 0x19, 0xFD,
|
||||
0xCE, 0x19, 0x01, 0x84, 0x5C, 0xA2, 0x5C, 0x85, 0xFE, 0x29, 0x04, 0x1E, 0x18, 0x26, 0x0F, 0x01,
|
||||
0x02, 0x01, 0x03, 0x05, 0x0B, 0x29, 0x06, 0x02, 0x03, 0x04, 0x11, 0x0B, 0x0D, 0x0A, 0x06, 0x12,
|
||||
0x0D, 0x0A, 0x07, 0x0C, 0x18, 0x0D, 0x10, 0x06, 0x18, 0x05, 0x27, 0x14, 0x09, 0x03, 0x0A, 0x0D,
|
||||
0x06, 0x09, 0x09, 0x0D, 0x0F, 0x14, 0x0C, 0x06, 0x03, 0x02, 0x04, 0x10, 0x0A, 0x11, 0x08, 0x09,
|
||||
0x0E, 0x0C, 0x07, 0x0C, 0x0C, 0x0A, 0x07, 0x0F, 0x20, 0x11, 0x18, 0x1E, 0x1A, 0x1E, 0x0C, 0x0B,
|
||||
0x03, 0xAA, 0xA5, 0x89, 0x8A, 0x1C, 0x1B, 0x00, 0x00, 0x05, 0x00, 0x14, 0x00, 0x53, 0x03, 0xEC,
|
||||
0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x2E, 0x00, 0x38, 0x00, 0x65, 0x00, 0x00, 0x01, 0x30,
|
||||
0x21, 0x11, 0x21, 0x32, 0x3D, 0x01, 0x34, 0x27, 0x32, 0x16, 0x1D, 0x01, 0x14, 0x06, 0x23, 0x21,
|
||||
0x26, 0x35, 0x11, 0x34, 0x33, 0x01, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07,
|
||||
0x17, 0x1E, 0x01, 0x1F, 0x01, 0x23, 0x2A, 0x01, 0x2E, 0x01, 0x23, 0x27, 0x15, 0x37, 0x32, 0x37,
|
||||
0x36, 0x27, 0x2E, 0x01, 0x2B, 0x01, 0x15, 0x05, 0x26, 0x27, 0x37, 0x32, 0x3F, 0x01, 0x16, 0x17,
|
||||
0x1E, 0x01, 0x37, 0x36, 0x27, 0x2E, 0x04, 0x37, 0x3E, 0x01, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14,
|
||||
0x06, 0x27, 0x26, 0x27, 0x26, 0x0E, 0x01, 0x1E, 0x02, 0x17, 0x16, 0x06, 0x07, 0x06, 0x07, 0x06,
|
||||
0x03, 0x1B, 0xFD, 0x2A, 0x02, 0xD6, 0xA0, 0xA0, 0x57, 0x7A, 0x7A, 0x57, 0xFD, 0x12, 0x19, 0x19,
|
||||
0x01, 0xD3, 0x47, 0x44, 0x11, 0x3E, 0x18, 0x21, 0x0B, 0x0C, 0x43, 0x04, 0x17, 0x1C, 0x1E, 0x16,
|
||||
0x26, 0x26, 0x03, 0x4D, 0x18, 0x1E, 0x11, 0x25, 0x3A, 0x0C, 0x22, 0x08, 0x03, 0x1B, 0x3E, 0x29,
|
||||
0xFE, 0xAC, 0x0D, 0x04, 0x02, 0x02, 0x1E, 0x1D, 0x03, 0x02, 0x0C, 0x4C, 0x13, 0x20, 0x07, 0x04,
|
||||
0x1B, 0x56, 0x2D, 0x1C, 0x01, 0x02, 0x44, 0x35, 0x49, 0x1F, 0x10, 0x03, 0x41, 0x01, 0x06, 0x0A,
|
||||
0x16, 0x3C, 0x18, 0x0C, 0x16, 0x5D, 0x15, 0x33, 0x03, 0x2B, 0x1E, 0x34, 0x59, 0x02, 0x84, 0xFE,
|
||||
0x00, 0xAF, 0xA2, 0xAF, 0x32, 0x85, 0x5C, 0xA2, 0x5C, 0x84, 0x01, 0x17, 0x02, 0x32, 0x19, 0xFE,
|
||||
0x2F, 0x01, 0x45, 0x01, 0x02, 0x19, 0x22, 0x32, 0x39, 0x0B, 0x08, 0x0F, 0x27, 0x2F, 0x24, 0x75,
|
||||
0x12, 0x01, 0x88, 0xBB, 0x04, 0x09, 0x2A, 0x0F, 0x0D, 0x53, 0x8A, 0x17, 0x1E, 0x04, 0x03, 0x03,
|
||||
0x0C, 0x04, 0x26, 0x0E, 0x0C, 0x14, 0x1A, 0x0E, 0x0E, 0x16, 0x16, 0x2C, 0x1A, 0x2D, 0x2D, 0x2A,
|
||||
0x16, 0x1D, 0x06, 0x04, 0x01, 0x1A, 0x09, 0x11, 0x09, 0x17, 0x18, 0x0D, 0x17, 0x0C, 0x1B, 0x71,
|
||||
0x1B, 0x12, 0x01, 0x03, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F,
|
||||
0x00, 0x1B, 0x00, 0x27, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02,
|
||||
0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13,
|
||||
0x33, 0x35, 0x33, 0x15, 0x33, 0x15, 0x23, 0x15, 0x23, 0x35, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77,
|
||||
0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84,
|
||||
0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC5, 0x4E, 0xC5, 0xC4, 0x50, 0xC4, 0x03, 0x3F,
|
||||
0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2,
|
||||
0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0xC4, 0xC5, 0x4E, 0xC5, 0xC5,
|
||||
0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x1F,
|
||||
0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24,
|
||||
0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x35, 0x21, 0x15, 0x02,
|
||||
0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C,
|
||||
0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8, 0x03, 0x3F,
|
||||
0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2,
|
||||
0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0x71, 0x4E, 0x4E, 0x00, 0x00, 0x00,
|
||||
0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x1B, 0x00, 0x25,
|
||||
0x00, 0x00, 0x00, 0x20, 0x0E, 0x01, 0x10, 0x1E, 0x01, 0x20, 0x3E, 0x01, 0x10, 0x26, 0x01, 0x12,
|
||||
0x37, 0x33, 0x13, 0x12, 0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30,
|
||||
0x27, 0x26, 0x2F, 0x01, 0x06, 0x07, 0x06, 0x32, 0x02, 0x86, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2,
|
||||
0x01, 0x0C, 0xE2, 0x84, 0x84, 0xFD, 0xA0, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E,
|
||||
0xD1, 0x2B, 0x37, 0x33, 0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03,
|
||||
0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0x9A, 0x01, 0x1A,
|
||||
0xEB, 0xFE, 0xFE, 0xFE, 0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C,
|
||||
0x02, 0xBE, 0x02, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B,
|
||||
0x00, 0x20, 0x00, 0x2B, 0x00, 0x35, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10,
|
||||
0x0E, 0x01, 0x20, 0x26, 0x01, 0x30, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x23, 0x27,
|
||||
0x19, 0x01, 0x33, 0x32, 0x37, 0x3E, 0x01, 0x35, 0x26, 0x07, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x1E,
|
||||
0x02, 0x15, 0x06, 0x27, 0x23, 0x35, 0x33, 0x16, 0x17, 0x16, 0x14, 0x07, 0x06, 0x14, 0x84, 0xE2,
|
||||
0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x01, 0xF7, 0x0A, 0x3A, 0x05, 0x04, 0x19,
|
||||
0x20, 0x3B, 0x1D, 0x8B, 0x81, 0x4E, 0xAF, 0x29, 0x3E, 0x4E, 0x01, 0xAE, 0x0D, 0x47, 0x46, 0x46,
|
||||
0x52, 0x2C, 0x1B, 0x01, 0xB7, 0x27, 0x4C, 0x4C, 0x07, 0x2C, 0x1E, 0x16, 0xFE, 0x01, 0x0C, 0xE2,
|
||||
0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x6D, 0x06, 0x21, 0x40, 0x2A, 0x24, 0x30,
|
||||
0x09, 0x05, 0x01, 0xFE, 0xFB, 0xFE, 0xFD, 0x03, 0x05, 0x4F, 0x41, 0x5B, 0x9B, 0x01, 0x8C, 0x01,
|
||||
0x0B, 0x21, 0x1A, 0x3E, 0xDA, 0x79, 0x01, 0x01, 0x0B, 0x54, 0x0E, 0x0A, 0x00, 0x02, 0x00, 0x14,
|
||||
0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x29, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01,
|
||||
0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x36, 0x14, 0x3B, 0x01, 0x37, 0x36, 0x37, 0x36,
|
||||
0x1F, 0x01, 0x33, 0x32, 0x34, 0x02, 0x26, 0x36, 0x34, 0x23, 0x0F, 0x01, 0x06, 0x07, 0x22, 0x2F,
|
||||
0x01, 0x23, 0x16, 0x1F, 0x01, 0x07, 0x14, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE,
|
||||
0xF4, 0xE2, 0x7B, 0x3D, 0x3F, 0x38, 0x3A, 0x01, 0x02, 0x3A, 0x39, 0x3F, 0x3E, 0xB0, 0x01, 0xA1,
|
||||
0x3C, 0x3C, 0x32, 0x33, 0x01, 0x02, 0x34, 0x34, 0x7A, 0x05, 0x4F, 0x4D, 0x58, 0xFE, 0x01, 0x0C,
|
||||
0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x62, 0x02, 0x59, 0x59, 0x02, 0x01, 0x5A,
|
||||
0x5B, 0x02, 0x01, 0x08, 0x04, 0xFB, 0x01, 0x01, 0x53, 0x53, 0x01, 0x54, 0x54, 0x06, 0x7A, 0x79,
|
||||
0x88, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B,
|
||||
0x00, 0x1B, 0x00, 0x00, 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36,
|
||||
0x01, 0x15, 0x33, 0x35, 0x13, 0x23, 0x07, 0x0E, 0x01, 0x2F, 0x01, 0x23, 0x22, 0x16, 0x1F, 0x01,
|
||||
0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x36, 0x68,
|
||||
0xBE, 0x77, 0x3B, 0x3C, 0x02, 0x3D, 0x3D, 0x3D, 0x3D, 0x01, 0x5F, 0x5E, 0x03, 0x70, 0x84, 0xE2,
|
||||
0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6D, 0xDA, 0x01, 0x2D, 0x65,
|
||||
0x66, 0x03, 0x67, 0x67, 0x01, 0x95, 0x96, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC,
|
||||
0x03, 0x4A, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x05, 0x21, 0x11, 0x10, 0x05, 0x21, 0x01, 0x21,
|
||||
0x35, 0x21, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x14, 0x02, 0xC4, 0xFD, 0x5C, 0x01, 0x70,
|
||||
0xFE, 0xF8, 0x68, 0x41, 0x02, 0x77, 0x01, 0x14, 0x01, 0xFD, 0x38, 0x57, 0x01, 0xB0, 0x00, 0x00,
|
||||
0x00, 0x03, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC, 0x03, 0x49, 0x00, 0x05, 0x00, 0x20, 0x00, 0x2B,
|
||||
0x00, 0x00, 0x17, 0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F, 0x01,
|
||||
0x33, 0x37, 0x2E, 0x02, 0x27, 0x34, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26,
|
||||
0x2B, 0x01, 0x05, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x16, 0x17, 0x16, 0x15, 0x14, 0x14, 0x02, 0xC4,
|
||||
0x01, 0x14, 0xFD, 0x2A, 0x69, 0x19, 0x2E, 0x28, 0x56, 0x2A, 0x3D, 0x3D, 0x01, 0x65, 0x2C, 0x20,
|
||||
0x0D, 0x66, 0x13, 0x06, 0x04, 0x09, 0x34, 0x20, 0x49, 0x15, 0x76, 0x77, 0x01, 0x02, 0x0C, 0x47,
|
||||
0x46, 0x4F, 0x56, 0x10, 0x20, 0x41, 0x03, 0x8A, 0xFE, 0xED, 0xFD, 0x89, 0xC2, 0xDA, 0x01, 0x01,
|
||||
0x1A, 0x81, 0x3D, 0x01, 0x01, 0xA3, 0x2C, 0x13, 0x01, 0x02, 0x13, 0x5A, 0x1A, 0x1C, 0x44, 0x21,
|
||||
0x13, 0x04, 0x01, 0xDA, 0x02, 0x85, 0x01, 0x08, 0x0F, 0x29, 0x3A, 0x00, 0x00, 0x03, 0x00, 0x14,
|
||||
0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x08, 0x00, 0x15, 0x00, 0x1B, 0x00, 0x00, 0x05, 0x21,
|
||||
0x11, 0x10, 0x21, 0x30, 0x21, 0x32, 0x15, 0x01, 0x21, 0x35, 0x23, 0x13, 0x35, 0x21, 0x15, 0x33,
|
||||
0x32, 0x22, 0x0F, 0x01, 0x05, 0x21, 0x35, 0x23, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x8A,
|
||||
0x01, 0xEC, 0x62, 0xFC, 0xCF, 0x01, 0x40, 0xE1, 0xD9, 0xFE, 0xDF, 0x5D, 0x5C, 0x01, 0x67, 0x68,
|
||||
0x01, 0x75, 0x01, 0x15, 0xC6, 0x4F, 0x05, 0x01, 0x89, 0x01, 0x8A, 0x63, 0xFD, 0xE1, 0x42, 0x01,
|
||||
0x0B, 0x3D, 0x42, 0x80, 0x80, 0x48, 0x42, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14,
|
||||
0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x07, 0x00, 0x22, 0x00, 0x2F, 0x00, 0x3C, 0x00, 0x00,
|
||||
0x17, 0x11, 0x34, 0x37, 0x21, 0x20, 0x19, 0x01, 0x01, 0x15, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F,
|
||||
0x02, 0x32, 0x35, 0x26, 0x27, 0x26, 0x27, 0x26, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26,
|
||||
0x23, 0x27, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14, 0x07, 0x0E, 0x01, 0x05,
|
||||
0x21, 0x35, 0x27, 0x13, 0x35, 0x21, 0x15, 0x33, 0x32, 0x14, 0x0F, 0x01, 0x14, 0x62, 0x01, 0xEC,
|
||||
0x01, 0x8A, 0xFE, 0x1E, 0x4E, 0x14, 0x29, 0x1E, 0x37, 0x22, 0x2F, 0x2F, 0x06, 0x3A, 0x1D, 0x1F,
|
||||
0x09, 0x09, 0x4E, 0x0E, 0x04, 0x05, 0x0F, 0x47, 0x15, 0x6F, 0x65, 0x82, 0x34, 0x37, 0x38, 0x07,
|
||||
0x23, 0x09, 0x13, 0x0D, 0x1A, 0xFD, 0xD6, 0x01, 0x40, 0xE1, 0xD8, 0xFE, 0xE0, 0x5C, 0x5C, 0x67,
|
||||
0x68, 0x05, 0x02, 0xB0, 0x62, 0x01, 0xFE, 0x76, 0xFE, 0x77, 0x01, 0x56, 0xC5, 0xA5, 0x01, 0x01,
|
||||
0x1C, 0x52, 0x34, 0x01, 0x01, 0x0E, 0x58, 0x2C, 0x13, 0x06, 0x04, 0x0F, 0x45, 0x1E, 0x14, 0x42,
|
||||
0x0D, 0x04, 0x01, 0xA7, 0x65, 0x01, 0x04, 0x2C, 0x21, 0x09, 0x07, 0x03, 0xE3, 0x41, 0x01, 0x01,
|
||||
0x0B, 0x3D, 0x42, 0x01, 0x80, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC,
|
||||
0x02, 0xAB, 0x00, 0x08, 0x00, 0x37, 0x00, 0x3D, 0x00, 0x00, 0x13, 0x30, 0x21, 0x11, 0x21, 0x22,
|
||||
0x3D, 0x01, 0x34, 0x05, 0x37, 0x34, 0x27, 0x26, 0x27, 0x26, 0x07, 0x06, 0x07, 0x0E, 0x01, 0x17,
|
||||
0x1E, 0x01, 0x17, 0x16, 0x14, 0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x22, 0x17,
|
||||
0x1E, 0x01, 0x17, 0x16, 0x37, 0x36, 0x27, 0x26, 0x27, 0x2E, 0x02, 0x37, 0x36, 0x33, 0x32, 0x1F,
|
||||
0x02, 0x33, 0x35, 0x23, 0x11, 0x23, 0xD6, 0x03, 0x16, 0xFC, 0xEA, 0xC2, 0x01, 0xC6, 0x02, 0x01,
|
||||
0x0C, 0x3A, 0x2B, 0x2D, 0x13, 0x10, 0x2B, 0x01, 0x33, 0x17, 0x55, 0x15, 0x04, 0x09, 0x14, 0x58,
|
||||
0x0C, 0x04, 0x02, 0x02, 0x26, 0x14, 0x01, 0x03, 0x08, 0x33, 0x38, 0x5F, 0x20, 0x10, 0x01, 0x03,
|
||||
0x3C, 0x12, 0x59, 0x11, 0x01, 0x02, 0x39, 0x2C, 0x09, 0x02, 0x9D, 0xE2, 0xA2, 0x40, 0x02, 0xAB,
|
||||
0xFD, 0xB2, 0xD2, 0xAA, 0xD2, 0xDC, 0x03, 0x07, 0x0B, 0x38, 0x10, 0x0C, 0x09, 0x04, 0x08, 0x19,
|
||||
0x6C, 0x17, 0x0B, 0x17, 0x11, 0x07, 0x17, 0x0A, 0x1A, 0x0A, 0x29, 0x0C, 0x04, 0x04, 0x02, 0x10,
|
||||
0x25, 0x37, 0x04, 0x06, 0x37, 0x1D, 0x1C, 0x3F, 0x19, 0x08, 0x16, 0x13, 0x0B, 0x1F, 0x2B, 0x04,
|
||||
0xE9, 0x37, 0x01, 0x13, 0x00, 0x04, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC, 0x02, 0xAB, 0x00, 0x07,
|
||||
0x00, 0x1F, 0x00, 0x2A, 0x00, 0x58, 0x00, 0x00, 0x01, 0x32, 0x1D, 0x01, 0x14, 0x23, 0x21, 0x11,
|
||||
0x01, 0x33, 0x35, 0x17, 0x1E, 0x03, 0x3B, 0x01, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x36, 0x37, 0x36,
|
||||
0x27, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x16, 0x17, 0x16, 0x07,
|
||||
0x06, 0x05, 0x16, 0x37, 0x36, 0x37, 0x3E, 0x01, 0x27, 0x2E, 0x03, 0x3E, 0x01, 0x17, 0x16, 0x17,
|
||||
0x30, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x06, 0x1E, 0x03, 0x17, 0x16,
|
||||
0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x07, 0x06, 0x23, 0x07, 0x16, 0x03, 0x2A, 0xC2, 0xC2, 0xFC,
|
||||
0xEA, 0x01, 0xEC, 0x41, 0x11, 0x1F, 0x17, 0x4D, 0x02, 0x27, 0x26, 0x16, 0x1E, 0x1C, 0x17, 0x04,
|
||||
0x43, 0x0C, 0x0B, 0x21, 0x18, 0x3E, 0x0F, 0x46, 0x47, 0x66, 0x25, 0x29, 0x3E, 0x1B, 0x03, 0x08,
|
||||
0x22, 0x0C, 0xFE, 0x4D, 0x22, 0x59, 0x34, 0x1E, 0x2B, 0x03, 0x33, 0x16, 0x5C, 0x16, 0x0C, 0x18,
|
||||
0x3C, 0x16, 0x0B, 0x05, 0x22, 0x21, 0x01, 0x03, 0x10, 0x1F, 0x49, 0x36, 0x43, 0x02, 0x01, 0x1C,
|
||||
0x2D, 0x56, 0x1B, 0x04, 0x07, 0x20, 0x13, 0x4B, 0x0D, 0x01, 0x04, 0x1D, 0x1E, 0x02, 0x02, 0x04,
|
||||
0x02, 0xAB, 0xD2, 0xAA, 0xD2, 0x02, 0x4E, 0xFE, 0x39, 0x89, 0x01, 0x01, 0x11, 0x75, 0x01, 0x25,
|
||||
0x2F, 0x27, 0x0F, 0x08, 0x0C, 0x38, 0x33, 0x21, 0x19, 0x02, 0x01, 0x8A, 0x53, 0x0D, 0x0F, 0x2A,
|
||||
0x09, 0x04, 0x8A, 0x3A, 0x03, 0x01, 0x12, 0x1B, 0x71, 0x1B, 0x0C, 0x17, 0x0D, 0x18, 0x17, 0x09,
|
||||
0x11, 0x09, 0x1A, 0x01, 0x01, 0x07, 0x1E, 0x15, 0x29, 0x01, 0x2D, 0x2D, 0x1A, 0x2C, 0x16, 0x16,
|
||||
0x0D, 0x0F, 0x1A, 0x14, 0x0C, 0x0D, 0x27, 0x04, 0x0C, 0x03, 0x03, 0x04, 0x1E, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x00,
|
||||
0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x15, 0x33, 0x15,
|
||||
0x33, 0x35, 0x33, 0x35, 0x23, 0x35, 0x23, 0x15, 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2,
|
||||
0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC4, 0x50, 0xC4, 0xC5, 0x4E, 0x03, 0x70, 0x84, 0xE2, 0xFE,
|
||||
0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0x4F, 0xC5, 0xC5, 0x4E, 0xC5, 0xC4,
|
||||
0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x00,
|
||||
0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x21, 0x35, 0x21,
|
||||
0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8,
|
||||
0xFE, 0x28, 0x03, 0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE,
|
||||
0x71, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xAE, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x15, 0x00, 0x2C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10,
|
||||
0x00, 0x64, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x85, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0xAF, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x04, 0x00, 0x10, 0x00, 0xE2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0D,
|
||||
0x01, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x10, 0x01, 0x3F, 0x00, 0x03,
|
||||
0x00, 0x01, 0x04, 0x09, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
|
||||
0x00, 0x01, 0x00, 0x20, 0x00, 0x42, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0E,
|
||||
0x00, 0x75, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x20, 0x00, 0x8D, 0x00, 0x03,
|
||||
0x00, 0x01, 0x04, 0x09, 0x00, 0x04, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09,
|
||||
0x00, 0x05, 0x00, 0x1A, 0x00, 0xF3, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x20,
|
||||
0x01, 0x1D, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x20, 0x00, 0x45, 0x00, 0x6D,
|
||||
0x00, 0x75, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x20, 0x00, 0x50,
|
||||
0x00, 0x72, 0x00, 0x6F, 0x00, 0x6A, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x00, 0x59, 0x75,
|
||||
0x7A, 0x75, 0x20, 0x45, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72, 0x20, 0x50, 0x72, 0x6F, 0x6A,
|
||||
0x65, 0x63, 0x74, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53,
|
||||
0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69,
|
||||
0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74,
|
||||
0x65, 0x6E, 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00,
|
||||
0x6C, 0x00, 0x61, 0x00, 0x72, 0x00, 0x00, 0x52, 0x65, 0x67, 0x75, 0x6C, 0x61, 0x72, 0x00, 0x00,
|
||||
0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00,
|
||||
0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00,
|
||||
0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F,
|
||||
0x6E, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53,
|
||||
0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F,
|
||||
0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E,
|
||||
0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00,
|
||||
0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00,
|
||||
0x00, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x31, 0x2E, 0x30, 0x30, 0x30, 0x00, 0x00,
|
||||
0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00,
|
||||
0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00,
|
||||
0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F,
|
||||
0x6E, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xB5, 0x00, 0x32,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04,
|
||||
0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0A, 0x01, 0x0B, 0x01, 0x0C,
|
||||
0x01, 0x0D, 0x01, 0x0E, 0x01, 0x0F, 0x01, 0x10, 0x01, 0x11, 0x01, 0x12, 0x01, 0x13, 0x01, 0x14,
|
||||
0x01, 0x15, 0x01, 0x16, 0x01, 0x17, 0x01, 0x18, 0x01, 0x19, 0x01, 0x1A, 0x01, 0x1B, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x30, 0x30, 0x30, 0x44, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x41, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x31, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x41, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x33, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x41, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x35, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x41, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x37, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x41, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x39, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x42, 0x33, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x42, 0x34, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x31, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x33, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x35, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x37, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x39, 0x07, 0x75,
|
||||
0x6E, 0x69, 0x45, 0x30, 0x45, 0x46, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x46, 0x30, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x0F,
|
||||
}};
|
||||
|
||||
} // namespace FileSys::SystemArchive::SharedFontData
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
|
||||
namespace FileSys::SystemArchive::SharedFontData {
|
||||
|
||||
extern const std::array<unsigned char, 2932> FONT_NINTENDO_EXTENDED;
|
||||
extern const std::array<unsigned char, 6024> FONT_NINTENDO_EXTENDED;
|
||||
|
||||
} // namespace FileSys::SystemArchive::SharedFontData
|
||||
|
||||
@@ -203,7 +203,7 @@ std::string VfsFile::GetFullPath() const {
|
||||
return GetContainingDirectory()->GetFullPath() + "/" + GetName();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
auto vec = Common::FS::SplitPathComponents(path);
|
||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
||||
vec.end());
|
||||
@@ -231,7 +231,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) co
|
||||
return dir->GetFile(vec.back());
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) const {
|
||||
VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
|
||||
if (IsRoot()) {
|
||||
return GetFileRelative(path);
|
||||
}
|
||||
@@ -239,7 +239,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(std::string_view path) co
|
||||
return GetParentDirectory()->GetFileAbsolute(path);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_view path) const {
|
||||
VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const {
|
||||
auto vec = Common::FS::SplitPathComponents(path);
|
||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
||||
vec.end());
|
||||
@@ -261,7 +261,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_vie
|
||||
return dir;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(std::string_view path) const {
|
||||
VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const {
|
||||
if (IsRoot()) {
|
||||
return GetDirectoryRelative(path);
|
||||
}
|
||||
@@ -269,14 +269,14 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(std::string_vie
|
||||
return GetParentDirectory()->GetDirectoryAbsolute(path);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> VfsDirectory::GetFile(std::string_view name) const {
|
||||
VirtualFile VfsDirectory::GetFile(std::string_view name) const {
|
||||
const auto& files = GetFiles();
|
||||
const auto iter = std::find_if(files.begin(), files.end(),
|
||||
[&name](const auto& file1) { return name == file1->GetName(); });
|
||||
return iter == files.end() ? nullptr : *iter;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VfsDirectory::GetSubdirectory(std::string_view name) const {
|
||||
VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const {
|
||||
const auto& subs = GetSubdirectories();
|
||||
const auto iter = std::find_if(subs.begin(), subs.end(),
|
||||
[&name](const auto& file1) { return name == file1->GetName(); });
|
||||
@@ -301,7 +301,7 @@ std::size_t VfsDirectory::GetSize() const {
|
||||
return file_total + subdir_total;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path) {
|
||||
VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) {
|
||||
auto vec = Common::FS::SplitPathComponents(path);
|
||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
||||
vec.end());
|
||||
@@ -324,7 +324,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(std::string_view path)
|
||||
return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path) {
|
||||
VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) {
|
||||
if (IsRoot()) {
|
||||
return CreateFileRelative(path);
|
||||
}
|
||||
@@ -332,7 +332,7 @@ std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(std::string_view path)
|
||||
return GetParentDirectory()->CreateFileAbsolute(path);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
||||
VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
||||
auto vec = Common::FS::SplitPathComponents(path);
|
||||
vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
|
||||
vec.end());
|
||||
@@ -355,7 +355,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(std::string_
|
||||
return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
|
||||
VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
|
||||
if (IsRoot()) {
|
||||
return CreateDirectoryRelative(path);
|
||||
}
|
||||
@@ -446,27 +446,27 @@ bool ReadOnlyVfsDirectory::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(std::string_view name) {
|
||||
VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
|
||||
VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
|
||||
VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
|
||||
VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
||||
VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
// Resizes the file to new_size. Returns whether or not the operation was successful.
|
||||
virtual bool Resize(std::size_t new_size) = 0;
|
||||
// Gets a pointer to the directory containing this file, returning nullptr if there is none.
|
||||
virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0;
|
||||
virtual VirtualDir GetContainingDirectory() const = 0;
|
||||
|
||||
// Returns whether or not the file can be written to.
|
||||
virtual bool IsWritable() const = 0;
|
||||
@@ -183,27 +183,27 @@ public:
|
||||
|
||||
// Retrives the file located at path as if the current directory was root. Returns nullptr if
|
||||
// not found.
|
||||
virtual std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const;
|
||||
virtual VirtualFile GetFileRelative(std::string_view path) const;
|
||||
// Calls GetFileRelative(path) on the root of the current directory.
|
||||
virtual std::shared_ptr<VfsFile> GetFileAbsolute(std::string_view path) const;
|
||||
virtual VirtualFile GetFileAbsolute(std::string_view path) const;
|
||||
|
||||
// Retrives the directory located at path as if the current directory was root. Returns nullptr
|
||||
// if not found.
|
||||
virtual std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const;
|
||||
virtual VirtualDir GetDirectoryRelative(std::string_view path) const;
|
||||
// Calls GetDirectoryRelative(path) on the root of the current directory.
|
||||
virtual std::shared_ptr<VfsDirectory> GetDirectoryAbsolute(std::string_view path) const;
|
||||
virtual VirtualDir GetDirectoryAbsolute(std::string_view path) const;
|
||||
|
||||
// Returns a vector containing all of the files in this directory.
|
||||
virtual std::vector<std::shared_ptr<VfsFile>> GetFiles() const = 0;
|
||||
virtual std::vector<VirtualFile> GetFiles() const = 0;
|
||||
// Returns the file with filename matching name. Returns nullptr if directory dosen't have a
|
||||
// file with name.
|
||||
virtual std::shared_ptr<VfsFile> GetFile(std::string_view name) const;
|
||||
virtual VirtualFile GetFile(std::string_view name) const;
|
||||
|
||||
// Returns a vector containing all of the subdirectories in this directory.
|
||||
virtual std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const = 0;
|
||||
virtual std::vector<VirtualDir> GetSubdirectories() const = 0;
|
||||
// Returns the directory with name matching name. Returns nullptr if directory dosen't have a
|
||||
// directory with name.
|
||||
virtual std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const;
|
||||
virtual VirtualDir GetSubdirectory(std::string_view name) const;
|
||||
|
||||
// Returns whether or not the directory can be written to.
|
||||
virtual bool IsWritable() const = 0;
|
||||
@@ -219,31 +219,31 @@ public:
|
||||
virtual std::size_t GetSize() const;
|
||||
// Returns the parent directory of this directory. Returns nullptr if this directory is root or
|
||||
// has no parent.
|
||||
virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0;
|
||||
virtual VirtualDir GetParentDirectory() const = 0;
|
||||
|
||||
// Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
|
||||
// if the operation failed.
|
||||
virtual std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) = 0;
|
||||
virtual VirtualDir CreateSubdirectory(std::string_view name) = 0;
|
||||
// Creates a new file with name name. Returns a pointer to the new file or nullptr if the
|
||||
// operation failed.
|
||||
virtual std::shared_ptr<VfsFile> CreateFile(std::string_view name) = 0;
|
||||
virtual VirtualFile CreateFile(std::string_view name) = 0;
|
||||
|
||||
// Creates a new file at the path relative to this directory. Also creates directories if
|
||||
// they do not exist and is supported by this implementation. Returns nullptr on any failure.
|
||||
virtual std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path);
|
||||
virtual VirtualFile CreateFileRelative(std::string_view path);
|
||||
|
||||
// Creates a new file at the path relative to root of this directory. Also creates directories
|
||||
// if they do not exist and is supported by this implementation. Returns nullptr on any failure.
|
||||
virtual std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path);
|
||||
virtual VirtualFile CreateFileAbsolute(std::string_view path);
|
||||
|
||||
// Creates a new directory at the path relative to this directory. Also creates directories if
|
||||
// they do not exist and is supported by this implementation. Returns nullptr on any failure.
|
||||
virtual std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path);
|
||||
virtual VirtualDir CreateDirectoryRelative(std::string_view path);
|
||||
|
||||
// Creates a new directory at the path relative to root of this directory. Also creates
|
||||
// directories if they do not exist and is supported by this implementation. Returns nullptr on
|
||||
// any failure.
|
||||
virtual std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path);
|
||||
virtual VirtualDir CreateDirectoryAbsolute(std::string_view path);
|
||||
|
||||
// Deletes the subdirectory with the given name and returns true on success.
|
||||
virtual bool DeleteSubdirectory(std::string_view name) = 0;
|
||||
@@ -280,12 +280,12 @@ class ReadOnlyVfsDirectory : public VfsDirectory {
|
||||
public:
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
|
||||
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
|
||||
std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path) override;
|
||||
std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
|
||||
std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path) override;
|
||||
std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
|
||||
VirtualDir CreateSubdirectory(std::string_view name) override;
|
||||
VirtualFile CreateFile(std::string_view name) override;
|
||||
VirtualFile CreateFileAbsolute(std::string_view path) override;
|
||||
VirtualFile CreateFileRelative(std::string_view path) override;
|
||||
VirtualDir CreateDirectoryAbsolute(std::string_view path) override;
|
||||
VirtualDir CreateDirectoryRelative(std::string_view path) override;
|
||||
bool DeleteSubdirectory(std::string_view name) override;
|
||||
bool DeleteSubdirectoryRecursive(std::string_view name) override;
|
||||
bool CleanSubdirectoryRecursive(std::string_view name) override;
|
||||
|
||||
@@ -46,7 +46,7 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> f
|
||||
if (files.size() == 1)
|
||||
return files[0];
|
||||
|
||||
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
|
||||
return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name)));
|
||||
}
|
||||
|
||||
VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
|
||||
@@ -71,20 +71,23 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
|
||||
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)));
|
||||
return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name)));
|
||||
}
|
||||
|
||||
std::string ConcatenatedVfsFile::GetName() const {
|
||||
if (files.empty())
|
||||
if (files.empty()) {
|
||||
return "";
|
||||
if (!name.empty())
|
||||
}
|
||||
if (!name.empty()) {
|
||||
return name;
|
||||
}
|
||||
return files.begin()->second->GetName();
|
||||
}
|
||||
|
||||
std::size_t ConcatenatedVfsFile::GetSize() const {
|
||||
if (files.empty())
|
||||
if (files.empty()) {
|
||||
return 0;
|
||||
}
|
||||
return files.rbegin()->first + files.rbegin()->second->GetSize();
|
||||
}
|
||||
|
||||
@@ -92,9 +95,10 @@ bool ConcatenatedVfsFile::Resize(std::size_t new_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const {
|
||||
if (files.empty())
|
||||
VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const {
|
||||
if (files.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return files.begin()->second->GetContainingDirectory();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
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;
|
||||
VirtualDir 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;
|
||||
|
||||
@@ -20,10 +20,10 @@ VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dir
|
||||
if (dirs.size() == 1)
|
||||
return dirs[0];
|
||||
|
||||
return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
|
||||
return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
for (const auto& layer : dirs) {
|
||||
const auto file = layer->GetFileRelative(path);
|
||||
if (file != nullptr)
|
||||
@@ -33,23 +33,23 @@ std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view p
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative(
|
||||
std::string_view path) const {
|
||||
VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) const {
|
||||
std::vector<VirtualDir> out;
|
||||
for (const auto& layer : dirs) {
|
||||
auto dir = layer->GetDirectoryRelative(path);
|
||||
if (dir != nullptr)
|
||||
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 {
|
||||
VirtualFile LayeredVfsDirectory::GetFile(std::string_view name) const {
|
||||
return GetFileRelative(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
|
||||
VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
|
||||
return GetDirectoryRelative(name);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ std::string LayeredVfsDirectory::GetFullPath() const {
|
||||
return dirs[0]->GetFullPath();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
|
||||
std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
|
||||
std::vector<VirtualFile> out;
|
||||
for (const auto& layer : dirs) {
|
||||
for (const auto& file : layer->GetFiles()) {
|
||||
@@ -72,7 +72,7 @@ std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const {
|
||||
std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const {
|
||||
std::vector<std::string> names;
|
||||
for (const auto& layer : dirs) {
|
||||
for (const auto& sd : layer->GetSubdirectories()) {
|
||||
@@ -101,15 +101,15 @@ std::string LayeredVfsDirectory::GetName() const {
|
||||
return name.empty() ? dirs[0]->GetName() : name;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const {
|
||||
VirtualDir LayeredVfsDirectory::GetParentDirectory() const {
|
||||
return dirs[0]->GetParentDirectory();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) {
|
||||
VirtualFile LayeredVfsDirectory::CreateFile(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,20 +21,20 @@ public:
|
||||
/// 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;
|
||||
VirtualFile GetFileRelative(std::string_view path) const override;
|
||||
VirtualDir GetDirectoryRelative(std::string_view path) const override;
|
||||
VirtualFile GetFile(std::string_view name) const override;
|
||||
VirtualDir 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;
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
std::vector<VirtualDir> 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;
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
VirtualDir CreateSubdirectory(std::string_view name) override;
|
||||
VirtualFile 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;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, std::size_t size_, std::size_t offset_,
|
||||
OffsetVfsFile::OffsetVfsFile(VirtualFile file_, std::size_t size_, std::size_t offset_,
|
||||
std::string name_, VirtualDir parent_)
|
||||
: file(file_), offset(offset_), size(size_), name(std::move(name_)),
|
||||
parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {}
|
||||
@@ -37,7 +37,7 @@ bool OffsetVfsFile::Resize(std::size_t new_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const {
|
||||
VirtualDir OffsetVfsFile::GetContainingDirectory() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,14 @@ namespace FileSys {
|
||||
// the size of this wrapper.
|
||||
class OffsetVfsFile : public VfsFile {
|
||||
public:
|
||||
OffsetVfsFile(std::shared_ptr<VfsFile> file, std::size_t size, std::size_t offset = 0,
|
||||
OffsetVfsFile(VirtualFile file, std::size_t size, std::size_t offset = 0,
|
||||
std::string new_name = "", VirtualDir new_parent = nullptr);
|
||||
~OffsetVfsFile() 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;
|
||||
VirtualDir 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;
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
private:
|
||||
std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
|
||||
|
||||
std::shared_ptr<VfsFile> file;
|
||||
VirtualFile file;
|
||||
std::size_t offset;
|
||||
std::size_t size;
|
||||
std::string name;
|
||||
|
||||
@@ -94,13 +94,9 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
|
||||
|
||||
VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
|
||||
const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
|
||||
const auto parent_path = FS::GetParentPath(path);
|
||||
|
||||
const auto path_fwd = FS::SanitizePath(path, FS::DirectorySeparator::ForwardSlash);
|
||||
if (!FS::Exists(path)) {
|
||||
if (!FS::CreateDirs(parent_path)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FS::CreateFullPath(path_fwd);
|
||||
if (!FS::CreateEmptyFile(path)) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -267,7 +263,7 @@ bool RealVfsFile::Resize(std::size_t new_size) {
|
||||
return backing->Resize(new_size);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
|
||||
VirtualDir RealVfsFile::GetContainingDirectory() const {
|
||||
return base.OpenDirectory(parent_path, perms);
|
||||
}
|
||||
|
||||
@@ -356,7 +352,7 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
|
||||
|
||||
RealVfsDirectory::~RealVfsDirectory() = default;
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
VirtualFile RealVfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) {
|
||||
return nullptr;
|
||||
@@ -364,7 +360,7 @@ std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path
|
||||
return base.OpenFile(full_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
|
||||
VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
|
||||
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) {
|
||||
return nullptr;
|
||||
@@ -372,20 +368,20 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string
|
||||
return base.OpenDirectory(full_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::GetFile(std::string_view name) const {
|
||||
VirtualFile RealVfsDirectory::GetFile(std::string_view name) const {
|
||||
return GetFileRelative(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetSubdirectory(std::string_view name) const {
|
||||
VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const {
|
||||
return GetDirectoryRelative(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view path) {
|
||||
VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view path) {
|
||||
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
return base.CreateFile(full_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
||||
VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
||||
const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
return base.CreateDirectory(full_path, perms);
|
||||
}
|
||||
@@ -395,11 +391,11 @@ bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
|
||||
return base.DeleteDirectory(full_path);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const {
|
||||
std::vector<VirtualFile> RealVfsDirectory::GetFiles() const {
|
||||
return IterateEntries<RealVfsFile, VfsFile>();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const {
|
||||
std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
|
||||
return IterateEntries<RealVfsDirectory, VfsDirectory>();
|
||||
}
|
||||
|
||||
@@ -415,7 +411,7 @@ std::string RealVfsDirectory::GetName() const {
|
||||
return path_components.back();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
|
||||
VirtualDir RealVfsDirectory::GetParentDirectory() const {
|
||||
if (path_components.size() <= 1) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -423,12 +419,12 @@ std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
|
||||
return base.OpenDirectory(parent_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
const std::string subdir_path = (path + DIR_SEP).append(name);
|
||||
return base.CreateDirectory(subdir_path, perms);
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(std::string_view name) {
|
||||
VirtualFile RealVfsDirectory::CreateFile(std::string_view name) {
|
||||
const std::string file_path = (path + DIR_SEP).append(name);
|
||||
return base.CreateFile(file_path, perms);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
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;
|
||||
VirtualDir 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;
|
||||
@@ -79,21 +79,21 @@ class RealVfsDirectory : public VfsDirectory {
|
||||
public:
|
||||
~RealVfsDirectory() override;
|
||||
|
||||
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::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
|
||||
std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
|
||||
VirtualFile GetFileRelative(std::string_view path) const override;
|
||||
VirtualDir GetDirectoryRelative(std::string_view path) const override;
|
||||
VirtualFile GetFile(std::string_view name) const override;
|
||||
VirtualDir GetSubdirectory(std::string_view name) const override;
|
||||
VirtualFile CreateFileRelative(std::string_view path) override;
|
||||
VirtualDir CreateDirectoryRelative(std::string_view path) override;
|
||||
bool DeleteSubdirectoryRecursive(std::string_view name) override;
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
std::vector<VirtualDir> 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;
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
VirtualDir CreateSubdirectory(std::string_view name) override;
|
||||
VirtualFile 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;
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
|
||||
VirtualDir GetContainingDirectory() const override {
|
||||
return parent;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ bool VectorVfsFile::Resize(size_t new_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const {
|
||||
VirtualDir VectorVfsFile::GetContainingDirectory() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@@ -68,11 +68,11 @@ VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
|
||||
|
||||
VectorVfsDirectory::~VectorVfsDirectory() = default;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> VectorVfsDirectory::GetFiles() const {
|
||||
std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const {
|
||||
return files;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> VectorVfsDirectory::GetSubdirectories() const {
|
||||
std::vector<VirtualDir> VectorVfsDirectory::GetSubdirectories() const {
|
||||
return dirs;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ std::string VectorVfsDirectory::GetName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VectorVfsDirectory::GetParentDirectory() const {
|
||||
VirtualDir VectorVfsDirectory::GetParentDirectory() const {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@@ -116,11 +116,11 @@ bool VectorVfsDirectory::Rename(std::string_view name_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> VectorVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsFile> VectorVfsDirectory::CreateFile(std::string_view name) {
|
||||
VirtualFile VectorVfsDirectory::CreateFile(std::string_view name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
|
||||
VirtualDir GetContainingDirectory() const override {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public:
|
||||
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;
|
||||
VirtualDir 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;
|
||||
@@ -106,17 +106,17 @@ public:
|
||||
VirtualDir parent = nullptr);
|
||||
~VectorVfsDirectory() override;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
std::vector<VirtualDir> GetSubdirectories() const override;
|
||||
bool IsWritable() const override;
|
||||
bool IsReadable() const override;
|
||||
std::string GetName() const override;
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
bool DeleteSubdirectory(std::string_view name) override;
|
||||
bool DeleteFile(std::string_view name) override;
|
||||
bool Rename(std::string_view name) override;
|
||||
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
|
||||
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
|
||||
VirtualDir CreateSubdirectory(std::string_view name) override;
|
||||
VirtualFile CreateFile(std::string_view name) override;
|
||||
|
||||
virtual void AddFile(VirtualFile file);
|
||||
virtual void AddDirectory(VirtualDir dir);
|
||||
|
||||
@@ -152,11 +152,11 @@ NAXContentType NAX::GetContentType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> NAX::GetFiles() const {
|
||||
std::vector<VirtualFile> NAX::GetFiles() const {
|
||||
return {dec_file};
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> NAX::GetSubdirectories() const {
|
||||
std::vector<VirtualDir> NAX::GetSubdirectories() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ std::string NAX::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const {
|
||||
VirtualDir NAX::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
|
||||
@@ -47,13 +47,13 @@ public:
|
||||
|
||||
NAXContentType GetContentType() const;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
std::vector<VirtualDir> GetSubdirectories() const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
VirtualDir GetParentDirectory() const override;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus Parse(std::string_view path);
|
||||
|
||||
@@ -53,72 +53,4 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function<void()> finished) con
|
||||
finished();
|
||||
}
|
||||
|
||||
ECommerceApplet::~ECommerceApplet() = default;
|
||||
|
||||
DefaultECommerceApplet::~DefaultECommerceApplet() = default;
|
||||
|
||||
void DefaultECommerceApplet::ShowApplicationInformation(
|
||||
std::function<void()> finished, u64 title_id, std::optional<u128> user_id,
|
||||
std::optional<bool> full_display, std::optional<std::string> extra_parameter) {
|
||||
const auto value = user_id.value_or(u128{});
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show application information for EShop, "
|
||||
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}",
|
||||
title_id, value[1], value[0],
|
||||
full_display.has_value() ? fmt::format("{}", *full_display) : "null",
|
||||
extra_parameter.value_or("null"));
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowAddOnContentList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id,
|
||||
std::optional<bool> full_display) {
|
||||
const auto value = user_id.value_or(u128{});
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show add on content list for EShop, "
|
||||
"title_id={:016X}, user_id={:016X}{:016X}, full_display={}",
|
||||
title_id, value[1], value[0],
|
||||
full_display.has_value() ? fmt::format("{}", *full_display) : "null");
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowSubscriptionList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id) {
|
||||
const auto value = user_id.value_or(u128{});
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show subscription list for EShop, title_id={:016X}, "
|
||||
"user_id={:016X}{:016X}",
|
||||
title_id, value[1], value[0]);
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowConsumableItemList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id) {
|
||||
const auto value = user_id.value_or(u128{});
|
||||
LOG_INFO(
|
||||
Service_AM,
|
||||
"Application requested frontend show consumable item list for EShop, title_id={:016X}, "
|
||||
"user_id={:016X}{:016X}",
|
||||
title_id, value[1], value[0]);
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowShopHome(std::function<void()> finished, u128 user_id,
|
||||
bool full_display) {
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, "
|
||||
"full_display={}",
|
||||
user_id[1], user_id[0], full_display);
|
||||
finished();
|
||||
}
|
||||
|
||||
void DefaultECommerceApplet::ShowSettings(std::function<void()> finished, u128 user_id,
|
||||
bool full_display) {
|
||||
LOG_INFO(Service_AM,
|
||||
"Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, "
|
||||
"full_display={}",
|
||||
user_id[1], user_id[0], full_display);
|
||||
finished();
|
||||
}
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -58,55 +58,4 @@ public:
|
||||
void ShowAllPhotos(std::function<void()> finished) const override;
|
||||
};
|
||||
|
||||
class ECommerceApplet {
|
||||
public:
|
||||
virtual ~ECommerceApplet();
|
||||
|
||||
// Shows a page with application icons, description, name, and price.
|
||||
virtual void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id = {},
|
||||
std::optional<bool> full_display = {},
|
||||
std::optional<std::string> extra_parameter = {}) = 0;
|
||||
|
||||
// Shows a page with all of the add on content available for a game, with name, description, and
|
||||
// price.
|
||||
virtual void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id = {},
|
||||
std::optional<bool> full_display = {}) = 0;
|
||||
|
||||
// Shows a page with all of the subscriptions (recurring payments) for a game, with name,
|
||||
// description, price, and renewal period.
|
||||
virtual void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id = {}) = 0;
|
||||
|
||||
// Shows a page with a list of any additional game related purchasable items (DLC,
|
||||
// subscriptions, etc) for a particular game, with name, description, type, and price.
|
||||
virtual void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id = {}) = 0;
|
||||
|
||||
// Shows the home page of the shop.
|
||||
virtual void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) = 0;
|
||||
|
||||
// Shows the user settings page of the shop.
|
||||
virtual void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) = 0;
|
||||
};
|
||||
|
||||
class DefaultECommerceApplet : public ECommerceApplet {
|
||||
public:
|
||||
~DefaultECommerceApplet() override;
|
||||
|
||||
void ShowApplicationInformation(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id, std::optional<bool> full_display,
|
||||
std::optional<std::string> extra_parameter) override;
|
||||
void ShowAddOnContentList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id,
|
||||
std::optional<bool> full_display) override;
|
||||
void ShowSubscriptionList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id) override;
|
||||
void ShowConsumableItemList(std::function<void()> finished, u64 title_id,
|
||||
std::optional<u128> user_id) override;
|
||||
void ShowShopHome(std::function<void()> finished, u128 user_id, bool full_display) override;
|
||||
void ShowSettings(std::function<void()> finished, u128 user_id, bool full_display) override;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -11,14 +11,22 @@ WebBrowserApplet::~WebBrowserApplet() = default;
|
||||
|
||||
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
|
||||
|
||||
void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename,
|
||||
std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) {
|
||||
LOG_INFO(Service_AM,
|
||||
"(STUBBED) called - No suitable web browser implementation found to open website page "
|
||||
"at '{}'!",
|
||||
filename);
|
||||
finished_callback();
|
||||
void DefaultWebBrowserApplet::OpenLocalWebPage(
|
||||
std::string_view local_url, std::function<void()> extract_romfs_callback,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open local web page at {}",
|
||||
local_url);
|
||||
|
||||
callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
|
||||
}
|
||||
|
||||
void DefaultWebBrowserApplet::OpenExternalWebPage(
|
||||
std::string_view external_url,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open external web page at {}",
|
||||
external_url);
|
||||
|
||||
callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/");
|
||||
}
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -7,22 +7,34 @@
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
#include "core/hle/service/am/applets/web_types.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
class WebBrowserApplet {
|
||||
public:
|
||||
virtual ~WebBrowserApplet();
|
||||
|
||||
virtual void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) = 0;
|
||||
virtual void OpenLocalWebPage(
|
||||
std::string_view local_url, std::function<void()> extract_romfs_callback,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0;
|
||||
|
||||
virtual void OpenExternalWebPage(
|
||||
std::string_view external_url,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback) const = 0;
|
||||
};
|
||||
|
||||
class DefaultWebBrowserApplet final : public WebBrowserApplet {
|
||||
public:
|
||||
~DefaultWebBrowserApplet() override;
|
||||
|
||||
void OpenPageLocal(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) override;
|
||||
void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
|
||||
callback) const override;
|
||||
|
||||
void OpenExternalWebPage(std::string_view external_url,
|
||||
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
|
||||
callback) const override;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
45
src/core/frontend/input_interpreter.cpp
Normal file
45
src/core/frontend/input_interpreter.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/input_interpreter.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
InputInterpreter::InputInterpreter(Core::System& system)
|
||||
: npad{system.ServiceManager()
|
||||
.GetService<Service::HID::Hid>("hid")
|
||||
->GetAppletResource()
|
||||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)} {}
|
||||
|
||||
InputInterpreter::~InputInterpreter() = default;
|
||||
|
||||
void InputInterpreter::PollInput() {
|
||||
const u32 button_state = npad.GetAndResetPressState();
|
||||
|
||||
previous_index = current_index;
|
||||
current_index = (current_index + 1) % button_states.size();
|
||||
|
||||
button_states[current_index] = button_state;
|
||||
}
|
||||
|
||||
bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const {
|
||||
const bool current_press =
|
||||
(button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
|
||||
const bool previous_press =
|
||||
(button_states[previous_index] & (1U << static_cast<u8>(button))) != 0;
|
||||
|
||||
return current_press && !previous_press;
|
||||
}
|
||||
|
||||
bool InputInterpreter::IsButtonHeld(HIDButton button) const {
|
||||
u32 held_buttons{button_states[0]};
|
||||
|
||||
for (std::size_t i = 1; i < button_states.size(); ++i) {
|
||||
held_buttons &= button_states[i];
|
||||
}
|
||||
|
||||
return (held_buttons & (1U << static_cast<u8>(button))) != 0;
|
||||
}
|
||||
120
src/core/frontend/input_interpreter.h
Normal file
120
src/core/frontend/input_interpreter.h
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::HID {
|
||||
class Controller_NPad;
|
||||
}
|
||||
|
||||
enum class HIDButton : u8 {
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
LStick,
|
||||
RStick,
|
||||
L,
|
||||
R,
|
||||
ZL,
|
||||
ZR,
|
||||
Plus,
|
||||
Minus,
|
||||
|
||||
DLeft,
|
||||
DUp,
|
||||
DRight,
|
||||
DDown,
|
||||
|
||||
LStickLeft,
|
||||
LStickUp,
|
||||
LStickRight,
|
||||
LStickDown,
|
||||
|
||||
RStickLeft,
|
||||
RStickUp,
|
||||
RStickRight,
|
||||
RStickDown,
|
||||
|
||||
LeftSL,
|
||||
LeftSR,
|
||||
|
||||
RightSL,
|
||||
RightSR,
|
||||
};
|
||||
|
||||
/**
|
||||
* The InputInterpreter class interfaces with HID to retrieve button press states.
|
||||
* Input is intended to be polled every 50ms so that a button is considered to be
|
||||
* held down after 400ms has elapsed since the initial button press and subsequent
|
||||
* repeated presses occur every 50ms.
|
||||
*/
|
||||
class InputInterpreter {
|
||||
public:
|
||||
explicit InputInterpreter(Core::System& system);
|
||||
virtual ~InputInterpreter();
|
||||
|
||||
/// Gets a button state from HID and inserts it into the array of button states.
|
||||
void PollInput();
|
||||
|
||||
/**
|
||||
* The specified button is considered to be pressed once
|
||||
* if it is currently pressed and not pressed previously.
|
||||
*
|
||||
* @param button The button to check.
|
||||
*
|
||||
* @returns True when the button is pressed once.
|
||||
*/
|
||||
[[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const;
|
||||
|
||||
/**
|
||||
* Checks whether any of the buttons in the parameter list is pressed once.
|
||||
*
|
||||
* @tparam HIDButton The buttons to check.
|
||||
*
|
||||
* @returns True when at least one of the buttons is pressed once.
|
||||
*/
|
||||
template <HIDButton... T>
|
||||
[[nodiscard]] bool IsAnyButtonPressedOnce() {
|
||||
return (IsButtonPressedOnce(T) || ...);
|
||||
}
|
||||
|
||||
/**
|
||||
* The specified button is considered to be held down if it is pressed in all 9 button states.
|
||||
*
|
||||
* @param button The button to check.
|
||||
*
|
||||
* @returns True when the button is held down.
|
||||
*/
|
||||
[[nodiscard]] bool IsButtonHeld(HIDButton button) const;
|
||||
|
||||
/**
|
||||
* Checks whether any of the buttons in the parameter list is held down.
|
||||
*
|
||||
* @tparam HIDButton The buttons to check.
|
||||
*
|
||||
* @returns True when at least one of the buttons is held down.
|
||||
*/
|
||||
template <HIDButton... T>
|
||||
[[nodiscard]] bool IsAnyButtonHeld() {
|
||||
return (IsButtonHeld(T) || ...);
|
||||
}
|
||||
|
||||
private:
|
||||
Service::HID::Controller_NPad& npad;
|
||||
|
||||
/// Stores 9 consecutive button states polled from HID.
|
||||
std::array<u32, 9> button_states{};
|
||||
|
||||
std::size_t previous_index{};
|
||||
std::size_t current_index{};
|
||||
};
|
||||
@@ -12,8 +12,9 @@
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/time_manager.h"
|
||||
#include "core/hle/result.h"
|
||||
@@ -58,7 +59,7 @@ ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 v
|
||||
}
|
||||
|
||||
ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
|
||||
SchedulerLock lock(system.Kernel());
|
||||
KScopedSchedulerLock lock(system.Kernel());
|
||||
const std::vector<std::shared_ptr<Thread>> waiting_threads =
|
||||
GetThreadsWaitingOnAddress(address);
|
||||
WakeThreads(waiting_threads, num_to_wake);
|
||||
@@ -67,7 +68,7 @@ ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) {
|
||||
|
||||
ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake) {
|
||||
SchedulerLock lock(system.Kernel());
|
||||
KScopedSchedulerLock lock(system.Kernel());
|
||||
auto& memory = system.Memory();
|
||||
|
||||
// Ensure that we can write to the address.
|
||||
@@ -92,7 +93,7 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32
|
||||
|
||||
ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value,
|
||||
s32 num_to_wake) {
|
||||
SchedulerLock lock(system.Kernel());
|
||||
KScopedSchedulerLock lock(system.Kernel());
|
||||
auto& memory = system.Memory();
|
||||
|
||||
// Ensure that we can write to the address.
|
||||
@@ -153,11 +154,11 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6
|
||||
bool should_decrement) {
|
||||
auto& memory = system.Memory();
|
||||
auto& kernel = system.Kernel();
|
||||
Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
Thread* current_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||
|
||||
Handle event_handle = InvalidHandle;
|
||||
{
|
||||
SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
|
||||
KScopedSchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
|
||||
|
||||
if (current_thread->IsPendingTermination()) {
|
||||
lock.CancelSleep();
|
||||
@@ -210,7 +211,7 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6
|
||||
}
|
||||
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
if (current_thread->IsWaitingForArbitration()) {
|
||||
RemoveThread(SharedFrom(current_thread));
|
||||
current_thread->WaitForArbitration(false);
|
||||
@@ -223,11 +224,11 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6
|
||||
ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
|
||||
auto& memory = system.Memory();
|
||||
auto& kernel = system.Kernel();
|
||||
Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
Thread* current_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||
|
||||
Handle event_handle = InvalidHandle;
|
||||
{
|
||||
SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
|
||||
KScopedSchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout);
|
||||
|
||||
if (current_thread->IsPendingTermination()) {
|
||||
lock.CancelSleep();
|
||||
@@ -265,7 +266,7 @@ ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 t
|
||||
}
|
||||
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
if (current_thread->IsWaitingForArbitration()) {
|
||||
RemoveThread(SharedFrom(current_thread));
|
||||
current_thread->WaitForArbitration(false);
|
||||
|
||||
52
src/core/hle/kernel/global_scheduler_context.cpp
Normal file
52
src/core/hle/kernel/global_scheduler_context.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/global_scheduler_context.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
GlobalSchedulerContext::GlobalSchedulerContext(KernelCore& kernel)
|
||||
: kernel{kernel}, scheduler_lock{kernel} {}
|
||||
|
||||
GlobalSchedulerContext::~GlobalSchedulerContext() = default;
|
||||
|
||||
void GlobalSchedulerContext::AddThread(std::shared_ptr<Thread> thread) {
|
||||
std::scoped_lock lock{global_list_guard};
|
||||
thread_list.push_back(std::move(thread));
|
||||
}
|
||||
|
||||
void GlobalSchedulerContext::RemoveThread(std::shared_ptr<Thread> thread) {
|
||||
std::scoped_lock lock{global_list_guard};
|
||||
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
|
||||
thread_list.end());
|
||||
}
|
||||
|
||||
void GlobalSchedulerContext::PreemptThreads() {
|
||||
// The priority levels at which the global scheduler preempts threads every 10 ms. They are
|
||||
// ordered from Core 0 to Core 3.
|
||||
static constexpr std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities{
|
||||
59,
|
||||
59,
|
||||
59,
|
||||
63,
|
||||
};
|
||||
|
||||
ASSERT(IsLocked());
|
||||
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||
const u32 priority = preemption_priorities[core_id];
|
||||
kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority);
|
||||
}
|
||||
}
|
||||
|
||||
bool GlobalSchedulerContext::IsLocked() const {
|
||||
return scheduler_lock.IsLockedByCurrentThread();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
81
src/core/hle/kernel/global_scheduler_context.h
Normal file
81
src/core/hle/kernel/global_scheduler_context.h
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/spin_lock.h"
|
||||
#include "core/hardware_properties.h"
|
||||
#include "core/hle/kernel/k_priority_queue.h"
|
||||
#include "core/hle/kernel/k_scheduler_lock.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class SchedulerLock;
|
||||
|
||||
using KSchedulerPriorityQueue =
|
||||
KPriorityQueue<Thread, Core::Hardware::NUM_CPU_CORES, THREADPRIO_LOWEST, THREADPRIO_HIGHEST>;
|
||||
constexpr s32 HighestCoreMigrationAllowedPriority = 2;
|
||||
|
||||
class GlobalSchedulerContext final {
|
||||
friend class KScheduler;
|
||||
|
||||
public:
|
||||
using LockType = KAbstractSchedulerLock<KScheduler>;
|
||||
|
||||
explicit GlobalSchedulerContext(KernelCore& kernel);
|
||||
~GlobalSchedulerContext();
|
||||
|
||||
/// Adds a new thread to the scheduler
|
||||
void AddThread(std::shared_ptr<Thread> thread);
|
||||
|
||||
/// Removes a thread from the scheduler
|
||||
void RemoveThread(std::shared_ptr<Thread> thread);
|
||||
|
||||
/// Returns a list of all threads managed by the scheduler
|
||||
[[nodiscard]] const std::vector<std::shared_ptr<Thread>>& GetThreadList() const {
|
||||
return thread_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the scheduling queues of threads at a preemption priority and then does
|
||||
* some core rebalancing. Preemption priorities can be found in the array
|
||||
* 'preemption_priorities'.
|
||||
*
|
||||
* @note This operation happens every 10ms.
|
||||
*/
|
||||
void PreemptThreads();
|
||||
|
||||
/// Returns true if the global scheduler lock is acquired
|
||||
bool IsLocked() const;
|
||||
|
||||
[[nodiscard]] LockType& SchedulerLock() {
|
||||
return scheduler_lock;
|
||||
}
|
||||
|
||||
[[nodiscard]] const LockType& SchedulerLock() const {
|
||||
return scheduler_lock;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class KScopedSchedulerLock;
|
||||
friend class KScopedSchedulerLockAndSleep;
|
||||
|
||||
KernelCore& kernel;
|
||||
|
||||
std::atomic_bool scheduler_update_needed{};
|
||||
KSchedulerPriorityQueue priority_queue;
|
||||
LockType scheduler_lock;
|
||||
|
||||
/// Lists all thread ids that aren't deleted/etc.
|
||||
std::vector<std::shared_ptr<Thread>> thread_list;
|
||||
Common::SpinLock global_list_guard{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -8,9 +8,9 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace Kernel {
|
||||
@@ -105,7 +105,7 @@ bool HandleTable::IsValid(Handle handle) const {
|
||||
|
||||
std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
|
||||
if (handle == CurrentThread) {
|
||||
return SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
|
||||
return SharedFrom(kernel.CurrentScheduler()->GetCurrentThread());
|
||||
} else if (handle == CurrentProcess) {
|
||||
return SharedFrom(kernel.CurrentProcess());
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/readable_event.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/time_manager.h"
|
||||
@@ -56,9 +57,9 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
|
||||
writable_event = pair.writable;
|
||||
}
|
||||
|
||||
Handle event_handle = InvalidHandle;
|
||||
{
|
||||
Handle event_handle = InvalidHandle;
|
||||
SchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout);
|
||||
KScopedSchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout);
|
||||
thread->SetHLECallback(
|
||||
[context = *this, callback](std::shared_ptr<Thread> thread) mutable -> bool {
|
||||
ThreadWakeupReason reason = thread->GetSignalingResult() == RESULT_TIMEOUT
|
||||
@@ -74,9 +75,8 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
|
||||
thread->SetStatus(ThreadStatus::WaitHLEEvent);
|
||||
thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
|
||||
readable_event->AddWaitingThread(thread);
|
||||
lock.Release();
|
||||
thread->SetHLETimeEvent(event_handle);
|
||||
}
|
||||
thread->SetHLETimeEvent(event_handle);
|
||||
|
||||
is_thread_waiting = true;
|
||||
|
||||
|
||||
58
src/core/hle/kernel/k_affinity_mask.h
Normal file
58
src/core/hle/kernel/k_affinity_mask.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// This file references various implementation details from Atmosphere, an open-source firmware for
|
||||
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hardware_properties.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KAffinityMask {
|
||||
public:
|
||||
constexpr KAffinityMask() = default;
|
||||
|
||||
[[nodiscard]] constexpr u64 GetAffinityMask() const {
|
||||
return this->mask;
|
||||
}
|
||||
|
||||
constexpr void SetAffinityMask(u64 new_mask) {
|
||||
ASSERT((new_mask & ~AllowedAffinityMask) == 0);
|
||||
this->mask = new_mask;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool GetAffinity(s32 core) const {
|
||||
return this->mask & GetCoreBit(core);
|
||||
}
|
||||
|
||||
constexpr void SetAffinity(s32 core, bool set) {
|
||||
ASSERT(0 <= core && core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
|
||||
|
||||
if (set) {
|
||||
this->mask |= GetCoreBit(core);
|
||||
} else {
|
||||
this->mask &= ~GetCoreBit(core);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void SetAll() {
|
||||
this->mask = AllowedAffinityMask;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] static constexpr u64 GetCoreBit(s32 core) {
|
||||
ASSERT(0 <= core && core < static_cast<s32>(Core::Hardware::NUM_CPU_CORES));
|
||||
return (1ULL << core);
|
||||
}
|
||||
|
||||
static constexpr u64 AllowedAffinityMask = (1ULL << Core::Hardware::NUM_CPU_CORES) - 1;
|
||||
|
||||
u64 mask{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
451
src/core/hle/kernel/k_priority_queue.h
Normal file
451
src/core/hle/kernel/k_priority_queue.h
Normal file
@@ -0,0 +1,451 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// This file references various implementation details from Atmosphere, an open-source firmware for
|
||||
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_set.h"
|
||||
#include "common/bit_util.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/concepts.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class Thread;
|
||||
|
||||
template <typename T>
|
||||
concept KPriorityQueueAffinityMask = !std::is_reference_v<T> && requires(T & t) {
|
||||
{ t.GetAffinityMask() }
|
||||
->Common::ConvertibleTo<u64>;
|
||||
{t.SetAffinityMask(std::declval<u64>())};
|
||||
|
||||
{ t.GetAffinity(std::declval<int32_t>()) }
|
||||
->std::same_as<bool>;
|
||||
{t.SetAffinity(std::declval<int32_t>(), std::declval<bool>())};
|
||||
{t.SetAll()};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept KPriorityQueueMember = !std::is_reference_v<T> && requires(T & t) {
|
||||
{typename T::QueueEntry()};
|
||||
{(typename T::QueueEntry()).Initialize()};
|
||||
{(typename T::QueueEntry()).SetPrev(std::addressof(t))};
|
||||
{(typename T::QueueEntry()).SetNext(std::addressof(t))};
|
||||
{ (typename T::QueueEntry()).GetNext() }
|
||||
->std::same_as<T*>;
|
||||
{ (typename T::QueueEntry()).GetPrev() }
|
||||
->std::same_as<T*>;
|
||||
{ t.GetPriorityQueueEntry(std::declval<s32>()) }
|
||||
->std::same_as<typename T::QueueEntry&>;
|
||||
|
||||
{t.GetAffinityMask()};
|
||||
{ typename std::remove_cvref<decltype(t.GetAffinityMask())>::type() }
|
||||
->KPriorityQueueAffinityMask;
|
||||
|
||||
{ t.GetActiveCore() }
|
||||
->Common::ConvertibleTo<s32>;
|
||||
{ t.GetPriority() }
|
||||
->Common::ConvertibleTo<s32>;
|
||||
};
|
||||
|
||||
template <typename Member, size_t _NumCores, int LowestPriority, int HighestPriority>
|
||||
requires KPriorityQueueMember<Member> class KPriorityQueue {
|
||||
public:
|
||||
using AffinityMaskType = typename std::remove_cv_t<
|
||||
typename std::remove_reference<decltype(std::declval<Member>().GetAffinityMask())>::type>;
|
||||
|
||||
static_assert(LowestPriority >= 0);
|
||||
static_assert(HighestPriority >= 0);
|
||||
static_assert(LowestPriority >= HighestPriority);
|
||||
static constexpr size_t NumPriority = LowestPriority - HighestPriority + 1;
|
||||
static constexpr size_t NumCores = _NumCores;
|
||||
|
||||
static constexpr bool IsValidCore(s32 core) {
|
||||
return 0 <= core && core < static_cast<s32>(NumCores);
|
||||
}
|
||||
|
||||
static constexpr bool IsValidPriority(s32 priority) {
|
||||
return HighestPriority <= priority && priority <= LowestPriority + 1;
|
||||
}
|
||||
|
||||
private:
|
||||
using Entry = typename Member::QueueEntry;
|
||||
|
||||
public:
|
||||
class KPerCoreQueue {
|
||||
private:
|
||||
std::array<Entry, NumCores> root{};
|
||||
|
||||
public:
|
||||
constexpr KPerCoreQueue() {
|
||||
for (auto& per_core_root : root) {
|
||||
per_core_root.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool PushBack(s32 core, Member* member) {
|
||||
// Get the entry associated with the member.
|
||||
Entry& member_entry = member->GetPriorityQueueEntry(core);
|
||||
|
||||
// Get the entry associated with the end of the queue.
|
||||
Member* tail = this->root[core].GetPrev();
|
||||
Entry& tail_entry =
|
||||
(tail != nullptr) ? tail->GetPriorityQueueEntry(core) : this->root[core];
|
||||
|
||||
// Link the entries.
|
||||
member_entry.SetPrev(tail);
|
||||
member_entry.SetNext(nullptr);
|
||||
tail_entry.SetNext(member);
|
||||
this->root[core].SetPrev(member);
|
||||
|
||||
return tail == nullptr;
|
||||
}
|
||||
|
||||
constexpr bool PushFront(s32 core, Member* member) {
|
||||
// Get the entry associated with the member.
|
||||
Entry& member_entry = member->GetPriorityQueueEntry(core);
|
||||
|
||||
// Get the entry associated with the front of the queue.
|
||||
Member* head = this->root[core].GetNext();
|
||||
Entry& head_entry =
|
||||
(head != nullptr) ? head->GetPriorityQueueEntry(core) : this->root[core];
|
||||
|
||||
// Link the entries.
|
||||
member_entry.SetPrev(nullptr);
|
||||
member_entry.SetNext(head);
|
||||
head_entry.SetPrev(member);
|
||||
this->root[core].SetNext(member);
|
||||
|
||||
return (head == nullptr);
|
||||
}
|
||||
|
||||
constexpr bool Remove(s32 core, Member* member) {
|
||||
// Get the entry associated with the member.
|
||||
Entry& member_entry = member->GetPriorityQueueEntry(core);
|
||||
|
||||
// Get the entries associated with next and prev.
|
||||
Member* prev = member_entry.GetPrev();
|
||||
Member* next = member_entry.GetNext();
|
||||
Entry& prev_entry =
|
||||
(prev != nullptr) ? prev->GetPriorityQueueEntry(core) : this->root[core];
|
||||
Entry& next_entry =
|
||||
(next != nullptr) ? next->GetPriorityQueueEntry(core) : this->root[core];
|
||||
|
||||
// Unlink.
|
||||
prev_entry.SetNext(next);
|
||||
next_entry.SetPrev(prev);
|
||||
|
||||
return (this->GetFront(core) == nullptr);
|
||||
}
|
||||
|
||||
constexpr Member* GetFront(s32 core) const {
|
||||
return this->root[core].GetNext();
|
||||
}
|
||||
};
|
||||
|
||||
class KPriorityQueueImpl {
|
||||
public:
|
||||
constexpr KPriorityQueueImpl() = default;
|
||||
|
||||
constexpr void PushBack(s32 priority, s32 core, Member* member) {
|
||||
ASSERT(IsValidCore(core));
|
||||
ASSERT(IsValidPriority(priority));
|
||||
|
||||
if (priority > LowestPriority) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->queues[priority].PushBack(core, member)) {
|
||||
this->available_priorities[core].SetBit(priority);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void PushFront(s32 priority, s32 core, Member* member) {
|
||||
ASSERT(IsValidCore(core));
|
||||
ASSERT(IsValidPriority(priority));
|
||||
|
||||
if (priority > LowestPriority) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->queues[priority].PushFront(core, member)) {
|
||||
this->available_priorities[core].SetBit(priority);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void Remove(s32 priority, s32 core, Member* member) {
|
||||
ASSERT(IsValidCore(core));
|
||||
ASSERT(IsValidPriority(priority));
|
||||
|
||||
if (priority > LowestPriority) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->queues[priority].Remove(core, member)) {
|
||||
this->available_priorities[core].ClearBit(priority);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Member* GetFront(s32 core) const {
|
||||
ASSERT(IsValidCore(core));
|
||||
|
||||
const s32 priority =
|
||||
static_cast<s32>(this->available_priorities[core].CountLeadingZero());
|
||||
if (priority <= LowestPriority) {
|
||||
return this->queues[priority].GetFront(core);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Member* GetFront(s32 priority, s32 core) const {
|
||||
ASSERT(IsValidCore(core));
|
||||
ASSERT(IsValidPriority(priority));
|
||||
|
||||
if (priority <= LowestPriority) {
|
||||
return this->queues[priority].GetFront(core);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Member* GetNext(s32 core, const Member* member) const {
|
||||
ASSERT(IsValidCore(core));
|
||||
|
||||
Member* next = member->GetPriorityQueueEntry(core).GetNext();
|
||||
if (next == nullptr) {
|
||||
const s32 priority = static_cast<s32>(
|
||||
this->available_priorities[core].GetNextSet(member->GetPriority()));
|
||||
if (priority <= LowestPriority) {
|
||||
next = this->queues[priority].GetFront(core);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
constexpr void MoveToFront(s32 priority, s32 core, Member* member) {
|
||||
ASSERT(IsValidCore(core));
|
||||
ASSERT(IsValidPriority(priority));
|
||||
|
||||
if (priority <= LowestPriority) {
|
||||
this->queues[priority].Remove(core, member);
|
||||
this->queues[priority].PushFront(core, member);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr Member* MoveToBack(s32 priority, s32 core, Member* member) {
|
||||
ASSERT(IsValidCore(core));
|
||||
ASSERT(IsValidPriority(priority));
|
||||
|
||||
if (priority <= LowestPriority) {
|
||||
this->queues[priority].Remove(core, member);
|
||||
this->queues[priority].PushBack(core, member);
|
||||
return this->queues[priority].GetFront(core);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<KPerCoreQueue, NumPriority> queues{};
|
||||
std::array<Common::BitSet64<NumPriority>, NumCores> available_priorities{};
|
||||
};
|
||||
|
||||
private:
|
||||
KPriorityQueueImpl scheduled_queue;
|
||||
KPriorityQueueImpl suggested_queue;
|
||||
|
||||
private:
|
||||
constexpr void ClearAffinityBit(u64& affinity, s32 core) {
|
||||
affinity &= ~(u64(1) << core);
|
||||
}
|
||||
|
||||
constexpr s32 GetNextCore(u64& affinity) {
|
||||
const s32 core = Common::CountTrailingZeroes64(affinity);
|
||||
ClearAffinityBit(affinity, core);
|
||||
return core;
|
||||
}
|
||||
|
||||
constexpr void PushBack(s32 priority, Member* member) {
|
||||
ASSERT(IsValidPriority(priority));
|
||||
|
||||
// Push onto the scheduled queue for its core, if we can.
|
||||
u64 affinity = member->GetAffinityMask().GetAffinityMask();
|
||||
if (const s32 core = member->GetActiveCore(); core >= 0) {
|
||||
this->scheduled_queue.PushBack(priority, core, member);
|
||||
ClearAffinityBit(affinity, core);
|
||||
}
|
||||
|
||||
// And suggest the thread for all other cores.
|
||||
while (affinity) {
|
||||
this->suggested_queue.PushBack(priority, GetNextCore(affinity), member);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void PushFront(s32 priority, Member* member) {
|
||||
ASSERT(IsValidPriority(priority));
|
||||
|
||||
// Push onto the scheduled queue for its core, if we can.
|
||||
u64 affinity = member->GetAffinityMask().GetAffinityMask();
|
||||
if (const s32 core = member->GetActiveCore(); core >= 0) {
|
||||
this->scheduled_queue.PushFront(priority, core, member);
|
||||
ClearAffinityBit(affinity, core);
|
||||
}
|
||||
|
||||
// And suggest the thread for all other cores.
|
||||
// Note: Nintendo pushes onto the back of the suggested queue, not the front.
|
||||
while (affinity) {
|
||||
this->suggested_queue.PushBack(priority, GetNextCore(affinity), member);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void Remove(s32 priority, Member* member) {
|
||||
ASSERT(IsValidPriority(priority));
|
||||
|
||||
// Remove from the scheduled queue for its core.
|
||||
u64 affinity = member->GetAffinityMask().GetAffinityMask();
|
||||
if (const s32 core = member->GetActiveCore(); core >= 0) {
|
||||
this->scheduled_queue.Remove(priority, core, member);
|
||||
ClearAffinityBit(affinity, core);
|
||||
}
|
||||
|
||||
// Remove from the suggested queue for all other cores.
|
||||
while (affinity) {
|
||||
this->suggested_queue.Remove(priority, GetNextCore(affinity), member);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr KPriorityQueue() = default;
|
||||
|
||||
// Getters.
|
||||
constexpr Member* GetScheduledFront(s32 core) const {
|
||||
return this->scheduled_queue.GetFront(core);
|
||||
}
|
||||
|
||||
constexpr Member* GetScheduledFront(s32 core, s32 priority) const {
|
||||
return this->scheduled_queue.GetFront(priority, core);
|
||||
}
|
||||
|
||||
constexpr Member* GetSuggestedFront(s32 core) const {
|
||||
return this->suggested_queue.GetFront(core);
|
||||
}
|
||||
|
||||
constexpr Member* GetSuggestedFront(s32 core, s32 priority) const {
|
||||
return this->suggested_queue.GetFront(priority, core);
|
||||
}
|
||||
|
||||
constexpr Member* GetScheduledNext(s32 core, const Member* member) const {
|
||||
return this->scheduled_queue.GetNext(core, member);
|
||||
}
|
||||
|
||||
constexpr Member* GetSuggestedNext(s32 core, const Member* member) const {
|
||||
return this->suggested_queue.GetNext(core, member);
|
||||
}
|
||||
|
||||
constexpr Member* GetSamePriorityNext(s32 core, const Member* member) const {
|
||||
return member->GetPriorityQueueEntry(core).GetNext();
|
||||
}
|
||||
|
||||
// Mutators.
|
||||
constexpr void PushBack(Member* member) {
|
||||
this->PushBack(member->GetPriority(), member);
|
||||
}
|
||||
|
||||
constexpr void Remove(Member* member) {
|
||||
this->Remove(member->GetPriority(), member);
|
||||
}
|
||||
|
||||
constexpr void MoveToScheduledFront(Member* member) {
|
||||
this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member);
|
||||
}
|
||||
|
||||
constexpr Thread* MoveToScheduledBack(Member* member) {
|
||||
return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(),
|
||||
member);
|
||||
}
|
||||
|
||||
// First class fancy operations.
|
||||
constexpr void ChangePriority(s32 prev_priority, bool is_running, Member* member) {
|
||||
ASSERT(IsValidPriority(prev_priority));
|
||||
|
||||
// Remove the member from the queues.
|
||||
const s32 new_priority = member->GetPriority();
|
||||
this->Remove(prev_priority, member);
|
||||
|
||||
// And enqueue. If the member is running, we want to keep it running.
|
||||
if (is_running) {
|
||||
this->PushFront(new_priority, member);
|
||||
} else {
|
||||
this->PushBack(new_priority, member);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void ChangeAffinityMask(s32 prev_core, const AffinityMaskType& prev_affinity,
|
||||
Member* member) {
|
||||
// Get the new information.
|
||||
const s32 priority = member->GetPriority();
|
||||
const AffinityMaskType& new_affinity = member->GetAffinityMask();
|
||||
const s32 new_core = member->GetActiveCore();
|
||||
|
||||
// Remove the member from all queues it was in before.
|
||||
for (s32 core = 0; core < static_cast<s32>(NumCores); core++) {
|
||||
if (prev_affinity.GetAffinity(core)) {
|
||||
if (core == prev_core) {
|
||||
this->scheduled_queue.Remove(priority, core, member);
|
||||
} else {
|
||||
this->suggested_queue.Remove(priority, core, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// And add the member to all queues it should be in now.
|
||||
for (s32 core = 0; core < static_cast<s32>(NumCores); core++) {
|
||||
if (new_affinity.GetAffinity(core)) {
|
||||
if (core == new_core) {
|
||||
this->scheduled_queue.PushBack(priority, core, member);
|
||||
} else {
|
||||
this->suggested_queue.PushBack(priority, core, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void ChangeCore(s32 prev_core, Member* member, bool to_front = false) {
|
||||
// Get the new information.
|
||||
const s32 new_core = member->GetActiveCore();
|
||||
const s32 priority = member->GetPriority();
|
||||
|
||||
// We don't need to do anything if the core is the same.
|
||||
if (prev_core != new_core) {
|
||||
// Remove from the scheduled queue for the previous core.
|
||||
if (prev_core >= 0) {
|
||||
this->scheduled_queue.Remove(priority, prev_core, member);
|
||||
}
|
||||
|
||||
// Remove from the suggested queue and add to the scheduled queue for the new core.
|
||||
if (new_core >= 0) {
|
||||
this->suggested_queue.Remove(priority, new_core, member);
|
||||
if (to_front) {
|
||||
this->scheduled_queue.PushFront(priority, new_core, member);
|
||||
} else {
|
||||
this->scheduled_queue.PushBack(priority, new_core, member);
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the suggested queue for the previous core.
|
||||
if (prev_core >= 0) {
|
||||
this->suggested_queue.PushBack(priority, prev_core, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
784
src/core/hle/kernel/k_scheduler.cpp
Normal file
784
src/core/hle/kernel/k_scheduler.cpp
Normal file
@@ -0,0 +1,784 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// This file references various implementation details from Atmosphere, an open-source firmware for
|
||||
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_util.h"
|
||||
#include "common/fiber.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/time_manager.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
static void IncrementScheduledCount(Kernel::Thread* thread) {
|
||||
if (auto process = thread->GetOwnerProcess(); process) {
|
||||
process->IncrementScheduledCount();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule,
|
||||
Core::EmuThreadHandle global_thread) {
|
||||
u32 current_core = global_thread.host_handle;
|
||||
bool must_context_switch = global_thread.guest_handle != InvalidHandle &&
|
||||
(current_core < Core::Hardware::NUM_CPU_CORES);
|
||||
|
||||
while (cores_pending_reschedule != 0) {
|
||||
u32 core = Common::CountTrailingZeroes64(cores_pending_reschedule);
|
||||
ASSERT(core < Core::Hardware::NUM_CPU_CORES);
|
||||
if (!must_context_switch || core != current_core) {
|
||||
auto& phys_core = kernel.PhysicalCore(core);
|
||||
phys_core.Interrupt();
|
||||
} else {
|
||||
must_context_switch = true;
|
||||
}
|
||||
cores_pending_reschedule &= ~(1ULL << core);
|
||||
}
|
||||
if (must_context_switch) {
|
||||
auto core_scheduler = kernel.CurrentScheduler();
|
||||
kernel.ExitSVCProfile();
|
||||
core_scheduler->RescheduleCurrentCore();
|
||||
kernel.EnterSVCProfile();
|
||||
}
|
||||
}
|
||||
|
||||
u64 KScheduler::UpdateHighestPriorityThread(Thread* highest_thread) {
|
||||
std::scoped_lock lock{guard};
|
||||
if (Thread* prev_highest_thread = this->state.highest_priority_thread;
|
||||
prev_highest_thread != highest_thread) {
|
||||
if (prev_highest_thread != nullptr) {
|
||||
IncrementScheduledCount(prev_highest_thread);
|
||||
prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks());
|
||||
}
|
||||
if (this->state.should_count_idle) {
|
||||
if (highest_thread != nullptr) {
|
||||
// if (Process* process = highest_thread->GetOwnerProcess(); process != nullptr) {
|
||||
// process->SetRunningThread(this->core_id, highest_thread,
|
||||
// this->state.idle_count);
|
||||
//}
|
||||
} else {
|
||||
this->state.idle_count++;
|
||||
}
|
||||
}
|
||||
|
||||
this->state.highest_priority_thread = highest_thread;
|
||||
this->state.needs_scheduling = true;
|
||||
return (1ULL << this->core_id);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// Clear that we need to update.
|
||||
ClearSchedulerUpdateNeeded(kernel);
|
||||
|
||||
u64 cores_needing_scheduling = 0, idle_cores = 0;
|
||||
Thread* top_threads[Core::Hardware::NUM_CPU_CORES];
|
||||
auto& priority_queue = GetPriorityQueue(kernel);
|
||||
|
||||
/// We want to go over all cores, finding the highest priority thread and determining if
|
||||
/// scheduling is needed for that core.
|
||||
for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||
Thread* top_thread = priority_queue.GetScheduledFront(static_cast<s32>(core_id));
|
||||
if (top_thread != nullptr) {
|
||||
// If the thread has no waiters, we need to check if the process has a thread pinned.
|
||||
// TODO(bunnei): Implement thread pinning
|
||||
} else {
|
||||
idle_cores |= (1ULL << core_id);
|
||||
}
|
||||
|
||||
top_threads[core_id] = top_thread;
|
||||
cores_needing_scheduling |=
|
||||
kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
|
||||
}
|
||||
|
||||
// Idle cores are bad. We're going to try to migrate threads to each idle core in turn.
|
||||
while (idle_cores != 0) {
|
||||
u32 core_id = Common::CountTrailingZeroes64(idle_cores);
|
||||
if (Thread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) {
|
||||
s32 migration_candidates[Core::Hardware::NUM_CPU_CORES];
|
||||
size_t num_candidates = 0;
|
||||
|
||||
// While we have a suggested thread, try to migrate it!
|
||||
while (suggested != nullptr) {
|
||||
// Check if the suggested thread is the top thread on its core.
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
if (Thread* top_thread =
|
||||
(suggested_core >= 0) ? top_threads[suggested_core] : nullptr;
|
||||
top_thread != suggested) {
|
||||
// Make sure we're not dealing with threads too high priority for migration.
|
||||
if (top_thread != nullptr &&
|
||||
top_thread->GetPriority() < HighestCoreMigrationAllowedPriority) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The suggested thread isn't bound to its core, so we can migrate it!
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested);
|
||||
|
||||
top_threads[core_id] = suggested;
|
||||
cores_needing_scheduling |=
|
||||
kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]);
|
||||
break;
|
||||
}
|
||||
|
||||
// Note this core as a candidate for migration.
|
||||
ASSERT(num_candidates < Core::Hardware::NUM_CPU_CORES);
|
||||
migration_candidates[num_candidates++] = suggested_core;
|
||||
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
|
||||
}
|
||||
|
||||
// If suggested is nullptr, we failed to migrate a specific thread. So let's try all our
|
||||
// candidate cores' top threads.
|
||||
if (suggested == nullptr) {
|
||||
for (size_t i = 0; i < num_candidates; i++) {
|
||||
// Check if there's some other thread that can run on the candidate core.
|
||||
const s32 candidate_core = migration_candidates[i];
|
||||
suggested = top_threads[candidate_core];
|
||||
if (Thread* next_on_candidate_core =
|
||||
priority_queue.GetScheduledNext(candidate_core, suggested);
|
||||
next_on_candidate_core != nullptr) {
|
||||
// The candidate core can run some other thread! We'll migrate its current
|
||||
// top thread to us.
|
||||
top_threads[candidate_core] = next_on_candidate_core;
|
||||
cores_needing_scheduling |=
|
||||
kernel.Scheduler(candidate_core)
|
||||
.UpdateHighestPriorityThread(top_threads[candidate_core]);
|
||||
|
||||
// Perform the migration.
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(candidate_core, suggested);
|
||||
|
||||
top_threads[core_id] = suggested;
|
||||
cores_needing_scheduling |=
|
||||
kernel.Scheduler(core_id).UpdateHighestPriorityThread(
|
||||
top_threads[core_id]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idle_cores &= ~(1ULL << core_id);
|
||||
}
|
||||
|
||||
return cores_needing_scheduling;
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadStateChanged(KernelCore& kernel, Thread* thread, u32 old_state) {
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// Check if the state has changed, because if it hasn't there's nothing to do.
|
||||
const auto cur_state = thread->scheduling_state;
|
||||
if (cur_state == old_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the priority queues.
|
||||
if (old_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
// If we were previously runnable, then we're not runnable now, and we should remove.
|
||||
GetPriorityQueue(kernel).Remove(thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
} else if (cur_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
// If we're now runnable, then we weren't previously, and we should add.
|
||||
GetPriorityQueue(kernel).PushBack(thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, Thread* thread, Thread* current_thread,
|
||||
u32 old_priority) {
|
||||
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// If the thread is runnable, we want to change its priority in the queue.
|
||||
if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
GetPriorityQueue(kernel).ChangePriority(
|
||||
old_priority, thread == kernel.CurrentScheduler()->GetCurrentThread(), thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, Thread* thread,
|
||||
const KAffinityMask& old_affinity, s32 old_core) {
|
||||
ASSERT(kernel.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// If the thread is runnable, we want to change its affinity in the queue.
|
||||
if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
GetPriorityQueue(kernel).ChangeAffinityMask(old_core, old_affinity, thread);
|
||||
IncrementScheduledCount(thread);
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::RotateScheduledQueue(s32 core_id, s32 priority) {
|
||||
ASSERT(system.GlobalSchedulerContext().IsLocked());
|
||||
|
||||
// Get a reference to the priority queue.
|
||||
auto& kernel = system.Kernel();
|
||||
auto& priority_queue = GetPriorityQueue(kernel);
|
||||
|
||||
// Rotate the front of the queue to the end.
|
||||
Thread* top_thread = priority_queue.GetScheduledFront(core_id, priority);
|
||||
Thread* next_thread = nullptr;
|
||||
if (top_thread != nullptr) {
|
||||
next_thread = priority_queue.MoveToScheduledBack(top_thread);
|
||||
if (next_thread != top_thread) {
|
||||
IncrementScheduledCount(top_thread);
|
||||
IncrementScheduledCount(next_thread);
|
||||
}
|
||||
}
|
||||
|
||||
// While we have a suggested thread, try to migrate it!
|
||||
{
|
||||
Thread* suggested = priority_queue.GetSuggestedFront(core_id, priority);
|
||||
while (suggested != nullptr) {
|
||||
// Check if the suggested thread is the top thread on its core.
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
if (Thread* top_on_suggested_core =
|
||||
(suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core)
|
||||
: nullptr;
|
||||
top_on_suggested_core != suggested) {
|
||||
// If the next thread is a new thread that has been waiting longer than our
|
||||
// suggestion, we prefer it to our suggestion.
|
||||
if (top_thread != next_thread && next_thread != nullptr &&
|
||||
next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick()) {
|
||||
suggested = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we're allowed to do a migration, do one.
|
||||
// NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion
|
||||
// to the front of the queue.
|
||||
if (top_on_suggested_core == nullptr ||
|
||||
top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested, true);
|
||||
IncrementScheduledCount(suggested);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the next suggestion.
|
||||
suggested = priority_queue.GetSamePriorityNext(core_id, suggested);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we might have migrated a thread with the same priority, check if we can do better.
|
||||
|
||||
{
|
||||
Thread* best_thread = priority_queue.GetScheduledFront(core_id);
|
||||
if (best_thread == GetCurrentThread()) {
|
||||
best_thread = priority_queue.GetScheduledNext(core_id, best_thread);
|
||||
}
|
||||
|
||||
// If the best thread we can choose has a priority the same or worse than ours, try to
|
||||
// migrate a higher priority thread.
|
||||
if (best_thread != nullptr && best_thread->GetPriority() >= static_cast<u32>(priority)) {
|
||||
Thread* suggested = priority_queue.GetSuggestedFront(core_id);
|
||||
while (suggested != nullptr) {
|
||||
// If the suggestion's priority is the same as ours, don't bother.
|
||||
if (suggested->GetPriority() >= best_thread->GetPriority()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the suggested thread is the top thread on its core.
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
if (Thread* top_on_suggested_core =
|
||||
(suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core)
|
||||
: nullptr;
|
||||
top_on_suggested_core != suggested) {
|
||||
// If we're allowed to do a migration, do one.
|
||||
// NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the
|
||||
// suggestion to the front of the queue.
|
||||
if (top_on_suggested_core == nullptr ||
|
||||
top_on_suggested_core->GetPriority() >=
|
||||
HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested, true);
|
||||
IncrementScheduledCount(suggested);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the next suggestion.
|
||||
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After a rotation, we need a scheduler update.
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
|
||||
bool KScheduler::CanSchedule(KernelCore& kernel) {
|
||||
return kernel.CurrentScheduler()->GetCurrentThread()->GetDisableDispatchCount() <= 1;
|
||||
}
|
||||
|
||||
bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) {
|
||||
return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) {
|
||||
kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) {
|
||||
kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void KScheduler::DisableScheduling(KernelCore& kernel) {
|
||||
if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
|
||||
ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 0);
|
||||
scheduler->GetCurrentThread()->DisableDispatch();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling,
|
||||
Core::EmuThreadHandle global_thread) {
|
||||
if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
|
||||
scheduler->GetCurrentThread()->EnableDispatch();
|
||||
}
|
||||
RescheduleCores(kernel, cores_needing_scheduling, global_thread);
|
||||
}
|
||||
|
||||
u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
|
||||
if (IsSchedulerUpdateNeeded(kernel)) {
|
||||
return UpdateHighestPriorityThreadsImpl(kernel);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) {
|
||||
return kernel.GlobalSchedulerContext().priority_queue;
|
||||
}
|
||||
|
||||
void KScheduler::YieldWithoutCoreMigration() {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
// Validate preconditions.
|
||||
ASSERT(CanSchedule(kernel));
|
||||
ASSERT(kernel.CurrentProcess() != nullptr);
|
||||
|
||||
// Get the current thread and process.
|
||||
Thread& cur_thread = *GetCurrentThread();
|
||||
Process& cur_process = *kernel.CurrentProcess();
|
||||
|
||||
// If the thread's yield count matches, there's nothing for us to do.
|
||||
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a reference to the priority queue.
|
||||
auto& priority_queue = GetPriorityQueue(kernel);
|
||||
|
||||
// Perform the yield.
|
||||
{
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
const auto cur_state = cur_thread.scheduling_state;
|
||||
if (cur_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
// Put the current thread at the back of the queue.
|
||||
Thread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread));
|
||||
IncrementScheduledCount(std::addressof(cur_thread));
|
||||
|
||||
// If the next thread is different, we have an update to perform.
|
||||
if (next_thread != std::addressof(cur_thread)) {
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
} else {
|
||||
// Otherwise, set the thread's yield count so that we won't waste work until the
|
||||
// process is scheduled again.
|
||||
cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::YieldWithCoreMigration() {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
// Validate preconditions.
|
||||
ASSERT(CanSchedule(kernel));
|
||||
ASSERT(kernel.CurrentProcess() != nullptr);
|
||||
|
||||
// Get the current thread and process.
|
||||
Thread& cur_thread = *GetCurrentThread();
|
||||
Process& cur_process = *kernel.CurrentProcess();
|
||||
|
||||
// If the thread's yield count matches, there's nothing for us to do.
|
||||
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a reference to the priority queue.
|
||||
auto& priority_queue = GetPriorityQueue(kernel);
|
||||
|
||||
// Perform the yield.
|
||||
{
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
const auto cur_state = cur_thread.scheduling_state;
|
||||
if (cur_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
// Get the current active core.
|
||||
const s32 core_id = cur_thread.GetActiveCore();
|
||||
|
||||
// Put the current thread at the back of the queue.
|
||||
Thread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread));
|
||||
IncrementScheduledCount(std::addressof(cur_thread));
|
||||
|
||||
// While we have a suggested thread, try to migrate it!
|
||||
bool recheck = false;
|
||||
Thread* suggested = priority_queue.GetSuggestedFront(core_id);
|
||||
while (suggested != nullptr) {
|
||||
// Check if the suggested thread is the thread running on its core.
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
|
||||
if (Thread* running_on_suggested_core =
|
||||
(suggested_core >= 0)
|
||||
? kernel.Scheduler(suggested_core).state.highest_priority_thread
|
||||
: nullptr;
|
||||
running_on_suggested_core != suggested) {
|
||||
// If the current thread's priority is higher than our suggestion's we prefer
|
||||
// the next thread to the suggestion. We also prefer the next thread when the
|
||||
// current thread's priority is equal to the suggestions, but the next thread
|
||||
// has been waiting longer.
|
||||
if ((suggested->GetPriority() > cur_thread.GetPriority()) ||
|
||||
(suggested->GetPriority() == cur_thread.GetPriority() &&
|
||||
next_thread != std::addressof(cur_thread) &&
|
||||
next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick())) {
|
||||
suggested = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we're allowed to do a migration, do one.
|
||||
// NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the
|
||||
// suggestion to the front of the queue.
|
||||
if (running_on_suggested_core == nullptr ||
|
||||
running_on_suggested_core->GetPriority() >=
|
||||
HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested, true);
|
||||
IncrementScheduledCount(suggested);
|
||||
break;
|
||||
} else {
|
||||
// We couldn't perform a migration, but we should check again on a future
|
||||
// yield.
|
||||
recheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the next suggestion.
|
||||
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
|
||||
}
|
||||
|
||||
// If we still have a suggestion or the next thread is different, we have an update to
|
||||
// perform.
|
||||
if (suggested != nullptr || next_thread != std::addressof(cur_thread)) {
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
} else if (!recheck) {
|
||||
// Otherwise if we don't need to re-check, set the thread's yield count so that we
|
||||
// won't waste work until the process is scheduled again.
|
||||
cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::YieldToAnyThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
// Validate preconditions.
|
||||
ASSERT(CanSchedule(kernel));
|
||||
ASSERT(kernel.CurrentProcess() != nullptr);
|
||||
|
||||
// Get the current thread and process.
|
||||
Thread& cur_thread = *GetCurrentThread();
|
||||
Process& cur_process = *kernel.CurrentProcess();
|
||||
|
||||
// If the thread's yield count matches, there's nothing for us to do.
|
||||
if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a reference to the priority queue.
|
||||
auto& priority_queue = GetPriorityQueue(kernel);
|
||||
|
||||
// Perform the yield.
|
||||
{
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
const auto cur_state = cur_thread.scheduling_state;
|
||||
if (cur_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
// Get the current active core.
|
||||
const s32 core_id = cur_thread.GetActiveCore();
|
||||
|
||||
// Migrate the current thread to core -1.
|
||||
cur_thread.SetActiveCore(-1);
|
||||
priority_queue.ChangeCore(core_id, std::addressof(cur_thread));
|
||||
IncrementScheduledCount(std::addressof(cur_thread));
|
||||
|
||||
// If there's nothing scheduled, we can try to perform a migration.
|
||||
if (priority_queue.GetScheduledFront(core_id) == nullptr) {
|
||||
// While we have a suggested thread, try to migrate it!
|
||||
Thread* suggested = priority_queue.GetSuggestedFront(core_id);
|
||||
while (suggested != nullptr) {
|
||||
// Check if the suggested thread is the top thread on its core.
|
||||
const s32 suggested_core = suggested->GetActiveCore();
|
||||
if (Thread* top_on_suggested_core =
|
||||
(suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core)
|
||||
: nullptr;
|
||||
top_on_suggested_core != suggested) {
|
||||
// If we're allowed to do a migration, do one.
|
||||
if (top_on_suggested_core == nullptr ||
|
||||
top_on_suggested_core->GetPriority() >=
|
||||
HighestCoreMigrationAllowedPriority) {
|
||||
suggested->SetActiveCore(core_id);
|
||||
priority_queue.ChangeCore(suggested_core, suggested);
|
||||
IncrementScheduledCount(suggested);
|
||||
}
|
||||
|
||||
// Regardless of whether we migrated, we had a candidate, so we're done.
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the next suggestion.
|
||||
suggested = priority_queue.GetSuggestedNext(core_id, suggested);
|
||||
}
|
||||
|
||||
// If the suggestion is different from the current thread, we need to perform an
|
||||
// update.
|
||||
if (suggested != std::addressof(cur_thread)) {
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
} else {
|
||||
// Otherwise, set the thread's yield count so that we won't waste work until the
|
||||
// process is scheduled again.
|
||||
cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount());
|
||||
}
|
||||
} else {
|
||||
// Otherwise, we have an update to perform.
|
||||
SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KScheduler::KScheduler(Core::System& system, std::size_t core_id)
|
||||
: system(system), core_id(core_id) {
|
||||
switch_fiber = std::make_shared<Common::Fiber>(OnSwitch, this);
|
||||
this->state.needs_scheduling = true;
|
||||
this->state.interrupt_task_thread_runnable = false;
|
||||
this->state.should_count_idle = false;
|
||||
this->state.idle_count = 0;
|
||||
this->state.idle_thread_stack = nullptr;
|
||||
this->state.highest_priority_thread = nullptr;
|
||||
}
|
||||
|
||||
KScheduler::~KScheduler() = default;
|
||||
|
||||
Thread* KScheduler::GetCurrentThread() const {
|
||||
if (current_thread) {
|
||||
return current_thread;
|
||||
}
|
||||
return idle_thread;
|
||||
}
|
||||
|
||||
u64 KScheduler::GetLastContextSwitchTicks() const {
|
||||
return last_context_switch_time;
|
||||
}
|
||||
|
||||
void KScheduler::RescheduleCurrentCore() {
|
||||
ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1);
|
||||
|
||||
auto& phys_core = system.Kernel().PhysicalCore(core_id);
|
||||
if (phys_core.IsInterrupted()) {
|
||||
phys_core.ClearInterrupt();
|
||||
}
|
||||
guard.lock();
|
||||
if (this->state.needs_scheduling) {
|
||||
Schedule();
|
||||
} else {
|
||||
guard.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::OnThreadStart() {
|
||||
SwitchContextStep2();
|
||||
}
|
||||
|
||||
void KScheduler::Unload(Thread* thread) {
|
||||
if (thread) {
|
||||
thread->SetIsRunning(false);
|
||||
if (thread->IsContinuousOnSVC() && !thread->IsHLEThread()) {
|
||||
system.ArmInterface(core_id).ExceptionalExit();
|
||||
thread->SetContinuousOnSVC(false);
|
||||
}
|
||||
if (!thread->IsHLEThread() && !thread->HasExited()) {
|
||||
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
||||
cpu_core.SaveContext(thread->GetContext32());
|
||||
cpu_core.SaveContext(thread->GetContext64());
|
||||
// Save the TPIDR_EL0 system register in case it was modified.
|
||||
thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
}
|
||||
thread->context_guard.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::Reload(Thread* thread) {
|
||||
if (thread) {
|
||||
ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
|
||||
"Thread must be runnable.");
|
||||
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
thread->SetIsRunning(true);
|
||||
thread->SetWasRunning(false);
|
||||
|
||||
auto* const thread_owner_process = thread->GetOwnerProcess();
|
||||
if (thread_owner_process != nullptr) {
|
||||
system.Kernel().MakeCurrentProcess(thread_owner_process);
|
||||
}
|
||||
if (!thread->IsHLEThread()) {
|
||||
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
||||
cpu_core.LoadContext(thread->GetContext32());
|
||||
cpu_core.LoadContext(thread->GetContext64());
|
||||
cpu_core.SetTlsAddress(thread->GetTLSAddress());
|
||||
cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::SwitchContextStep2() {
|
||||
// Load context of new thread
|
||||
Reload(current_thread);
|
||||
|
||||
RescheduleCurrentCore();
|
||||
}
|
||||
|
||||
void KScheduler::ScheduleImpl() {
|
||||
Thread* previous_thread = current_thread;
|
||||
current_thread = state.highest_priority_thread;
|
||||
|
||||
this->state.needs_scheduling = false;
|
||||
|
||||
if (current_thread == previous_thread) {
|
||||
guard.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
Process* const previous_process = system.Kernel().CurrentProcess();
|
||||
|
||||
UpdateLastContextSwitchTime(previous_thread, previous_process);
|
||||
|
||||
// Save context for previous thread
|
||||
Unload(previous_thread);
|
||||
|
||||
std::shared_ptr<Common::Fiber>* old_context;
|
||||
if (previous_thread != nullptr) {
|
||||
old_context = &previous_thread->GetHostContext();
|
||||
} else {
|
||||
old_context = &idle_thread->GetHostContext();
|
||||
}
|
||||
guard.unlock();
|
||||
|
||||
Common::Fiber::YieldTo(*old_context, switch_fiber);
|
||||
/// When a thread wakes up, the scheduler may have changed to other in another core.
|
||||
auto& next_scheduler = *system.Kernel().CurrentScheduler();
|
||||
next_scheduler.SwitchContextStep2();
|
||||
}
|
||||
|
||||
void KScheduler::OnSwitch(void* this_scheduler) {
|
||||
KScheduler* sched = static_cast<KScheduler*>(this_scheduler);
|
||||
sched->SwitchToCurrent();
|
||||
}
|
||||
|
||||
void KScheduler::SwitchToCurrent() {
|
||||
while (true) {
|
||||
{
|
||||
std::scoped_lock lock{guard};
|
||||
current_thread = state.highest_priority_thread;
|
||||
this->state.needs_scheduling = false;
|
||||
}
|
||||
const auto is_switch_pending = [this] {
|
||||
std::scoped_lock lock{guard};
|
||||
return state.needs_scheduling.load(std::memory_order_relaxed);
|
||||
};
|
||||
do {
|
||||
if (current_thread != nullptr && !current_thread->IsHLEThread()) {
|
||||
current_thread->context_guard.lock();
|
||||
if (!current_thread->IsRunnable()) {
|
||||
current_thread->context_guard.unlock();
|
||||
break;
|
||||
}
|
||||
if (static_cast<u32>(current_thread->GetProcessorID()) != core_id) {
|
||||
current_thread->context_guard.unlock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::shared_ptr<Common::Fiber>* next_context;
|
||||
if (current_thread != nullptr) {
|
||||
next_context = ¤t_thread->GetHostContext();
|
||||
} else {
|
||||
next_context = &idle_thread->GetHostContext();
|
||||
}
|
||||
Common::Fiber::YieldTo(switch_fiber, *next_context);
|
||||
} while (!is_switch_pending());
|
||||
}
|
||||
}
|
||||
|
||||
void KScheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
|
||||
const u64 prev_switch_ticks = last_context_switch_time;
|
||||
const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
|
||||
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
|
||||
|
||||
if (thread != nullptr) {
|
||||
thread->UpdateCPUTimeTicks(update_ticks);
|
||||
}
|
||||
|
||||
if (process != nullptr) {
|
||||
process->UpdateCPUTimeTicks(update_ticks);
|
||||
}
|
||||
|
||||
last_context_switch_time = most_recent_switch_ticks;
|
||||
}
|
||||
|
||||
void KScheduler::Initialize() {
|
||||
std::string name = "Idle Thread Id:" + std::to_string(core_id);
|
||||
std::function<void(void*)> init_func = Core::CpuManager::GetIdleThreadStartFunc();
|
||||
void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
|
||||
ThreadType type = static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE);
|
||||
auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast<u32>(core_id), 0,
|
||||
nullptr, std::move(init_func), init_func_parameter);
|
||||
idle_thread = thread_res.Unwrap().get();
|
||||
|
||||
{
|
||||
KScopedSchedulerLock lock{system.Kernel()};
|
||||
idle_thread->SetStatus(ThreadStatus::Ready);
|
||||
}
|
||||
}
|
||||
|
||||
KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel)
|
||||
: KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {}
|
||||
|
||||
KScopedSchedulerLock::~KScopedSchedulerLock() = default;
|
||||
|
||||
} // namespace Kernel
|
||||
201
src/core/hle/kernel/k_scheduler.h
Normal file
201
src/core/hle/kernel/k_scheduler.h
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// This file references various implementation details from Atmosphere, an open-source firmware for
|
||||
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/spin_lock.h"
|
||||
#include "core/hle/kernel/global_scheduler_context.h"
|
||||
#include "core/hle/kernel/k_priority_queue.h"
|
||||
#include "core/hle/kernel/k_scheduler_lock.h"
|
||||
#include "core/hle/kernel/k_scoped_lock.h"
|
||||
|
||||
namespace Common {
|
||||
class Fiber;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class Process;
|
||||
class SchedulerLock;
|
||||
class Thread;
|
||||
|
||||
class KScheduler final {
|
||||
public:
|
||||
explicit KScheduler(Core::System& system, std::size_t core_id);
|
||||
~KScheduler();
|
||||
|
||||
/// Reschedules to the next available thread (call after current thread is suspended)
|
||||
void RescheduleCurrentCore();
|
||||
|
||||
/// Reschedules cores pending reschedule, to be called on EnableScheduling.
|
||||
static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule,
|
||||
Core::EmuThreadHandle global_thread);
|
||||
|
||||
/// The next two are for SingleCore Only.
|
||||
/// Unload current thread before preempting core.
|
||||
void Unload(Thread* thread);
|
||||
|
||||
/// Reload current thread after core preemption.
|
||||
void Reload(Thread* thread);
|
||||
|
||||
/// Gets the current running thread
|
||||
[[nodiscard]] Thread* GetCurrentThread() const;
|
||||
|
||||
/// Gets the timestamp for the last context switch in ticks.
|
||||
[[nodiscard]] u64 GetLastContextSwitchTicks() const;
|
||||
|
||||
[[nodiscard]] bool ContextSwitchPending() const {
|
||||
return state.needs_scheduling.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void Initialize();
|
||||
|
||||
void OnThreadStart();
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Common::Fiber>& ControlContext() {
|
||||
return switch_fiber;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::shared_ptr<Common::Fiber>& ControlContext() const {
|
||||
return switch_fiber;
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 UpdateHighestPriorityThread(Thread* highest_thread);
|
||||
|
||||
/**
|
||||
* Takes a thread and moves it to the back of the it's priority list.
|
||||
*
|
||||
* @note This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
void YieldWithoutCoreMigration();
|
||||
|
||||
/**
|
||||
* Takes a thread and moves it to the back of the it's priority list.
|
||||
* Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
|
||||
* a better priority than the next thread in the core.
|
||||
*
|
||||
* @note This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
void YieldWithCoreMigration();
|
||||
|
||||
/**
|
||||
* Takes a thread and moves it out of the scheduling queue.
|
||||
* and into the suggested queue. If no thread can be scheduled afterwards in that core,
|
||||
* a suggested thread is obtained instead.
|
||||
*
|
||||
* @note This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
void YieldToAnyThread();
|
||||
|
||||
/// Notify the scheduler a thread's status has changed.
|
||||
static void OnThreadStateChanged(KernelCore& kernel, Thread* thread, u32 old_state);
|
||||
|
||||
/// Notify the scheduler a thread's priority has changed.
|
||||
static void OnThreadPriorityChanged(KernelCore& kernel, Thread* thread, Thread* current_thread,
|
||||
u32 old_priority);
|
||||
|
||||
/// Notify the scheduler a thread's core and/or affinity mask has changed.
|
||||
static void OnThreadAffinityMaskChanged(KernelCore& kernel, Thread* thread,
|
||||
const KAffinityMask& old_affinity, s32 old_core);
|
||||
|
||||
static bool CanSchedule(KernelCore& kernel);
|
||||
static bool IsSchedulerUpdateNeeded(const KernelCore& kernel);
|
||||
static void SetSchedulerUpdateNeeded(KernelCore& kernel);
|
||||
static void ClearSchedulerUpdateNeeded(KernelCore& kernel);
|
||||
static void DisableScheduling(KernelCore& kernel);
|
||||
static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling,
|
||||
Core::EmuThreadHandle global_thread);
|
||||
[[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel);
|
||||
|
||||
private:
|
||||
friend class GlobalSchedulerContext;
|
||||
|
||||
/**
|
||||
* Takes care of selecting the new scheduled threads in three steps:
|
||||
*
|
||||
* 1. First a thread is selected from the top of the priority queue. If no thread
|
||||
* is obtained then we move to step two, else we are done.
|
||||
*
|
||||
* 2. Second we try to get a suggested thread that's not assigned to any core or
|
||||
* that is not the top thread in that core.
|
||||
*
|
||||
* 3. Third is no suggested thread is found, we do a second pass and pick a running
|
||||
* thread in another core and swap it with its current thread.
|
||||
*
|
||||
* returns the cores needing scheduling.
|
||||
*/
|
||||
[[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel);
|
||||
|
||||
[[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel);
|
||||
|
||||
void RotateScheduledQueue(s32 core_id, s32 priority);
|
||||
|
||||
void Schedule() {
|
||||
ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1);
|
||||
this->ScheduleImpl();
|
||||
}
|
||||
|
||||
/// Switches the CPU's active thread context to that of the specified thread
|
||||
void ScheduleImpl();
|
||||
|
||||
/// When a thread wakes up, it must run this through it's new scheduler
|
||||
void SwitchContextStep2();
|
||||
|
||||
/**
|
||||
* Called on every context switch to update the internal timestamp
|
||||
* This also updates the running time ticks for the given thread and
|
||||
* process using the following difference:
|
||||
*
|
||||
* ticks += most_recent_ticks - last_context_switch_ticks
|
||||
*
|
||||
* The internal tick timestamp for the scheduler is simply the
|
||||
* most recent tick count retrieved. No special arithmetic is
|
||||
* applied to it.
|
||||
*/
|
||||
void UpdateLastContextSwitchTime(Thread* thread, Process* process);
|
||||
|
||||
static void OnSwitch(void* this_scheduler);
|
||||
void SwitchToCurrent();
|
||||
|
||||
Thread* current_thread{};
|
||||
Thread* idle_thread{};
|
||||
|
||||
std::shared_ptr<Common::Fiber> switch_fiber{};
|
||||
|
||||
struct SchedulingState {
|
||||
std::atomic<bool> needs_scheduling;
|
||||
bool interrupt_task_thread_runnable{};
|
||||
bool should_count_idle{};
|
||||
u64 idle_count{};
|
||||
Thread* highest_priority_thread{};
|
||||
void* idle_thread_stack{};
|
||||
};
|
||||
|
||||
SchedulingState state;
|
||||
|
||||
Core::System& system;
|
||||
u64 last_context_switch_time{};
|
||||
const std::size_t core_id;
|
||||
|
||||
Common::SpinLock guard{};
|
||||
};
|
||||
|
||||
class KScopedSchedulerLock : KScopedLock<GlobalSchedulerContext::LockType> {
|
||||
public:
|
||||
explicit KScopedSchedulerLock(KernelCore& kernel);
|
||||
~KScopedSchedulerLock();
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
75
src/core/hle/kernel/k_scheduler_lock.h
Normal file
75
src/core/hle/kernel/k_scheduler_lock.h
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// This file references various implementation details from Atmosphere, an open-source firmware for
|
||||
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/spin_lock.h"
|
||||
#include "core/hardware_properties.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
|
||||
template <typename SchedulerType>
|
||||
class KAbstractSchedulerLock {
|
||||
public:
|
||||
explicit KAbstractSchedulerLock(KernelCore& kernel) : kernel{kernel} {}
|
||||
|
||||
bool IsLockedByCurrentThread() const {
|
||||
return this->owner_thread == kernel.GetCurrentEmuThreadID();
|
||||
}
|
||||
|
||||
void Lock() {
|
||||
if (this->IsLockedByCurrentThread()) {
|
||||
// If we already own the lock, we can just increment the count.
|
||||
ASSERT(this->lock_count > 0);
|
||||
this->lock_count++;
|
||||
} else {
|
||||
// Otherwise, we want to disable scheduling and acquire the spinlock.
|
||||
SchedulerType::DisableScheduling(kernel);
|
||||
this->spin_lock.lock();
|
||||
|
||||
// For debug, ensure that our state is valid.
|
||||
ASSERT(this->lock_count == 0);
|
||||
ASSERT(this->owner_thread == Core::EmuThreadHandle::InvalidHandle());
|
||||
|
||||
// Increment count, take ownership.
|
||||
this->lock_count = 1;
|
||||
this->owner_thread = kernel.GetCurrentEmuThreadID();
|
||||
}
|
||||
}
|
||||
|
||||
void Unlock() {
|
||||
ASSERT(this->IsLockedByCurrentThread());
|
||||
ASSERT(this->lock_count > 0);
|
||||
|
||||
// Release an instance of the lock.
|
||||
if ((--this->lock_count) == 0) {
|
||||
// We're no longer going to hold the lock. Take note of what cores need scheduling.
|
||||
const u64 cores_needing_scheduling =
|
||||
SchedulerType::UpdateHighestPriorityThreads(kernel);
|
||||
Core::EmuThreadHandle leaving_thread = owner_thread;
|
||||
|
||||
// Note that we no longer hold the lock, and unlock the spinlock.
|
||||
this->owner_thread = Core::EmuThreadHandle::InvalidHandle();
|
||||
this->spin_lock.unlock();
|
||||
|
||||
// Enable scheduling, and perform a rescheduling operation.
|
||||
SchedulerType::EnableScheduling(kernel, cores_needing_scheduling, leaving_thread);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
KernelCore& kernel;
|
||||
Common::SpinLock spin_lock{};
|
||||
s32 lock_count{};
|
||||
Core::EmuThreadHandle owner_thread{Core::EmuThreadHandle::InvalidHandle()};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
41
src/core/hle/kernel/k_scoped_lock.h
Normal file
41
src/core/hle/kernel/k_scoped_lock.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// This file references various implementation details from Atmosphere, an open-source firmware for
|
||||
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
template <typename T>
|
||||
concept KLockable = !std::is_reference_v<T> && requires(T & t) {
|
||||
{ t.Lock() }
|
||||
->std::same_as<void>;
|
||||
{ t.Unlock() }
|
||||
->std::same_as<void>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
requires KLockable<T> class KScopedLock {
|
||||
public:
|
||||
explicit KScopedLock(T* l) : lock_ptr(l) {
|
||||
this->lock_ptr->Lock();
|
||||
}
|
||||
explicit KScopedLock(T& l) : KScopedLock(std::addressof(l)) { /* ... */
|
||||
}
|
||||
~KScopedLock() {
|
||||
this->lock_ptr->Unlock();
|
||||
}
|
||||
|
||||
KScopedLock(const KScopedLock&) = delete;
|
||||
KScopedLock(KScopedLock&&) = delete;
|
||||
|
||||
private:
|
||||
T* lock_ptr;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
50
src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
Normal file
50
src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// This file references various implementation details from Atmosphere, an open-source firmware for
|
||||
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/time_manager.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KScopedSchedulerLockAndSleep {
|
||||
public:
|
||||
explicit KScopedSchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, Thread* t,
|
||||
s64 timeout)
|
||||
: kernel(kernel), event_handle(event_handle), thread(t), timeout_tick(timeout) {
|
||||
event_handle = InvalidHandle;
|
||||
|
||||
// Lock the scheduler.
|
||||
kernel.GlobalSchedulerContext().scheduler_lock.Lock();
|
||||
}
|
||||
|
||||
~KScopedSchedulerLockAndSleep() {
|
||||
// Register the sleep.
|
||||
if (this->timeout_tick > 0) {
|
||||
kernel.TimeManager().ScheduleTimeEvent(event_handle, this->thread, this->timeout_tick);
|
||||
}
|
||||
|
||||
// Unlock the scheduler.
|
||||
kernel.GlobalSchedulerContext().scheduler_lock.Unlock();
|
||||
}
|
||||
|
||||
void CancelSleep() {
|
||||
this->timeout_tick = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
KernelCore& kernel;
|
||||
Handle& event_handle;
|
||||
Thread* thread{};
|
||||
s64 timeout_tick{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/memory/memory_layout.h"
|
||||
#include "core/hle/kernel/memory/memory_manager.h"
|
||||
@@ -34,7 +35,6 @@
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/kernel/synchronization.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
@@ -49,17 +49,18 @@ namespace Kernel {
|
||||
|
||||
struct KernelCore::Impl {
|
||||
explicit Impl(Core::System& system, KernelCore& kernel)
|
||||
: global_scheduler{kernel}, synchronization{system}, time_manager{system},
|
||||
global_handle_table{kernel}, system{system} {}
|
||||
: synchronization{system}, time_manager{system}, global_handle_table{kernel}, system{
|
||||
system} {}
|
||||
|
||||
void SetMulticore(bool is_multicore) {
|
||||
this->is_multicore = is_multicore;
|
||||
}
|
||||
|
||||
void Initialize(KernelCore& kernel) {
|
||||
Shutdown();
|
||||
RegisterHostThread();
|
||||
|
||||
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
|
||||
|
||||
InitializePhysicalCores();
|
||||
InitializeSystemResourceLimit(kernel);
|
||||
InitializeMemoryLayout();
|
||||
@@ -86,29 +87,20 @@ struct KernelCore::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < cores.size(); i++) {
|
||||
cores[i].Shutdown();
|
||||
schedulers[i].reset();
|
||||
}
|
||||
cores.clear();
|
||||
|
||||
process_list.clear();
|
||||
|
||||
current_process = nullptr;
|
||||
|
||||
system_resource_limit = nullptr;
|
||||
|
||||
global_handle_table.Clear();
|
||||
|
||||
preemption_event = nullptr;
|
||||
|
||||
global_scheduler.Shutdown();
|
||||
|
||||
named_ports.clear();
|
||||
|
||||
for (auto& core : cores) {
|
||||
core.Shutdown();
|
||||
}
|
||||
cores.clear();
|
||||
|
||||
exclusive_monitor.reset();
|
||||
|
||||
num_host_threads = 0;
|
||||
@@ -121,7 +113,7 @@ struct KernelCore::Impl {
|
||||
exclusive_monitor =
|
||||
Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES);
|
||||
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
||||
schedulers[i] = std::make_unique<Kernel::Scheduler>(system, i);
|
||||
schedulers[i] = std::make_unique<Kernel::KScheduler>(system, i);
|
||||
cores.emplace_back(i, system, *schedulers[i], interrupts);
|
||||
}
|
||||
}
|
||||
@@ -154,8 +146,8 @@ struct KernelCore::Impl {
|
||||
preemption_event = Core::Timing::CreateEvent(
|
||||
"PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) {
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
global_scheduler.PreemptThreads();
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
global_scheduler_context->PreemptThreads();
|
||||
}
|
||||
const auto time_interval = std::chrono::nanoseconds{
|
||||
Core::Timing::msToCycles(std::chrono::milliseconds(10))};
|
||||
@@ -245,7 +237,7 @@ struct KernelCore::Impl {
|
||||
if (result.host_handle >= Core::Hardware::NUM_CPU_CORES) {
|
||||
return result;
|
||||
}
|
||||
const Kernel::Scheduler& sched = cores[result.host_handle].Scheduler();
|
||||
const Kernel::KScheduler& sched = cores[result.host_handle].Scheduler();
|
||||
const Kernel::Thread* current = sched.GetCurrentThread();
|
||||
if (current != nullptr && !current->IsPhantomMode()) {
|
||||
result.guest_handle = current->GetGlobalHandle();
|
||||
@@ -314,7 +306,7 @@ struct KernelCore::Impl {
|
||||
// Lists all processes that exist in the current session.
|
||||
std::vector<std::shared_ptr<Process>> process_list;
|
||||
Process* current_process = nullptr;
|
||||
Kernel::GlobalScheduler global_scheduler;
|
||||
std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
|
||||
Kernel::Synchronization synchronization;
|
||||
Kernel::TimeManager time_manager;
|
||||
|
||||
@@ -355,7 +347,7 @@ struct KernelCore::Impl {
|
||||
|
||||
std::array<std::shared_ptr<Thread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{};
|
||||
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
|
||||
std::array<std::unique_ptr<Kernel::Scheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
|
||||
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
|
||||
|
||||
bool is_multicore{};
|
||||
std::thread::id single_core_thread_id{};
|
||||
@@ -415,19 +407,19 @@ const std::vector<std::shared_ptr<Process>>& KernelCore::GetProcessList() const
|
||||
return impl->process_list;
|
||||
}
|
||||
|
||||
Kernel::GlobalScheduler& KernelCore::GlobalScheduler() {
|
||||
return impl->global_scheduler;
|
||||
Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() {
|
||||
return *impl->global_scheduler_context;
|
||||
}
|
||||
|
||||
const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
|
||||
return impl->global_scheduler;
|
||||
const Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() const {
|
||||
return *impl->global_scheduler_context;
|
||||
}
|
||||
|
||||
Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) {
|
||||
Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) {
|
||||
return *impl->schedulers[id];
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) const {
|
||||
const Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) const {
|
||||
return *impl->schedulers[id];
|
||||
}
|
||||
|
||||
@@ -451,16 +443,13 @@ const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
|
||||
return impl->cores[core_id];
|
||||
}
|
||||
|
||||
Kernel::Scheduler& KernelCore::CurrentScheduler() {
|
||||
Kernel::KScheduler* KernelCore::CurrentScheduler() {
|
||||
u32 core_id = impl->GetCurrentHostThreadID();
|
||||
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
|
||||
return *impl->schedulers[core_id];
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& KernelCore::CurrentScheduler() const {
|
||||
u32 core_id = impl->GetCurrentHostThreadID();
|
||||
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
|
||||
return *impl->schedulers[core_id];
|
||||
if (core_id >= Core::Hardware::NUM_CPU_CORES) {
|
||||
// This is expected when called from not a guest thread
|
||||
return {};
|
||||
}
|
||||
return impl->schedulers[core_id].get();
|
||||
}
|
||||
|
||||
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& KernelCore::Interrupts() {
|
||||
@@ -623,7 +612,7 @@ const Kernel::SharedMemory& KernelCore::GetTimeSharedMem() const {
|
||||
void KernelCore::Suspend(bool in_suspention) {
|
||||
const bool should_suspend = exception_exited || in_suspention;
|
||||
{
|
||||
SchedulerLock lock(*this);
|
||||
KScopedSchedulerLock lock(*this);
|
||||
ThreadStatus status = should_suspend ? ThreadStatus::Ready : ThreadStatus::WaitSleep;
|
||||
for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
|
||||
impl->suspend_threads[i]->SetStatus(status);
|
||||
|
||||
@@ -35,12 +35,12 @@ class SlabHeap;
|
||||
|
||||
class AddressArbiter;
|
||||
class ClientPort;
|
||||
class GlobalScheduler;
|
||||
class GlobalSchedulerContext;
|
||||
class HandleTable;
|
||||
class PhysicalCore;
|
||||
class Process;
|
||||
class ResourceLimit;
|
||||
class Scheduler;
|
||||
class KScheduler;
|
||||
class SharedMemory;
|
||||
class Synchronization;
|
||||
class Thread;
|
||||
@@ -102,16 +102,16 @@ public:
|
||||
const std::vector<std::shared_ptr<Process>>& GetProcessList() const;
|
||||
|
||||
/// Gets the sole instance of the global scheduler
|
||||
Kernel::GlobalScheduler& GlobalScheduler();
|
||||
Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
|
||||
|
||||
/// Gets the sole instance of the global scheduler
|
||||
const Kernel::GlobalScheduler& GlobalScheduler() const;
|
||||
const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const;
|
||||
|
||||
/// Gets the sole instance of the Scheduler assoviated with cpu core 'id'
|
||||
Kernel::Scheduler& Scheduler(std::size_t id);
|
||||
Kernel::KScheduler& Scheduler(std::size_t id);
|
||||
|
||||
/// Gets the sole instance of the Scheduler assoviated with cpu core 'id'
|
||||
const Kernel::Scheduler& Scheduler(std::size_t id) const;
|
||||
const Kernel::KScheduler& Scheduler(std::size_t id) const;
|
||||
|
||||
/// Gets the an instance of the respective physical CPU core.
|
||||
Kernel::PhysicalCore& PhysicalCore(std::size_t id);
|
||||
@@ -120,10 +120,7 @@ public:
|
||||
const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
|
||||
|
||||
/// Gets the sole instance of the Scheduler at the current running core.
|
||||
Kernel::Scheduler& CurrentScheduler();
|
||||
|
||||
/// Gets the sole instance of the Scheduler at the current running core.
|
||||
const Kernel::Scheduler& CurrentScheduler() const;
|
||||
Kernel::KScheduler* CurrentScheduler();
|
||||
|
||||
/// Gets the an instance of the current physical CPU core.
|
||||
Kernel::PhysicalCore& CurrentPhysicalCore();
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
@@ -73,9 +73,9 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
|
||||
auto& kernel = system.Kernel();
|
||||
std::shared_ptr<Thread> current_thread =
|
||||
SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
|
||||
SharedFrom(kernel.CurrentScheduler()->GetCurrentThread());
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
// The mutex address must be 4-byte aligned
|
||||
if ((address % sizeof(u32)) != 0) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
@@ -114,7 +114,7 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
}
|
||||
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
auto* owner = current_thread->GetLockOwner();
|
||||
if (owner != nullptr) {
|
||||
owner->RemoveMutexWaiter(current_thread);
|
||||
@@ -153,10 +153,10 @@ std::pair<ResultCode, std::shared_ptr<Thread>> Mutex::Unlock(std::shared_ptr<Thr
|
||||
|
||||
ResultCode Mutex::Release(VAddr address) {
|
||||
auto& kernel = system.Kernel();
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
std::shared_ptr<Thread> current_thread =
|
||||
SharedFrom(kernel.CurrentScheduler().GetCurrentThread());
|
||||
SharedFrom(kernel.CurrentScheduler()->GetCurrentThread());
|
||||
|
||||
auto [result, new_owner] = Unlock(current_thread, address);
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
#include "core/arm/dynarmic/arm_dynarmic_32.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_64.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system,
|
||||
Kernel::Scheduler& scheduler, Core::CPUInterrupts& interrupts)
|
||||
Kernel::KScheduler& scheduler, Core::CPUInterrupts& interrupts)
|
||||
: core_index{core_index}, system{system}, scheduler{scheduler},
|
||||
interrupts{interrupts}, guard{std::make_unique<Common::SpinLock>()} {}
|
||||
|
||||
@@ -43,10 +43,6 @@ void PhysicalCore::Idle() {
|
||||
interrupts[core_index].AwaitInterrupt();
|
||||
}
|
||||
|
||||
void PhysicalCore::Shutdown() {
|
||||
scheduler.Shutdown();
|
||||
}
|
||||
|
||||
bool PhysicalCore::IsInterrupted() const {
|
||||
return interrupts[core_index].IsInterrupted();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class SpinLock;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class Scheduler;
|
||||
class KScheduler;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Core {
|
||||
@@ -28,7 +28,7 @@ namespace Kernel {
|
||||
|
||||
class PhysicalCore {
|
||||
public:
|
||||
PhysicalCore(std::size_t core_index, Core::System& system, Kernel::Scheduler& scheduler,
|
||||
PhysicalCore(std::size_t core_index, Core::System& system, Kernel::KScheduler& scheduler,
|
||||
Core::CPUInterrupts& interrupts);
|
||||
~PhysicalCore();
|
||||
|
||||
@@ -55,9 +55,6 @@ public:
|
||||
/// Check if this core is interrupted
|
||||
bool IsInterrupted() const;
|
||||
|
||||
// Shutdown this physical core.
|
||||
void Shutdown();
|
||||
|
||||
bool IsInitialized() const {
|
||||
return arm_interface != nullptr;
|
||||
}
|
||||
@@ -82,18 +79,18 @@ public:
|
||||
return core_index;
|
||||
}
|
||||
|
||||
Kernel::Scheduler& Scheduler() {
|
||||
Kernel::KScheduler& Scheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& Scheduler() const {
|
||||
const Kernel::KScheduler& Scheduler() const {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::size_t core_index;
|
||||
Core::System& system;
|
||||
Kernel::Scheduler& scheduler;
|
||||
Kernel::KScheduler& scheduler;
|
||||
Core::CPUInterrupts& interrupts;
|
||||
std::unique_ptr<Common::SpinLock> guard;
|
||||
std::unique_ptr<Core::ARM_Interface> arm_interface;
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/memory/memory_block_manager.h"
|
||||
#include "core/hle/kernel/memory/page_table.h"
|
||||
#include "core/hle/kernel/memory/slab_heap.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/memory.h"
|
||||
@@ -54,7 +54,7 @@ void SetupMainThread(Core::System& system, Process& owner_process, u32 priority,
|
||||
auto& kernel = system.Kernel();
|
||||
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
|
||||
{
|
||||
SchedulerLock lock{kernel};
|
||||
KScopedSchedulerLock lock{kernel};
|
||||
thread->SetStatus(ThreadStatus::Ready);
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,7 @@ void Process::UnregisterThread(const Thread* thread) {
|
||||
}
|
||||
|
||||
ResultCode Process::ClearSignalState() {
|
||||
SchedulerLock lock(system.Kernel());
|
||||
KScopedSchedulerLock lock(system.Kernel());
|
||||
if (status == ProcessStatus::Exited) {
|
||||
LOG_ERROR(Kernel, "called on a terminated process instance.");
|
||||
return ERR_INVALID_STATE;
|
||||
@@ -314,7 +314,7 @@ void Process::PrepareForTermination() {
|
||||
if (thread->GetOwnerProcess() != this)
|
||||
continue;
|
||||
|
||||
if (thread.get() == system.CurrentScheduler().GetCurrentThread())
|
||||
if (thread.get() == kernel.CurrentScheduler()->GetCurrentThread())
|
||||
continue;
|
||||
|
||||
// TODO(Subv): When are the other running/ready threads terminated?
|
||||
@@ -325,7 +325,7 @@ void Process::PrepareForTermination() {
|
||||
}
|
||||
};
|
||||
|
||||
stop_threads(system.GlobalScheduler().GetThreadList());
|
||||
stop_threads(system.GlobalSchedulerContext().GetThreadList());
|
||||
|
||||
FreeTLSRegion(tls_region_address);
|
||||
tls_region_address = 0;
|
||||
@@ -347,7 +347,7 @@ static auto FindTLSPageWithAvailableSlots(std::vector<TLSPage>& tls_pages) {
|
||||
}
|
||||
|
||||
VAddr Process::CreateTLSRegion() {
|
||||
SchedulerLock lock(system.Kernel());
|
||||
KScopedSchedulerLock lock(system.Kernel());
|
||||
if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)};
|
||||
tls_page_iter != tls_pages.cend()) {
|
||||
return *tls_page_iter->ReserveSlot();
|
||||
@@ -378,7 +378,7 @@ VAddr Process::CreateTLSRegion() {
|
||||
}
|
||||
|
||||
void Process::FreeTLSRegion(VAddr tls_address) {
|
||||
SchedulerLock lock(system.Kernel());
|
||||
KScopedSchedulerLock lock(system.Kernel());
|
||||
const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE);
|
||||
auto iter =
|
||||
std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) {
|
||||
|
||||
@@ -216,6 +216,16 @@ public:
|
||||
total_process_running_time_ticks += ticks;
|
||||
}
|
||||
|
||||
/// Gets the process schedule count, used for thread yelding
|
||||
s64 GetScheduledCount() const {
|
||||
return schedule_count;
|
||||
}
|
||||
|
||||
/// Increments the process schedule count, used for thread yielding.
|
||||
void IncrementScheduledCount() {
|
||||
++schedule_count;
|
||||
}
|
||||
|
||||
/// Gets 8 bytes of random data for svcGetInfo RandomEntropy
|
||||
u64 GetRandomEntropy(std::size_t index) const {
|
||||
return random_entropy.at(index);
|
||||
@@ -397,6 +407,9 @@ private:
|
||||
/// Name of this process
|
||||
std::string name;
|
||||
|
||||
/// Schedule count of this process
|
||||
s64 schedule_count{};
|
||||
|
||||
/// System context
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/readable_event.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace Kernel {
|
||||
@@ -39,7 +39,7 @@ void ReadableEvent::Clear() {
|
||||
}
|
||||
|
||||
ResultCode ReadableEvent::Reset() {
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
if (!is_signaled) {
|
||||
LOG_TRACE(Kernel, "Handle is not signaled! object_id={}, object_type={}, object_name={}",
|
||||
GetObjectId(), GetTypeName(), GetName());
|
||||
|
||||
@@ -1,819 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
//
|
||||
// SelectThreads, Yield functions originally by TuxSH.
|
||||
// licensed under GPLv2 or later under exception provided by the author.
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_util.h"
|
||||
#include "common/fiber.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/time_manager.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {}
|
||||
|
||||
GlobalScheduler::~GlobalScheduler() = default;
|
||||
|
||||
void GlobalScheduler::AddThread(std::shared_ptr<Thread> thread) {
|
||||
std::scoped_lock lock{global_list_guard};
|
||||
thread_list.push_back(std::move(thread));
|
||||
}
|
||||
|
||||
void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> thread) {
|
||||
std::scoped_lock lock{global_list_guard};
|
||||
thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread),
|
||||
thread_list.end());
|
||||
}
|
||||
|
||||
u32 GlobalScheduler::SelectThreads() {
|
||||
ASSERT(is_locked);
|
||||
const auto update_thread = [](Thread* thread, Scheduler& sched) {
|
||||
std::scoped_lock lock{sched.guard};
|
||||
if (thread != sched.selected_thread_set.get()) {
|
||||
if (thread == nullptr) {
|
||||
++sched.idle_selection_count;
|
||||
}
|
||||
sched.selected_thread_set = SharedFrom(thread);
|
||||
}
|
||||
const bool reschedule_pending =
|
||||
sched.is_context_switch_pending || (sched.selected_thread_set != sched.current_thread);
|
||||
sched.is_context_switch_pending = reschedule_pending;
|
||||
std::atomic_thread_fence(std::memory_order_seq_cst);
|
||||
return reschedule_pending;
|
||||
};
|
||||
if (!is_reselection_pending.load()) {
|
||||
return 0;
|
||||
}
|
||||
std::array<Thread*, Core::Hardware::NUM_CPU_CORES> top_threads{};
|
||||
|
||||
u32 idle_cores{};
|
||||
|
||||
// Step 1: Get top thread in schedule queue.
|
||||
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
Thread* top_thread =
|
||||
scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front();
|
||||
if (top_thread != nullptr) {
|
||||
// TODO(Blinkhawk): Implement Thread Pinning
|
||||
} else {
|
||||
idle_cores |= (1U << core);
|
||||
}
|
||||
top_threads[core] = top_thread;
|
||||
}
|
||||
|
||||
while (idle_cores != 0) {
|
||||
u32 core_id = Common::CountTrailingZeroes32(idle_cores);
|
||||
|
||||
if (!suggested_queue[core_id].empty()) {
|
||||
std::array<s32, Core::Hardware::NUM_CPU_CORES> migration_candidates{};
|
||||
std::size_t num_candidates = 0;
|
||||
auto iter = suggested_queue[core_id].begin();
|
||||
Thread* suggested = nullptr;
|
||||
// Step 2: Try selecting a suggested thread.
|
||||
while (iter != suggested_queue[core_id].end()) {
|
||||
suggested = *iter;
|
||||
iter++;
|
||||
s32 suggested_core_id = suggested->GetProcessorID();
|
||||
Thread* top_thread =
|
||||
suggested_core_id >= 0 ? top_threads[suggested_core_id] : nullptr;
|
||||
if (top_thread != suggested) {
|
||||
if (top_thread != nullptr &&
|
||||
top_thread->GetPriority() < THREADPRIO_MAX_CORE_MIGRATION) {
|
||||
suggested = nullptr;
|
||||
break;
|
||||
// There's a too high thread to do core migration, cancel
|
||||
}
|
||||
TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id), suggested);
|
||||
break;
|
||||
}
|
||||
suggested = nullptr;
|
||||
migration_candidates[num_candidates++] = suggested_core_id;
|
||||
}
|
||||
// Step 3: Select a suggested thread from another core
|
||||
if (suggested == nullptr) {
|
||||
for (std::size_t i = 0; i < num_candidates; i++) {
|
||||
s32 candidate_core = migration_candidates[i];
|
||||
suggested = top_threads[candidate_core];
|
||||
auto it = scheduled_queue[candidate_core].begin();
|
||||
it++;
|
||||
Thread* next = it != scheduled_queue[candidate_core].end() ? *it : nullptr;
|
||||
if (next != nullptr) {
|
||||
TransferToCore(suggested->GetPriority(), static_cast<s32>(core_id),
|
||||
suggested);
|
||||
top_threads[candidate_core] = next;
|
||||
break;
|
||||
} else {
|
||||
suggested = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
top_threads[core_id] = suggested;
|
||||
}
|
||||
|
||||
idle_cores &= ~(1U << core_id);
|
||||
}
|
||||
u32 cores_needing_context_switch{};
|
||||
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
Scheduler& sched = kernel.Scheduler(core);
|
||||
ASSERT(top_threads[core] == nullptr ||
|
||||
static_cast<u32>(top_threads[core]->GetProcessorID()) == core);
|
||||
if (update_thread(top_threads[core], sched)) {
|
||||
cores_needing_context_switch |= (1U << core);
|
||||
}
|
||||
}
|
||||
return cores_needing_context_switch;
|
||||
}
|
||||
|
||||
bool GlobalScheduler::YieldThread(Thread* yielding_thread) {
|
||||
ASSERT(is_locked);
|
||||
// Note: caller should use critical section, etc.
|
||||
if (!yielding_thread->IsRunnable()) {
|
||||
// Normally this case shouldn't happen except for SetThreadActivity.
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
|
||||
const u32 priority = yielding_thread->GetPriority();
|
||||
|
||||
// Yield the thread
|
||||
Reschedule(priority, core_id, yielding_thread);
|
||||
const Thread* const winner = scheduled_queue[core_id].front();
|
||||
if (kernel.GetCurrentHostThreadID() != core_id) {
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
|
||||
}
|
||||
|
||||
bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) {
|
||||
ASSERT(is_locked);
|
||||
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
|
||||
// etc.
|
||||
if (!yielding_thread->IsRunnable()) {
|
||||
// Normally this case shouldn't happen except for SetThreadActivity.
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
|
||||
const u32 priority = yielding_thread->GetPriority();
|
||||
|
||||
// Yield the thread
|
||||
Reschedule(priority, core_id, yielding_thread);
|
||||
|
||||
std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
|
||||
for (std::size_t i = 0; i < current_threads.size(); i++) {
|
||||
current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
|
||||
}
|
||||
|
||||
Thread* next_thread = scheduled_queue[core_id].front(priority);
|
||||
Thread* winner = nullptr;
|
||||
for (auto& thread : suggested_queue[core_id]) {
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (source_core >= 0) {
|
||||
if (current_threads[source_core] != nullptr) {
|
||||
if (thread == current_threads[source_core] ||
|
||||
current_threads[source_core]->GetPriority() < min_regular_priority) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (next_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks() ||
|
||||
next_thread->GetPriority() < thread->GetPriority()) {
|
||||
if (thread->GetPriority() <= priority) {
|
||||
winner = thread;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (winner != nullptr) {
|
||||
if (winner != yielding_thread) {
|
||||
TransferToCore(winner->GetPriority(), s32(core_id), winner);
|
||||
}
|
||||
} else {
|
||||
winner = next_thread;
|
||||
}
|
||||
|
||||
if (kernel.GetCurrentHostThreadID() != core_id) {
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
|
||||
}
|
||||
|
||||
bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) {
|
||||
ASSERT(is_locked);
|
||||
// Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section,
|
||||
// etc.
|
||||
if (!yielding_thread->IsRunnable()) {
|
||||
// Normally this case shouldn't happen except for SetThreadActivity.
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
Thread* winner = nullptr;
|
||||
const u32 core_id = static_cast<u32>(yielding_thread->GetProcessorID());
|
||||
|
||||
// Remove the thread from its scheduled mlq, put it on the corresponding "suggested" one instead
|
||||
TransferToCore(yielding_thread->GetPriority(), -1, yielding_thread);
|
||||
|
||||
// If the core is idle, perform load balancing, excluding the threads that have just used this
|
||||
// function...
|
||||
if (scheduled_queue[core_id].empty()) {
|
||||
// Here, "current_threads" is calculated after the ""yield"", unlike yield -1
|
||||
std::array<Thread*, Core::Hardware::NUM_CPU_CORES> current_threads;
|
||||
for (std::size_t i = 0; i < current_threads.size(); i++) {
|
||||
current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front();
|
||||
}
|
||||
for (auto& thread : suggested_queue[core_id]) {
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (source_core < 0 || thread == current_threads[source_core]) {
|
||||
continue;
|
||||
}
|
||||
if (current_threads[source_core] == nullptr ||
|
||||
current_threads[source_core]->GetPriority() >= min_regular_priority) {
|
||||
winner = thread;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (winner != nullptr) {
|
||||
if (winner != yielding_thread) {
|
||||
TransferToCore(winner->GetPriority(), static_cast<s32>(core_id), winner);
|
||||
}
|
||||
} else {
|
||||
winner = yielding_thread;
|
||||
}
|
||||
} else {
|
||||
winner = scheduled_queue[core_id].front();
|
||||
}
|
||||
|
||||
if (kernel.GetCurrentHostThreadID() != core_id) {
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
return AskForReselectionOrMarkRedundant(yielding_thread, winner);
|
||||
}
|
||||
|
||||
void GlobalScheduler::PreemptThreads() {
|
||||
ASSERT(is_locked);
|
||||
for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||
const u32 priority = preemption_priorities[core_id];
|
||||
|
||||
if (scheduled_queue[core_id].size(priority) > 0) {
|
||||
if (scheduled_queue[core_id].size(priority) > 1) {
|
||||
scheduled_queue[core_id].front(priority)->IncrementYieldCount();
|
||||
}
|
||||
scheduled_queue[core_id].yield(priority);
|
||||
if (scheduled_queue[core_id].size(priority) > 1) {
|
||||
scheduled_queue[core_id].front(priority)->IncrementYieldCount();
|
||||
}
|
||||
}
|
||||
|
||||
Thread* current_thread =
|
||||
scheduled_queue[core_id].empty() ? nullptr : scheduled_queue[core_id].front();
|
||||
Thread* winner = nullptr;
|
||||
for (auto& thread : suggested_queue[core_id]) {
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (thread->GetPriority() != priority) {
|
||||
continue;
|
||||
}
|
||||
if (source_core >= 0) {
|
||||
Thread* next_thread = scheduled_queue[source_core].empty()
|
||||
? nullptr
|
||||
: scheduled_queue[source_core].front();
|
||||
if (next_thread != nullptr && next_thread->GetPriority() < 2) {
|
||||
break;
|
||||
}
|
||||
if (next_thread == thread) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (current_thread != nullptr &&
|
||||
current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
|
||||
winner = thread;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (winner != nullptr) {
|
||||
TransferToCore(winner->GetPriority(), s32(core_id), winner);
|
||||
current_thread =
|
||||
winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread;
|
||||
}
|
||||
|
||||
if (current_thread != nullptr && current_thread->GetPriority() > priority) {
|
||||
for (auto& thread : suggested_queue[core_id]) {
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (thread->GetPriority() < priority) {
|
||||
continue;
|
||||
}
|
||||
if (source_core >= 0) {
|
||||
Thread* next_thread = scheduled_queue[source_core].empty()
|
||||
? nullptr
|
||||
: scheduled_queue[source_core].front();
|
||||
if (next_thread != nullptr && next_thread->GetPriority() < 2) {
|
||||
break;
|
||||
}
|
||||
if (next_thread == thread) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (current_thread != nullptr &&
|
||||
current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) {
|
||||
winner = thread;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (winner != nullptr) {
|
||||
TransferToCore(winner->GetPriority(), s32(core_id), winner);
|
||||
current_thread = winner;
|
||||
}
|
||||
}
|
||||
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalScheduler::EnableInterruptAndSchedule(u32 cores_pending_reschedule,
|
||||
Core::EmuThreadHandle global_thread) {
|
||||
u32 current_core = global_thread.host_handle;
|
||||
bool must_context_switch = global_thread.guest_handle != InvalidHandle &&
|
||||
(current_core < Core::Hardware::NUM_CPU_CORES);
|
||||
while (cores_pending_reschedule != 0) {
|
||||
u32 core = Common::CountTrailingZeroes32(cores_pending_reschedule);
|
||||
ASSERT(core < Core::Hardware::NUM_CPU_CORES);
|
||||
if (!must_context_switch || core != current_core) {
|
||||
auto& phys_core = kernel.PhysicalCore(core);
|
||||
phys_core.Interrupt();
|
||||
} else {
|
||||
must_context_switch = true;
|
||||
}
|
||||
cores_pending_reschedule &= ~(1U << core);
|
||||
}
|
||||
if (must_context_switch) {
|
||||
auto& core_scheduler = kernel.CurrentScheduler();
|
||||
kernel.ExitSVCProfile();
|
||||
core_scheduler.TryDoContextSwitch();
|
||||
kernel.EnterSVCProfile();
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalScheduler::Suggest(u32 priority, std::size_t core, Thread* thread) {
|
||||
ASSERT(is_locked);
|
||||
suggested_queue[core].add(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::Unsuggest(u32 priority, std::size_t core, Thread* thread) {
|
||||
ASSERT(is_locked);
|
||||
suggested_queue[core].remove(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::Schedule(u32 priority, std::size_t core, Thread* thread) {
|
||||
ASSERT(is_locked);
|
||||
ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
|
||||
scheduled_queue[core].add(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::SchedulePrepend(u32 priority, std::size_t core, Thread* thread) {
|
||||
ASSERT(is_locked);
|
||||
ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core.");
|
||||
scheduled_queue[core].add(thread, priority, false);
|
||||
}
|
||||
|
||||
void GlobalScheduler::Reschedule(u32 priority, std::size_t core, Thread* thread) {
|
||||
ASSERT(is_locked);
|
||||
scheduled_queue[core].remove(thread, priority);
|
||||
scheduled_queue[core].add(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::Unschedule(u32 priority, std::size_t core, Thread* thread) {
|
||||
ASSERT(is_locked);
|
||||
scheduled_queue[core].remove(thread, priority);
|
||||
}
|
||||
|
||||
void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) {
|
||||
ASSERT(is_locked);
|
||||
const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT;
|
||||
const s32 source_core = thread->GetProcessorID();
|
||||
if (source_core == destination_core || !schedulable) {
|
||||
return;
|
||||
}
|
||||
thread->SetProcessorID(destination_core);
|
||||
if (source_core >= 0) {
|
||||
Unschedule(priority, static_cast<u32>(source_core), thread);
|
||||
}
|
||||
if (destination_core >= 0) {
|
||||
Unsuggest(priority, static_cast<u32>(destination_core), thread);
|
||||
Schedule(priority, static_cast<u32>(destination_core), thread);
|
||||
}
|
||||
if (source_core >= 0) {
|
||||
Suggest(priority, static_cast<u32>(source_core), thread);
|
||||
}
|
||||
}
|
||||
|
||||
bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread,
|
||||
const Thread* winner) {
|
||||
if (current_thread == winner) {
|
||||
current_thread->IncrementYieldCount();
|
||||
return true;
|
||||
} else {
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalScheduler::AdjustSchedulingOnStatus(Thread* thread, u32 old_flags) {
|
||||
if (old_flags == thread->scheduling_state) {
|
||||
return;
|
||||
}
|
||||
ASSERT(is_locked);
|
||||
|
||||
if (old_flags == static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
// In this case the thread was running, now it's pausing/exitting
|
||||
if (thread->processor_id >= 0) {
|
||||
Unschedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
|
||||
}
|
||||
|
||||
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
if (core != static_cast<u32>(thread->processor_id) &&
|
||||
((thread->affinity_mask >> core) & 1) != 0) {
|
||||
Unsuggest(thread->current_priority, core, thread);
|
||||
}
|
||||
}
|
||||
} else if (thread->scheduling_state == static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
// The thread is now set to running from being stopped
|
||||
if (thread->processor_id >= 0) {
|
||||
Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
|
||||
}
|
||||
|
||||
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
if (core != static_cast<u32>(thread->processor_id) &&
|
||||
((thread->affinity_mask >> core) & 1) != 0) {
|
||||
Suggest(thread->current_priority, core, thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetReselectionPending();
|
||||
}
|
||||
|
||||
void GlobalScheduler::AdjustSchedulingOnPriority(Thread* thread, u32 old_priority) {
|
||||
if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable)) {
|
||||
return;
|
||||
}
|
||||
ASSERT(is_locked);
|
||||
if (thread->processor_id >= 0) {
|
||||
Unschedule(old_priority, static_cast<u32>(thread->processor_id), thread);
|
||||
}
|
||||
|
||||
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
if (core != static_cast<u32>(thread->processor_id) &&
|
||||
((thread->affinity_mask >> core) & 1) != 0) {
|
||||
Unsuggest(old_priority, core, thread);
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->processor_id >= 0) {
|
||||
if (thread == kernel.CurrentScheduler().GetCurrentThread()) {
|
||||
SchedulePrepend(thread->current_priority, static_cast<u32>(thread->processor_id),
|
||||
thread);
|
||||
} else {
|
||||
Schedule(thread->current_priority, static_cast<u32>(thread->processor_id), thread);
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
if (core != static_cast<u32>(thread->processor_id) &&
|
||||
((thread->affinity_mask >> core) & 1) != 0) {
|
||||
Suggest(thread->current_priority, core, thread);
|
||||
}
|
||||
}
|
||||
thread->IncrementYieldCount();
|
||||
SetReselectionPending();
|
||||
}
|
||||
|
||||
void GlobalScheduler::AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask,
|
||||
s32 old_core) {
|
||||
if (thread->scheduling_state != static_cast<u32>(ThreadSchedStatus::Runnable) ||
|
||||
thread->current_priority >= THREADPRIO_COUNT) {
|
||||
return;
|
||||
}
|
||||
ASSERT(is_locked);
|
||||
|
||||
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
if (((old_affinity_mask >> core) & 1) != 0) {
|
||||
if (core == static_cast<u32>(old_core)) {
|
||||
Unschedule(thread->current_priority, core, thread);
|
||||
} else {
|
||||
Unsuggest(thread->current_priority, core, thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
if (((thread->affinity_mask >> core) & 1) != 0) {
|
||||
if (core == static_cast<u32>(thread->processor_id)) {
|
||||
Schedule(thread->current_priority, core, thread);
|
||||
} else {
|
||||
Suggest(thread->current_priority, core, thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thread->IncrementYieldCount();
|
||||
SetReselectionPending();
|
||||
}
|
||||
|
||||
void GlobalScheduler::Shutdown() {
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
scheduled_queue[core].clear();
|
||||
suggested_queue[core].clear();
|
||||
}
|
||||
thread_list.clear();
|
||||
}
|
||||
|
||||
void GlobalScheduler::Lock() {
|
||||
Core::EmuThreadHandle current_thread = kernel.GetCurrentEmuThreadID();
|
||||
ASSERT(!current_thread.IsInvalid());
|
||||
if (current_thread == current_owner) {
|
||||
++scope_lock;
|
||||
} else {
|
||||
inner_lock.lock();
|
||||
is_locked = true;
|
||||
current_owner = current_thread;
|
||||
ASSERT(current_owner != Core::EmuThreadHandle::InvalidHandle());
|
||||
scope_lock = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalScheduler::Unlock() {
|
||||
if (--scope_lock != 0) {
|
||||
ASSERT(scope_lock > 0);
|
||||
return;
|
||||
}
|
||||
u32 cores_pending_reschedule = SelectThreads();
|
||||
Core::EmuThreadHandle leaving_thread = current_owner;
|
||||
current_owner = Core::EmuThreadHandle::InvalidHandle();
|
||||
scope_lock = 1;
|
||||
is_locked = false;
|
||||
inner_lock.unlock();
|
||||
EnableInterruptAndSchedule(cores_pending_reschedule, leaving_thread);
|
||||
}
|
||||
|
||||
Scheduler::Scheduler(Core::System& system, std::size_t core_id) : system(system), core_id(core_id) {
|
||||
switch_fiber = std::make_shared<Common::Fiber>(std::function<void(void*)>(OnSwitch), this);
|
||||
}
|
||||
|
||||
Scheduler::~Scheduler() = default;
|
||||
|
||||
bool Scheduler::HaveReadyThreads() const {
|
||||
return system.GlobalScheduler().HaveReadyThreads(core_id);
|
||||
}
|
||||
|
||||
Thread* Scheduler::GetCurrentThread() const {
|
||||
if (current_thread) {
|
||||
return current_thread.get();
|
||||
}
|
||||
return idle_thread.get();
|
||||
}
|
||||
|
||||
Thread* Scheduler::GetSelectedThread() const {
|
||||
return selected_thread.get();
|
||||
}
|
||||
|
||||
u64 Scheduler::GetLastContextSwitchTicks() const {
|
||||
return last_context_switch_time;
|
||||
}
|
||||
|
||||
void Scheduler::TryDoContextSwitch() {
|
||||
auto& phys_core = system.Kernel().CurrentPhysicalCore();
|
||||
if (phys_core.IsInterrupted()) {
|
||||
phys_core.ClearInterrupt();
|
||||
}
|
||||
guard.lock();
|
||||
if (is_context_switch_pending) {
|
||||
SwitchContext();
|
||||
} else {
|
||||
guard.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::OnThreadStart() {
|
||||
SwitchContextStep2();
|
||||
}
|
||||
|
||||
void Scheduler::Unload(Thread* thread) {
|
||||
if (thread) {
|
||||
thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
|
||||
thread->SetIsRunning(false);
|
||||
if (thread->IsContinuousOnSVC() && !thread->IsHLEThread()) {
|
||||
system.ArmInterface(core_id).ExceptionalExit();
|
||||
thread->SetContinuousOnSVC(false);
|
||||
}
|
||||
if (!thread->IsHLEThread() && !thread->HasExited()) {
|
||||
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
||||
cpu_core.SaveContext(thread->GetContext32());
|
||||
cpu_core.SaveContext(thread->GetContext64());
|
||||
// Save the TPIDR_EL0 system register in case it was modified.
|
||||
thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
}
|
||||
thread->context_guard.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::Unload() {
|
||||
Unload(current_thread.get());
|
||||
}
|
||||
|
||||
void Scheduler::Reload(Thread* thread) {
|
||||
if (thread) {
|
||||
ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable,
|
||||
"Thread must be runnable.");
|
||||
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
thread->SetIsRunning(true);
|
||||
thread->SetWasRunning(false);
|
||||
thread->last_running_ticks = system.CoreTiming().GetCPUTicks();
|
||||
|
||||
auto* const thread_owner_process = thread->GetOwnerProcess();
|
||||
if (thread_owner_process != nullptr) {
|
||||
system.Kernel().MakeCurrentProcess(thread_owner_process);
|
||||
}
|
||||
if (!thread->IsHLEThread()) {
|
||||
Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
|
||||
cpu_core.LoadContext(thread->GetContext32());
|
||||
cpu_core.LoadContext(thread->GetContext64());
|
||||
cpu_core.SetTlsAddress(thread->GetTLSAddress());
|
||||
cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::Reload() {
|
||||
Reload(current_thread.get());
|
||||
}
|
||||
|
||||
void Scheduler::SwitchContextStep2() {
|
||||
// Load context of new thread
|
||||
Reload(selected_thread.get());
|
||||
|
||||
TryDoContextSwitch();
|
||||
}
|
||||
|
||||
void Scheduler::SwitchContext() {
|
||||
current_thread_prev = current_thread;
|
||||
selected_thread = selected_thread_set;
|
||||
Thread* previous_thread = current_thread_prev.get();
|
||||
Thread* new_thread = selected_thread.get();
|
||||
current_thread = selected_thread;
|
||||
|
||||
is_context_switch_pending = false;
|
||||
|
||||
if (new_thread == previous_thread) {
|
||||
guard.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
Process* const previous_process = system.Kernel().CurrentProcess();
|
||||
|
||||
UpdateLastContextSwitchTime(previous_thread, previous_process);
|
||||
|
||||
// Save context for previous thread
|
||||
Unload(previous_thread);
|
||||
|
||||
std::shared_ptr<Common::Fiber>* old_context;
|
||||
if (previous_thread != nullptr) {
|
||||
old_context = &previous_thread->GetHostContext();
|
||||
} else {
|
||||
old_context = &idle_thread->GetHostContext();
|
||||
}
|
||||
guard.unlock();
|
||||
|
||||
Common::Fiber::YieldTo(*old_context, switch_fiber);
|
||||
/// When a thread wakes up, the scheduler may have changed to other in another core.
|
||||
auto& next_scheduler = system.Kernel().CurrentScheduler();
|
||||
next_scheduler.SwitchContextStep2();
|
||||
}
|
||||
|
||||
void Scheduler::OnSwitch(void* this_scheduler) {
|
||||
Scheduler* sched = static_cast<Scheduler*>(this_scheduler);
|
||||
sched->SwitchToCurrent();
|
||||
}
|
||||
|
||||
void Scheduler::SwitchToCurrent() {
|
||||
while (true) {
|
||||
{
|
||||
std::scoped_lock lock{guard};
|
||||
selected_thread = selected_thread_set;
|
||||
current_thread = selected_thread;
|
||||
is_context_switch_pending = false;
|
||||
}
|
||||
const auto is_switch_pending = [this] {
|
||||
std::scoped_lock lock{guard};
|
||||
return is_context_switch_pending;
|
||||
};
|
||||
do {
|
||||
if (current_thread != nullptr && !current_thread->IsHLEThread()) {
|
||||
current_thread->context_guard.lock();
|
||||
if (!current_thread->IsRunnable()) {
|
||||
current_thread->context_guard.unlock();
|
||||
break;
|
||||
}
|
||||
if (static_cast<u32>(current_thread->GetProcessorID()) != core_id) {
|
||||
current_thread->context_guard.unlock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::shared_ptr<Common::Fiber>* next_context;
|
||||
if (current_thread != nullptr) {
|
||||
next_context = ¤t_thread->GetHostContext();
|
||||
} else {
|
||||
next_context = &idle_thread->GetHostContext();
|
||||
}
|
||||
Common::Fiber::YieldTo(switch_fiber, *next_context);
|
||||
} while (!is_switch_pending());
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
|
||||
const u64 prev_switch_ticks = last_context_switch_time;
|
||||
const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks();
|
||||
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
|
||||
|
||||
if (thread != nullptr) {
|
||||
thread->UpdateCPUTimeTicks(update_ticks);
|
||||
}
|
||||
|
||||
if (process != nullptr) {
|
||||
process->UpdateCPUTimeTicks(update_ticks);
|
||||
}
|
||||
|
||||
last_context_switch_time = most_recent_switch_ticks;
|
||||
}
|
||||
|
||||
void Scheduler::Initialize() {
|
||||
std::string name = "Idle Thread Id:" + std::to_string(core_id);
|
||||
std::function<void(void*)> init_func = Core::CpuManager::GetIdleThreadStartFunc();
|
||||
void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater();
|
||||
ThreadType type = static_cast<ThreadType>(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE);
|
||||
auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast<u32>(core_id), 0,
|
||||
nullptr, std::move(init_func), init_func_parameter);
|
||||
idle_thread = std::move(thread_res).Unwrap();
|
||||
}
|
||||
|
||||
void Scheduler::Shutdown() {
|
||||
current_thread = nullptr;
|
||||
selected_thread = nullptr;
|
||||
}
|
||||
|
||||
SchedulerLock::SchedulerLock(KernelCore& kernel) : kernel{kernel} {
|
||||
kernel.GlobalScheduler().Lock();
|
||||
}
|
||||
|
||||
SchedulerLock::~SchedulerLock() {
|
||||
kernel.GlobalScheduler().Unlock();
|
||||
}
|
||||
|
||||
SchedulerLockAndSleep::SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle,
|
||||
Thread* time_task, s64 nanoseconds)
|
||||
: SchedulerLock{kernel}, event_handle{event_handle}, time_task{time_task}, nanoseconds{
|
||||
nanoseconds} {
|
||||
event_handle = InvalidHandle;
|
||||
}
|
||||
|
||||
SchedulerLockAndSleep::~SchedulerLockAndSleep() {
|
||||
if (sleep_cancelled) {
|
||||
return;
|
||||
}
|
||||
auto& time_manager = kernel.TimeManager();
|
||||
time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
|
||||
}
|
||||
|
||||
void SchedulerLockAndSleep::Release() {
|
||||
if (sleep_cancelled) {
|
||||
return;
|
||||
}
|
||||
auto& time_manager = kernel.TimeManager();
|
||||
time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds);
|
||||
sleep_cancelled = true;
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,320 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/multi_level_queue.h"
|
||||
#include "common/spin_lock.h"
|
||||
#include "core/hardware_properties.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace Common {
|
||||
class Fiber;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class ARM_Interface;
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class Process;
|
||||
class SchedulerLock;
|
||||
|
||||
class GlobalScheduler final {
|
||||
public:
|
||||
explicit GlobalScheduler(KernelCore& kernel);
|
||||
~GlobalScheduler();
|
||||
|
||||
/// Adds a new thread to the scheduler
|
||||
void AddThread(std::shared_ptr<Thread> thread);
|
||||
|
||||
/// Removes a thread from the scheduler
|
||||
void RemoveThread(std::shared_ptr<Thread> thread);
|
||||
|
||||
/// Returns a list of all threads managed by the scheduler
|
||||
const std::vector<std::shared_ptr<Thread>>& GetThreadList() const {
|
||||
return thread_list;
|
||||
}
|
||||
|
||||
/// Notify the scheduler a thread's status has changed.
|
||||
void AdjustSchedulingOnStatus(Thread* thread, u32 old_flags);
|
||||
|
||||
/// Notify the scheduler a thread's priority has changed.
|
||||
void AdjustSchedulingOnPriority(Thread* thread, u32 old_priority);
|
||||
|
||||
/// Notify the scheduler a thread's core and/or affinity mask has changed.
|
||||
void AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, s32 old_core);
|
||||
|
||||
/**
|
||||
* Takes care of selecting the new scheduled threads in three steps:
|
||||
*
|
||||
* 1. First a thread is selected from the top of the priority queue. If no thread
|
||||
* is obtained then we move to step two, else we are done.
|
||||
*
|
||||
* 2. Second we try to get a suggested thread that's not assigned to any core or
|
||||
* that is not the top thread in that core.
|
||||
*
|
||||
* 3. Third is no suggested thread is found, we do a second pass and pick a running
|
||||
* thread in another core and swap it with its current thread.
|
||||
*
|
||||
* returns the cores needing scheduling.
|
||||
*/
|
||||
u32 SelectThreads();
|
||||
|
||||
bool HaveReadyThreads(std::size_t core_id) const {
|
||||
return !scheduled_queue[core_id].empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a thread and moves it to the back of the it's priority list.
|
||||
*
|
||||
* @note This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
bool YieldThread(Thread* thread);
|
||||
|
||||
/**
|
||||
* Takes a thread and moves it to the back of the it's priority list.
|
||||
* Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or
|
||||
* a better priority than the next thread in the core.
|
||||
*
|
||||
* @note This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
bool YieldThreadAndBalanceLoad(Thread* thread);
|
||||
|
||||
/**
|
||||
* Takes a thread and moves it out of the scheduling queue.
|
||||
* and into the suggested queue. If no thread can be scheduled afterwards in that core,
|
||||
* a suggested thread is obtained instead.
|
||||
*
|
||||
* @note This operation can be redundant and no scheduling is changed if marked as so.
|
||||
*/
|
||||
bool YieldThreadAndWaitForLoadBalancing(Thread* thread);
|
||||
|
||||
/**
|
||||
* Rotates the scheduling queues of threads at a preemption priority and then does
|
||||
* some core rebalancing. Preemption priorities can be found in the array
|
||||
* 'preemption_priorities'.
|
||||
*
|
||||
* @note This operation happens every 10ms.
|
||||
*/
|
||||
void PreemptThreads();
|
||||
|
||||
u32 CpuCoresCount() const {
|
||||
return Core::Hardware::NUM_CPU_CORES;
|
||||
}
|
||||
|
||||
void SetReselectionPending() {
|
||||
is_reselection_pending.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
bool IsReselectionPending() const {
|
||||
return is_reselection_pending.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
friend class SchedulerLock;
|
||||
|
||||
/// Lock the scheduler to the current thread.
|
||||
void Lock();
|
||||
|
||||
/// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling
|
||||
/// and reschedules current core if needed.
|
||||
void Unlock();
|
||||
|
||||
void EnableInterruptAndSchedule(u32 cores_pending_reschedule,
|
||||
Core::EmuThreadHandle global_thread);
|
||||
|
||||
/**
|
||||
* Add a thread to the suggested queue of a cpu core. Suggested threads may be
|
||||
* picked if no thread is scheduled to run on the core.
|
||||
*/
|
||||
void Suggest(u32 priority, std::size_t core, Thread* thread);
|
||||
|
||||
/**
|
||||
* Remove a thread to the suggested queue of a cpu core. Suggested threads may be
|
||||
* picked if no thread is scheduled to run on the core.
|
||||
*/
|
||||
void Unsuggest(u32 priority, std::size_t core, Thread* thread);
|
||||
|
||||
/**
|
||||
* Add a thread to the scheduling queue of a cpu core. The thread is added at the
|
||||
* back the queue in its priority level.
|
||||
*/
|
||||
void Schedule(u32 priority, std::size_t core, Thread* thread);
|
||||
|
||||
/**
|
||||
* Add a thread to the scheduling queue of a cpu core. The thread is added at the
|
||||
* front the queue in its priority level.
|
||||
*/
|
||||
void SchedulePrepend(u32 priority, std::size_t core, Thread* thread);
|
||||
|
||||
/// Reschedule an already scheduled thread based on a new priority
|
||||
void Reschedule(u32 priority, std::size_t core, Thread* thread);
|
||||
|
||||
/// Unschedules a thread.
|
||||
void Unschedule(u32 priority, std::size_t core, Thread* thread);
|
||||
|
||||
/**
|
||||
* Transfers a thread into an specific core. If the destination_core is -1
|
||||
* it will be unscheduled from its source code and added into its suggested
|
||||
* queue.
|
||||
*/
|
||||
void TransferToCore(u32 priority, s32 destination_core, Thread* thread);
|
||||
|
||||
bool AskForReselectionOrMarkRedundant(Thread* current_thread, const Thread* winner);
|
||||
|
||||
static constexpr u32 min_regular_priority = 2;
|
||||
std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES>
|
||||
scheduled_queue;
|
||||
std::array<Common::MultiLevelQueue<Thread*, THREADPRIO_COUNT>, Core::Hardware::NUM_CPU_CORES>
|
||||
suggested_queue;
|
||||
std::atomic<bool> is_reselection_pending{false};
|
||||
|
||||
// The priority levels at which the global scheduler preempts threads every 10 ms. They are
|
||||
// ordered from Core 0 to Core 3.
|
||||
std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62};
|
||||
|
||||
/// Scheduler lock mechanisms.
|
||||
bool is_locked{};
|
||||
std::mutex inner_lock;
|
||||
std::atomic<s64> scope_lock{};
|
||||
Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()};
|
||||
|
||||
Common::SpinLock global_list_guard{};
|
||||
|
||||
/// Lists all thread ids that aren't deleted/etc.
|
||||
std::vector<std::shared_ptr<Thread>> thread_list;
|
||||
KernelCore& kernel;
|
||||
};
|
||||
|
||||
class Scheduler final {
|
||||
public:
|
||||
explicit Scheduler(Core::System& system, std::size_t core_id);
|
||||
~Scheduler();
|
||||
|
||||
/// Returns whether there are any threads that are ready to run.
|
||||
bool HaveReadyThreads() const;
|
||||
|
||||
/// Reschedules to the next available thread (call after current thread is suspended)
|
||||
void TryDoContextSwitch();
|
||||
|
||||
/// The next two are for SingleCore Only.
|
||||
/// Unload current thread before preempting core.
|
||||
void Unload(Thread* thread);
|
||||
void Unload();
|
||||
/// Reload current thread after core preemption.
|
||||
void Reload(Thread* thread);
|
||||
void Reload();
|
||||
|
||||
/// Gets the current running thread
|
||||
Thread* GetCurrentThread() const;
|
||||
|
||||
/// Gets the currently selected thread from the top of the multilevel queue
|
||||
Thread* GetSelectedThread() const;
|
||||
|
||||
/// Gets the timestamp for the last context switch in ticks.
|
||||
u64 GetLastContextSwitchTicks() const;
|
||||
|
||||
bool ContextSwitchPending() const {
|
||||
return is_context_switch_pending;
|
||||
}
|
||||
|
||||
void Initialize();
|
||||
|
||||
/// Shutdowns the scheduler.
|
||||
void Shutdown();
|
||||
|
||||
void OnThreadStart();
|
||||
|
||||
std::shared_ptr<Common::Fiber>& ControlContext() {
|
||||
return switch_fiber;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Common::Fiber>& ControlContext() const {
|
||||
return switch_fiber;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class GlobalScheduler;
|
||||
|
||||
/// Switches the CPU's active thread context to that of the specified thread
|
||||
void SwitchContext();
|
||||
|
||||
/// When a thread wakes up, it must run this through it's new scheduler
|
||||
void SwitchContextStep2();
|
||||
|
||||
/**
|
||||
* Called on every context switch to update the internal timestamp
|
||||
* This also updates the running time ticks for the given thread and
|
||||
* process using the following difference:
|
||||
*
|
||||
* ticks += most_recent_ticks - last_context_switch_ticks
|
||||
*
|
||||
* The internal tick timestamp for the scheduler is simply the
|
||||
* most recent tick count retrieved. No special arithmetic is
|
||||
* applied to it.
|
||||
*/
|
||||
void UpdateLastContextSwitchTime(Thread* thread, Process* process);
|
||||
|
||||
static void OnSwitch(void* this_scheduler);
|
||||
void SwitchToCurrent();
|
||||
|
||||
std::shared_ptr<Thread> current_thread = nullptr;
|
||||
std::shared_ptr<Thread> selected_thread = nullptr;
|
||||
std::shared_ptr<Thread> current_thread_prev = nullptr;
|
||||
std::shared_ptr<Thread> selected_thread_set = nullptr;
|
||||
std::shared_ptr<Thread> idle_thread = nullptr;
|
||||
|
||||
std::shared_ptr<Common::Fiber> switch_fiber = nullptr;
|
||||
|
||||
Core::System& system;
|
||||
u64 last_context_switch_time = 0;
|
||||
u64 idle_selection_count = 0;
|
||||
const std::size_t core_id;
|
||||
|
||||
Common::SpinLock guard{};
|
||||
|
||||
bool is_context_switch_pending = false;
|
||||
};
|
||||
|
||||
class SchedulerLock {
|
||||
public:
|
||||
[[nodiscard]] explicit SchedulerLock(KernelCore& kernel);
|
||||
~SchedulerLock();
|
||||
|
||||
protected:
|
||||
KernelCore& kernel;
|
||||
};
|
||||
|
||||
class SchedulerLockAndSleep : public SchedulerLock {
|
||||
public:
|
||||
explicit SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, Thread* time_task,
|
||||
s64 nanoseconds);
|
||||
~SchedulerLockAndSleep();
|
||||
|
||||
void CancelSleep() {
|
||||
sleep_cancelled = true;
|
||||
}
|
||||
|
||||
void Release();
|
||||
|
||||
private:
|
||||
Handle& event_handle;
|
||||
Thread* time_task;
|
||||
s64 nanoseconds;
|
||||
bool sleep_cancelled{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -14,9 +14,9 @@
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/kernel/session.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
@@ -170,7 +170,7 @@ ResultCode ServerSession::CompleteSyncRequest() {
|
||||
|
||||
// Some service requests require the thread to block
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
if (!context.IsThreadWaiting()) {
|
||||
context.GetThread().ResumeFromWait();
|
||||
context.GetThread().SetSynchronizationResults(nullptr, result);
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/memory/memory_block.h"
|
||||
#include "core/hle/kernel/memory/page_table.h"
|
||||
@@ -32,7 +34,6 @@
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/readable_event.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/kernel/svc_types.h"
|
||||
@@ -234,8 +235,7 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si
|
||||
|
||||
static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask,
|
||||
u32 attribute) {
|
||||
return SetMemoryAttribute(system, static_cast<VAddr>(address), static_cast<std::size_t>(size),
|
||||
mask, attribute);
|
||||
return SetMemoryAttribute(system, address, size, mask, attribute);
|
||||
}
|
||||
|
||||
/// Maps a memory range into a different range.
|
||||
@@ -255,8 +255,7 @@ static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr
|
||||
}
|
||||
|
||||
static ResultCode MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
|
||||
return MapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
|
||||
static_cast<std::size_t>(size));
|
||||
return MapMemory(system, dst_addr, src_addr, size);
|
||||
}
|
||||
|
||||
/// Unmaps a region that was previously mapped with svcMapMemory
|
||||
@@ -276,8 +275,7 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad
|
||||
}
|
||||
|
||||
static ResultCode UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) {
|
||||
return UnmapMemory(system, static_cast<VAddr>(dst_addr), static_cast<VAddr>(src_addr),
|
||||
static_cast<std::size_t>(size));
|
||||
return UnmapMemory(system, dst_addr, src_addr, size);
|
||||
}
|
||||
|
||||
/// Connect to an OS service given the port name, returns the handle to the port to out
|
||||
@@ -332,7 +330,8 @@ static ResultCode ConnectToNamedPort32(Core::System& system, Handle* out_handle,
|
||||
|
||||
/// Makes a blocking IPC call to an OS service.
|
||||
static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
|
||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||
auto& kernel = system.Kernel();
|
||||
const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
|
||||
std::shared_ptr<ClientSession> session = handle_table.Get<ClientSession>(handle);
|
||||
if (!session) {
|
||||
LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle);
|
||||
@@ -341,9 +340,9 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
|
||||
|
||||
auto thread = system.CurrentScheduler().GetCurrentThread();
|
||||
auto thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||
{
|
||||
SchedulerLock lock(system.Kernel());
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
thread->InvalidateHLECallback();
|
||||
thread->SetStatus(ThreadStatus::WaitIPC);
|
||||
session->SendSyncRequest(SharedFrom(thread), system.Memory(), system.CoreTiming());
|
||||
@@ -352,12 +351,12 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
|
||||
if (thread->HasHLECallback()) {
|
||||
Handle event_handle = thread->GetHLETimeEvent();
|
||||
if (event_handle != InvalidHandle) {
|
||||
auto& time_manager = system.Kernel().TimeManager();
|
||||
auto& time_manager = kernel.TimeManager();
|
||||
time_manager.UnscheduleTimeEvent(event_handle);
|
||||
}
|
||||
|
||||
{
|
||||
SchedulerLock lock(system.Kernel());
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
auto* sync_object = thread->GetHLESyncObject();
|
||||
sync_object->RemoveWaitingThread(SharedFrom(thread));
|
||||
}
|
||||
@@ -531,8 +530,7 @@ static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_hand
|
||||
|
||||
static ResultCode ArbitrateLock32(Core::System& system, Handle holding_thread_handle,
|
||||
u32 mutex_addr, Handle requesting_thread_handle) {
|
||||
return ArbitrateLock(system, holding_thread_handle, static_cast<VAddr>(mutex_addr),
|
||||
requesting_thread_handle);
|
||||
return ArbitrateLock(system, holding_thread_handle, mutex_addr, requesting_thread_handle);
|
||||
}
|
||||
|
||||
/// Unlock a mutex
|
||||
@@ -555,7 +553,7 @@ static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) {
|
||||
}
|
||||
|
||||
static ResultCode ArbitrateUnlock32(Core::System& system, u32 mutex_addr) {
|
||||
return ArbitrateUnlock(system, static_cast<VAddr>(mutex_addr));
|
||||
return ArbitrateUnlock(system, mutex_addr);
|
||||
}
|
||||
|
||||
enum class BreakType : u32 {
|
||||
@@ -658,7 +656,6 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
|
||||
info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt);
|
||||
|
||||
if (!break_reason.signal_debugger) {
|
||||
SchedulerLock lock(system.Kernel());
|
||||
LOG_CRITICAL(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
@@ -666,18 +663,14 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
|
||||
|
||||
handle_debug_buffer(info1, info2);
|
||||
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
|
||||
const auto thread_processor_id = current_thread->GetProcessorID();
|
||||
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
|
||||
|
||||
// Kill the current thread
|
||||
system.Kernel().ExceptionalExit();
|
||||
current_thread->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) {
|
||||
Break(system, reason, static_cast<u64>(info1), static_cast<u64>(info2));
|
||||
Break(system, reason, info1, info2);
|
||||
}
|
||||
|
||||
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
|
||||
@@ -922,7 +915,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
}
|
||||
|
||||
const auto& core_timing = system.CoreTiming();
|
||||
const auto& scheduler = system.CurrentScheduler();
|
||||
const auto& scheduler = *system.Kernel().CurrentScheduler();
|
||||
const auto* const current_thread = scheduler.GetCurrentThread();
|
||||
const bool same_thread = current_thread == thread.get();
|
||||
|
||||
@@ -948,7 +941,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
|
||||
static ResultCode GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low,
|
||||
u32 info_id, u32 handle, u32 sub_id_high) {
|
||||
const u64 sub_id{static_cast<u64>(sub_id_low | (static_cast<u64>(sub_id_high) << 32))};
|
||||
const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)};
|
||||
u64 res_value{};
|
||||
|
||||
const ResultCode result{GetInfo(system, &res_value, info_id, handle, sub_id)};
|
||||
@@ -1009,7 +1002,7 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size)
|
||||
}
|
||||
|
||||
static ResultCode MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
|
||||
return MapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
|
||||
return MapPhysicalMemory(system, addr, size);
|
||||
}
|
||||
|
||||
/// Unmaps memory previously mapped via MapPhysicalMemory
|
||||
@@ -1063,7 +1056,7 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size
|
||||
}
|
||||
|
||||
static ResultCode UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) {
|
||||
return UnmapPhysicalMemory(system, static_cast<VAddr>(addr), static_cast<std::size_t>(size));
|
||||
return UnmapPhysicalMemory(system, addr, size);
|
||||
}
|
||||
|
||||
/// Sets the thread activity
|
||||
@@ -1090,7 +1083,7 @@ static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 act
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (thread.get() == system.CurrentScheduler().GetCurrentThread()) {
|
||||
if (thread.get() == system.Kernel().CurrentScheduler()->GetCurrentThread()) {
|
||||
LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread");
|
||||
return ERR_BUSY;
|
||||
}
|
||||
@@ -1123,7 +1116,7 @@ static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, H
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (thread.get() == system.CurrentScheduler().GetCurrentThread()) {
|
||||
if (thread.get() == system.Kernel().CurrentScheduler()->GetCurrentThread()) {
|
||||
LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread");
|
||||
return ERR_BUSY;
|
||||
}
|
||||
@@ -1144,7 +1137,7 @@ static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, H
|
||||
}
|
||||
|
||||
static ResultCode GetThreadContext32(Core::System& system, u32 thread_context, Handle handle) {
|
||||
return GetThreadContext(system, static_cast<VAddr>(thread_context), handle);
|
||||
return GetThreadContext(system, thread_context, handle);
|
||||
}
|
||||
|
||||
/// Gets the priority for the specified thread
|
||||
@@ -1281,8 +1274,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han
|
||||
|
||||
static ResultCode MapSharedMemory32(Core::System& system, Handle shared_memory_handle, u32 addr,
|
||||
u32 size, u32 permissions) {
|
||||
return MapSharedMemory(system, shared_memory_handle, static_cast<VAddr>(addr),
|
||||
static_cast<std::size_t>(size), permissions);
|
||||
return MapSharedMemory(system, shared_memory_handle, addr, size, permissions);
|
||||
}
|
||||
|
||||
static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address,
|
||||
@@ -1480,7 +1472,7 @@ static void ExitProcess(Core::System& system) {
|
||||
current_process->PrepareForTermination();
|
||||
|
||||
// Kill the current thread
|
||||
system.CurrentScheduler().GetCurrentThread()->Stop();
|
||||
system.Kernel().CurrentScheduler()->GetCurrentThread()->Stop();
|
||||
}
|
||||
|
||||
static void ExitProcess32(Core::System& system) {
|
||||
@@ -1552,8 +1544,7 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
|
||||
|
||||
static ResultCode CreateThread32(Core::System& system, Handle* out_handle, u32 priority,
|
||||
u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) {
|
||||
return CreateThread(system, out_handle, static_cast<VAddr>(entry_point), static_cast<u64>(arg),
|
||||
static_cast<VAddr>(stack_top), priority, processor_id);
|
||||
return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id);
|
||||
}
|
||||
|
||||
/// Starts the thread for the provided handle
|
||||
@@ -1581,8 +1572,8 @@ static ResultCode StartThread32(Core::System& system, Handle thread_handle) {
|
||||
static void ExitThread(Core::System& system) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
|
||||
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
system.GlobalScheduler().RemoveThread(SharedFrom(current_thread));
|
||||
auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
|
||||
system.GlobalSchedulerContext().RemoveThread(SharedFrom(current_thread));
|
||||
current_thread->Stop();
|
||||
}
|
||||
|
||||
@@ -1592,53 +1583,39 @@ static void ExitThread32(Core::System& system) {
|
||||
|
||||
/// Sleep the current thread
|
||||
static void SleepThread(Core::System& system, s64 nanoseconds) {
|
||||
LOG_DEBUG(Kernel_SVC, "called nanoseconds={}", nanoseconds);
|
||||
LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
|
||||
|
||||
enum class SleepType : s64 {
|
||||
YieldWithoutLoadBalancing = 0,
|
||||
YieldWithLoadBalancing = -1,
|
||||
YieldWithoutCoreMigration = 0,
|
||||
YieldWithCoreMigration = -1,
|
||||
YieldAndWaitForLoadBalancing = -2,
|
||||
};
|
||||
|
||||
auto& scheduler = system.CurrentScheduler();
|
||||
auto* const current_thread = scheduler.GetCurrentThread();
|
||||
bool is_redundant = false;
|
||||
|
||||
auto& scheduler = *system.Kernel().CurrentScheduler();
|
||||
if (nanoseconds <= 0) {
|
||||
switch (static_cast<SleepType>(nanoseconds)) {
|
||||
case SleepType::YieldWithoutLoadBalancing: {
|
||||
auto pair = current_thread->YieldSimple();
|
||||
is_redundant = pair.second;
|
||||
case SleepType::YieldWithoutCoreMigration: {
|
||||
scheduler.YieldWithoutCoreMigration();
|
||||
break;
|
||||
}
|
||||
case SleepType::YieldWithLoadBalancing: {
|
||||
auto pair = current_thread->YieldAndBalanceLoad();
|
||||
is_redundant = pair.second;
|
||||
case SleepType::YieldWithCoreMigration: {
|
||||
scheduler.YieldWithCoreMigration();
|
||||
break;
|
||||
}
|
||||
case SleepType::YieldAndWaitForLoadBalancing: {
|
||||
auto pair = current_thread->YieldAndWaitForLoadBalancing();
|
||||
is_redundant = pair.second;
|
||||
scheduler.YieldToAnyThread();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
|
||||
}
|
||||
} else {
|
||||
current_thread->Sleep(nanoseconds);
|
||||
}
|
||||
|
||||
if (is_redundant && !system.Kernel().IsMulticore()) {
|
||||
system.Kernel().ExitSVCProfile();
|
||||
system.CoreTiming().AddTicks(1000U);
|
||||
system.GetCpuManager().PreemptSingleCore();
|
||||
system.Kernel().EnterSVCProfile();
|
||||
scheduler.GetCurrentThread()->Sleep(nanoseconds);
|
||||
}
|
||||
}
|
||||
|
||||
static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) {
|
||||
const s64 nanoseconds = static_cast<s64>(static_cast<u64>(nanoseconds_low) |
|
||||
(static_cast<u64>(nanoseconds_high) << 32));
|
||||
const auto nanoseconds = static_cast<s64>(u64{nanoseconds_low} | (u64{nanoseconds_high} << 32));
|
||||
SleepThread(system, nanoseconds);
|
||||
}
|
||||
|
||||
@@ -1668,10 +1645,10 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
|
||||
ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4));
|
||||
auto& kernel = system.Kernel();
|
||||
Handle event_handle;
|
||||
Thread* current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
auto* const current_process = system.Kernel().CurrentProcess();
|
||||
Thread* current_thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||
auto* const current_process = kernel.CurrentProcess();
|
||||
{
|
||||
SchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds);
|
||||
KScopedSchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds);
|
||||
const auto& handle_table = current_process->GetHandleTable();
|
||||
std::shared_ptr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
ASSERT(thread);
|
||||
@@ -1707,7 +1684,7 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
|
||||
}
|
||||
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
auto* owner = current_thread->GetLockOwner();
|
||||
if (owner != nullptr) {
|
||||
@@ -1724,10 +1701,8 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add
|
||||
static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 mutex_addr,
|
||||
u32 condition_variable_addr, Handle thread_handle,
|
||||
u32 nanoseconds_low, u32 nanoseconds_high) {
|
||||
const s64 nanoseconds =
|
||||
static_cast<s64>(nanoseconds_low | (static_cast<u64>(nanoseconds_high) << 32));
|
||||
return WaitProcessWideKeyAtomic(system, static_cast<VAddr>(mutex_addr),
|
||||
static_cast<VAddr>(condition_variable_addr), thread_handle,
|
||||
const auto nanoseconds = static_cast<s64>(nanoseconds_low | (u64{nanoseconds_high} << 32));
|
||||
return WaitProcessWideKeyAtomic(system, mutex_addr, condition_variable_addr, thread_handle,
|
||||
nanoseconds);
|
||||
}
|
||||
|
||||
@@ -1740,7 +1715,7 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_
|
||||
|
||||
// Retrieve a list of all threads that are waiting for this condition variable.
|
||||
auto& kernel = system.Kernel();
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
auto* const current_process = kernel.CurrentProcess();
|
||||
std::vector<std::shared_ptr<Thread>> waiting_threads =
|
||||
current_process->GetConditionVariableThreads(condition_variable_addr);
|
||||
@@ -1833,8 +1808,8 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type,
|
||||
|
||||
static ResultCode WaitForAddress32(Core::System& system, u32 address, u32 type, s32 value,
|
||||
u32 timeout_low, u32 timeout_high) {
|
||||
s64 timeout = static_cast<s64>(timeout_low | (static_cast<u64>(timeout_high) << 32));
|
||||
return WaitForAddress(system, static_cast<VAddr>(address), type, value, timeout);
|
||||
const auto timeout = static_cast<s64>(timeout_low | (u64{timeout_high} << 32));
|
||||
return WaitForAddress(system, address, type, value, timeout);
|
||||
}
|
||||
|
||||
// Signals to an address (via Address Arbiter)
|
||||
@@ -1862,7 +1837,7 @@ static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type,
|
||||
|
||||
static ResultCode SignalToAddress32(Core::System& system, u32 address, u32 type, s32 value,
|
||||
s32 num_to_wake) {
|
||||
return SignalToAddress(system, static_cast<VAddr>(address), type, value, num_to_wake);
|
||||
return SignalToAddress(system, address, type, value, num_to_wake);
|
||||
}
|
||||
|
||||
static void KernelDebug([[maybe_unused]] Core::System& system,
|
||||
@@ -1893,7 +1868,7 @@ static u64 GetSystemTick(Core::System& system) {
|
||||
}
|
||||
|
||||
static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) {
|
||||
u64 time = GetSystemTick(system);
|
||||
const auto time = GetSystemTick(system);
|
||||
*time_low = static_cast<u32>(time);
|
||||
*time_high = static_cast<u32>(time >> 32);
|
||||
}
|
||||
@@ -1984,8 +1959,7 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd
|
||||
|
||||
static ResultCode CreateTransferMemory32(Core::System& system, Handle* handle, u32 addr, u32 size,
|
||||
u32 permissions) {
|
||||
return CreateTransferMemory(system, handle, static_cast<VAddr>(addr),
|
||||
static_cast<std::size_t>(size), permissions);
|
||||
return CreateTransferMemory(system, handle, addr, size, permissions);
|
||||
}
|
||||
|
||||
static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, u32* core,
|
||||
@@ -2003,7 +1977,7 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle,
|
||||
}
|
||||
|
||||
*core = thread->GetIdealCore();
|
||||
*mask = thread->GetAffinityMask();
|
||||
*mask = thread->GetAffinityMask().GetAffinityMask();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -2075,8 +2049,7 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
|
||||
|
||||
static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, u32 core,
|
||||
u32 affinity_mask_low, u32 affinity_mask_high) {
|
||||
const u64 affinity_mask =
|
||||
static_cast<u64>(affinity_mask_low) | (static_cast<u64>(affinity_mask_high) << 32);
|
||||
const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32);
|
||||
return SetThreadCoreMask(system, thread_handle, core, affinity_mask);
|
||||
}
|
||||
|
||||
@@ -2341,9 +2314,10 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode FlushProcessDataCache32(Core::System& system, Handle handle, u32 address,
|
||||
u32 size) {
|
||||
// Note(Blinkhawk): For emulation purposes of the data cache this is mostly a nope
|
||||
static ResultCode FlushProcessDataCache32([[maybe_unused]] Core::System& system,
|
||||
[[maybe_unused]] Handle handle,
|
||||
[[maybe_unused]] u32 address, [[maybe_unused]] u32 size) {
|
||||
// Note(Blinkhawk): For emulation purposes of the data cache this is mostly a no-op,
|
||||
// as all emulation is done in the same cache level in host architecture, thus data cache
|
||||
// does not need flushing.
|
||||
LOG_DEBUG(Kernel_SVC, "called");
|
||||
@@ -2639,7 +2613,7 @@ void Call(Core::System& system, u32 immediate) {
|
||||
auto& kernel = system.Kernel();
|
||||
kernel.EnterSVCProfile();
|
||||
|
||||
auto* thread = system.CurrentScheduler().GetCurrentThread();
|
||||
auto* thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||
thread->SetContinuousOnSVC(true);
|
||||
|
||||
const FunctionDef* info = system.CurrentProcess()->Is64BitProcess() ? GetSVCInfo64(immediate)
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/synchronization.h"
|
||||
#include "core/hle/kernel/synchronization_object.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
@@ -18,7 +19,7 @@ Synchronization::Synchronization(Core::System& system) : system{system} {}
|
||||
|
||||
void Synchronization::SignalObject(SynchronizationObject& obj) const {
|
||||
auto& kernel = system.Kernel();
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
if (obj.IsSignaled()) {
|
||||
for (auto thread : obj.GetWaitingThreads()) {
|
||||
if (thread->GetSchedulingStatus() == ThreadSchedStatus::Paused) {
|
||||
@@ -37,10 +38,10 @@ void Synchronization::SignalObject(SynchronizationObject& obj) const {
|
||||
std::pair<ResultCode, Handle> Synchronization::WaitFor(
|
||||
std::vector<std::shared_ptr<SynchronizationObject>>& sync_objects, s64 nano_seconds) {
|
||||
auto& kernel = system.Kernel();
|
||||
auto* const thread = system.CurrentScheduler().GetCurrentThread();
|
||||
auto* const thread = kernel.CurrentScheduler()->GetCurrentThread();
|
||||
Handle event_handle = InvalidHandle;
|
||||
{
|
||||
SchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds);
|
||||
KScopedSchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds);
|
||||
const auto itr =
|
||||
std::find_if(sync_objects.begin(), sync_objects.end(),
|
||||
[thread](const std::shared_ptr<SynchronizationObject>& object) {
|
||||
@@ -89,7 +90,7 @@ std::pair<ResultCode, Handle> Synchronization::WaitFor(
|
||||
}
|
||||
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
ResultCode signaling_result = thread->GetSignalingResult();
|
||||
SynchronizationObject* signaling_object = thread->GetSignalingObject();
|
||||
thread->SetSynchronizationObjects(nullptr);
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
#include "core/hardware_properties.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/time_manager.h"
|
||||
#include "core/hle/result.h"
|
||||
@@ -50,7 +51,7 @@ Thread::~Thread() = default;
|
||||
|
||||
void Thread::Stop() {
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
SetStatus(ThreadStatus::Dead);
|
||||
Signal();
|
||||
kernel.GlobalHandleTable().Close(global_handle);
|
||||
@@ -67,7 +68,7 @@ void Thread::Stop() {
|
||||
}
|
||||
|
||||
void Thread::ResumeFromWait() {
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
switch (status) {
|
||||
case ThreadStatus::Paused:
|
||||
case ThreadStatus::WaitSynch:
|
||||
@@ -99,19 +100,18 @@ void Thread::ResumeFromWait() {
|
||||
}
|
||||
|
||||
void Thread::OnWakeUp() {
|
||||
SchedulerLock lock(kernel);
|
||||
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
SetStatus(ThreadStatus::Ready);
|
||||
}
|
||||
|
||||
ResultCode Thread::Start() {
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
SetStatus(ThreadStatus::Ready);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void Thread::CancelWait() {
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
if (GetSchedulingStatus() != ThreadSchedStatus::Paused || !is_waiting_on_sync) {
|
||||
is_sync_cancelled = true;
|
||||
return;
|
||||
@@ -186,12 +186,14 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadTy
|
||||
thread->status = ThreadStatus::Dormant;
|
||||
thread->entry_point = entry_point;
|
||||
thread->stack_top = stack_top;
|
||||
thread->disable_count = 1;
|
||||
thread->tpidr_el0 = 0;
|
||||
thread->nominal_priority = thread->current_priority = priority;
|
||||
thread->last_running_ticks = 0;
|
||||
thread->schedule_count = -1;
|
||||
thread->last_scheduled_tick = 0;
|
||||
thread->processor_id = processor_id;
|
||||
thread->ideal_core = processor_id;
|
||||
thread->affinity_mask = 1ULL << processor_id;
|
||||
thread->affinity_mask.SetAffinity(processor_id, true);
|
||||
thread->wait_objects = nullptr;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
@@ -201,7 +203,7 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadTy
|
||||
thread->owner_process = owner_process;
|
||||
thread->type = type_flags;
|
||||
if ((type_flags & THREADTYPE_IDLE) == 0) {
|
||||
auto& scheduler = kernel.GlobalScheduler();
|
||||
auto& scheduler = kernel.GlobalSchedulerContext();
|
||||
scheduler.AddThread(thread);
|
||||
}
|
||||
if (owner_process) {
|
||||
@@ -225,7 +227,7 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(Core::System& system, ThreadTy
|
||||
}
|
||||
|
||||
void Thread::SetPriority(u32 priority) {
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
|
||||
"Invalid priority value.");
|
||||
nominal_priority = priority;
|
||||
@@ -362,7 +364,7 @@ bool Thread::InvokeHLECallback(std::shared_ptr<Thread> thread) {
|
||||
}
|
||||
|
||||
ResultCode Thread::SetActivity(ThreadActivity value) {
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
|
||||
auto sched_status = GetSchedulingStatus();
|
||||
|
||||
@@ -391,7 +393,7 @@ ResultCode Thread::SetActivity(ThreadActivity value) {
|
||||
ResultCode Thread::Sleep(s64 nanoseconds) {
|
||||
Handle event_handle{};
|
||||
{
|
||||
SchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds);
|
||||
KScopedSchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds);
|
||||
SetStatus(ThreadStatus::WaitSleep);
|
||||
}
|
||||
|
||||
@@ -402,39 +404,12 @@ ResultCode Thread::Sleep(s64 nanoseconds) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
std::pair<ResultCode, bool> Thread::YieldSimple() {
|
||||
bool is_redundant = false;
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
is_redundant = kernel.GlobalScheduler().YieldThread(this);
|
||||
}
|
||||
return {RESULT_SUCCESS, is_redundant};
|
||||
}
|
||||
|
||||
std::pair<ResultCode, bool> Thread::YieldAndBalanceLoad() {
|
||||
bool is_redundant = false;
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
is_redundant = kernel.GlobalScheduler().YieldThreadAndBalanceLoad(this);
|
||||
}
|
||||
return {RESULT_SUCCESS, is_redundant};
|
||||
}
|
||||
|
||||
std::pair<ResultCode, bool> Thread::YieldAndWaitForLoadBalancing() {
|
||||
bool is_redundant = false;
|
||||
{
|
||||
SchedulerLock lock(kernel);
|
||||
is_redundant = kernel.GlobalScheduler().YieldThreadAndWaitForLoadBalancing(this);
|
||||
}
|
||||
return {RESULT_SUCCESS, is_redundant};
|
||||
}
|
||||
|
||||
void Thread::AddSchedulingFlag(ThreadSchedFlags flag) {
|
||||
const u32 old_state = scheduling_state;
|
||||
pausing_state |= static_cast<u32>(flag);
|
||||
const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
|
||||
scheduling_state = base_scheduling | pausing_state;
|
||||
kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
|
||||
KScheduler::OnThreadStateChanged(kernel, this, old_state);
|
||||
}
|
||||
|
||||
void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) {
|
||||
@@ -442,23 +417,24 @@ void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) {
|
||||
pausing_state &= ~static_cast<u32>(flag);
|
||||
const u32 base_scheduling = static_cast<u32>(GetSchedulingStatus());
|
||||
scheduling_state = base_scheduling | pausing_state;
|
||||
kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
|
||||
KScheduler::OnThreadStateChanged(kernel, this, old_state);
|
||||
}
|
||||
|
||||
void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) {
|
||||
const u32 old_state = scheduling_state;
|
||||
scheduling_state = (scheduling_state & static_cast<u32>(ThreadSchedMasks::HighMask)) |
|
||||
static_cast<u32>(new_status);
|
||||
kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state);
|
||||
KScheduler::OnThreadStateChanged(kernel, this, old_state);
|
||||
}
|
||||
|
||||
void Thread::SetCurrentPriority(u32 new_priority) {
|
||||
const u32 old_priority = std::exchange(current_priority, new_priority);
|
||||
kernel.GlobalScheduler().AdjustSchedulingOnPriority(this, old_priority);
|
||||
KScheduler::OnThreadPriorityChanged(kernel, this, kernel.CurrentScheduler()->GetCurrentThread(),
|
||||
old_priority);
|
||||
}
|
||||
|
||||
ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
|
||||
SchedulerLock lock(kernel);
|
||||
KScopedSchedulerLock lock(kernel);
|
||||
const auto HighestSetCore = [](u64 mask, u32 max_cores) {
|
||||
for (s32 core = static_cast<s32>(max_cores - 1); core >= 0; core--) {
|
||||
if (((mask >> core) & 1) != 0) {
|
||||
@@ -479,20 +455,21 @@ ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) {
|
||||
}
|
||||
if (use_override) {
|
||||
ideal_core_override = new_core;
|
||||
affinity_mask_override = new_affinity_mask;
|
||||
} else {
|
||||
const u64 old_affinity_mask = std::exchange(affinity_mask, new_affinity_mask);
|
||||
const auto old_affinity_mask = affinity_mask;
|
||||
affinity_mask.SetAffinityMask(new_affinity_mask);
|
||||
ideal_core = new_core;
|
||||
if (old_affinity_mask != new_affinity_mask) {
|
||||
if (old_affinity_mask.GetAffinityMask() != new_affinity_mask) {
|
||||
const s32 old_core = processor_id;
|
||||
if (processor_id >= 0 && ((affinity_mask >> processor_id) & 1) == 0) {
|
||||
if (processor_id >= 0 && !affinity_mask.GetAffinity(processor_id)) {
|
||||
if (static_cast<s32>(ideal_core) < 0) {
|
||||
processor_id = HighestSetCore(affinity_mask, Core::Hardware::NUM_CPU_CORES);
|
||||
processor_id = HighestSetCore(affinity_mask.GetAffinityMask(),
|
||||
Core::Hardware::NUM_CPU_CORES);
|
||||
} else {
|
||||
processor_id = ideal_core;
|
||||
}
|
||||
}
|
||||
kernel.GlobalScheduler().AdjustSchedulingOnAffinity(this, old_affinity_mask, old_core);
|
||||
KScheduler::OnThreadAffinityMaskChanged(kernel, this, old_affinity_mask, old_core);
|
||||
}
|
||||
}
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/spin_lock.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/hle/kernel/k_affinity_mask.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/synchronization_object.h"
|
||||
#include "core/hle/result.h"
|
||||
@@ -27,10 +29,10 @@ class System;
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class GlobalScheduler;
|
||||
class GlobalSchedulerContext;
|
||||
class KernelCore;
|
||||
class Process;
|
||||
class Scheduler;
|
||||
class KScheduler;
|
||||
|
||||
enum ThreadPriority : u32 {
|
||||
THREADPRIO_HIGHEST = 0, ///< Highest thread priority
|
||||
@@ -345,8 +347,12 @@ public:
|
||||
|
||||
void SetStatus(ThreadStatus new_status);
|
||||
|
||||
u64 GetLastRunningTicks() const {
|
||||
return last_running_ticks;
|
||||
s64 GetLastScheduledTick() const {
|
||||
return this->last_scheduled_tick;
|
||||
}
|
||||
|
||||
void SetLastScheduledTick(s64 tick) {
|
||||
this->last_scheduled_tick = tick;
|
||||
}
|
||||
|
||||
u64 GetTotalCPUTimeTicks() const {
|
||||
@@ -361,10 +367,18 @@ public:
|
||||
return processor_id;
|
||||
}
|
||||
|
||||
s32 GetActiveCore() const {
|
||||
return GetProcessorID();
|
||||
}
|
||||
|
||||
void SetProcessorID(s32 new_core) {
|
||||
processor_id = new_core;
|
||||
}
|
||||
|
||||
void SetActiveCore(s32 new_core) {
|
||||
processor_id = new_core;
|
||||
}
|
||||
|
||||
Process* GetOwnerProcess() {
|
||||
return owner_process;
|
||||
}
|
||||
@@ -469,7 +483,7 @@ public:
|
||||
return ideal_core;
|
||||
}
|
||||
|
||||
u64 GetAffinityMask() const {
|
||||
const KAffinityMask& GetAffinityMask() const {
|
||||
return affinity_mask;
|
||||
}
|
||||
|
||||
@@ -478,21 +492,12 @@ public:
|
||||
/// Sleeps this thread for the given amount of nanoseconds.
|
||||
ResultCode Sleep(s64 nanoseconds);
|
||||
|
||||
/// Yields this thread without rebalancing loads.
|
||||
std::pair<ResultCode, bool> YieldSimple();
|
||||
|
||||
/// Yields this thread and does a load rebalancing.
|
||||
std::pair<ResultCode, bool> YieldAndBalanceLoad();
|
||||
|
||||
/// Yields this thread and if the core is left idle, loads are rebalanced
|
||||
std::pair<ResultCode, bool> YieldAndWaitForLoadBalancing();
|
||||
|
||||
void IncrementYieldCount() {
|
||||
yield_count++;
|
||||
s64 GetYieldScheduleCount() const {
|
||||
return this->schedule_count;
|
||||
}
|
||||
|
||||
u64 GetYieldCount() const {
|
||||
return yield_count;
|
||||
void SetYieldScheduleCount(s64 count) {
|
||||
this->schedule_count = count;
|
||||
}
|
||||
|
||||
ThreadSchedStatus GetSchedulingStatus() const {
|
||||
@@ -568,9 +573,59 @@ public:
|
||||
return has_exited;
|
||||
}
|
||||
|
||||
class QueueEntry {
|
||||
public:
|
||||
constexpr QueueEntry() = default;
|
||||
|
||||
constexpr void Initialize() {
|
||||
this->prev = nullptr;
|
||||
this->next = nullptr;
|
||||
}
|
||||
|
||||
constexpr Thread* GetPrev() const {
|
||||
return this->prev;
|
||||
}
|
||||
constexpr Thread* GetNext() const {
|
||||
return this->next;
|
||||
}
|
||||
constexpr void SetPrev(Thread* thread) {
|
||||
this->prev = thread;
|
||||
}
|
||||
constexpr void SetNext(Thread* thread) {
|
||||
this->next = thread;
|
||||
}
|
||||
|
||||
private:
|
||||
Thread* prev{};
|
||||
Thread* next{};
|
||||
};
|
||||
|
||||
QueueEntry& GetPriorityQueueEntry(s32 core) {
|
||||
return this->per_core_priority_queue_entry[core];
|
||||
}
|
||||
|
||||
const QueueEntry& GetPriorityQueueEntry(s32 core) const {
|
||||
return this->per_core_priority_queue_entry[core];
|
||||
}
|
||||
|
||||
s32 GetDisableDispatchCount() const {
|
||||
return disable_count;
|
||||
}
|
||||
|
||||
void DisableDispatch() {
|
||||
ASSERT(GetDisableDispatchCount() >= 0);
|
||||
disable_count++;
|
||||
}
|
||||
|
||||
void EnableDispatch() {
|
||||
ASSERT(GetDisableDispatchCount() > 0);
|
||||
disable_count--;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class GlobalScheduler;
|
||||
friend class Scheduler;
|
||||
friend class GlobalSchedulerContext;
|
||||
friend class KScheduler;
|
||||
friend class Process;
|
||||
|
||||
void SetSchedulingStatus(ThreadSchedStatus new_status);
|
||||
void AddSchedulingFlag(ThreadSchedFlags flag);
|
||||
@@ -583,12 +638,14 @@ private:
|
||||
ThreadContext64 context_64{};
|
||||
std::shared_ptr<Common::Fiber> host_context{};
|
||||
|
||||
u64 thread_id = 0;
|
||||
|
||||
ThreadStatus status = ThreadStatus::Dormant;
|
||||
u32 scheduling_state = 0;
|
||||
|
||||
u64 thread_id = 0;
|
||||
|
||||
VAddr entry_point = 0;
|
||||
VAddr stack_top = 0;
|
||||
std::atomic_int disable_count = 0;
|
||||
|
||||
ThreadType type;
|
||||
|
||||
@@ -602,9 +659,8 @@ private:
|
||||
u32 current_priority = 0;
|
||||
|
||||
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
u64 yield_count = 0; ///< Number of redundant yields carried by this thread.
|
||||
///< a redundant yield is one where no scheduling is changed
|
||||
s64 schedule_count{};
|
||||
s64 last_scheduled_tick{};
|
||||
|
||||
s32 processor_id = 0;
|
||||
|
||||
@@ -646,16 +702,16 @@ private:
|
||||
Handle hle_time_event;
|
||||
SynchronizationObject* hle_object;
|
||||
|
||||
Scheduler* scheduler = nullptr;
|
||||
KScheduler* scheduler = nullptr;
|
||||
|
||||
std::array<QueueEntry, Core::Hardware::NUM_CPU_CORES> per_core_priority_queue_entry{};
|
||||
|
||||
u32 ideal_core{0xFFFFFFFF};
|
||||
u64 affinity_mask{0x1};
|
||||
KAffinityMask affinity_mask{};
|
||||
|
||||
s32 ideal_core_override = -1;
|
||||
u64 affinity_mask_override = 0x1;
|
||||
u32 affinity_override_count = 0;
|
||||
|
||||
u32 scheduling_state = 0;
|
||||
u32 pausing_state = 0;
|
||||
bool is_running = false;
|
||||
bool is_waiting_on_sync = false;
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/time_manager.h"
|
||||
|
||||
@@ -18,12 +18,18 @@ TimeManager::TimeManager(Core::System& system_) : system{system_} {
|
||||
time_manager_event_type = Core::Timing::CreateEvent(
|
||||
"Kernel::TimeManagerCallback",
|
||||
[this](std::uintptr_t thread_handle, std::chrono::nanoseconds) {
|
||||
const SchedulerLock lock(system.Kernel());
|
||||
const KScopedSchedulerLock lock(system.Kernel());
|
||||
const auto proper_handle = static_cast<Handle>(thread_handle);
|
||||
if (cancelled_events[proper_handle]) {
|
||||
return;
|
||||
|
||||
std::shared_ptr<Thread> thread;
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
if (cancelled_events[proper_handle]) {
|
||||
return;
|
||||
}
|
||||
thread = system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
|
||||
}
|
||||
auto thread = this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle);
|
||||
|
||||
if (thread) {
|
||||
// Thread can be null if process has exited
|
||||
thread->OnWakeUp();
|
||||
@@ -56,6 +62,7 @@ void TimeManager::UnscheduleTimeEvent(Handle event_handle) {
|
||||
}
|
||||
|
||||
void TimeManager::CancelTimeEvent(Thread* time_task) {
|
||||
std::lock_guard lock{mutex};
|
||||
const Handle event_handle = time_task->GetGlobalHandle();
|
||||
UnscheduleTimeEvent(event_handle);
|
||||
}
|
||||
|
||||
@@ -142,14 +142,14 @@ void Applet::Initialize() {
|
||||
|
||||
AppletFrontendSet::AppletFrontendSet() = default;
|
||||
|
||||
AppletFrontendSet::AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce,
|
||||
ErrorApplet error, ParentalControlsApplet parental_controls,
|
||||
PhotoViewer photo_viewer, ProfileSelect profile_select,
|
||||
SoftwareKeyboard software_keyboard, WebBrowser web_browser)
|
||||
: controller{std::move(controller)}, e_commerce{std::move(e_commerce)}, error{std::move(error)},
|
||||
parental_controls{std::move(parental_controls)}, photo_viewer{std::move(photo_viewer)},
|
||||
profile_select{std::move(profile_select)}, software_keyboard{std::move(software_keyboard)},
|
||||
web_browser{std::move(web_browser)} {}
|
||||
AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
|
||||
ParentalControlsApplet parental_controls_applet,
|
||||
PhotoViewer photo_viewer_, ProfileSelect profile_select_,
|
||||
SoftwareKeyboard software_keyboard_, WebBrowser web_browser_)
|
||||
: controller{std::move(controller_applet)}, error{std::move(error_applet)},
|
||||
parental_controls{std::move(parental_controls_applet)},
|
||||
photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)},
|
||||
software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {}
|
||||
|
||||
AppletFrontendSet::~AppletFrontendSet() = default;
|
||||
|
||||
@@ -170,10 +170,6 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
|
||||
frontend.controller = std::move(set.controller);
|
||||
}
|
||||
|
||||
if (set.e_commerce != nullptr) {
|
||||
frontend.e_commerce = std::move(set.e_commerce);
|
||||
}
|
||||
|
||||
if (set.error != nullptr) {
|
||||
frontend.error = std::move(set.error);
|
||||
}
|
||||
@@ -210,10 +206,6 @@ void AppletManager::SetDefaultAppletsIfMissing() {
|
||||
std::make_unique<Core::Frontend::DefaultControllerApplet>(system.ServiceManager());
|
||||
}
|
||||
|
||||
if (frontend.e_commerce == nullptr) {
|
||||
frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
|
||||
}
|
||||
|
||||
if (frontend.error == nullptr) {
|
||||
frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
|
||||
}
|
||||
@@ -257,13 +249,14 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
|
||||
return std::make_shared<ProfileSelect>(system, *frontend.profile_select);
|
||||
case AppletId::SoftwareKeyboard:
|
||||
return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard);
|
||||
case AppletId::Web:
|
||||
case AppletId::Shop:
|
||||
case AppletId::OfflineWeb:
|
||||
case AppletId::LoginShare:
|
||||
case AppletId::WebAuth:
|
||||
return std::make_shared<WebBrowser>(system, *frontend.web_browser);
|
||||
case AppletId::PhotoViewer:
|
||||
return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer);
|
||||
case AppletId::LibAppletShop:
|
||||
return std::make_shared<WebBrowser>(system, *frontend.web_browser,
|
||||
frontend.e_commerce.get());
|
||||
case AppletId::LibAppletOff:
|
||||
return std::make_shared<WebBrowser>(system, *frontend.web_browser);
|
||||
default:
|
||||
UNIMPLEMENTED_MSG(
|
||||
"No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
|
||||
|
||||
@@ -50,13 +50,13 @@ enum class AppletId : u32 {
|
||||
ProfileSelect = 0x10,
|
||||
SoftwareKeyboard = 0x11,
|
||||
MiiEdit = 0x12,
|
||||
LibAppletWeb = 0x13,
|
||||
LibAppletShop = 0x14,
|
||||
Web = 0x13,
|
||||
Shop = 0x14,
|
||||
PhotoViewer = 0x15,
|
||||
Settings = 0x16,
|
||||
LibAppletOff = 0x17,
|
||||
LibAppletWhitelisted = 0x18,
|
||||
LibAppletAuth = 0x19,
|
||||
OfflineWeb = 0x17,
|
||||
LoginShare = 0x18,
|
||||
WebAuth = 0x19,
|
||||
MyPage = 0x1A,
|
||||
};
|
||||
|
||||
@@ -157,7 +157,6 @@ protected:
|
||||
|
||||
struct AppletFrontendSet {
|
||||
using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
|
||||
using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
|
||||
using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
|
||||
using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
|
||||
using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
|
||||
@@ -166,10 +165,10 @@ struct AppletFrontendSet {
|
||||
using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
|
||||
|
||||
AppletFrontendSet();
|
||||
AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, ErrorApplet error,
|
||||
ParentalControlsApplet parental_controls, PhotoViewer photo_viewer,
|
||||
ProfileSelect profile_select, SoftwareKeyboard software_keyboard,
|
||||
WebBrowser web_browser);
|
||||
AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
|
||||
ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_,
|
||||
ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_,
|
||||
WebBrowser web_browser_);
|
||||
~AppletFrontendSet();
|
||||
|
||||
AppletFrontendSet(const AppletFrontendSet&) = delete;
|
||||
@@ -179,7 +178,6 @@ struct AppletFrontendSet {
|
||||
AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
|
||||
|
||||
ControllerApplet controller;
|
||||
ECommerceApplet e_commerce;
|
||||
ErrorApplet error;
|
||||
ParentalControlsApplet parental_controls;
|
||||
PhotoViewer photo_viewer;
|
||||
|
||||
@@ -1,238 +1,271 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.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/system_archive/system_archive.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/frontend/applets/general_frontend.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applets/web_browser.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/hle/service/ns/pl_u.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
enum class WebArgTLVType : u16 {
|
||||
InitialURL = 0x1,
|
||||
ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name.
|
||||
CallbackURL = 0x3,
|
||||
CallbackableURL = 0x4,
|
||||
ApplicationID = 0x5,
|
||||
DocumentPath = 0x6,
|
||||
DocumentKind = 0x7,
|
||||
SystemDataID = 0x8,
|
||||
ShareStartPage = 0x9,
|
||||
Whitelist = 0xA,
|
||||
News = 0xB,
|
||||
UserID = 0xE,
|
||||
AlbumEntry0 = 0xF,
|
||||
ScreenShotEnabled = 0x10,
|
||||
EcClientCertEnabled = 0x11,
|
||||
Unk12 = 0x12,
|
||||
PlayReportEnabled = 0x13,
|
||||
Unk14 = 0x14,
|
||||
Unk15 = 0x15,
|
||||
BootDisplayKind = 0x17,
|
||||
BackgroundKind = 0x18,
|
||||
FooterEnabled = 0x19,
|
||||
PointerEnabled = 0x1A,
|
||||
LeftStickMode = 0x1B,
|
||||
KeyRepeatFrame1 = 0x1C,
|
||||
KeyRepeatFrame2 = 0x1D,
|
||||
BootAsMediaPlayerInv = 0x1E,
|
||||
DisplayUrlKind = 0x1F,
|
||||
BootAsMediaPlayer = 0x21,
|
||||
ShopJumpEnabled = 0x22,
|
||||
MediaAutoPlayEnabled = 0x23,
|
||||
LobbyParameter = 0x24,
|
||||
ApplicationAlbumEntry = 0x26,
|
||||
JsExtensionEnabled = 0x27,
|
||||
AdditionalCommentText = 0x28,
|
||||
TouchEnabledOnContents = 0x29,
|
||||
UserAgentAdditionalString = 0x2A,
|
||||
AdditionalMediaData0 = 0x2B,
|
||||
MediaPlayerAutoCloseEnabled = 0x2C,
|
||||
PageCacheEnabled = 0x2D,
|
||||
WebAudioEnabled = 0x2E,
|
||||
Unk2F = 0x2F,
|
||||
YouTubeVideoWhitelist = 0x31,
|
||||
FooterFixedKind = 0x32,
|
||||
PageFadeEnabled = 0x33,
|
||||
MediaCreatorApplicationRatingAge = 0x34,
|
||||
BootLoadingIconEnabled = 0x35,
|
||||
PageScrollIndicationEnabled = 0x36,
|
||||
MediaPlayerSpeedControlEnabled = 0x37,
|
||||
AlbumEntry1 = 0x38,
|
||||
AlbumEntry2 = 0x39,
|
||||
AlbumEntry3 = 0x3A,
|
||||
AdditionalMediaData1 = 0x3B,
|
||||
AdditionalMediaData2 = 0x3C,
|
||||
AdditionalMediaData3 = 0x3D,
|
||||
BootFooterButton = 0x3E,
|
||||
OverrideWebAudioVolume = 0x3F,
|
||||
OverrideMediaAudioVolume = 0x40,
|
||||
BootMode = 0x41,
|
||||
WebSessionEnabled = 0x42,
|
||||
};
|
||||
|
||||
enum class ShimKind : u32 {
|
||||
Shop = 1,
|
||||
Login = 2,
|
||||
Offline = 3,
|
||||
Share = 4,
|
||||
Web = 5,
|
||||
Wifi = 6,
|
||||
Lobby = 7,
|
||||
};
|
||||
|
||||
enum class ShopWebTarget {
|
||||
ApplicationInfo,
|
||||
AddOnContentList,
|
||||
SubscriptionList,
|
||||
ConsumableItemList,
|
||||
Home,
|
||||
Settings,
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t SHIM_KIND_COUNT = 0x8;
|
||||
template <typename T>
|
||||
void ParseRawValue(T& value, const std::vector<u8>& data) {
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
"It's undefined behavior to use memcpy with non-trivially copyable objects");
|
||||
std::memcpy(&value, data.data(), data.size());
|
||||
}
|
||||
|
||||
struct WebArgHeader {
|
||||
u16 count;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
ShimKind kind;
|
||||
};
|
||||
static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
|
||||
template <typename T>
|
||||
T ParseRawValue(const std::vector<u8>& data) {
|
||||
T value;
|
||||
ParseRawValue(value, data);
|
||||
return value;
|
||||
}
|
||||
|
||||
struct WebArgTLV {
|
||||
WebArgTLVType type;
|
||||
u16 size;
|
||||
u32 offset;
|
||||
};
|
||||
static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size.");
|
||||
std::string ParseStringValue(const std::vector<u8>& data) {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()),
|
||||
data.size());
|
||||
}
|
||||
|
||||
struct WebCommonReturnValue {
|
||||
u32 result_code;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
std::array<char, 0x1000> last_url;
|
||||
u64 last_url_size;
|
||||
};
|
||||
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
|
||||
std::string GetMainURL(const std::string& url) {
|
||||
const auto index = url.find('?');
|
||||
|
||||
struct WebWifiPageArg {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
std::array<char, 0x100> connection_test_url;
|
||||
std::array<char, 0x400> initial_url;
|
||||
std::array<u8, 0x10> nifm_network_uuid;
|
||||
u32 nifm_requirement;
|
||||
};
|
||||
static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size.");
|
||||
if (index == std::string::npos) {
|
||||
return url;
|
||||
}
|
||||
|
||||
struct WebWifiReturnValue {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32 result;
|
||||
};
|
||||
static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size.");
|
||||
return url.substr(0, index);
|
||||
}
|
||||
|
||||
enum class OfflineWebSource : u32 {
|
||||
OfflineHtmlPage = 0x1,
|
||||
ApplicationLegalInformation = 0x2,
|
||||
SystemDataPage = 0x3,
|
||||
};
|
||||
WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
|
||||
std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
|
||||
|
||||
std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>& arg) {
|
||||
if (arg.size() < sizeof(WebArgHeader))
|
||||
if (web_arg.size() == sizeof(WebArgHeader)) {
|
||||
return {};
|
||||
|
||||
WebArgHeader header{};
|
||||
std::memcpy(&header, arg.data(), sizeof(WebArgHeader));
|
||||
|
||||
std::map<WebArgTLVType, std::vector<u8>> out;
|
||||
u64 offset = sizeof(WebArgHeader);
|
||||
for (std::size_t i = 0; i < header.count; ++i) {
|
||||
if (arg.size() < (offset + sizeof(WebArgTLV)))
|
||||
return out;
|
||||
|
||||
WebArgTLV tlv{};
|
||||
std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV));
|
||||
offset += sizeof(WebArgTLV);
|
||||
|
||||
offset += tlv.offset;
|
||||
if (arg.size() < (offset + tlv.size))
|
||||
return out;
|
||||
|
||||
std::vector<u8> data(tlv.size);
|
||||
std::memcpy(data.data(), arg.data() + offset, tlv.size);
|
||||
offset += tlv.size;
|
||||
|
||||
out.insert_or_assign(tlv.type, data);
|
||||
}
|
||||
|
||||
return out;
|
||||
WebArgInputTLVMap input_tlv_map;
|
||||
|
||||
u64 current_offset = sizeof(WebArgHeader);
|
||||
|
||||
for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) {
|
||||
if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) {
|
||||
return input_tlv_map;
|
||||
}
|
||||
|
||||
WebArgInputTLV input_tlv;
|
||||
std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV));
|
||||
|
||||
current_offset += sizeof(WebArgInputTLV);
|
||||
|
||||
if (web_arg.size() < current_offset + input_tlv.arg_data_size) {
|
||||
return input_tlv_map;
|
||||
}
|
||||
|
||||
std::vector<u8> data(input_tlv.arg_data_size);
|
||||
std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size);
|
||||
|
||||
current_offset += input_tlv.arg_data_size;
|
||||
|
||||
input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data));
|
||||
}
|
||||
|
||||
return input_tlv_map;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id,
|
||||
FileSys::ContentRecordType type) {
|
||||
const auto& installed{system.GetContentProvider()};
|
||||
const auto res = installed.GetEntry(title_id, type);
|
||||
FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id,
|
||||
FileSys::ContentRecordType nca_type) {
|
||||
if (nca_type == FileSys::ContentRecordType::Data) {
|
||||
const auto nca =
|
||||
system.GetFileSystemController().GetSystemNANDContents()->GetEntry(title_id, nca_type);
|
||||
|
||||
if (res != nullptr) {
|
||||
return res->GetRomFS();
|
||||
if (nca == nullptr) {
|
||||
LOG_ERROR(Service_AM,
|
||||
"NCA of type={} with title_id={:016X} is not found in the System NAND!",
|
||||
nca_type, title_id);
|
||||
return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
|
||||
}
|
||||
|
||||
return nca->GetRomFS();
|
||||
} else {
|
||||
const auto nca = system.GetContentProvider().GetEntry(title_id, nca_type);
|
||||
|
||||
if (nca == nullptr) {
|
||||
LOG_ERROR(Service_AM,
|
||||
"NCA of type={} with title_id={:016X} is not found in the ContentProvider!",
|
||||
nca_type, title_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
|
||||
return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type);
|
||||
}
|
||||
|
||||
if (type == FileSys::ContentRecordType::Data) {
|
||||
return FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
void ExtractSharedFonts(Core::System& system) {
|
||||
static constexpr std::array<const char*, 7> DECRYPTED_SHARED_FONTS{
|
||||
"FontStandard.ttf",
|
||||
"FontChineseSimplified.ttf",
|
||||
"FontExtendedChineseSimplified.ttf",
|
||||
"FontChineseTraditional.ttf",
|
||||
"FontKorean.ttf",
|
||||
"FontNintendoExtended.ttf",
|
||||
"FontNintendoExtended2.ttf",
|
||||
};
|
||||
|
||||
WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
|
||||
Core::Frontend::ECommerceApplet* frontend_e_commerce_)
|
||||
: Applet{system_.Kernel()}, frontend(frontend_),
|
||||
frontend_e_commerce(frontend_e_commerce_), system{system_} {}
|
||||
for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) {
|
||||
const auto fonts_dir = Common::FS::SanitizePath(
|
||||
fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)),
|
||||
Common::FS::DirectorySeparator::PlatformDefault);
|
||||
|
||||
const auto font_file_path =
|
||||
Common::FS::SanitizePath(fmt::format("{}/{}", fonts_dir, DECRYPTED_SHARED_FONTS[i]),
|
||||
Common::FS::DirectorySeparator::PlatformDefault);
|
||||
|
||||
if (Common::FS::Exists(font_file_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto font = NS::SHARED_FONTS[i];
|
||||
const auto font_title_id = static_cast<u64>(font.first);
|
||||
|
||||
const auto nca = system.GetFileSystemController().GetSystemNANDContents()->GetEntry(
|
||||
font_title_id, FileSys::ContentRecordType::Data);
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
|
||||
if (!nca) {
|
||||
romfs = FileSys::SystemArchive::SynthesizeSystemArchive(font_title_id);
|
||||
} else {
|
||||
romfs = nca->GetRomFS();
|
||||
}
|
||||
|
||||
if (!romfs) {
|
||||
LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} cannot be extracted!",
|
||||
font_title_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto extracted_romfs = FileSys::ExtractRomFS(romfs);
|
||||
|
||||
if (!extracted_romfs) {
|
||||
LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} failed to extract!",
|
||||
font_title_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto font_file = extracted_romfs->GetFile(font.second);
|
||||
|
||||
if (!font_file) {
|
||||
LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} has no font file \"{}\"!",
|
||||
font_title_id, font.second);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<u32> font_data_u32(font_file->GetSize() / sizeof(u32));
|
||||
font_file->ReadBytes<u32>(font_data_u32.data(), font_file->GetSize());
|
||||
|
||||
std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
|
||||
Common::swap32);
|
||||
|
||||
std::vector<u8> decrypted_data(font_file->GetSize() - 8);
|
||||
|
||||
NS::DecryptSharedFontToTTF(font_data_u32, decrypted_data);
|
||||
|
||||
FileSys::VirtualFile decrypted_font = std::make_shared<FileSys::VectorVfsFile>(
|
||||
std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]);
|
||||
|
||||
const auto temp_dir =
|
||||
system.GetFilesystem()->CreateDirectory(fonts_dir, FileSys::Mode::ReadWrite);
|
||||
|
||||
const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]);
|
||||
|
||||
FileSys::VfsRawCopy(decrypted_font, out_file);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WebBrowser::WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_)
|
||||
: Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
|
||||
|
||||
WebBrowser::~WebBrowser() = default;
|
||||
|
||||
void WebBrowser::Initialize() {
|
||||
Applet::Initialize();
|
||||
|
||||
complete = false;
|
||||
temporary_dir.clear();
|
||||
filename.clear();
|
||||
status = RESULT_SUCCESS;
|
||||
LOG_INFO(Service_AM, "Initializing Web Browser Applet.");
|
||||
|
||||
LOG_DEBUG(Service_AM,
|
||||
"Initializing Applet with common_args: arg_version={}, lib_version={}, "
|
||||
"play_startup_sound={}, size={}, system_tick={}, theme_color={}",
|
||||
common_args.arguments_version, common_args.library_version,
|
||||
common_args.play_startup_sound, common_args.size, common_args.system_tick,
|
||||
common_args.theme_color);
|
||||
|
||||
web_applet_version = WebAppletVersion{common_args.library_version};
|
||||
|
||||
const auto web_arg_storage = broker.PopNormalDataToApplet();
|
||||
ASSERT(web_arg_storage != nullptr);
|
||||
|
||||
const auto& web_arg = web_arg_storage->GetData();
|
||||
ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; });
|
||||
|
||||
ASSERT(web_arg.size() >= 0x8);
|
||||
std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind));
|
||||
web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header);
|
||||
|
||||
args = GetWebArguments(web_arg);
|
||||
LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}",
|
||||
web_arg_header.total_tlv_entries, web_arg_header.shim_kind);
|
||||
|
||||
InitializeInternal();
|
||||
ExtractSharedFonts(system);
|
||||
|
||||
switch (web_arg_header.shim_kind) {
|
||||
case ShimKind::Shop:
|
||||
InitializeShop();
|
||||
break;
|
||||
case ShimKind::Login:
|
||||
InitializeLogin();
|
||||
break;
|
||||
case ShimKind::Offline:
|
||||
InitializeOffline();
|
||||
break;
|
||||
case ShimKind::Share:
|
||||
InitializeShare();
|
||||
break;
|
||||
case ShimKind::Web:
|
||||
InitializeWeb();
|
||||
break;
|
||||
case ShimKind::Wifi:
|
||||
InitializeWifi();
|
||||
break;
|
||||
case ShimKind::Lobby:
|
||||
InitializeLobby();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool WebBrowser::TransactionComplete() const {
|
||||
@@ -244,315 +277,202 @@ ResultCode WebBrowser::GetStatus() const {
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteInteractive() {
|
||||
UNIMPLEMENTED_MSG("Unexpected interactive data recieved!");
|
||||
UNIMPLEMENTED_MSG("WebSession is not implemented");
|
||||
}
|
||||
|
||||
void WebBrowser::Execute() {
|
||||
if (complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != RESULT_SUCCESS) {
|
||||
complete = true;
|
||||
|
||||
// This is a workaround in order not to softlock yuzu when an error happens during the
|
||||
// webapplet init. In order to avoid an svcBreak, the status is set to RESULT_SUCCESS
|
||||
Finalize();
|
||||
status = RESULT_SUCCESS;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ExecuteInternal();
|
||||
}
|
||||
|
||||
void WebBrowser::UnpackRomFS() {
|
||||
if (unpacked)
|
||||
return;
|
||||
|
||||
ASSERT(offline_romfs != nullptr);
|
||||
const auto dir =
|
||||
FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
|
||||
const auto& vfs{system.GetFilesystem()};
|
||||
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
|
||||
FileSys::VfsRawCopyD(dir, temp_dir);
|
||||
|
||||
unpacked = true;
|
||||
}
|
||||
|
||||
void WebBrowser::Finalize() {
|
||||
complete = true;
|
||||
|
||||
WebCommonReturnValue out{};
|
||||
out.result_code = 0;
|
||||
out.last_url_size = 0;
|
||||
|
||||
std::vector<u8> data(sizeof(WebCommonReturnValue));
|
||||
std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue));
|
||||
|
||||
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(data)));
|
||||
broker.SignalStateChanged();
|
||||
|
||||
if (!temporary_dir.empty() && Common::FS::IsDirectory(temporary_dir)) {
|
||||
Common::FS::DeleteDirRecursively(temporary_dir);
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowser::InitializeInternal() {
|
||||
using WebAppletInitializer = void (WebBrowser::*)();
|
||||
|
||||
constexpr std::array<WebAppletInitializer, SHIM_KIND_COUNT> functions{
|
||||
nullptr, &WebBrowser::InitializeShop,
|
||||
nullptr, &WebBrowser::InitializeOffline,
|
||||
nullptr, nullptr,
|
||||
nullptr, nullptr,
|
||||
};
|
||||
|
||||
const auto index = static_cast<u32>(kind);
|
||||
|
||||
if (index > functions.size() || functions[index] == nullptr) {
|
||||
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto function = functions[index];
|
||||
(this->*function)();
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteInternal() {
|
||||
using WebAppletExecutor = void (WebBrowser::*)();
|
||||
|
||||
constexpr std::array<WebAppletExecutor, SHIM_KIND_COUNT> functions{
|
||||
nullptr, &WebBrowser::ExecuteShop,
|
||||
nullptr, &WebBrowser::ExecuteOffline,
|
||||
nullptr, nullptr,
|
||||
nullptr, nullptr,
|
||||
};
|
||||
|
||||
const auto index = static_cast<u32>(kind);
|
||||
|
||||
if (index > functions.size() || functions[index] == nullptr) {
|
||||
LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto function = functions[index];
|
||||
(this->*function)();
|
||||
}
|
||||
|
||||
void WebBrowser::InitializeShop() {
|
||||
if (frontend_e_commerce == nullptr) {
|
||||
LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!");
|
||||
status = RESULT_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto user_id_data = args.find(WebArgTLVType::UserID);
|
||||
|
||||
user_id = std::nullopt;
|
||||
if (user_id_data != args.end()) {
|
||||
user_id = u128{};
|
||||
std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128));
|
||||
}
|
||||
|
||||
const auto url = args.find(WebArgTLVType::ShopArgumentsURL);
|
||||
|
||||
if (url == args.end()) {
|
||||
LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!");
|
||||
status = RESULT_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> split_query;
|
||||
Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(url->second.data()), url->second.size()),
|
||||
'?', split_query);
|
||||
|
||||
// 2 -> Main URL '?' Query Parameters
|
||||
// Less is missing info, More is malformed
|
||||
if (split_query.size() != 2) {
|
||||
LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed");
|
||||
status = RESULT_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> queries;
|
||||
Common::SplitString(split_query[1], '&', queries);
|
||||
|
||||
const auto split_single_query =
|
||||
[](const std::string& in) -> std::pair<std::string, std::string> {
|
||||
const auto index = in.find('=');
|
||||
if (index == std::string::npos || index == in.size() - 1) {
|
||||
return {in, ""};
|
||||
}
|
||||
|
||||
return {in.substr(0, index), in.substr(index + 1)};
|
||||
};
|
||||
|
||||
std::transform(queries.begin(), queries.end(),
|
||||
std::inserter(shop_query, std::next(shop_query.begin())), split_single_query);
|
||||
|
||||
const auto scene = shop_query.find("scene");
|
||||
|
||||
if (scene == shop_query.end()) {
|
||||
LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!");
|
||||
status = RESULT_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
const std::map<std::string, ShopWebTarget, std::less<>> target_map{
|
||||
{"product_detail", ShopWebTarget::ApplicationInfo},
|
||||
{"aocs", ShopWebTarget::AddOnContentList},
|
||||
{"subscriptions", ShopWebTarget::SubscriptionList},
|
||||
{"consumption", ShopWebTarget::ConsumableItemList},
|
||||
{"settings", ShopWebTarget::Settings},
|
||||
{"top", ShopWebTarget::Home},
|
||||
};
|
||||
|
||||
const auto target = target_map.find(scene->second);
|
||||
if (target == target_map.end()) {
|
||||
LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second);
|
||||
status = RESULT_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
shop_web_target = target->second;
|
||||
|
||||
const auto title_id_data = shop_query.find("dst_app_id");
|
||||
if (title_id_data != shop_query.end()) {
|
||||
title_id = std::stoull(title_id_data->second, nullptr, 0x10);
|
||||
}
|
||||
|
||||
const auto mode_data = shop_query.find("mode");
|
||||
if (mode_data != shop_query.end()) {
|
||||
shop_full_display = mode_data->second == "full";
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowser::InitializeOffline() {
|
||||
if (args.find(WebArgTLVType::DocumentPath) == args.end() ||
|
||||
args.find(WebArgTLVType::DocumentKind) == args.end() ||
|
||||
args.find(WebArgTLVType::ApplicationID) == args.end()) {
|
||||
status = RESULT_UNKNOWN;
|
||||
LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!");
|
||||
}
|
||||
|
||||
const auto url_data = args[WebArgTLVType::DocumentPath];
|
||||
filename = Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(url_data.data()), url_data.size());
|
||||
|
||||
OfflineWebSource source;
|
||||
ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4);
|
||||
std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource));
|
||||
|
||||
constexpr std::array<const char*, 3> WEB_SOURCE_NAMES{
|
||||
"manual",
|
||||
"legal",
|
||||
"system",
|
||||
};
|
||||
|
||||
temporary_dir =
|
||||
Common::FS::SanitizePath(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) +
|
||||
"web_applet_" + WEB_SOURCE_NAMES[static_cast<u32>(source) - 1],
|
||||
Common::FS::DirectorySeparator::PlatformDefault);
|
||||
Common::FS::DeleteDirRecursively(temporary_dir);
|
||||
|
||||
u64 title_id = 0; // 0 corresponds to current process
|
||||
ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8);
|
||||
std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64));
|
||||
FileSys::ContentRecordType type = FileSys::ContentRecordType::Data;
|
||||
|
||||
switch (source) {
|
||||
case OfflineWebSource::OfflineHtmlPage:
|
||||
// While there is an AppID TLV field, in official SW this is always ignored.
|
||||
title_id = 0;
|
||||
type = FileSys::ContentRecordType::HtmlDocument;
|
||||
switch (web_arg_header.shim_kind) {
|
||||
case ShimKind::Shop:
|
||||
ExecuteShop();
|
||||
break;
|
||||
case OfflineWebSource::ApplicationLegalInformation:
|
||||
type = FileSys::ContentRecordType::LegalInformation;
|
||||
case ShimKind::Login:
|
||||
ExecuteLogin();
|
||||
break;
|
||||
case OfflineWebSource::SystemDataPage:
|
||||
type = FileSys::ContentRecordType::Data;
|
||||
case ShimKind::Offline:
|
||||
ExecuteOffline();
|
||||
break;
|
||||
}
|
||||
|
||||
if (title_id == 0) {
|
||||
title_id = system.CurrentProcess()->GetTitleID();
|
||||
}
|
||||
|
||||
offline_romfs = GetApplicationRomFS(system, title_id, type);
|
||||
if (offline_romfs == nullptr) {
|
||||
status = RESULT_UNKNOWN;
|
||||
LOG_ERROR(Service_AM, "Failed to find offline data for request!");
|
||||
}
|
||||
|
||||
std::string path_additional_directory;
|
||||
if (source == OfflineWebSource::OfflineHtmlPage) {
|
||||
path_additional_directory = std::string(DIR_SEP).append("html-document");
|
||||
}
|
||||
|
||||
filename =
|
||||
Common::FS::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename,
|
||||
Common::FS::DirectorySeparator::PlatformDefault);
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteShop() {
|
||||
const auto callback = [this]() { Finalize(); };
|
||||
|
||||
const auto check_optional_parameter = [this](const auto& p) {
|
||||
if (!p.has_value()) {
|
||||
LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!");
|
||||
status = RESULT_UNKNOWN;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
switch (shop_web_target) {
|
||||
case ShopWebTarget::ApplicationInfo:
|
||||
if (!check_optional_parameter(title_id))
|
||||
return;
|
||||
frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id,
|
||||
shop_full_display, shop_extra_parameter);
|
||||
case ShimKind::Share:
|
||||
ExecuteShare();
|
||||
break;
|
||||
case ShopWebTarget::AddOnContentList:
|
||||
if (!check_optional_parameter(title_id))
|
||||
return;
|
||||
frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display);
|
||||
case ShimKind::Web:
|
||||
ExecuteWeb();
|
||||
break;
|
||||
case ShopWebTarget::ConsumableItemList:
|
||||
if (!check_optional_parameter(title_id))
|
||||
return;
|
||||
frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id);
|
||||
case ShimKind::Wifi:
|
||||
ExecuteWifi();
|
||||
break;
|
||||
case ShopWebTarget::Home:
|
||||
if (!check_optional_parameter(user_id))
|
||||
return;
|
||||
if (!check_optional_parameter(shop_full_display))
|
||||
return;
|
||||
frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display);
|
||||
break;
|
||||
case ShopWebTarget::Settings:
|
||||
if (!check_optional_parameter(user_id))
|
||||
return;
|
||||
if (!check_optional_parameter(shop_full_display))
|
||||
return;
|
||||
frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display);
|
||||
break;
|
||||
case ShopWebTarget::SubscriptionList:
|
||||
if (!check_optional_parameter(title_id))
|
||||
return;
|
||||
frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id);
|
||||
case ShimKind::Lobby:
|
||||
ExecuteLobby();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
|
||||
WebBrowserExit(WebExitReason::EndButtonPressed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebBrowser::ExtractOfflineRomFS() {
|
||||
LOG_DEBUG(Service_AM, "Extracting RomFS to {}", offline_cache_dir);
|
||||
|
||||
const auto extracted_romfs_dir =
|
||||
FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
|
||||
|
||||
const auto temp_dir =
|
||||
system.GetFilesystem()->CreateDirectory(offline_cache_dir, FileSys::Mode::ReadWrite);
|
||||
|
||||
FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir);
|
||||
}
|
||||
|
||||
void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) {
|
||||
if ((web_arg_header.shim_kind == ShimKind::Share &&
|
||||
web_applet_version >= WebAppletVersion::Version196608) ||
|
||||
(web_arg_header.shim_kind == ShimKind::Web &&
|
||||
web_applet_version >= WebAppletVersion::Version524288)) {
|
||||
// TODO: Push Output TLVs instead of a WebCommonReturnValue
|
||||
}
|
||||
|
||||
WebCommonReturnValue web_common_return_value;
|
||||
|
||||
web_common_return_value.exit_reason = exit_reason;
|
||||
std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size());
|
||||
web_common_return_value.last_url_size = last_url.size();
|
||||
|
||||
LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}",
|
||||
exit_reason, last_url, last_url.size());
|
||||
|
||||
complete = true;
|
||||
std::vector<u8> out_data(sizeof(WebCommonReturnValue));
|
||||
std::memcpy(out_data.data(), &web_common_return_value, out_data.size());
|
||||
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
|
||||
broker.SignalStateChanged();
|
||||
}
|
||||
|
||||
bool WebBrowser::InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const {
|
||||
return web_arg_input_tlv_map.find(input_tlv_type) != web_arg_input_tlv_map.end();
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> WebBrowser::GetInputTLVData(WebArgInputTLVType input_tlv_type) {
|
||||
const auto map_it = web_arg_input_tlv_map.find(input_tlv_type);
|
||||
|
||||
if (map_it == web_arg_input_tlv_map.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return map_it->second;
|
||||
}
|
||||
|
||||
void WebBrowser::InitializeShop() {}
|
||||
|
||||
void WebBrowser::InitializeLogin() {}
|
||||
|
||||
void WebBrowser::InitializeOffline() {
|
||||
const auto document_path =
|
||||
ParseStringValue(GetInputTLVData(WebArgInputTLVType::DocumentPath).value());
|
||||
|
||||
const auto document_kind =
|
||||
ParseRawValue<DocumentKind>(GetInputTLVData(WebArgInputTLVType::DocumentKind).value());
|
||||
|
||||
std::string additional_paths;
|
||||
|
||||
switch (document_kind) {
|
||||
case DocumentKind::OfflineHtmlPage:
|
||||
default:
|
||||
title_id = system.CurrentProcess()->GetTitleID();
|
||||
nca_type = FileSys::ContentRecordType::HtmlDocument;
|
||||
additional_paths = "html-document";
|
||||
break;
|
||||
case DocumentKind::ApplicationLegalInformation:
|
||||
title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::ApplicationID).value());
|
||||
nca_type = FileSys::ContentRecordType::LegalInformation;
|
||||
break;
|
||||
case DocumentKind::SystemDataPage:
|
||||
title_id = ParseRawValue<u64>(GetInputTLVData(WebArgInputTLVType::SystemDataID).value());
|
||||
nca_type = FileSys::ContentRecordType::Data;
|
||||
break;
|
||||
}
|
||||
|
||||
static constexpr std::array<const char*, 3> RESOURCE_TYPES{
|
||||
"manual",
|
||||
"legal_information",
|
||||
"system_data",
|
||||
};
|
||||
|
||||
offline_cache_dir = Common::FS::SanitizePath(
|
||||
fmt::format("{}/offline_web_applet_{}/{:016X}",
|
||||
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir),
|
||||
RESOURCE_TYPES[static_cast<u32>(document_kind) - 1], title_id),
|
||||
Common::FS::DirectorySeparator::PlatformDefault);
|
||||
|
||||
offline_document = Common::FS::SanitizePath(
|
||||
fmt::format("{}/{}/{}", offline_cache_dir, additional_paths, document_path),
|
||||
Common::FS::DirectorySeparator::PlatformDefault);
|
||||
}
|
||||
|
||||
void WebBrowser::InitializeShare() {}
|
||||
|
||||
void WebBrowser::InitializeWeb() {
|
||||
external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value());
|
||||
}
|
||||
|
||||
void WebBrowser::InitializeWifi() {}
|
||||
|
||||
void WebBrowser::InitializeLobby() {}
|
||||
|
||||
void WebBrowser::ExecuteShop() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented");
|
||||
WebBrowserExit(WebExitReason::EndButtonPressed);
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteLogin() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented");
|
||||
WebBrowserExit(WebExitReason::EndButtonPressed);
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteOffline() {
|
||||
frontend.OpenPageLocal(
|
||||
filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
|
||||
const auto main_url = Common::FS::SanitizePath(GetMainURL(offline_document),
|
||||
Common::FS::DirectorySeparator::PlatformDefault);
|
||||
|
||||
if (!Common::FS::Exists(main_url)) {
|
||||
offline_romfs = GetOfflineRomFS(system, title_id, nca_type);
|
||||
|
||||
if (offline_romfs == nullptr) {
|
||||
LOG_ERROR(Service_AM,
|
||||
"RomFS with title_id={:016X} and nca_type={} cannot be extracted!", title_id,
|
||||
nca_type);
|
||||
WebBrowserExit(WebExitReason::WindowClosed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Service_AM, "Opening offline document at {}", offline_document);
|
||||
|
||||
frontend.OpenLocalWebPage(
|
||||
offline_document, [this] { ExtractOfflineRomFS(); },
|
||||
[this](WebExitReason exit_reason, std::string last_url) {
|
||||
WebBrowserExit(exit_reason, last_url);
|
||||
});
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteShare() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented");
|
||||
WebBrowserExit(WebExitReason::EndButtonPressed);
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteWeb() {
|
||||
LOG_INFO(Service_AM, "Opening external URL at {}", external_url);
|
||||
|
||||
frontend.OpenExternalWebPage(external_url,
|
||||
[this](WebExitReason exit_reason, std::string last_url) {
|
||||
WebBrowserExit(exit_reason, last_url);
|
||||
});
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteWifi() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented");
|
||||
WebBrowserExit(WebExitReason::EndButtonPressed);
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteLobby() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented");
|
||||
WebBrowserExit(WebExitReason::EndButtonPressed);
|
||||
}
|
||||
} // namespace Service::AM::Applets
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
#include "core/hle/service/am/applets/web_types.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
namespace FileSys {
|
||||
enum class ContentRecordType : u8;
|
||||
}
|
||||
|
||||
enum class ShimKind : u32;
|
||||
enum class ShopWebTarget;
|
||||
enum class WebArgTLVType : u16;
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
class WebBrowser final : public Applet {
|
||||
public:
|
||||
WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
|
||||
Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr);
|
||||
WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_);
|
||||
|
||||
~WebBrowser() override;
|
||||
|
||||
@@ -33,49 +36,50 @@ public:
|
||||
void ExecuteInteractive() override;
|
||||
void Execute() override;
|
||||
|
||||
// Callback to be fired when the frontend needs the manual RomFS unpacked to temporary
|
||||
// directory. This is a blocking call and may take a while as some manuals can be up to 100MB in
|
||||
// size. Attempting to access files at filename before invocation is likely to not work.
|
||||
void UnpackRomFS();
|
||||
void ExtractOfflineRomFS();
|
||||
|
||||
// Callback to be fired when the frontend is finished browsing. This will delete the temporary
|
||||
// manual RomFS extracted files, so ensure this is only called at actual finalization.
|
||||
void Finalize();
|
||||
void WebBrowserExit(WebExitReason exit_reason, std::string last_url = "");
|
||||
|
||||
private:
|
||||
void InitializeInternal();
|
||||
void ExecuteInternal();
|
||||
bool InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const;
|
||||
|
||||
// Specific initializers for the types of web applets
|
||||
std::optional<std::vector<u8>> GetInputTLVData(WebArgInputTLVType input_tlv_type);
|
||||
|
||||
// Initializers for the various types of browser applets
|
||||
void InitializeShop();
|
||||
void InitializeLogin();
|
||||
void InitializeOffline();
|
||||
void InitializeShare();
|
||||
void InitializeWeb();
|
||||
void InitializeWifi();
|
||||
void InitializeLobby();
|
||||
|
||||
// Specific executors for the types of web applets
|
||||
// Executors for the various types of browser applets
|
||||
void ExecuteShop();
|
||||
void ExecuteLogin();
|
||||
void ExecuteOffline();
|
||||
void ExecuteShare();
|
||||
void ExecuteWeb();
|
||||
void ExecuteWifi();
|
||||
void ExecuteLobby();
|
||||
|
||||
Core::Frontend::WebBrowserApplet& frontend;
|
||||
const Core::Frontend::WebBrowserApplet& frontend;
|
||||
|
||||
// Extra frontends for specialized functions
|
||||
Core::Frontend::ECommerceApplet* frontend_e_commerce;
|
||||
bool complete{false};
|
||||
ResultCode status{RESULT_SUCCESS};
|
||||
|
||||
bool complete = false;
|
||||
bool unpacked = false;
|
||||
ResultCode status = RESULT_SUCCESS;
|
||||
|
||||
ShimKind kind;
|
||||
std::map<WebArgTLVType, std::vector<u8>> args;
|
||||
WebAppletVersion web_applet_version;
|
||||
WebExitReason web_exit_reason;
|
||||
WebArgHeader web_arg_header;
|
||||
WebArgInputTLVMap web_arg_input_tlv_map;
|
||||
|
||||
u64 title_id;
|
||||
FileSys::ContentRecordType nca_type;
|
||||
std::string offline_cache_dir;
|
||||
std::string offline_document;
|
||||
FileSys::VirtualFile offline_romfs;
|
||||
std::string temporary_dir;
|
||||
std::string filename;
|
||||
|
||||
ShopWebTarget shop_web_target;
|
||||
std::map<std::string, std::string, std::less<>> shop_query;
|
||||
std::optional<u64> title_id = 0;
|
||||
std::optional<u128> user_id;
|
||||
std::optional<bool> shop_full_display;
|
||||
std::string shop_extra_parameter;
|
||||
std::string external_url;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
178
src/core/hle/service/am/applets/web_types.h
Normal file
178
src/core/hle/service/am/applets/web_types.h
Normal file
@@ -0,0 +1,178 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
enum class WebAppletVersion : u32_le {
|
||||
Version0 = 0x0, // Only used by WifiWebAuthApplet
|
||||
Version131072 = 0x20000, // 1.0.0 - 2.3.0
|
||||
Version196608 = 0x30000, // 3.0.0 - 4.1.0
|
||||
Version327680 = 0x50000, // 5.0.0 - 5.1.0
|
||||
Version393216 = 0x60000, // 6.0.0 - 7.0.1
|
||||
Version524288 = 0x80000, // 8.0.0+
|
||||
};
|
||||
|
||||
enum class ShimKind : u32 {
|
||||
Shop = 1,
|
||||
Login = 2,
|
||||
Offline = 3,
|
||||
Share = 4,
|
||||
Web = 5,
|
||||
Wifi = 6,
|
||||
Lobby = 7,
|
||||
};
|
||||
|
||||
enum class WebExitReason : u32 {
|
||||
EndButtonPressed = 0,
|
||||
BackButtonPressed = 1,
|
||||
ExitRequested = 2,
|
||||
CallbackURL = 3,
|
||||
WindowClosed = 4,
|
||||
ErrorDialog = 7,
|
||||
};
|
||||
|
||||
enum class WebArgInputTLVType : u16 {
|
||||
InitialURL = 0x1,
|
||||
CallbackURL = 0x3,
|
||||
CallbackableURL = 0x4,
|
||||
ApplicationID = 0x5,
|
||||
DocumentPath = 0x6,
|
||||
DocumentKind = 0x7,
|
||||
SystemDataID = 0x8,
|
||||
ShareStartPage = 0x9,
|
||||
Whitelist = 0xA,
|
||||
News = 0xB,
|
||||
UserID = 0xE,
|
||||
AlbumEntry0 = 0xF,
|
||||
ScreenShotEnabled = 0x10,
|
||||
EcClientCertEnabled = 0x11,
|
||||
PlayReportEnabled = 0x13,
|
||||
BootDisplayKind = 0x17,
|
||||
BackgroundKind = 0x18,
|
||||
FooterEnabled = 0x19,
|
||||
PointerEnabled = 0x1A,
|
||||
LeftStickMode = 0x1B,
|
||||
KeyRepeatFrame1 = 0x1C,
|
||||
KeyRepeatFrame2 = 0x1D,
|
||||
BootAsMediaPlayerInverted = 0x1E,
|
||||
DisplayURLKind = 0x1F,
|
||||
BootAsMediaPlayer = 0x21,
|
||||
ShopJumpEnabled = 0x22,
|
||||
MediaAutoPlayEnabled = 0x23,
|
||||
LobbyParameter = 0x24,
|
||||
ApplicationAlbumEntry = 0x26,
|
||||
JsExtensionEnabled = 0x27,
|
||||
AdditionalCommentText = 0x28,
|
||||
TouchEnabledOnContents = 0x29,
|
||||
UserAgentAdditionalString = 0x2A,
|
||||
AdditionalMediaData0 = 0x2B,
|
||||
MediaPlayerAutoCloseEnabled = 0x2C,
|
||||
PageCacheEnabled = 0x2D,
|
||||
WebAudioEnabled = 0x2E,
|
||||
YouTubeVideoWhitelist = 0x31,
|
||||
FooterFixedKind = 0x32,
|
||||
PageFadeEnabled = 0x33,
|
||||
MediaCreatorApplicationRatingAge = 0x34,
|
||||
BootLoadingIconEnabled = 0x35,
|
||||
PageScrollIndicatorEnabled = 0x36,
|
||||
MediaPlayerSpeedControlEnabled = 0x37,
|
||||
AlbumEntry1 = 0x38,
|
||||
AlbumEntry2 = 0x39,
|
||||
AlbumEntry3 = 0x3A,
|
||||
AdditionalMediaData1 = 0x3B,
|
||||
AdditionalMediaData2 = 0x3C,
|
||||
AdditionalMediaData3 = 0x3D,
|
||||
BootFooterButton = 0x3E,
|
||||
OverrideWebAudioVolume = 0x3F,
|
||||
OverrideMediaAudioVolume = 0x40,
|
||||
BootMode = 0x41,
|
||||
WebSessionEnabled = 0x42,
|
||||
MediaPlayerOfflineEnabled = 0x43,
|
||||
};
|
||||
|
||||
enum class WebArgOutputTLVType : u16 {
|
||||
ShareExitReason = 0x1,
|
||||
LastURL = 0x2,
|
||||
LastURLSize = 0x3,
|
||||
SharePostResult = 0x4,
|
||||
PostServiceName = 0x5,
|
||||
PostServiceNameSize = 0x6,
|
||||
PostID = 0x7,
|
||||
PostIDSize = 0x8,
|
||||
MediaPlayerAutoClosedByCompletion = 0x9,
|
||||
};
|
||||
|
||||
enum class DocumentKind : u32 {
|
||||
OfflineHtmlPage = 1,
|
||||
ApplicationLegalInformation = 2,
|
||||
SystemDataPage = 3,
|
||||
};
|
||||
|
||||
enum class ShareStartPage : u32 {
|
||||
Default,
|
||||
Settings,
|
||||
};
|
||||
|
||||
enum class BootDisplayKind : u32 {
|
||||
Default,
|
||||
White,
|
||||
Black,
|
||||
};
|
||||
|
||||
enum class BackgroundKind : u32 {
|
||||
Default,
|
||||
};
|
||||
|
||||
enum class LeftStickMode : u32 {
|
||||
Pointer,
|
||||
Cursor,
|
||||
};
|
||||
|
||||
enum class WebSessionBootMode : u32 {
|
||||
AllForeground,
|
||||
AllForegroundInitiallyHidden,
|
||||
};
|
||||
|
||||
struct WebArgHeader {
|
||||
u16 total_tlv_entries{};
|
||||
INSERT_PADDING_BYTES(2);
|
||||
ShimKind shim_kind{};
|
||||
};
|
||||
static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
|
||||
|
||||
struct WebArgInputTLV {
|
||||
WebArgInputTLVType input_tlv_type{};
|
||||
u16 arg_data_size{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
};
|
||||
static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size.");
|
||||
|
||||
struct WebArgOutputTLV {
|
||||
WebArgOutputTLVType output_tlv_type{};
|
||||
u16 arg_data_size{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
};
|
||||
static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size.");
|
||||
|
||||
struct WebCommonReturnValue {
|
||||
WebExitReason exit_reason{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
std::array<char, 0x1000> last_url{};
|
||||
u64 last_url_size{};
|
||||
};
|
||||
static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
|
||||
|
||||
using WebArgInputTLVMap = std::unordered_map<WebArgInputTLVType, std::vector<u8>>;
|
||||
|
||||
} // namespace Service::AM::Applets
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <vector>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
@@ -23,11 +24,8 @@
|
||||
|
||||
namespace Service::AOC {
|
||||
|
||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000;
|
||||
|
||||
static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
|
||||
return (title_id & DLC_BASE_TITLE_ID_MASK) == base;
|
||||
return FileSys::GetBaseTitleID(title_id) == base;
|
||||
}
|
||||
|
||||
static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
|
||||
@@ -48,6 +46,62 @@ static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
|
||||
return add_on_content;
|
||||
}
|
||||
|
||||
class IPurchaseEventManager final : public ServiceFramework<IPurchaseEventManager> {
|
||||
public:
|
||||
explicit IPurchaseEventManager(Core::System& system_)
|
||||
: ServiceFramework{system_, "IPurchaseEventManager"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"},
|
||||
{1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"},
|
||||
{2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"},
|
||||
{3, nullptr, "PopPurchasedProductInfo"},
|
||||
{4, nullptr, "PopPurchasedProductInfoWithUid"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
|
||||
purchased_event = Kernel::WritableEvent::CreateEventPair(
|
||||
system.Kernel(), "IPurchaseEventManager:PurchasedEvent");
|
||||
}
|
||||
|
||||
private:
|
||||
void SetDefaultDeliveryTarget(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto unknown_1 = rp.Pop<u64>();
|
||||
[[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer();
|
||||
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void SetDeliveryTarget(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto unknown_1 = rp.Pop<u64>();
|
||||
[[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer();
|
||||
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetPurchasedEventReadableHandle(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AOC, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(purchased_event.readable);
|
||||
}
|
||||
|
||||
Kernel::EventPair purchased_event;
|
||||
};
|
||||
|
||||
AOC_U::AOC_U(Core::System& system_)
|
||||
: ServiceFramework{system_, "aoc:u"}, add_on_content{AccumulateAOCTitleIDs(system)} {
|
||||
// clang-format off
|
||||
@@ -62,8 +116,8 @@ AOC_U::AOC_U(Core::System& system_)
|
||||
{7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},
|
||||
{8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"},
|
||||
{9, nullptr, "GetAddOnContentLostErrorCode"},
|
||||
{100, nullptr, "CreateEcPurchasedEventManager"},
|
||||
{101, nullptr, "CreatePermanentEcPurchasedEventManager"},
|
||||
{100, &AOC_U::CreateEcPurchasedEventManager, "CreateEcPurchasedEventManager"},
|
||||
{101, &AOC_U::CreatePermanentEcPurchasedEventManager, "CreatePermanentEcPurchasedEventManager"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -123,11 +177,11 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
const auto& disabled = Settings::values.disabled_addons[current];
|
||||
if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) {
|
||||
for (u64 content_id : add_on_content) {
|
||||
if ((content_id & DLC_BASE_TITLE_ID_MASK) != current) {
|
||||
if (FileSys::GetBaseTitleID(content_id) != current) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out.push_back(static_cast<u32>(content_id & 0x7FF));
|
||||
out.push_back(static_cast<u32>(FileSys::GetAOCID(content_id)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +223,7 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
const auto res = pm.GetControlMetadata();
|
||||
if (res.first == nullptr) {
|
||||
rb.Push(title_id + DLC_BASE_TO_AOC_ID);
|
||||
rb.Push(FileSys::GetAOCBaseTitleID(title_id));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -201,6 +255,22 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) {
|
||||
rb.PushCopyObjects(aoc_change_event.readable);
|
||||
}
|
||||
|
||||
void AOC_U::CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IPurchaseEventManager>(system);
|
||||
}
|
||||
|
||||
void AOC_U::CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IPurchaseEventManager>(system);
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
|
||||
std::make_shared<AOC_U>(system)->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ private:
|
||||
void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx);
|
||||
void PrepareAddOnContent(Kernel::HLERequestContext& ctx);
|
||||
void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx);
|
||||
void CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
|
||||
void CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::vector<u64> add_on_content;
|
||||
Kernel::EventPair aoc_change_event;
|
||||
|
||||
@@ -298,6 +298,31 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess()
|
||||
return romfs_factory->OpenCurrentProcess(system.CurrentProcess()->GetTitleID());
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFS(
|
||||
u64 title_id, FileSys::ContentRecordType type) const {
|
||||
LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id);
|
||||
|
||||
if (romfs_factory == nullptr) {
|
||||
// TODO: Find a better error code for this
|
||||
return RESULT_UNKNOWN;
|
||||
}
|
||||
|
||||
return romfs_factory->OpenPatchedRomFS(title_id, type);
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFSWithProgramIndex(
|
||||
u64 title_id, u8 program_index, FileSys::ContentRecordType type) const {
|
||||
LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id,
|
||||
program_index);
|
||||
|
||||
if (romfs_factory == nullptr) {
|
||||
// TODO: Find a better error code for this
|
||||
return RESULT_UNKNOWN;
|
||||
}
|
||||
|
||||
return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type);
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS(
|
||||
u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const {
|
||||
LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}",
|
||||
|
||||
@@ -66,6 +66,10 @@ public:
|
||||
|
||||
void SetPackedUpdate(FileSys::VirtualFile update_raw);
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const;
|
||||
ResultVal<FileSys::VirtualFile> OpenPatchedRomFS(u64 title_id,
|
||||
FileSys::ContentRecordType type) const;
|
||||
ResultVal<FileSys::VirtualFile> OpenPatchedRomFSWithProgramIndex(
|
||||
u64 title_id, u8 program_index, FileSys::ContentRecordType type) const;
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
|
||||
FileSys::ContentRecordType type) const;
|
||||
ResultVal<FileSys::VirtualDir> CreateSaveData(
|
||||
|
||||
@@ -717,7 +717,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
|
||||
{202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"},
|
||||
{203, &FSP_SRV::OpenPatchDataStorageByCurrentProcess, "OpenPatchDataStorageByCurrentProcess"},
|
||||
{204, nullptr, "OpenDataFileSystemByProgramIndex"},
|
||||
{205, nullptr, "OpenDataStorageByProgramIndex"},
|
||||
{205, &FSP_SRV::OpenDataStorageWithProgramIndex, "OpenDataStorageWithProgramIndex"},
|
||||
{400, nullptr, "OpenDeviceOperator"},
|
||||
{500, nullptr, "OpenSdCardDetectionEventNotifier"},
|
||||
{501, nullptr, "OpenGameCardDetectionEventNotifier"},
|
||||
@@ -994,6 +994,32 @@ void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ct
|
||||
rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
void FSP_SRV::OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto program_index = rp.PopRaw<u8>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called, program_index={}", program_index);
|
||||
|
||||
auto romfs = fsc.OpenPatchedRomFSWithProgramIndex(
|
||||
system.CurrentProcess()->GetTitleID(), program_index, FileSys::ContentRecordType::Program);
|
||||
|
||||
if (romfs.Failed()) {
|
||||
// TODO: Find the right error code to use here
|
||||
LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_UNKNOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
auto storage = std::make_shared<IStorage>(system, std::move(romfs.Unwrap()));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IStorage>(std::move(storage));
|
||||
}
|
||||
|
||||
void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
log_mode = rp.PopEnum<LogMode>();
|
||||
|
||||
@@ -49,6 +49,7 @@ private:
|
||||
void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx);
|
||||
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
void OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx);
|
||||
void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
|
||||
void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx);
|
||||
void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -116,6 +116,31 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller_NPad::IsNpadIdValid(u32 npad_id) {
|
||||
switch (npad_id) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case NPAD_UNKNOWN:
|
||||
case NPAD_HANDHELD:
|
||||
return true;
|
||||
default:
|
||||
LOG_ERROR(Service_HID, "Invalid npad id {}", npad_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller_NPad::IsDeviceHandleValid(const DeviceHandle& device_handle) {
|
||||
return IsNpadIdValid(device_handle.npad_id) &&
|
||||
device_handle.npad_type < NpadType::MaxNpadType &&
|
||||
device_handle.device_index < DeviceIndex::MaxDeviceIndex;
|
||||
}
|
||||
|
||||
Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {}
|
||||
|
||||
Controller_NPad::~Controller_NPad() {
|
||||
@@ -742,6 +767,10 @@ bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size
|
||||
|
||||
void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle,
|
||||
const VibrationValue& vibration_value) {
|
||||
if (!IsDeviceHandleValid(vibration_device_handle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
|
||||
return;
|
||||
}
|
||||
@@ -798,12 +827,20 @@ void Controller_NPad::VibrateControllers(const std::vector<DeviceHandle>& vibrat
|
||||
|
||||
Controller_NPad::VibrationValue Controller_NPad::GetLastVibration(
|
||||
const DeviceHandle& vibration_device_handle) const {
|
||||
if (!IsDeviceHandleValid(vibration_device_handle)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
|
||||
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
|
||||
return latest_vibration_values[npad_index][device_index];
|
||||
}
|
||||
|
||||
void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_device_handle) {
|
||||
if (!IsDeviceHandleValid(vibration_device_handle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
|
||||
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
|
||||
InitializeVibrationDeviceAtIndex(npad_index, device_index);
|
||||
@@ -824,6 +861,10 @@ void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
|
||||
}
|
||||
|
||||
bool Controller_NPad::IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const {
|
||||
if (!IsDeviceHandleValid(vibration_device_handle)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
|
||||
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
|
||||
return vibration_devices_mounted[npad_index][device_index];
|
||||
@@ -1017,7 +1058,7 @@ void Controller_NPad::ClearAllControllers() {
|
||||
}
|
||||
|
||||
u32 Controller_NPad::GetAndResetPressState() {
|
||||
return std::exchange(press_state, 0);
|
||||
return press_state.exchange(0);
|
||||
}
|
||||
|
||||
bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/frontend/input.h"
|
||||
@@ -56,12 +57,14 @@ public:
|
||||
JoyconLeft = 6,
|
||||
JoyconRight = 7,
|
||||
Pokeball = 9,
|
||||
MaxNpadType = 10,
|
||||
};
|
||||
|
||||
enum class DeviceIndex : u8 {
|
||||
Left = 0,
|
||||
Right = 1,
|
||||
None = 2,
|
||||
MaxDeviceIndex = 3,
|
||||
};
|
||||
|
||||
enum class GyroscopeZeroDriftMode : u32 {
|
||||
@@ -213,6 +216,8 @@ public:
|
||||
static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type);
|
||||
static std::size_t NPadIdToIndex(u32 npad_id);
|
||||
static u32 IndexToNPad(std::size_t index);
|
||||
static bool IsNpadIdValid(u32 npad_id);
|
||||
static bool IsDeviceHandleValid(const DeviceHandle& device_handle);
|
||||
|
||||
private:
|
||||
struct CommonHeader {
|
||||
@@ -411,7 +416,7 @@ private:
|
||||
bool IsControllerSupported(NPadControllerType controller) const;
|
||||
void RequestPadStateUpdate(u32 npad_id);
|
||||
|
||||
u32 press_state{};
|
||||
std::atomic<u32> press_state{};
|
||||
|
||||
NpadStyleSet style{};
|
||||
std::array<NPadEntry, 10> shared_memory_entries{};
|
||||
|
||||
@@ -217,7 +217,7 @@ public:
|
||||
{1, nullptr, "RefreshDebugAvailability"},
|
||||
{2, nullptr, "ClearDebugResponse"},
|
||||
{3, nullptr, "RegisterDebugResponse"},
|
||||
{4, nullptr, "IsLargeResourceAvailable"},
|
||||
{4, &NIM_ECA::IsLargeResourceAvailable, "IsLargeResourceAvailable"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -231,6 +231,18 @@ private:
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IShopServiceAccessServer>(system);
|
||||
}
|
||||
|
||||
void IsLargeResourceAvailable(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto unknown{rp.Pop<u64>()};
|
||||
|
||||
LOG_INFO(Service_NIM, "(STUBBED) called, unknown={}", unknown);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(false);
|
||||
}
|
||||
};
|
||||
|
||||
class NIM_SHP final : public ServiceFramework<NIM_SHP> {
|
||||
|
||||
@@ -673,7 +673,7 @@ public:
|
||||
explicit NS_VM(Core::System& system_) : ServiceFramework{system_, "ns:vm"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{1200, nullptr, "NeedsUpdateVulnerability"},
|
||||
{1200, &NS_VM::NeedsUpdateVulnerability, "NeedsUpdateVulnerability"},
|
||||
{1201, nullptr, "UpdateSafeSystemVersionForDebug"},
|
||||
{1202, nullptr, "GetSafeSystemVersion"},
|
||||
};
|
||||
@@ -681,6 +681,15 @@ public:
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void NeedsUpdateVulnerability(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(false);
|
||||
}
|
||||
};
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
|
||||
|
||||
@@ -27,29 +27,11 @@
|
||||
|
||||
namespace Service::NS {
|
||||
|
||||
enum class FontArchives : u64 {
|
||||
Extension = 0x0100000000000810,
|
||||
Standard = 0x0100000000000811,
|
||||
Korean = 0x0100000000000812,
|
||||
ChineseTraditional = 0x0100000000000813,
|
||||
ChineseSimple = 0x0100000000000814,
|
||||
};
|
||||
|
||||
struct FontRegion {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
};
|
||||
|
||||
constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
|
||||
std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
|
||||
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
|
||||
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
|
||||
std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
|
||||
std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
|
||||
std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
|
||||
std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
|
||||
};
|
||||
|
||||
// The below data is specific to shared font data dumped from Switch on f/w 2.2
|
||||
// Virtual address and offsets/sizes likely will vary by dump
|
||||
[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
|
||||
@@ -80,6 +62,18 @@ static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMem
|
||||
offset += transformed_font.size() * sizeof(u32);
|
||||
}
|
||||
|
||||
void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output) {
|
||||
ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
|
||||
|
||||
const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
|
||||
std::vector<u32> transformed_font(input.size());
|
||||
// TODO(ogniK): Figure out a better way to do this
|
||||
std::transform(input.begin(), input.end(), transformed_font.begin(),
|
||||
[&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
|
||||
transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size
|
||||
std::memcpy(output.data(), transformed_font.data() + 2, transformed_font.size() * sizeof(u32));
|
||||
}
|
||||
|
||||
void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output,
|
||||
std::size_t& offset) {
|
||||
ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
|
||||
|
||||
@@ -16,6 +16,25 @@ class FileSystemController;
|
||||
|
||||
namespace NS {
|
||||
|
||||
enum class FontArchives : u64 {
|
||||
Extension = 0x0100000000000810,
|
||||
Standard = 0x0100000000000811,
|
||||
Korean = 0x0100000000000812,
|
||||
ChineseTraditional = 0x0100000000000813,
|
||||
ChineseSimple = 0x0100000000000814,
|
||||
};
|
||||
|
||||
constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
|
||||
std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
|
||||
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
|
||||
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
|
||||
std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
|
||||
std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
|
||||
std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
|
||||
std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"),
|
||||
};
|
||||
|
||||
void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output);
|
||||
void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset);
|
||||
|
||||
class PL_U final : public ServiceFramework<PL_U> {
|
||||
|
||||
@@ -22,10 +22,11 @@ BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id, u64 layer_id)
|
||||
BufferQueue::~BufferQueue() = default;
|
||||
|
||||
void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) {
|
||||
ASSERT(slot < buffer_slots);
|
||||
LOG_WARNING(Service, "Adding graphics buffer {}", slot);
|
||||
|
||||
free_buffers.push_back(slot);
|
||||
queue.push_back({
|
||||
buffers[slot] = {
|
||||
.slot = slot,
|
||||
.status = Buffer::Status::Free,
|
||||
.igbp_buffer = igbp_buffer,
|
||||
@@ -33,7 +34,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
|
||||
.crop_rect = {},
|
||||
.swap_interval = 0,
|
||||
.multi_fence = {},
|
||||
});
|
||||
};
|
||||
|
||||
buffer_wait_event.writable->Signal();
|
||||
}
|
||||
@@ -44,73 +45,57 @@ std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::Dequeue
|
||||
if (free_buffers.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto f_itr = free_buffers.begin();
|
||||
auto itr = queue.end();
|
||||
auto slot = buffers.size();
|
||||
|
||||
while (f_itr != free_buffers.end()) {
|
||||
auto slot = *f_itr;
|
||||
itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
|
||||
// Only consider free buffers. Buffers become free once again after they've been
|
||||
// Acquired and Released by the compositor, see the NVFlinger::Compose method.
|
||||
if (buffer.status != Buffer::Status::Free) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer.slot != slot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure that the parameters match.
|
||||
return buffer.igbp_buffer.width == width && buffer.igbp_buffer.height == height;
|
||||
});
|
||||
|
||||
if (itr != queue.end()) {
|
||||
const Buffer& buffer = buffers[*f_itr];
|
||||
if (buffer.status == Buffer::Status::Free && buffer.igbp_buffer.width == width &&
|
||||
buffer.igbp_buffer.height == height) {
|
||||
slot = *f_itr;
|
||||
free_buffers.erase(f_itr);
|
||||
break;
|
||||
}
|
||||
++f_itr;
|
||||
}
|
||||
|
||||
if (itr == queue.end()) {
|
||||
if (slot == buffers.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
itr->status = Buffer::Status::Dequeued;
|
||||
return {{itr->slot, &itr->multi_fence}};
|
||||
buffers[slot].status = Buffer::Status::Dequeued;
|
||||
return {{buffers[slot].slot, &buffers[slot].multi_fence}};
|
||||
}
|
||||
|
||||
const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const {
|
||||
auto itr = std::find_if(queue.begin(), queue.end(),
|
||||
[&](const Buffer& buffer) { return buffer.slot == slot; });
|
||||
ASSERT(itr != queue.end());
|
||||
ASSERT(itr->status == Buffer::Status::Dequeued);
|
||||
return itr->igbp_buffer;
|
||||
ASSERT(slot < buffers.size());
|
||||
ASSERT(buffers[slot].status == Buffer::Status::Dequeued);
|
||||
ASSERT(buffers[slot].slot == slot);
|
||||
|
||||
return buffers[slot].igbp_buffer;
|
||||
}
|
||||
|
||||
void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
|
||||
const Common::Rectangle<int>& crop_rect, u32 swap_interval,
|
||||
Service::Nvidia::MultiFence& multi_fence) {
|
||||
auto itr = std::find_if(queue.begin(), queue.end(),
|
||||
[&](const Buffer& buffer) { return buffer.slot == slot; });
|
||||
ASSERT(itr != queue.end());
|
||||
ASSERT(itr->status == Buffer::Status::Dequeued);
|
||||
itr->status = Buffer::Status::Queued;
|
||||
itr->transform = transform;
|
||||
itr->crop_rect = crop_rect;
|
||||
itr->swap_interval = swap_interval;
|
||||
itr->multi_fence = multi_fence;
|
||||
ASSERT(slot < buffers.size());
|
||||
ASSERT(buffers[slot].status == Buffer::Status::Dequeued);
|
||||
ASSERT(buffers[slot].slot == slot);
|
||||
|
||||
buffers[slot].status = Buffer::Status::Queued;
|
||||
buffers[slot].transform = transform;
|
||||
buffers[slot].crop_rect = crop_rect;
|
||||
buffers[slot].swap_interval = swap_interval;
|
||||
buffers[slot].multi_fence = multi_fence;
|
||||
queue_sequence.push_back(slot);
|
||||
}
|
||||
|
||||
void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence) {
|
||||
const auto itr = std::find_if(queue.begin(), queue.end(),
|
||||
[slot](const Buffer& buffer) { return buffer.slot == slot; });
|
||||
ASSERT(itr != queue.end());
|
||||
ASSERT(itr->status != Buffer::Status::Free);
|
||||
itr->status = Buffer::Status::Free;
|
||||
itr->multi_fence = multi_fence;
|
||||
itr->swap_interval = 0;
|
||||
ASSERT(slot < buffers.size());
|
||||
ASSERT(buffers[slot].status != Buffer::Status::Free);
|
||||
ASSERT(buffers[slot].slot == slot);
|
||||
|
||||
buffers[slot].status = Buffer::Status::Free;
|
||||
buffers[slot].multi_fence = multi_fence;
|
||||
buffers[slot].swap_interval = 0;
|
||||
|
||||
free_buffers.push_back(slot);
|
||||
|
||||
@@ -118,38 +103,39 @@ void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& mult
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
|
||||
auto itr = queue.end();
|
||||
std::size_t buffer_slot = buffers.size();
|
||||
// Iterate to find a queued buffer matching the requested slot.
|
||||
while (itr == queue.end() && !queue_sequence.empty()) {
|
||||
const u32 slot = queue_sequence.front();
|
||||
itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) {
|
||||
return buffer.status == Buffer::Status::Queued && buffer.slot == slot;
|
||||
});
|
||||
while (buffer_slot == buffers.size() && !queue_sequence.empty()) {
|
||||
const auto slot = static_cast<std::size_t>(queue_sequence.front());
|
||||
ASSERT(slot < buffers.size());
|
||||
if (buffers[slot].status == Buffer::Status::Queued) {
|
||||
ASSERT(buffers[slot].slot == slot);
|
||||
buffer_slot = slot;
|
||||
}
|
||||
queue_sequence.pop_front();
|
||||
}
|
||||
if (itr == queue.end()) {
|
||||
if (buffer_slot == buffers.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
itr->status = Buffer::Status::Acquired;
|
||||
return *itr;
|
||||
buffers[buffer_slot].status = Buffer::Status::Acquired;
|
||||
return {{buffers[buffer_slot]}};
|
||||
}
|
||||
|
||||
void BufferQueue::ReleaseBuffer(u32 slot) {
|
||||
auto itr = std::find_if(queue.begin(), queue.end(),
|
||||
[&](const Buffer& buffer) { return buffer.slot == slot; });
|
||||
ASSERT(itr != queue.end());
|
||||
ASSERT(itr->status == Buffer::Status::Acquired);
|
||||
itr->status = Buffer::Status::Free;
|
||||
ASSERT(slot < buffers.size());
|
||||
ASSERT(buffers[slot].status == Buffer::Status::Acquired);
|
||||
ASSERT(buffers[slot].slot == slot);
|
||||
|
||||
buffers[slot].status = Buffer::Status::Free;
|
||||
free_buffers.push_back(slot);
|
||||
|
||||
buffer_wait_event.writable->Signal();
|
||||
}
|
||||
|
||||
void BufferQueue::Disconnect() {
|
||||
queue.clear();
|
||||
buffers.fill({});
|
||||
queue_sequence.clear();
|
||||
id = 1;
|
||||
layer_id = 1;
|
||||
buffer_wait_event.writable->Signal();
|
||||
}
|
||||
|
||||
u32 BufferQueue::Query(QueryType type) {
|
||||
|
||||
@@ -21,6 +21,7 @@ class KernelCore;
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
|
||||
constexpr u32 buffer_slots = 0x40;
|
||||
struct IGBPBuffer {
|
||||
u32_le magic;
|
||||
u32_le width;
|
||||
@@ -114,7 +115,7 @@ private:
|
||||
u64 layer_id;
|
||||
|
||||
std::list<u32> free_buffers;
|
||||
std::vector<Buffer> queue;
|
||||
std::array<Buffer, buffer_slots> buffers;
|
||||
std::list<u32> queue_sequence;
|
||||
Kernel::EventPair buffer_wait_event;
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
|
||||
class PCIe final : public ServiceFramework<PCIe> {
|
||||
public:
|
||||
explicit PCIe(Core::System& system_) : ServiceFramework{system, "pcie"} {
|
||||
explicit PCIe(Core::System& system_) : ServiceFramework{system_, "pcie"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "RegisterClassDriver"},
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/service/time/interface.h"
|
||||
#include "core/hle/service/time/time.h"
|
||||
#include "core/hle/service/time/time_sharedmemory.h"
|
||||
|
||||
@@ -282,18 +282,24 @@ public:
|
||||
void DeserializeData() override {
|
||||
[[maybe_unused]] const std::u16string token = ReadInterfaceToken();
|
||||
data = Read<Data>();
|
||||
buffer = Read<NVFlinger::IGBPBuffer>();
|
||||
if (data.contains_object != 0) {
|
||||
buffer_container = Read<BufferContainer>();
|
||||
}
|
||||
}
|
||||
|
||||
struct Data {
|
||||
u32_le slot;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u32_le graphic_buffer_length;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u32_le contains_object;
|
||||
};
|
||||
|
||||
Data data;
|
||||
NVFlinger::IGBPBuffer buffer;
|
||||
struct BufferContainer {
|
||||
u32_le graphic_buffer_length;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
NVFlinger::IGBPBuffer buffer{};
|
||||
};
|
||||
|
||||
Data data{};
|
||||
BufferContainer buffer_container{};
|
||||
};
|
||||
|
||||
class IGBPSetPreallocatedBufferResponseParcel : public Parcel {
|
||||
@@ -547,7 +553,7 @@ private:
|
||||
case TransactionId::SetPreallocatedBuffer: {
|
||||
IGBPSetPreallocatedBufferRequestParcel request{ctx.ReadBuffer()};
|
||||
|
||||
buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer);
|
||||
buffer_queue.SetPreallocatedBuffer(request.data.slot, request.buffer_container.buffer);
|
||||
|
||||
IGBPSetPreallocatedBufferResponseParcel response{};
|
||||
ctx.WriteBuffer(response.Serialize());
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
* @param file std::shared_ptr<VfsFile> open file
|
||||
* @param file open file
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||
*/
|
||||
static FileType IdentifyType(const FileSys::VirtualFile& file);
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
* @param file std::shared_ptr<VfsFile> open file
|
||||
* @param file open file
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||
*/
|
||||
static FileType IdentifyType(const FileSys::VirtualFile& file);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user