Compare commits
55 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ddf8f34db | ||
|
|
e6ee31a8e9 | ||
|
|
d3bfb102d8 | ||
|
|
d43769f93f | ||
|
|
1a5d6de0d4 | ||
|
|
e51d715700 | ||
|
|
fe292573de | ||
|
|
6f16826260 | ||
|
|
30dfd89126 | ||
|
|
f85f2b3728 | ||
|
|
baed7e1fba | ||
|
|
3e2380327a | ||
|
|
cf3a6dd4a1 | ||
|
|
af672d8abf | ||
|
|
839dbd9710 | ||
|
|
39f08e551d | ||
|
|
5ae5610c13 | ||
|
|
15b2e2ec13 | ||
|
|
e7e347a828 | ||
|
|
137d43fa2f | ||
|
|
8679934693 | ||
|
|
024eec02a5 | ||
|
|
dade709f63 | ||
|
|
02841052aa | ||
|
|
37ee05f7c0 | ||
|
|
ccf0a9cb38 | ||
|
|
fd312abedd | ||
|
|
4f18d35888 | ||
|
|
0d83f8f255 | ||
|
|
cf463a9b67 | ||
|
|
9aaf1c0df8 | ||
|
|
cba78dc70a | ||
|
|
d80dd2b812 | ||
|
|
1c7c798e9e | ||
|
|
57e47bae32 | ||
|
|
226dc914b3 | ||
|
|
8f5e2a2b83 | ||
|
|
215b65fe75 | ||
|
|
fdb35760a7 | ||
|
|
bfe84f06f2 | ||
|
|
a4595bb939 | ||
|
|
6d441828e6 | ||
|
|
003b44822a | ||
|
|
42fb4e82d3 | ||
|
|
4c2a94fa94 | ||
|
|
21b2411c44 | ||
|
|
3c9e70fefa | ||
|
|
bd14f397ce | ||
|
|
393042c09c | ||
|
|
1835911425 | ||
|
|
f72046099a | ||
|
|
7d86a008e2 | ||
|
|
62225ae050 | ||
|
|
aa0c82e405 | ||
|
|
32fc31fb13 |
@@ -344,14 +344,6 @@ ELSEIF (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
|
||||
set(PLATFORM_LIBRARIES rt)
|
||||
ENDIF (APPLE)
|
||||
|
||||
# MINGW: GCC does not support codecvt, so use iconv instead
|
||||
if (UNIX OR MINGW)
|
||||
find_library(ICONV_LIBRARY NAMES iconv)
|
||||
if (ICONV_LIBRARY)
|
||||
list(APPEND PLATFORM_LIBRARIES ${ICONV_LIBRARY})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Setup a custom clang-format target (if clang-format can be found) that will run
|
||||
# against all the src files. This should be used before making a pull request.
|
||||
# =======================================================================
|
||||
|
||||
@@ -285,7 +285,7 @@ private:
|
||||
template <typename T>
|
||||
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
|
||||
#ifdef _MSC_VER
|
||||
fstream.open(Common::UTF8ToTStr(filename).c_str(), openmode);
|
||||
fstream.open(Common::UTF8ToUTF16W(filename).c_str(), openmode);
|
||||
#else
|
||||
fstream.open(filename.c_str(), openmode);
|
||||
#endif
|
||||
|
||||
@@ -31,7 +31,7 @@ std::string FormatLogMessage(const Entry& entry) {
|
||||
}
|
||||
|
||||
void PrintMessage(const Entry& entry) {
|
||||
auto str = FormatLogMessage(entry) + '\n';
|
||||
const auto str = FormatLogMessage(entry).append(1, '\n');
|
||||
fputs(str.c_str(), stderr);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <codecvt>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -13,11 +14,7 @@
|
||||
#include "common/string_util.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <codecvt>
|
||||
#include <windows.h>
|
||||
#include "common/common_funcs.h"
|
||||
#else
|
||||
#include <iconv.h>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
@@ -195,11 +192,9 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
std::string UTF16ToUTF8(const std::u16string& input) {
|
||||
#if _MSC_VER >= 1900
|
||||
// Workaround for missing char16_t/char32_t instantiations in MSVC2015
|
||||
#ifdef _MSC_VER
|
||||
// Workaround for missing char16_t/char32_t instantiations in MSVC2017
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
|
||||
std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend());
|
||||
return convert.to_bytes(tmp_buffer);
|
||||
@@ -210,8 +205,8 @@ std::string UTF16ToUTF8(const std::u16string& input) {
|
||||
}
|
||||
|
||||
std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
#if _MSC_VER >= 1900
|
||||
// Workaround for missing char16_t/char32_t instantiations in MSVC2015
|
||||
#ifdef _MSC_VER
|
||||
// Workaround for missing char16_t/char32_t instantiations in MSVC2017
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
|
||||
auto tmp_buffer = convert.from_bytes(input);
|
||||
return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend());
|
||||
@@ -221,6 +216,7 @@ std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static std::wstring CPToUTF16(u32 code_page, const std::string& input) {
|
||||
const auto size =
|
||||
MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
|
||||
@@ -261,124 +257,6 @@ std::wstring UTF8ToUTF16W(const std::string& input) {
|
||||
return CPToUTF16(CP_UTF8, input);
|
||||
}
|
||||
|
||||
std::string SHIFTJISToUTF8(const std::string& input) {
|
||||
return UTF16ToUTF8(CPToUTF16(932, input));
|
||||
}
|
||||
|
||||
std::string CP1252ToUTF8(const std::string& input) {
|
||||
return UTF16ToUTF8(CPToUTF16(1252, input));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template <typename T>
|
||||
static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& input) {
|
||||
iconv_t const conv_desc = iconv_open("UTF-8", fromcode);
|
||||
if ((iconv_t)(-1) == conv_desc) {
|
||||
LOG_ERROR(Common, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno));
|
||||
iconv_close(conv_desc);
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::size_t in_bytes = sizeof(T) * input.size();
|
||||
// Multiply by 4, which is the max number of bytes to encode a codepoint
|
||||
const std::size_t out_buffer_size = 4 * in_bytes;
|
||||
|
||||
std::string out_buffer(out_buffer_size, '\0');
|
||||
|
||||
auto src_buffer = &input[0];
|
||||
std::size_t src_bytes = in_bytes;
|
||||
auto dst_buffer = &out_buffer[0];
|
||||
std::size_t dst_bytes = out_buffer.size();
|
||||
|
||||
while (0 != src_bytes) {
|
||||
std::size_t const iconv_result =
|
||||
iconv(conv_desc, (char**)(&src_buffer), &src_bytes, &dst_buffer, &dst_bytes);
|
||||
|
||||
if (static_cast<std::size_t>(-1) == iconv_result) {
|
||||
if (EILSEQ == errno || EINVAL == errno) {
|
||||
// Try to skip the bad character
|
||||
if (0 != src_bytes) {
|
||||
--src_bytes;
|
||||
++src_buffer;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Common, "iconv failure [{}]: {}", fromcode, strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string result;
|
||||
out_buffer.resize(out_buffer_size - dst_bytes);
|
||||
out_buffer.swap(result);
|
||||
|
||||
iconv_close(conv_desc);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::u16string UTF8ToUTF16(const std::string& input) {
|
||||
iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8");
|
||||
if ((iconv_t)(-1) == conv_desc) {
|
||||
LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: {}", strerror(errno));
|
||||
iconv_close(conv_desc);
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::size_t in_bytes = sizeof(char) * input.size();
|
||||
// Multiply by 4, which is the max number of bytes to encode a codepoint
|
||||
const std::size_t out_buffer_size = 4 * sizeof(char16_t) * in_bytes;
|
||||
|
||||
std::u16string out_buffer(out_buffer_size, char16_t{});
|
||||
|
||||
char* src_buffer = const_cast<char*>(&input[0]);
|
||||
std::size_t src_bytes = in_bytes;
|
||||
char* dst_buffer = (char*)(&out_buffer[0]);
|
||||
std::size_t dst_bytes = out_buffer.size();
|
||||
|
||||
while (0 != src_bytes) {
|
||||
std::size_t const iconv_result =
|
||||
iconv(conv_desc, &src_buffer, &src_bytes, &dst_buffer, &dst_bytes);
|
||||
|
||||
if (static_cast<std::size_t>(-1) == iconv_result) {
|
||||
if (EILSEQ == errno || EINVAL == errno) {
|
||||
// Try to skip the bad character
|
||||
if (0 != src_bytes) {
|
||||
--src_bytes;
|
||||
++src_buffer;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Common, "iconv failure [UTF-8]: {}", strerror(errno));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::u16string result;
|
||||
out_buffer.resize(out_buffer_size - dst_bytes);
|
||||
out_buffer.swap(result);
|
||||
|
||||
iconv_close(conv_desc);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string UTF16ToUTF8(const std::u16string& input) {
|
||||
return CodeToUTF8("UTF-16LE", input);
|
||||
}
|
||||
|
||||
std::string CP1252ToUTF8(const std::string& input) {
|
||||
// return CodeToUTF8("CP1252//TRANSLIT", input);
|
||||
// return CodeToUTF8("CP1252//IGNORE", input);
|
||||
return CodeToUTF8("CP1252", input);
|
||||
}
|
||||
|
||||
std::string SHIFTJISToUTF8(const std::string& input) {
|
||||
// return CodeToUTF8("CP932", input);
|
||||
return CodeToUTF8("SJIS", input);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len) {
|
||||
|
||||
@@ -72,31 +72,10 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
|
||||
std::string UTF16ToUTF8(const std::u16string& input);
|
||||
std::u16string UTF8ToUTF16(const std::string& input);
|
||||
|
||||
std::string CP1252ToUTF8(const std::string& str);
|
||||
std::string SHIFTJISToUTF8(const std::string& str);
|
||||
|
||||
#ifdef _WIN32
|
||||
std::string UTF16ToUTF8(const std::wstring& input);
|
||||
std::wstring UTF8ToUTF16W(const std::string& str);
|
||||
|
||||
#ifdef _UNICODE
|
||||
inline std::string TStrToUTF8(const std::wstring& str) {
|
||||
return UTF16ToUTF8(str);
|
||||
}
|
||||
|
||||
inline std::wstring UTF8ToTStr(const std::string& str) {
|
||||
return UTF8ToUTF16W(str);
|
||||
}
|
||||
#else
|
||||
inline std::string TStrToUTF8(const std::string& str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
inline std::string UTF8ToTStr(const std::string& str) {
|
||||
return str;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,8 @@ add_library(core STATIC
|
||||
file_sys/errors.h
|
||||
file_sys/fsmitm_romfsbuild.cpp
|
||||
file_sys/fsmitm_romfsbuild.h
|
||||
file_sys/ips_layer.cpp
|
||||
file_sys/ips_layer.h
|
||||
file_sys/mode.h
|
||||
file_sys/nca_metadata.cpp
|
||||
file_sys/nca_metadata.h
|
||||
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
parent.jit->HaltExecution();
|
||||
parent.SetPC(pc);
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
parent.SaveContext(thread->context);
|
||||
parent.SaveContext(thread->GetContext());
|
||||
GDBStub::Break();
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
return;
|
||||
|
||||
@@ -195,7 +195,7 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
|
||||
}
|
||||
Kernel::Thread* thread = Kernel::GetCurrentThread();
|
||||
SaveContext(thread->context);
|
||||
SaveContext(thread->GetContext());
|
||||
if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) {
|
||||
last_bkpt_hit = false;
|
||||
GDBStub::Break();
|
||||
|
||||
@@ -20,7 +20,9 @@ namespace FileSys {
|
||||
|
||||
constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"};
|
||||
|
||||
XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
|
||||
XCI::XCI(VirtualFile file_)
|
||||
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
|
||||
partitions(0x4) {
|
||||
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
|
||||
status = Loader::ResultStatus::ErrorBadXCIHeader;
|
||||
return;
|
||||
|
||||
88
src/core/file_sys/ips_layer.cpp
Normal file
88
src/core/file_sys/ips_layer.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class IPSFileType {
|
||||
IPS,
|
||||
IPS32,
|
||||
Error,
|
||||
};
|
||||
|
||||
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
|
||||
if (magic.size() != 5)
|
||||
return IPSFileType::Error;
|
||||
if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'})
|
||||
return IPSFileType::IPS;
|
||||
if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'})
|
||||
return IPSFileType::IPS32;
|
||||
return IPSFileType::Error;
|
||||
}
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
if (in == nullptr || ips == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const auto type = IdentifyMagic(ips->ReadBytes(0x5));
|
||||
if (type == IPSFileType::Error)
|
||||
return nullptr;
|
||||
|
||||
auto in_data = in->ReadAllBytes();
|
||||
|
||||
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
|
||||
u64 offset = 5; // After header
|
||||
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
|
||||
offset += temp.size();
|
||||
if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} ||
|
||||
type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) {
|
||||
break;
|
||||
}
|
||||
|
||||
u32 real_offset{};
|
||||
if (type == IPSFileType::IPS32)
|
||||
real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
|
||||
else
|
||||
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
|
||||
|
||||
u16 data_size{};
|
||||
if (ips->ReadObject(&data_size, offset) != sizeof(u16))
|
||||
return nullptr;
|
||||
data_size = Common::swap16(data_size);
|
||||
offset += sizeof(u16);
|
||||
|
||||
if (data_size == 0) { // RLE
|
||||
u16 rle_size{};
|
||||
if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
|
||||
return nullptr;
|
||||
rle_size = Common::swap16(data_size);
|
||||
offset += sizeof(u16);
|
||||
|
||||
const auto data = ips->ReadByte(offset++);
|
||||
if (data == boost::none)
|
||||
return nullptr;
|
||||
|
||||
if (real_offset + rle_size > in_data.size())
|
||||
rle_size = in_data.size() - real_offset;
|
||||
std::memset(in_data.data() + real_offset, data.get(), rle_size);
|
||||
} else { // Standard Patch
|
||||
auto read = data_size;
|
||||
if (real_offset + read > in_data.size())
|
||||
read = in_data.size() - real_offset;
|
||||
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
|
||||
return nullptr;
|
||||
offset += data_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'})
|
||||
return nullptr;
|
||||
return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
15
src/core/file_sys/ips_layer.h
Normal file
15
src/core/file_sys/ips_layer.h
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips);
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -2,22 +2,36 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_layered.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
|
||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
|
||||
struct NSOBuildHeader {
|
||||
u32_le magic;
|
||||
INSERT_PADDING_BYTES(0x3C);
|
||||
std::array<u8, 0x20> build_id;
|
||||
INSERT_PADDING_BYTES(0xA0);
|
||||
};
|
||||
static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
|
||||
|
||||
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
||||
std::array<u8, sizeof(u32)> bytes{};
|
||||
@@ -32,15 +46,6 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
||||
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
||||
}
|
||||
|
||||
constexpr std::array<const char*, 2> PATCH_TYPE_NAMES{
|
||||
"Update",
|
||||
"LayeredFS",
|
||||
};
|
||||
|
||||
std::string FormatPatchTypeName(PatchType type) {
|
||||
return PATCH_TYPE_NAMES.at(static_cast<std::size_t>(type));
|
||||
}
|
||||
|
||||
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
|
||||
|
||||
PatchManager::~PatchManager() = default;
|
||||
@@ -68,6 +73,79 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) {
|
||||
std::vector<VirtualFile> ips;
|
||||
ips.reserve(patch_dirs.size());
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto exefs_dir = subdir->GetSubdirectory("exefs");
|
||||
if (exefs_dir != nullptr) {
|
||||
for (const auto& file : exefs_dir->GetFiles()) {
|
||||
if (file->GetExtension() != "ips")
|
||||
continue;
|
||||
auto name = file->GetName();
|
||||
const auto p1 = name.substr(0, name.find('.'));
|
||||
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
|
||||
|
||||
if (build_id == this_build_id)
|
||||
ips.push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ips;
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
if (nso.size() < 0x100)
|
||||
return nso;
|
||||
|
||||
NSOBuildHeader header;
|
||||
std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
|
||||
|
||||
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
||||
return nso;
|
||||
|
||||
const auto build_id_raw = Common::HexArrayToString(header.build_id);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
const auto ips = CollectIPSPatches(patch_dirs, build_id);
|
||||
|
||||
auto out = nso;
|
||||
for (const auto& ips_file : ips) {
|
||||
LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"",
|
||||
ips_file->GetContainingDirectory()->GetParentDirectory()->GetName());
|
||||
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file);
|
||||
if (patched != nullptr)
|
||||
out = patched->ReadAllBytes();
|
||||
}
|
||||
|
||||
if (out.size() < 0x100)
|
||||
return nso;
|
||||
std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
|
||||
return out;
|
||||
}
|
||||
|
||||
bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
const auto build_id_raw = Common::HexArrayToString(build_id_);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
return !CollectIPSPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
|
||||
@@ -135,31 +213,80 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
|
||||
return romfs;
|
||||
}
|
||||
|
||||
std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
|
||||
std::map<PatchType, std::string> out;
|
||||
static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
|
||||
if (to.empty())
|
||||
to += with;
|
||||
else
|
||||
to += ", " + with;
|
||||
}
|
||||
|
||||
static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
||||
}
|
||||
|
||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const {
|
||||
std::map<std::string, std::string, std::less<>> out;
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
PatchManager update{update_tid};
|
||||
auto [nacp, discard_icon_file] = update.GetControlMetadata();
|
||||
|
||||
if (nacp != nullptr) {
|
||||
out[PatchType::Update] = nacp->GetVersionString();
|
||||
out.insert_or_assign("Update", nacp->GetVersionString());
|
||||
} else {
|
||||
if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
const auto meta_ver = installed->GetEntryVersion(update_tid);
|
||||
if (meta_ver == boost::none || meta_ver.get() == 0) {
|
||||
out[PatchType::Update] = "";
|
||||
out.insert_or_assign("Update", "");
|
||||
} else {
|
||||
out[PatchType::Update] =
|
||||
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements);
|
||||
out.insert_or_assign(
|
||||
"Update",
|
||||
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto lfs_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
if (lfs_dir != nullptr && lfs_dir->GetSize() > 0)
|
||||
out.insert_or_assign(PatchType::LayeredFS, "");
|
||||
// General Mods (LayeredFS and IPS)
|
||||
const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs")))
|
||||
AppendCommaIfNotEmpty(types, "IPS");
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
|
||||
if (types.empty())
|
||||
continue;
|
||||
|
||||
out.insert_or_assign(mod->GetName(), types);
|
||||
}
|
||||
}
|
||||
|
||||
// DLC
|
||||
const auto dlc_entries = installed->ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
|
||||
std::vector<RegisteredCacheEntry> dlc_match;
|
||||
dlc_match.reserve(dlc_entries.size());
|
||||
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||
[this, &installed](const RegisteredCacheEntry& entry) {
|
||||
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
|
||||
installed->GetEntry(entry)->GetStatus() ==
|
||||
Loader::ResultStatus::Success;
|
||||
});
|
||||
if (!dlc_match.empty()) {
|
||||
// Ensure sorted so DLC IDs show in order.
|
||||
std::sort(dlc_match.begin(), dlc_match.end());
|
||||
|
||||
std::string list;
|
||||
for (size_t i = 0; i < dlc_match.size() - 1; ++i)
|
||||
list += fmt::format("{}, ", dlc_match[i].title_id & 0x7FF);
|
||||
|
||||
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
|
||||
|
||||
out.insert_or_assign("DLC", std::move(list));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,6 @@ enum class TitleVersionFormat : u8 {
|
||||
std::string FormatTitleVersion(u32 version,
|
||||
TitleVersionFormat format = TitleVersionFormat::ThreeElements);
|
||||
|
||||
enum class PatchType {
|
||||
Update,
|
||||
LayeredFS,
|
||||
};
|
||||
|
||||
std::string FormatPatchTypeName(PatchType type);
|
||||
|
||||
// A centralized class to manage patches to games.
|
||||
class PatchManager {
|
||||
public:
|
||||
@@ -41,6 +34,14 @@ public:
|
||||
// - Game Updates
|
||||
VirtualDir PatchExeFS(VirtualDir exefs) const;
|
||||
|
||||
// Currently tracked NSO patches:
|
||||
// - IPS
|
||||
std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
|
||||
|
||||
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - Game Updates
|
||||
// - LayeredFS
|
||||
@@ -48,8 +49,8 @@ public:
|
||||
ContentRecordType type = ContentRecordType::Program) const;
|
||||
|
||||
// Returns a vector of pairs between patch names and patch versions.
|
||||
// i.e. Update v80 will return {Update, 80}
|
||||
std::map<PatchType, std::string> GetPatchVersionNames() const;
|
||||
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
|
||||
std::map<std::string, std::string, std::less<>> GetPatchVersionNames() const;
|
||||
|
||||
// Given title_id of the program, attempts to get the control data of the update and parse it,
|
||||
// falling back to the base control data.
|
||||
|
||||
@@ -39,36 +39,35 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
|
||||
std::shared_ptr<NCA> res;
|
||||
|
||||
switch (storage) {
|
||||
case StorageId::NandSystem: {
|
||||
const auto res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
if (res == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
const auto romfs = res->GetRomFS();
|
||||
if (romfs == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
return MakeResult<VirtualFile>(romfs);
|
||||
}
|
||||
case StorageId::NandUser: {
|
||||
const auto res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
|
||||
if (res == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
const auto romfs = res->GetRomFS();
|
||||
if (romfs == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
return MakeResult<VirtualFile>(romfs);
|
||||
}
|
||||
case StorageId::None:
|
||||
res = Service::FileSystem::GetUnionContents()->GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::NandSystem:
|
||||
res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::NandUser:
|
||||
res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::SdCard:
|
||||
res = Service::FileSystem::GetSDMCContents()->GetEntry(title_id, type);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
|
||||
}
|
||||
|
||||
if (res == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
const auto romfs = res->GetRomFS();
|
||||
if (romfs == nullptr) {
|
||||
// TODO(DarkLordZach): Find the right error code to use here
|
||||
return ResultCode(-1);
|
||||
}
|
||||
return MakeResult<VirtualFile>(romfs);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -18,6 +18,39 @@
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
namespace {
|
||||
void SetTicketKeys(const std::vector<VirtualFile>& files) {
|
||||
Core::Crypto::KeyManager keys;
|
||||
|
||||
for (const auto& ticket_file : files) {
|
||||
if (ticket_file == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ticket_file->GetExtension() != "tik") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ticket_file->GetSize() <
|
||||
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Core::Crypto::Key128 key{};
|
||||
ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
|
||||
|
||||
// We get the name without the extension in order to create the rights ID.
|
||||
std::string name_only(ticket_file->GetName());
|
||||
name_only.erase(name_only.size() - 4);
|
||||
|
||||
const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
|
||||
keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
NSP::NSP(VirtualFile file_)
|
||||
: file(std::move(file_)), status{Loader::ResultStatus::Success},
|
||||
pfs(std::make_shared<PartitionFilesystem>(file)) {
|
||||
@@ -26,83 +59,16 @@ NSP::NSP(VirtualFile file_)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto files = pfs->GetFiles();
|
||||
|
||||
if (IsDirectoryExeFS(pfs)) {
|
||||
extracted = true;
|
||||
exefs = pfs;
|
||||
|
||||
const auto& files = pfs->GetFiles();
|
||||
const auto romfs_iter =
|
||||
std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
|
||||
return file->GetName().find(".romfs") != std::string::npos;
|
||||
});
|
||||
if (romfs_iter != files.end())
|
||||
romfs = *romfs_iter;
|
||||
InitializeExeFSAndRomFS(files);
|
||||
return;
|
||||
}
|
||||
|
||||
extracted = false;
|
||||
const auto files = pfs->GetFiles();
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
for (const auto& ticket_file : files) {
|
||||
if (ticket_file->GetExtension() == "tik") {
|
||||
if (ticket_file == nullptr ||
|
||||
ticket_file->GetSize() <
|
||||
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Core::Crypto::Key128 key{};
|
||||
ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
|
||||
std::string_view name_only(ticket_file->GetName());
|
||||
name_only.remove_suffix(4);
|
||||
const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
|
||||
keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& outer_file : files) {
|
||||
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") {
|
||||
const auto nca = std::make_shared<NCA>(outer_file);
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success) {
|
||||
program_status[nca->GetTitleId()] = nca->GetStatus();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
|
||||
for (const auto& inner_file : section0->GetFiles()) {
|
||||
if (inner_file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
const CNMT cnmt(inner_file);
|
||||
auto& ncas_title = ncas[cnmt.GetTitleID()];
|
||||
|
||||
ncas_title[ContentRecordType::Meta] = nca;
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
|
||||
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
|
||||
if (next_file == nullptr) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"NCA with ID {}.nca is listed in content metadata, but cannot "
|
||||
"be found in PFS. NSP appears to be corrupted.",
|
||||
id_string);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto next_nca = std::make_shared<NCA>(next_file);
|
||||
if (next_nca->GetType() == NCAContentType::Program)
|
||||
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
|
||||
if (next_nca->GetStatus() == Loader::ResultStatus::Success)
|
||||
ncas_title[rec.type] = std::move(next_nca);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SetTicketKeys(files);
|
||||
ReadNCAs(files);
|
||||
}
|
||||
|
||||
NSP::~NSP() = default;
|
||||
@@ -242,4 +208,63 @@ VirtualDir NSP::GetParentDirectory() const {
|
||||
bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
|
||||
exefs = pfs;
|
||||
|
||||
const auto romfs_iter = std::find_if(files.begin(), files.end(), [](const VirtualFile& file) {
|
||||
return file->GetName().rfind(".romfs") != std::string::npos;
|
||||
});
|
||||
|
||||
if (romfs_iter == files.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
romfs = *romfs_iter;
|
||||
}
|
||||
|
||||
void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
for (const auto& outer_file : files) {
|
||||
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto nca = std::make_shared<NCA>(outer_file);
|
||||
if (nca->GetStatus() != Loader::ResultStatus::Success) {
|
||||
program_status[nca->GetTitleId()] = nca->GetStatus();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
|
||||
for (const auto& inner_file : section0->GetFiles()) {
|
||||
if (inner_file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
const CNMT cnmt(inner_file);
|
||||
auto& ncas_title = ncas[cnmt.GetTitleID()];
|
||||
|
||||
ncas_title[ContentRecordType::Meta] = nca;
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
|
||||
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
|
||||
if (next_file == nullptr) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"NCA with ID {}.nca is listed in content metadata, but cannot "
|
||||
"be found in PFS. NSP appears to be corrupted.",
|
||||
id_string);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto next_nca = std::make_shared<NCA>(next_file);
|
||||
if (next_nca->GetType() == NCAContentType::Program)
|
||||
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
|
||||
if (next_nca->GetStatus() == Loader::ResultStatus::Success)
|
||||
ncas_title[rec.type] = std::move(next_nca);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -59,9 +59,12 @@ protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
|
||||
void ReadNCAs(const std::vector<VirtualFile>& files);
|
||||
|
||||
VirtualFile file;
|
||||
|
||||
bool extracted;
|
||||
bool extracted = false;
|
||||
Loader::ResultStatus status;
|
||||
std::map<u64, Loader::ResultStatus> program_status;
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ static Kernel::Thread* FindThreadById(int id) {
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (auto& thread : threads) {
|
||||
if (thread->GetThreadId() == static_cast<u32>(id)) {
|
||||
if (thread->GetThreadID() == static_cast<u32>(id)) {
|
||||
current_core = core;
|
||||
return thread.get();
|
||||
}
|
||||
@@ -223,16 +223,18 @@ static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& thread_context = thread->GetContext();
|
||||
|
||||
if (id < SP_REGISTER) {
|
||||
return thread->context.cpu_registers[id];
|
||||
return thread_context.cpu_registers[id];
|
||||
} else if (id == SP_REGISTER) {
|
||||
return thread->context.sp;
|
||||
return thread_context.sp;
|
||||
} else if (id == PC_REGISTER) {
|
||||
return thread->context.pc;
|
||||
return thread_context.pc;
|
||||
} else if (id == PSTATE_REGISTER) {
|
||||
return thread->context.pstate;
|
||||
return thread_context.pstate;
|
||||
} else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
|
||||
return thread->context.vector_registers[id - UC_ARM64_REG_Q0][0];
|
||||
return thread_context.vector_registers[id - UC_ARM64_REG_Q0][0];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
@@ -243,16 +245,18 @@ static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
auto& thread_context = thread->GetContext();
|
||||
|
||||
if (id < SP_REGISTER) {
|
||||
thread->context.cpu_registers[id] = val;
|
||||
thread_context.cpu_registers[id] = val;
|
||||
} else if (id == SP_REGISTER) {
|
||||
thread->context.sp = val;
|
||||
thread_context.sp = val;
|
||||
} else if (id == PC_REGISTER) {
|
||||
thread->context.pc = val;
|
||||
thread_context.pc = val;
|
||||
} else if (id == PSTATE_REGISTER) {
|
||||
thread->context.pstate = static_cast<u32>(val);
|
||||
thread_context.pstate = static_cast<u32>(val);
|
||||
} else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
|
||||
thread->context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
|
||||
thread_context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,7 +599,7 @@ static void HandleQuery() {
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
val += fmt::format("{:x}", thread->GetThreadId());
|
||||
val += fmt::format("{:x}", thread->GetThreadID());
|
||||
val += ",";
|
||||
}
|
||||
}
|
||||
@@ -612,7 +616,7 @@ static void HandleQuery() {
|
||||
for (const auto& thread : threads) {
|
||||
buffer +=
|
||||
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
|
||||
thread->GetThreadId(), core, thread->GetThreadId());
|
||||
thread->GetThreadID(), core, thread->GetThreadID());
|
||||
}
|
||||
}
|
||||
buffer += "</threads>";
|
||||
@@ -693,7 +697,7 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
|
||||
}
|
||||
|
||||
if (thread) {
|
||||
buffer += fmt::format(";thread:{:x};", thread->GetThreadId());
|
||||
buffer += fmt::format(";thread:{:x};", thread->GetThreadID());
|
||||
}
|
||||
|
||||
SendReply(buffer.c_str());
|
||||
@@ -857,7 +861,9 @@ static void WriteRegister() {
|
||||
}
|
||||
|
||||
// Update Unicorn context skipping scheduler, no running threads at this point
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
Core::System::GetInstance()
|
||||
.ArmInterface(current_core)
|
||||
.LoadContext(current_thread->GetContext());
|
||||
|
||||
SendReply("OK");
|
||||
}
|
||||
@@ -886,7 +892,9 @@ static void WriteRegisters() {
|
||||
}
|
||||
|
||||
// Update Unicorn context skipping scheduler, no running threads at this point
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
Core::System::GetInstance()
|
||||
.ArmInterface(current_core)
|
||||
.LoadContext(current_thread->GetContext());
|
||||
|
||||
SendReply("OK");
|
||||
}
|
||||
@@ -960,7 +968,9 @@ static void Step() {
|
||||
if (command_length > 1) {
|
||||
RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread);
|
||||
// Update Unicorn context skipping scheduler, no running threads at this point
|
||||
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
|
||||
Core::System::GetInstance()
|
||||
.ArmInterface(current_core)
|
||||
.LoadContext(current_thread->GetContext());
|
||||
}
|
||||
step_loop = true;
|
||||
halt_loop = true;
|
||||
|
||||
@@ -23,13 +23,13 @@ namespace AddressArbiter {
|
||||
// Performs actual address waiting logic.
|
||||
static ResultCode WaitForAddress(VAddr address, s64 timeout) {
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
current_thread->arb_wait_address = address;
|
||||
current_thread->status = ThreadStatus::WaitArb;
|
||||
current_thread->wakeup_callback = nullptr;
|
||||
current_thread->SetArbiterWaitAddress(address);
|
||||
current_thread->SetStatus(ThreadStatus::WaitArb);
|
||||
current_thread->InvalidateWakeupCallback();
|
||||
|
||||
current_thread->WakeAfterDelay(timeout);
|
||||
|
||||
Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr arb_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
auto& thread_list = scheduler->GetThreadList();
|
||||
const auto& thread_list = scheduler->GetThreadList();
|
||||
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->arb_wait_address == arb_addr)
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetArbiterWaitAddress() == arb_addr)
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
};
|
||||
@@ -57,7 +57,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
|
||||
// Sort them by priority, such that the highest priority ones come first.
|
||||
std::sort(threads.begin(), threads.end(),
|
||||
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
|
||||
return lhs->current_priority < rhs->current_priority;
|
||||
return lhs->GetPriority() < rhs->GetPriority();
|
||||
});
|
||||
|
||||
return threads;
|
||||
@@ -73,9 +73,9 @@ static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num
|
||||
|
||||
// Signal the waiting threads.
|
||||
for (std::size_t i = 0; i < last; i++) {
|
||||
ASSERT(waiting_threads[i]->status == ThreadStatus::WaitArb);
|
||||
ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
|
||||
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
waiting_threads[i]->arb_wait_address = 0;
|
||||
waiting_threads[i]->SetArbiterWaitAddress(0);
|
||||
waiting_threads[i]->ResumeFromWait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
|
||||
Kernel::SharedPtr<Kernel::Event> event) {
|
||||
|
||||
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
|
||||
thread->wakeup_callback = [context = *this, callback](
|
||||
thread->SetWakeupCallback([context = *this, callback](
|
||||
ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index) mutable -> bool {
|
||||
ASSERT(thread->status == ThreadStatus::WaitHLEEvent);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent);
|
||||
callback(thread, context, reason);
|
||||
context.WriteToOutgoingCommandBuffer(*thread);
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
if (!event) {
|
||||
// Create event if not provided
|
||||
@@ -59,8 +59,8 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
|
||||
}
|
||||
|
||||
event->Clear();
|
||||
thread->status = ThreadStatus::WaitHLEEvent;
|
||||
thread->wait_objects = {event};
|
||||
thread->SetStatus(ThreadStatus::WaitHLEEvent);
|
||||
thread->SetWaitObjects({event});
|
||||
event->AddWaitingThread(thread);
|
||||
|
||||
if (timeout > 0) {
|
||||
@@ -209,7 +209,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
|
||||
|
||||
ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) {
|
||||
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
|
||||
Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
|
||||
Memory::ReadBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
|
||||
dst_cmdbuf.size() * sizeof(u32));
|
||||
|
||||
// The header was already built in the internal command buffer. Attempt to parse it to verify
|
||||
@@ -268,7 +268,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread)
|
||||
}
|
||||
|
||||
// Copy the translated command buffer back into the thread's command buffer area.
|
||||
Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
|
||||
Memory::WriteBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
|
||||
dst_cmdbuf.size() * sizeof(u32));
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
@@ -46,40 +46,40 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
|
||||
|
||||
bool resume = true;
|
||||
|
||||
if (thread->status == ThreadStatus::WaitSynchAny ||
|
||||
thread->status == ThreadStatus::WaitSynchAll ||
|
||||
thread->status == ThreadStatus::WaitHLEEvent) {
|
||||
if (thread->GetStatus() == ThreadStatus::WaitSynchAny ||
|
||||
thread->GetStatus() == ThreadStatus::WaitSynchAll ||
|
||||
thread->GetStatus() == ThreadStatus::WaitHLEEvent) {
|
||||
// Remove the thread from each of its waiting objects' waitlists
|
||||
for (auto& object : thread->wait_objects) {
|
||||
for (const auto& object : thread->GetWaitObjects()) {
|
||||
object->RemoveWaitingThread(thread.get());
|
||||
}
|
||||
thread->wait_objects.clear();
|
||||
thread->ClearWaitObjects();
|
||||
|
||||
// Invoke the wakeup callback before clearing the wait objects
|
||||
if (thread->wakeup_callback) {
|
||||
resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
|
||||
if (thread->HasWakeupCallback()) {
|
||||
resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 ||
|
||||
thread->wait_handle) {
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
|
||||
thread->GetWaitHandle() != 0) {
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetCondVarWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
|
||||
auto lock_owner = thread->lock_owner;
|
||||
auto* const lock_owner = thread->GetLockOwner();
|
||||
// Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
|
||||
// and don't have a lock owner unless SignalProcessWideKey was called first and the thread
|
||||
// wasn't awakened due to the mutex already being acquired.
|
||||
if (lock_owner) {
|
||||
if (lock_owner != nullptr) {
|
||||
lock_owner->RemoveMutexWaiter(thread);
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->arb_wait_address != 0) {
|
||||
ASSERT(thread->status == ThreadStatus::WaitArb);
|
||||
thread->arb_wait_address = 0;
|
||||
if (thread->GetArbiterWaitAddress() != 0) {
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitArb);
|
||||
thread->SetArbiterWaitAddress(0);
|
||||
}
|
||||
|
||||
if (resume) {
|
||||
|
||||
@@ -28,11 +28,11 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
|
||||
SharedPtr<Thread> highest_priority_thread;
|
||||
u32 num_waiters = 0;
|
||||
|
||||
for (auto& thread : current_thread->wait_mutex_threads) {
|
||||
if (thread->mutex_wait_address != mutex_addr)
|
||||
for (const auto& thread : current_thread->GetMutexWaitingThreads()) {
|
||||
if (thread->GetMutexWaitAddress() != mutex_addr)
|
||||
continue;
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
|
||||
++num_waiters;
|
||||
if (highest_priority_thread == nullptr ||
|
||||
@@ -47,12 +47,12 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
|
||||
/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
|
||||
static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
|
||||
SharedPtr<Thread> new_owner) {
|
||||
auto threads = current_thread->wait_mutex_threads;
|
||||
for (auto& thread : threads) {
|
||||
if (thread->mutex_wait_address != mutex_addr)
|
||||
const auto threads = current_thread->GetMutexWaitingThreads();
|
||||
for (const auto& thread : threads) {
|
||||
if (thread->GetMutexWaitAddress() != mutex_addr)
|
||||
continue;
|
||||
|
||||
ASSERT(thread->lock_owner == current_thread);
|
||||
ASSERT(thread->GetLockOwner() == current_thread);
|
||||
current_thread->RemoveMutexWaiter(thread);
|
||||
if (new_owner != thread)
|
||||
new_owner->AddMutexWaiter(thread);
|
||||
@@ -84,11 +84,11 @@ ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle ho
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
// Wait until the mutex is released
|
||||
GetCurrentThread()->mutex_wait_address = address;
|
||||
GetCurrentThread()->wait_handle = requesting_thread_handle;
|
||||
GetCurrentThread()->SetMutexWaitAddress(address);
|
||||
GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
|
||||
|
||||
GetCurrentThread()->status = ThreadStatus::WaitMutex;
|
||||
GetCurrentThread()->wakeup_callback = nullptr;
|
||||
GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
|
||||
GetCurrentThread()->InvalidateWakeupCallback();
|
||||
|
||||
// Update the lock holder thread's priority to prevent priority inversion.
|
||||
holding_thread->AddMutexWaiter(GetCurrentThread());
|
||||
@@ -115,7 +115,7 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
// Transfer the ownership of the mutex from the previous owner to the new one.
|
||||
TransferMutexOwnership(address, GetCurrentThread(), thread);
|
||||
|
||||
u32 mutex_value = thread->wait_handle;
|
||||
u32 mutex_value = thread->GetWaitHandle();
|
||||
|
||||
if (num_waiters >= 2) {
|
||||
// Notify the guest that there are still some threads waiting for the mutex
|
||||
@@ -125,13 +125,13 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
// Grant the mutex to the next waiting thread and resume it.
|
||||
Memory::Write32(address, mutex_value);
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
thread->ResumeFromWait();
|
||||
|
||||
thread->lock_owner = nullptr;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
thread->SetLockOwner(nullptr);
|
||||
thread->SetCondVarWaitAddress(0);
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -144,15 +144,15 @@ void Process::PrepareForTermination() {
|
||||
|
||||
const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) {
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->owner_process != this)
|
||||
if (thread->GetOwnerProcess() != this)
|
||||
continue;
|
||||
|
||||
if (thread == GetCurrentThread())
|
||||
continue;
|
||||
|
||||
// TODO(Subv): When are the other running/ready threads terminated?
|
||||
ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny ||
|
||||
thread->status == ThreadStatus::WaitSynchAll,
|
||||
ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynchAny ||
|
||||
thread->GetStatus() == ThreadStatus::WaitSynchAll,
|
||||
"Exiting processes with non-waiting threads is currently unimplemented");
|
||||
|
||||
thread->Stop();
|
||||
|
||||
@@ -38,10 +38,10 @@ Thread* Scheduler::PopNextReadyThread() {
|
||||
Thread* next = nullptr;
|
||||
Thread* thread = GetCurrentThread();
|
||||
|
||||
if (thread && thread->status == ThreadStatus::Running) {
|
||||
if (thread && thread->GetStatus() == ThreadStatus::Running) {
|
||||
// We have to do better than the current thread.
|
||||
// This call returns null when that's not possible.
|
||||
next = ready_queue.pop_first_better(thread->current_priority);
|
||||
next = ready_queue.pop_first_better(thread->GetPriority());
|
||||
if (!next) {
|
||||
// Otherwise just keep going with the current thread
|
||||
next = thread;
|
||||
@@ -58,22 +58,21 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
// Save context for previous thread
|
||||
if (previous_thread) {
|
||||
previous_thread->last_running_ticks = CoreTiming::GetTicks();
|
||||
cpu_core.SaveContext(previous_thread->context);
|
||||
cpu_core.SaveContext(previous_thread->GetContext());
|
||||
// Save the TPIDR_EL0 system register in case it was modified.
|
||||
previous_thread->tpidr_el0 = cpu_core.GetTPIDR_EL0();
|
||||
previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
|
||||
|
||||
if (previous_thread->status == ThreadStatus::Running) {
|
||||
if (previous_thread->GetStatus() == ThreadStatus::Running) {
|
||||
// This is only the case when a reschedule is triggered without the current thread
|
||||
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
|
||||
ready_queue.push_front(previous_thread->current_priority, previous_thread);
|
||||
previous_thread->status = ThreadStatus::Ready;
|
||||
ready_queue.push_front(previous_thread->GetPriority(), previous_thread);
|
||||
previous_thread->SetStatus(ThreadStatus::Ready);
|
||||
}
|
||||
}
|
||||
|
||||
// Load context of new thread
|
||||
if (new_thread) {
|
||||
ASSERT_MSG(new_thread->status == ThreadStatus::Ready,
|
||||
ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready,
|
||||
"Thread must be ready to become running.");
|
||||
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
@@ -83,15 +82,16 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
current_thread = new_thread;
|
||||
|
||||
ready_queue.remove(new_thread->current_priority, new_thread);
|
||||
new_thread->status = ThreadStatus::Running;
|
||||
ready_queue.remove(new_thread->GetPriority(), new_thread);
|
||||
new_thread->SetStatus(ThreadStatus::Running);
|
||||
|
||||
if (previous_process != current_thread->owner_process) {
|
||||
Core::CurrentProcess() = current_thread->owner_process;
|
||||
const auto thread_owner_process = current_thread->GetOwnerProcess();
|
||||
if (previous_process != thread_owner_process) {
|
||||
Core::CurrentProcess() = thread_owner_process;
|
||||
SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
|
||||
}
|
||||
|
||||
cpu_core.LoadContext(new_thread->context);
|
||||
cpu_core.LoadContext(new_thread->GetContext());
|
||||
cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
|
||||
cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
@@ -136,14 +136,14 @@ void Scheduler::RemoveThread(Thread* thread) {
|
||||
void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::Ready);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
|
||||
ready_queue.push_back(priority, thread);
|
||||
}
|
||||
|
||||
void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::Ready);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
|
||||
ready_queue.remove(priority, thread);
|
||||
}
|
||||
|
||||
@@ -151,8 +151,8 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
|
||||
std::lock_guard<std::mutex> lock(scheduler_mutex);
|
||||
|
||||
// If thread was ready, adjust queues
|
||||
if (thread->status == ThreadStatus::Ready)
|
||||
ready_queue.move(thread, thread->current_priority, priority);
|
||||
if (thread->GetStatus() == ThreadStatus::Ready)
|
||||
ready_queue.move(thread, thread->GetPriority(), priority);
|
||||
else
|
||||
ready_queue.prepare(priority);
|
||||
}
|
||||
|
||||
@@ -120,10 +120,10 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
|
||||
result = hle_handler->HandleSyncRequest(context);
|
||||
}
|
||||
|
||||
if (thread->status == ThreadStatus::Running) {
|
||||
if (thread->GetStatus() == ThreadStatus::Running) {
|
||||
// Put the thread to sleep until the server replies, it will be awoken in
|
||||
// svcReplyAndReceive for LLE servers.
|
||||
thread->status = ThreadStatus::WaitIPC;
|
||||
thread->SetStatus(ThreadStatus::WaitIPC);
|
||||
|
||||
if (hle_handler != nullptr) {
|
||||
// For HLE services, we put the request threads to sleep for a short duration to
|
||||
|
||||
@@ -156,7 +156,7 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
*thread_id = thread->GetThreadId();
|
||||
*thread_id = thread->GetThreadID();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
|
||||
/// Default thread wakeup callback for WaitSynchronization
|
||||
static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index) {
|
||||
ASSERT(thread->status == ThreadStatus::WaitSynchAny);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
|
||||
|
||||
if (reason == ThreadWakeupReason::Timeout) {
|
||||
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
|
||||
@@ -204,10 +204,10 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
|
||||
if (handle_count > MaxHandles)
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::TooLarge);
|
||||
|
||||
auto thread = GetCurrentThread();
|
||||
auto* const thread = GetCurrentThread();
|
||||
|
||||
using ObjectPtr = SharedPtr<WaitObject>;
|
||||
std::vector<ObjectPtr> objects(handle_count);
|
||||
using ObjectPtr = Thread::ThreadWaitObjects::value_type;
|
||||
Thread::ThreadWaitObjects objects(handle_count);
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
|
||||
for (u64 i = 0; i < handle_count; ++i) {
|
||||
@@ -244,14 +244,14 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
|
||||
for (auto& object : objects)
|
||||
object->AddWaitingThread(thread);
|
||||
|
||||
thread->wait_objects = std::move(objects);
|
||||
thread->status = ThreadStatus::WaitSynchAny;
|
||||
thread->SetWaitObjects(std::move(objects));
|
||||
thread->SetStatus(ThreadStatus::WaitSynchAny);
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
thread->WakeAfterDelay(nano_seconds);
|
||||
thread->wakeup_callback = DefaultThreadWakeupCallback;
|
||||
thread->SetWakeupCallback(DefaultThreadWakeupCallback);
|
||||
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
|
||||
return RESULT_TIMEOUT;
|
||||
}
|
||||
@@ -266,7 +266,7 @@ static ResultCode CancelSynchronization(Handle thread_handle) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::WaitSynchAny);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
|
||||
thread->SetWaitSynchronizationResult(
|
||||
ResultCode(ErrorModule::Kernel, ErrCodes::SynchronizationCanceled));
|
||||
thread->ResumeFromWait();
|
||||
@@ -425,7 +425,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
|
||||
}
|
||||
|
||||
const auto current_process = Core::CurrentProcess();
|
||||
if (thread->owner_process != current_process) {
|
||||
if (thread->GetOwnerProcess() != current_process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
@@ -433,7 +433,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
|
||||
return ERR_ALREADY_REGISTERED;
|
||||
}
|
||||
|
||||
Core::ARM_Interface::ThreadContext ctx = thread->context;
|
||||
Core::ARM_Interface::ThreadContext ctx = thread->GetContext();
|
||||
// Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
|
||||
ctx.pstate &= 0xFF0FFE20;
|
||||
|
||||
@@ -479,14 +479,14 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||
|
||||
thread->SetPriority(priority);
|
||||
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Get which CPU core is executing the current thread
|
||||
static u32 GetCurrentProcessorNumber() {
|
||||
LOG_TRACE(Kernel_SVC, "called");
|
||||
return GetCurrentThread()->processor_id;
|
||||
return GetCurrentThread()->GetProcessorID();
|
||||
}
|
||||
|
||||
static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size,
|
||||
@@ -622,10 +622,14 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
CASCADE_RESULT(SharedPtr<Thread> thread,
|
||||
Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
|
||||
Core::CurrentProcess()));
|
||||
CASCADE_RESULT(thread->guest_handle, kernel.HandleTable().Create(thread));
|
||||
*out_handle = thread->guest_handle;
|
||||
const auto new_guest_handle = kernel.HandleTable().Create(thread);
|
||||
if (new_guest_handle.Failed()) {
|
||||
return new_guest_handle.Code();
|
||||
}
|
||||
thread->SetGuestHandle(*new_guest_handle);
|
||||
*out_handle = *new_guest_handle;
|
||||
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
|
||||
@@ -645,10 +649,10 @@ static ResultCode StartThread(Handle thread_handle) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ASSERT(thread->status == ThreadStatus::Dormant);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::Dormant);
|
||||
|
||||
thread->ResumeFromWait();
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -694,17 +698,17 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
|
||||
CASCADE_CODE(Mutex::Release(mutex_addr));
|
||||
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
current_thread->condvar_wait_address = condition_variable_addr;
|
||||
current_thread->mutex_wait_address = mutex_addr;
|
||||
current_thread->wait_handle = thread_handle;
|
||||
current_thread->status = ThreadStatus::WaitMutex;
|
||||
current_thread->wakeup_callback = nullptr;
|
||||
current_thread->SetCondVarWaitAddress(condition_variable_addr);
|
||||
current_thread->SetMutexWaitAddress(mutex_addr);
|
||||
current_thread->SetWaitHandle(thread_handle);
|
||||
current_thread->SetStatus(ThreadStatus::WaitMutex);
|
||||
current_thread->InvalidateWakeupCallback();
|
||||
|
||||
current_thread->WakeAfterDelay(nano_seconds);
|
||||
|
||||
// Note: Deliberately don't attempt to inherit the lock owner's priority.
|
||||
|
||||
Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -713,14 +717,14 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
|
||||
condition_variable_addr, target);
|
||||
|
||||
auto RetrieveWaitingThreads = [](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr condvar_addr) {
|
||||
const auto RetrieveWaitingThreads = [](std::size_t core_index,
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr condvar_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
auto& thread_list = scheduler->GetThreadList();
|
||||
const auto& thread_list = scheduler->GetThreadList();
|
||||
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->condvar_wait_address == condvar_addr)
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetCondVarWaitAddress() == condvar_addr)
|
||||
waiting_threads.push_back(thread);
|
||||
}
|
||||
};
|
||||
@@ -734,7 +738,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
// Sort them by priority, such that the highest priority ones come first.
|
||||
std::sort(waiting_threads.begin(), waiting_threads.end(),
|
||||
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
|
||||
return lhs->current_priority < rhs->current_priority;
|
||||
return lhs->GetPriority() < rhs->GetPriority();
|
||||
});
|
||||
|
||||
// Only process up to 'target' threads, unless 'target' is -1, in which case process
|
||||
@@ -750,7 +754,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
for (std::size_t index = 0; index < last; ++index) {
|
||||
auto& thread = waiting_threads[index];
|
||||
|
||||
ASSERT(thread->condvar_wait_address == condition_variable_addr);
|
||||
ASSERT(thread->GetCondVarWaitAddress() == condition_variable_addr);
|
||||
|
||||
std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex();
|
||||
|
||||
@@ -759,42 +763,43 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
// Atomically read the value of the mutex.
|
||||
u32 mutex_val = 0;
|
||||
do {
|
||||
monitor.SetExclusive(current_core, thread->mutex_wait_address);
|
||||
monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
|
||||
|
||||
// If the mutex is not yet acquired, acquire it.
|
||||
mutex_val = Memory::Read32(thread->mutex_wait_address);
|
||||
mutex_val = Memory::Read32(thread->GetMutexWaitAddress());
|
||||
|
||||
if (mutex_val != 0) {
|
||||
monitor.ClearExclusive();
|
||||
break;
|
||||
}
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address,
|
||||
thread->wait_handle));
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
|
||||
thread->GetWaitHandle()));
|
||||
|
||||
if (mutex_val == 0) {
|
||||
// We were able to acquire the mutex, resume this thread.
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
thread->ResumeFromWait();
|
||||
|
||||
auto lock_owner = thread->lock_owner;
|
||||
if (lock_owner)
|
||||
auto* const lock_owner = thread->GetLockOwner();
|
||||
if (lock_owner != nullptr) {
|
||||
lock_owner->RemoveMutexWaiter(thread);
|
||||
}
|
||||
|
||||
thread->lock_owner = nullptr;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
thread->SetLockOwner(nullptr);
|
||||
thread->SetMutexWaitAddress(0);
|
||||
thread->SetCondVarWaitAddress(0);
|
||||
thread->SetWaitHandle(0);
|
||||
} else {
|
||||
// Atomically signal that the mutex now has a waiting thread.
|
||||
do {
|
||||
monitor.SetExclusive(current_core, thread->mutex_wait_address);
|
||||
monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
|
||||
|
||||
// Ensure that the mutex value is still what we expect.
|
||||
u32 value = Memory::Read32(thread->mutex_wait_address);
|
||||
u32 value = Memory::Read32(thread->GetMutexWaitAddress());
|
||||
// TODO(Subv): When this happens, the kernel just clears the exclusive state and
|
||||
// retries the initial read for this thread.
|
||||
ASSERT_MSG(mutex_val == value, "Unhandled synchronization primitive case");
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address,
|
||||
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
|
||||
mutex_val | Mutex::MutexHasWaitersFlag));
|
||||
|
||||
// The mutex is already owned by some other thread, make this thread wait on it.
|
||||
@@ -802,12 +807,12 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
|
||||
auto owner = kernel.HandleTable().Get<Thread>(owner_handle);
|
||||
ASSERT(owner);
|
||||
ASSERT(thread->status == ThreadStatus::WaitMutex);
|
||||
thread->wakeup_callback = nullptr;
|
||||
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
|
||||
thread->InvalidateWakeupCallback();
|
||||
|
||||
owner->AddMutexWaiter(thread);
|
||||
|
||||
Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule();
|
||||
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,8 +918,8 @@ static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask)
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
*core = thread->ideal_core;
|
||||
*mask = thread->affinity_mask;
|
||||
*core = thread->GetIdealCore();
|
||||
*mask = thread->GetAffinityMask();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -930,11 +935,13 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
|
||||
}
|
||||
|
||||
if (core == static_cast<u32>(THREADPROCESSORID_DEFAULT)) {
|
||||
ASSERT(thread->owner_process->GetDefaultProcessorID() !=
|
||||
static_cast<u8>(THREADPROCESSORID_DEFAULT));
|
||||
const u8 default_processor_id = thread->GetOwnerProcess()->GetDefaultProcessorID();
|
||||
|
||||
ASSERT(default_processor_id != static_cast<u8>(THREADPROCESSORID_DEFAULT));
|
||||
|
||||
// Set the target CPU to the one specified in the process' exheader.
|
||||
core = thread->owner_process->GetDefaultProcessorID();
|
||||
mask = 1ull << core;
|
||||
core = default_processor_id;
|
||||
mask = 1ULL << core;
|
||||
}
|
||||
|
||||
if (mask == 0) {
|
||||
@@ -945,7 +952,7 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
|
||||
static constexpr u32 OnlyChangeMask = static_cast<u32>(-3);
|
||||
|
||||
if (core == OnlyChangeMask) {
|
||||
core = thread->ideal_core;
|
||||
core = thread->GetIdealCore();
|
||||
} else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ void Thread::Stop() {
|
||||
|
||||
void WaitCurrentThread_Sleep() {
|
||||
Thread* thread = GetCurrentThread();
|
||||
thread->status = ThreadStatus::WaitSleep;
|
||||
thread->SetStatus(ThreadStatus::WaitSleep);
|
||||
}
|
||||
|
||||
void ExitCurrentThread() {
|
||||
@@ -169,7 +169,7 @@ void Thread::ResumeFromWait() {
|
||||
next_scheduler->ScheduleThread(this, current_priority);
|
||||
|
||||
// Change thread's scheduler
|
||||
scheduler = next_scheduler;
|
||||
scheduler = next_scheduler.get();
|
||||
|
||||
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
|
||||
}
|
||||
@@ -233,7 +233,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
thread->name = std::move(name);
|
||||
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
|
||||
thread->owner_process = owner_process;
|
||||
thread->scheduler = Core::System::GetInstance().Scheduler(processor_id);
|
||||
thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get();
|
||||
thread->scheduler->AddThread(thread, priority);
|
||||
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
|
||||
|
||||
@@ -269,9 +269,9 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri
|
||||
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
|
||||
|
||||
// Register 1 must be a handle to the main thread
|
||||
thread->guest_handle = kernel.HandleTable().Create(thread).Unwrap();
|
||||
|
||||
thread->context.cpu_registers[1] = thread->guest_handle;
|
||||
const Handle guest_handle = kernel.HandleTable().Create(thread).Unwrap();
|
||||
thread->SetGuestHandle(guest_handle);
|
||||
thread->GetContext().cpu_registers[1] = guest_handle;
|
||||
|
||||
// Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
|
||||
thread->ResumeFromWait();
|
||||
@@ -299,6 +299,18 @@ VAddr Thread::GetCommandBufferAddress() const {
|
||||
return GetTLSAddress() + CommandHeaderOffset;
|
||||
}
|
||||
|
||||
void Thread::SetStatus(ThreadStatus new_status) {
|
||||
if (new_status == status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == ThreadStatus::Running) {
|
||||
last_running_ticks = CoreTiming::GetTicks();
|
||||
}
|
||||
|
||||
status = new_status;
|
||||
}
|
||||
|
||||
void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
if (thread->lock_owner == this) {
|
||||
// If the thread is already waiting for this thread to release the mutex, ensure that the
|
||||
@@ -388,11 +400,23 @@ void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
next_scheduler->ScheduleThread(this, current_priority);
|
||||
|
||||
// Change thread's scheduler
|
||||
scheduler = next_scheduler;
|
||||
scheduler = next_scheduler.get();
|
||||
|
||||
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
|
||||
}
|
||||
|
||||
bool Thread::AllWaitObjectsReady() {
|
||||
return std::none_of(
|
||||
wait_objects.begin(), wait_objects.end(),
|
||||
[this](const SharedPtr<WaitObject>& object) { return object->ShouldWait(this); });
|
||||
}
|
||||
|
||||
bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index) {
|
||||
ASSERT(wakeup_callback);
|
||||
return wakeup_callback(reason, std::move(thread), std::move(object), index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,6 +65,15 @@ public:
|
||||
using TLSMemory = std::vector<u8>;
|
||||
using TLSMemoryPtr = std::shared_ptr<TLSMemory>;
|
||||
|
||||
using MutexWaitingThreads = std::vector<SharedPtr<Thread>>;
|
||||
|
||||
using ThreadContext = Core::ARM_Interface::ThreadContext;
|
||||
|
||||
using ThreadWaitObjects = std::vector<SharedPtr<WaitObject>>;
|
||||
|
||||
using WakeupCallback = std::function<bool(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index)>;
|
||||
|
||||
/**
|
||||
* Creates and returns a new thread. The new thread is immediately scheduled
|
||||
* @param kernel The kernel instance this thread will be created under.
|
||||
@@ -105,6 +114,14 @@ public:
|
||||
return current_priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the thread's nominal priority.
|
||||
* @return The current thread's nominal priority.
|
||||
*/
|
||||
u32 GetNominalPriority() const {
|
||||
return nominal_priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the thread's current priority
|
||||
* @param priority The new priority
|
||||
@@ -133,7 +150,7 @@ public:
|
||||
* Gets the thread's thread ID
|
||||
* @return The thread's ID
|
||||
*/
|
||||
u32 GetThreadId() const {
|
||||
u32 GetThreadID() const {
|
||||
return thread_id;
|
||||
}
|
||||
|
||||
@@ -203,6 +220,11 @@ public:
|
||||
return tpidr_el0;
|
||||
}
|
||||
|
||||
/// Sets the value of the TPIDR_EL0 Read/Write system register for this thread.
|
||||
void SetTPIDR_EL0(u64 value) {
|
||||
tpidr_el0 = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the address of the current thread's command buffer, located in the TLS.
|
||||
* @returns VAddr of the thread's command buffer.
|
||||
@@ -218,69 +240,193 @@ public:
|
||||
return status == ThreadStatus::WaitSynchAll;
|
||||
}
|
||||
|
||||
Core::ARM_Interface::ThreadContext context;
|
||||
ThreadContext& GetContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
u32 thread_id;
|
||||
const ThreadContext& GetContext() const {
|
||||
return context;
|
||||
}
|
||||
|
||||
ThreadStatus status;
|
||||
VAddr entry_point;
|
||||
VAddr stack_top;
|
||||
ThreadStatus GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
u32 nominal_priority; ///< Nominal thread priority, as set by the emulated application
|
||||
u32 current_priority; ///< Current thread priority, can be temporarily changed
|
||||
void SetStatus(ThreadStatus new_status);
|
||||
|
||||
u64 last_running_ticks; ///< CPU tick when thread was last running
|
||||
u64 GetLastRunningTicks() const {
|
||||
return last_running_ticks;
|
||||
}
|
||||
|
||||
s32 processor_id;
|
||||
s32 GetProcessorID() const {
|
||||
return processor_id;
|
||||
}
|
||||
|
||||
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
|
||||
u64 tpidr_el0; ///< TPIDR_EL0 read/write system register.
|
||||
SharedPtr<Process>& GetOwnerProcess() {
|
||||
return owner_process;
|
||||
}
|
||||
|
||||
SharedPtr<Process> owner_process; ///< Process that owns this thread
|
||||
const SharedPtr<Process>& GetOwnerProcess() const {
|
||||
return owner_process;
|
||||
}
|
||||
|
||||
/// Objects that the thread is waiting on, in the same order as they were
|
||||
// passed to WaitSynchronization1/N.
|
||||
std::vector<SharedPtr<WaitObject>> wait_objects;
|
||||
const ThreadWaitObjects& GetWaitObjects() const {
|
||||
return wait_objects;
|
||||
}
|
||||
|
||||
/// List of threads that are waiting for a mutex that is held by this thread.
|
||||
std::vector<SharedPtr<Thread>> wait_mutex_threads;
|
||||
void SetWaitObjects(ThreadWaitObjects objects) {
|
||||
wait_objects = std::move(objects);
|
||||
}
|
||||
|
||||
/// Thread that owns the lock that this thread is waiting for.
|
||||
SharedPtr<Thread> lock_owner;
|
||||
void ClearWaitObjects() {
|
||||
wait_objects.clear();
|
||||
}
|
||||
|
||||
// If waiting on a ConditionVariable, this is the ConditionVariable address
|
||||
VAddr condvar_wait_address;
|
||||
VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
|
||||
Handle wait_handle; ///< The handle used to wait for the mutex.
|
||||
/// Determines whether all the objects this thread is waiting on are ready.
|
||||
bool AllWaitObjectsReady();
|
||||
|
||||
// If waiting for an AddressArbiter, this is the address being waited on.
|
||||
VAddr arb_wait_address{0};
|
||||
const MutexWaitingThreads& GetMutexWaitingThreads() const {
|
||||
return wait_mutex_threads;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
Thread* GetLockOwner() const {
|
||||
return lock_owner.get();
|
||||
}
|
||||
|
||||
/// Handle used by guest emulated application to access this thread
|
||||
Handle guest_handle;
|
||||
void SetLockOwner(SharedPtr<Thread> owner) {
|
||||
lock_owner = std::move(owner);
|
||||
}
|
||||
|
||||
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
|
||||
Handle callback_handle;
|
||||
VAddr GetCondVarWaitAddress() const {
|
||||
return condvar_wait_address;
|
||||
}
|
||||
|
||||
using WakeupCallback = bool(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index);
|
||||
// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
|
||||
// was waiting via WaitSynchronizationN then the object will be the last object that became
|
||||
// available. In case of a timeout, the object will be nullptr.
|
||||
std::function<WakeupCallback> wakeup_callback;
|
||||
void SetCondVarWaitAddress(VAddr address) {
|
||||
condvar_wait_address = address;
|
||||
}
|
||||
|
||||
std::shared_ptr<Scheduler> scheduler;
|
||||
VAddr GetMutexWaitAddress() const {
|
||||
return mutex_wait_address;
|
||||
}
|
||||
|
||||
u32 ideal_core{0xFFFFFFFF};
|
||||
u64 affinity_mask{0x1};
|
||||
void SetMutexWaitAddress(VAddr address) {
|
||||
mutex_wait_address = address;
|
||||
}
|
||||
|
||||
Handle GetWaitHandle() const {
|
||||
return wait_handle;
|
||||
}
|
||||
|
||||
void SetWaitHandle(Handle handle) {
|
||||
wait_handle = handle;
|
||||
}
|
||||
|
||||
VAddr GetArbiterWaitAddress() const {
|
||||
return arb_wait_address;
|
||||
}
|
||||
|
||||
void SetArbiterWaitAddress(VAddr address) {
|
||||
arb_wait_address = address;
|
||||
}
|
||||
|
||||
void SetGuestHandle(Handle handle) {
|
||||
guest_handle = handle;
|
||||
}
|
||||
|
||||
bool HasWakeupCallback() const {
|
||||
return wakeup_callback != nullptr;
|
||||
}
|
||||
|
||||
void SetWakeupCallback(WakeupCallback callback) {
|
||||
wakeup_callback = std::move(callback);
|
||||
}
|
||||
|
||||
void InvalidateWakeupCallback() {
|
||||
SetWakeupCallback(nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the thread's wakeup callback.
|
||||
*
|
||||
* @pre A valid wakeup callback has been set. Violating this precondition
|
||||
* will cause an assertion to trigger.
|
||||
*/
|
||||
bool InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, std::size_t index);
|
||||
|
||||
u32 GetIdealCore() const {
|
||||
return ideal_core;
|
||||
}
|
||||
|
||||
u64 GetAffinityMask() const {
|
||||
return affinity_mask;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Thread(KernelCore& kernel);
|
||||
~Thread() override;
|
||||
|
||||
Core::ARM_Interface::ThreadContext context{};
|
||||
|
||||
u32 thread_id = 0;
|
||||
|
||||
ThreadStatus status = ThreadStatus::Dormant;
|
||||
|
||||
VAddr entry_point = 0;
|
||||
VAddr stack_top = 0;
|
||||
|
||||
u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
|
||||
u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
|
||||
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
|
||||
s32 processor_id = 0;
|
||||
|
||||
VAddr tls_address = 0; ///< Virtual address of the Thread Local Storage of the thread
|
||||
u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register.
|
||||
|
||||
/// Process that owns this thread
|
||||
SharedPtr<Process> owner_process;
|
||||
|
||||
/// Objects that the thread is waiting on, in the same order as they were
|
||||
/// passed to WaitSynchronization1/N.
|
||||
ThreadWaitObjects wait_objects;
|
||||
|
||||
/// List of threads that are waiting for a mutex that is held by this thread.
|
||||
MutexWaitingThreads wait_mutex_threads;
|
||||
|
||||
/// Thread that owns the lock that this thread is waiting for.
|
||||
SharedPtr<Thread> lock_owner;
|
||||
|
||||
/// If waiting on a ConditionVariable, this is the ConditionVariable address
|
||||
VAddr condvar_wait_address = 0;
|
||||
/// If waiting on a Mutex, this is the mutex address
|
||||
VAddr mutex_wait_address = 0;
|
||||
/// The handle used to wait for the mutex.
|
||||
Handle wait_handle = 0;
|
||||
|
||||
/// If waiting for an AddressArbiter, this is the address being waited on.
|
||||
VAddr arb_wait_address{0};
|
||||
|
||||
/// Handle used by guest emulated application to access this thread
|
||||
Handle guest_handle = 0;
|
||||
|
||||
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
|
||||
Handle callback_handle = 0;
|
||||
|
||||
/// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
|
||||
/// was waiting via WaitSynchronizationN then the object will be the last object that became
|
||||
/// available. In case of a timeout, the object will be nullptr.
|
||||
WakeupCallback wakeup_callback;
|
||||
|
||||
Scheduler* scheduler = nullptr;
|
||||
|
||||
u32 ideal_core{0xFFFFFFFF};
|
||||
u64 affinity_mask{0x1};
|
||||
|
||||
TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>();
|
||||
|
||||
std::string name;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,13 +35,15 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
|
||||
u32 candidate_priority = THREADPRIO_LOWEST + 1;
|
||||
|
||||
for (const auto& thread : waiting_threads) {
|
||||
const ThreadStatus thread_status = thread->GetStatus();
|
||||
|
||||
// The list of waiting threads must not contain threads that are not waiting to be awakened.
|
||||
ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny ||
|
||||
thread->status == ThreadStatus::WaitSynchAll ||
|
||||
thread->status == ThreadStatus::WaitHLEEvent,
|
||||
ASSERT_MSG(thread_status == ThreadStatus::WaitSynchAny ||
|
||||
thread_status == ThreadStatus::WaitSynchAll ||
|
||||
thread_status == ThreadStatus::WaitHLEEvent,
|
||||
"Inconsistent thread statuses in waiting_threads");
|
||||
|
||||
if (thread->current_priority >= candidate_priority)
|
||||
if (thread->GetPriority() >= candidate_priority)
|
||||
continue;
|
||||
|
||||
if (ShouldWait(thread.get()))
|
||||
@@ -50,16 +52,13 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
|
||||
// A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or
|
||||
// in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready.
|
||||
bool ready_to_run = true;
|
||||
if (thread->status == ThreadStatus::WaitSynchAll) {
|
||||
ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(),
|
||||
[&thread](const SharedPtr<WaitObject>& object) {
|
||||
return object->ShouldWait(thread.get());
|
||||
});
|
||||
if (thread_status == ThreadStatus::WaitSynchAll) {
|
||||
ready_to_run = thread->AllWaitObjectsReady();
|
||||
}
|
||||
|
||||
if (ready_to_run) {
|
||||
candidate = thread.get();
|
||||
candidate_priority = thread->current_priority;
|
||||
candidate_priority = thread->GetPriority();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,24 +74,24 @@ void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) {
|
||||
if (!thread->IsSleepingOnWaitAll()) {
|
||||
Acquire(thread.get());
|
||||
} else {
|
||||
for (auto& object : thread->wait_objects) {
|
||||
for (const auto& object : thread->GetWaitObjects()) {
|
||||
ASSERT(!object->ShouldWait(thread.get()));
|
||||
object->Acquire(thread.get());
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t index = thread->GetWaitObjectIndex(this);
|
||||
const std::size_t index = thread->GetWaitObjectIndex(this);
|
||||
|
||||
for (auto& object : thread->wait_objects)
|
||||
for (const auto& object : thread->GetWaitObjects())
|
||||
object->RemoveWaitingThread(thread.get());
|
||||
thread->wait_objects.clear();
|
||||
thread->ClearWaitObjects();
|
||||
|
||||
thread->CancelWakeupTimer();
|
||||
|
||||
bool resume = true;
|
||||
|
||||
if (thread->wakeup_callback)
|
||||
resume = thread->wakeup_callback(ThreadWakeupReason::Signal, thread, this, index);
|
||||
if (thread->HasWakeupCallback())
|
||||
resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Signal, thread, this, index);
|
||||
|
||||
if (resume)
|
||||
thread->ResumeFromWait();
|
||||
|
||||
@@ -2,22 +2,57 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/aoc/aoc_u.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Service::AOC {
|
||||
|
||||
AOC_U::AOC_U() : ServiceFramework("aoc:u") {
|
||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
constexpr u64 DLC_BASE_TO_AOC_ID_MASK = 0x1000;
|
||||
|
||||
static bool CheckAOCTitleIDMatchesBase(u64 base, u64 aoc) {
|
||||
return (aoc & DLC_BASE_TITLE_ID_MASK) == base;
|
||||
}
|
||||
|
||||
static std::vector<u64> AccumulateAOCTitleIDs() {
|
||||
std::vector<u64> add_on_content;
|
||||
const auto rcu = FileSystem::GetUnionContents();
|
||||
const auto list =
|
||||
rcu->ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
|
||||
std::transform(list.begin(), list.end(), std::back_inserter(add_on_content),
|
||||
[](const FileSys::RegisteredCacheEntry& rce) { return rce.title_id; });
|
||||
add_on_content.erase(
|
||||
std::remove_if(
|
||||
add_on_content.begin(), add_on_content.end(),
|
||||
[&rcu](u64 tid) {
|
||||
return rcu->GetEntry(tid, FileSys::ContentRecordType::Data)->GetStatus() !=
|
||||
Loader::ResultStatus::Success;
|
||||
}),
|
||||
add_on_content.end());
|
||||
return add_on_content;
|
||||
}
|
||||
|
||||
AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs()) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "CountAddOnContentByApplicationId"},
|
||||
{1, nullptr, "ListAddOnContentByApplicationId"},
|
||||
{2, &AOC_U::CountAddOnContent, "CountAddOnContent"},
|
||||
{3, &AOC_U::ListAddOnContent, "ListAddOnContent"},
|
||||
{4, nullptr, "GetAddOnContentBaseIdByApplicationId"},
|
||||
{5, nullptr, "GetAddOnContentBaseId"},
|
||||
{5, &AOC_U::GetAddOnContentBaseId, "GetAddOnContentBaseId"},
|
||||
{6, nullptr, "PrepareAddOnContentByApplicationId"},
|
||||
{7, nullptr, "PrepareAddOnContent"},
|
||||
{7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},
|
||||
{8, nullptr, "GetAddOnContentListChangedEvent"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
@@ -28,15 +63,59 @@ AOC_U::~AOC_U() = default;
|
||||
void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(0);
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called");
|
||||
|
||||
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
rb.Push<u32>(std::count_if(add_on_content.begin(), add_on_content.end(), [¤t](u64 tid) {
|
||||
return (tid & DLC_BASE_TITLE_ID_MASK) == current;
|
||||
}));
|
||||
}
|
||||
|
||||
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto offset = rp.PopRaw<u32>();
|
||||
auto count = rp.PopRaw<u32>();
|
||||
|
||||
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
|
||||
std::vector<u32> out;
|
||||
for (size_t i = 0; i < add_on_content.size(); ++i) {
|
||||
if ((add_on_content[i] & DLC_BASE_TITLE_ID_MASK) == current)
|
||||
out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
|
||||
}
|
||||
|
||||
if (out.size() < offset) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(DarkLordZach): Find the correct error code.
|
||||
rb.Push(ResultCode(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
count = std::min<size_t>(out.size() - offset, count);
|
||||
std::rotate(out.begin(), out.begin() + offset, out.end());
|
||||
out.resize(count);
|
||||
|
||||
ctx.WriteBuffer(out);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(0);
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called");
|
||||
rb.Push(Core::System::GetInstance().CurrentProcess()->GetTitleID() | DLC_BASE_TO_AOC_ID_MASK);
|
||||
}
|
||||
|
||||
void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto aoc_id = rp.PopRaw<u32>();
|
||||
|
||||
LOG_WARNING(Service_AOC, "(STUBBED) called with aoc_id={:08X}", aoc_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
|
||||
@@ -16,6 +16,10 @@ public:
|
||||
private:
|
||||
void CountAddOnContent(Kernel::HLERequestContext& ctx);
|
||||
void ListAddOnContent(Kernel::HLERequestContext& ctx);
|
||||
void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx);
|
||||
void PrepareAddOnContent(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::vector<u64> add_on_content;
|
||||
};
|
||||
|
||||
/// Registers all AOC services with the specified service manager.
|
||||
|
||||
@@ -468,6 +468,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
{80, nullptr, "OpenSaveDataMetaFile"},
|
||||
{81, nullptr, "OpenSaveDataTransferManager"},
|
||||
{82, nullptr, "OpenSaveDataTransferManagerVersion2"},
|
||||
{83, nullptr, "OpenSaveDataTransferProhibiterForCloudBackUp"},
|
||||
{100, nullptr, "OpenImageDirectoryFileSystem"},
|
||||
{110, nullptr, "OpenContentStorageFileSystem"},
|
||||
{200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
|
||||
@@ -495,6 +496,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
{613, nullptr, "VerifySaveDataFileSystemBySaveDataSpaceId"},
|
||||
{614, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId"},
|
||||
{615, nullptr, "QuerySaveDataInternalStorageTotalSize"},
|
||||
{616, nullptr, "GetSaveDataCommitId"},
|
||||
{620, nullptr, "SetSdCardEncryptionSeed"},
|
||||
{630, nullptr, "SetSdCardAccessibility"},
|
||||
{631, nullptr, "IsSdCardAccessible"},
|
||||
|
||||
@@ -21,29 +21,29 @@ public:
|
||||
{0, nullptr, "Unknown1"},
|
||||
{1, nullptr, "Unknown2"},
|
||||
{2, nullptr, "Unknown3"},
|
||||
{3, nullptr, "Unknown4"},
|
||||
{4, nullptr, "Unknown5"},
|
||||
{5, nullptr, "Unknown6"},
|
||||
{3, nullptr, "GetCurrentBacklightLevel"},
|
||||
{4, nullptr, "Unknown4"},
|
||||
{5, nullptr, "GetAlsComputedBacklightLevel"},
|
||||
{6, nullptr, "TurnOffBacklight"},
|
||||
{7, nullptr, "TurnOnBacklight"},
|
||||
{8, nullptr, "GetBacklightStatus"},
|
||||
{9, nullptr, "Unknown7"},
|
||||
{10, nullptr, "Unknown8"},
|
||||
{11, nullptr, "Unknown9"},
|
||||
{12, nullptr, "Unknown10"},
|
||||
{13, nullptr, "Unknown11"},
|
||||
{14, nullptr, "Unknown12"},
|
||||
{15, nullptr, "Unknown13"},
|
||||
{9, nullptr, "Unknown5"},
|
||||
{10, nullptr, "Unknown6"},
|
||||
{11, nullptr, "Unknown7"},
|
||||
{12, nullptr, "Unknown8"},
|
||||
{13, nullptr, "Unknown9"},
|
||||
{14, nullptr, "Unknown10"},
|
||||
{15, nullptr, "GetAutoBrightnessSetting"},
|
||||
{16, nullptr, "ReadRawLightSensor"},
|
||||
{17, nullptr, "Unknown14"},
|
||||
{18, nullptr, "Unknown15"},
|
||||
{19, nullptr, "Unknown16"},
|
||||
{20, nullptr, "Unknown17"},
|
||||
{21, nullptr, "Unknown18"},
|
||||
{22, nullptr, "Unknown19"},
|
||||
{23, nullptr, "Unknown20"},
|
||||
{24, nullptr, "Unknown21"},
|
||||
{25, nullptr, "Unknown22"},
|
||||
{17, nullptr, "Unknown11"},
|
||||
{18, nullptr, "Unknown12"},
|
||||
{19, nullptr, "Unknown13"},
|
||||
{20, nullptr, "Unknown14"},
|
||||
{21, nullptr, "Unknown15"},
|
||||
{22, nullptr, "Unknown16"},
|
||||
{23, nullptr, "Unknown17"},
|
||||
{24, nullptr, "Unknown18"},
|
||||
{25, nullptr, "Unknown19"},
|
||||
{26, &LBL::EnableVrMode, "EnableVrMode"},
|
||||
{27, &LBL::DisableVrMode, "DisableVrMode"},
|
||||
{28, &LBL::GetVrMode, "GetVrMode"},
|
||||
|
||||
@@ -130,6 +130,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
|
||||
}
|
||||
|
||||
process.LoadFromMetadata(metadata);
|
||||
const FileSys::PatchManager pm(metadata.GetTitleID());
|
||||
|
||||
// Load NSO modules
|
||||
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
|
||||
@@ -139,7 +140,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
|
||||
const FileSys::VirtualFile module_file = dir->GetFile(module);
|
||||
if (module_file != nullptr) {
|
||||
const VAddr load_addr = next_load_addr;
|
||||
next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr);
|
||||
next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr, pm);
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -36,8 +37,7 @@ struct NsoHeader {
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u8 flags;
|
||||
std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
|
||||
u32_le bss_size;
|
||||
INSERT_PADDING_BYTES(0x1c);
|
||||
std::array<u8, 0x20> build_id;
|
||||
std::array<u32_le, 3> segments_compressed_size;
|
||||
|
||||
bool IsSegmentCompressed(size_t segment_num) const {
|
||||
@@ -93,7 +93,8 @@ static constexpr u32 PageAlignSize(u32 size) {
|
||||
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
|
||||
}
|
||||
|
||||
VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
|
||||
VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
|
||||
boost::optional<FileSys::PatchManager> pm) {
|
||||
if (file == nullptr)
|
||||
return {};
|
||||
|
||||
@@ -142,6 +143,17 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
|
||||
const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
|
||||
program_image.resize(image_size);
|
||||
|
||||
// Apply patches if necessary
|
||||
if (pm != boost::none && pm->HasNSOPatch(nso_header.build_id)) {
|
||||
std::vector<u8> pi_header(program_image.size() + 0x100);
|
||||
std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
|
||||
std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());
|
||||
|
||||
pi_header = pm->PatchNSO(pi_header);
|
||||
|
||||
std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size());
|
||||
}
|
||||
|
||||
// Load codeset for current process
|
||||
codeset->name = file->GetName();
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/linker.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
@@ -26,7 +27,8 @@ public:
|
||||
return IdentifyType(file);
|
||||
}
|
||||
|
||||
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base);
|
||||
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base,
|
||||
boost::optional<FileSys::PatchManager> pm = boost::none);
|
||||
|
||||
ResultStatus Load(Kernel::Process& process) override;
|
||||
};
|
||||
|
||||
@@ -27,6 +27,8 @@ add_library(video_core STATIC
|
||||
renderer_base.h
|
||||
renderer_opengl/gl_buffer_cache.cpp
|
||||
renderer_opengl/gl_buffer_cache.h
|
||||
renderer_opengl/gl_primitive_assembler.cpp
|
||||
renderer_opengl/gl_primitive_assembler.h
|
||||
renderer_opengl/gl_rasterizer.cpp
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_rasterizer_cache.cpp
|
||||
|
||||
@@ -744,6 +744,12 @@ public:
|
||||
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(end_addr_high) << 32) |
|
||||
end_addr_low);
|
||||
}
|
||||
|
||||
/// Adjust the index buffer offset so it points to the first desired index.
|
||||
GPUVAddr IndexStart() const {
|
||||
return StartAddress() + static_cast<size_t>(first) *
|
||||
static_cast<size_t>(FormatSizeInBytes());
|
||||
}
|
||||
} index_array;
|
||||
|
||||
INSERT_PADDING_WORDS(0x7);
|
||||
|
||||
@@ -34,7 +34,7 @@ GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size
|
||||
}
|
||||
|
||||
AlignBuffer(alignment);
|
||||
GLintptr uploaded_offset = buffer_offset;
|
||||
const GLintptr uploaded_offset = buffer_offset;
|
||||
|
||||
Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
|
||||
|
||||
@@ -57,13 +57,23 @@ GLintptr OGLBufferCache::UploadHostMemory(const void* raw_pointer, std::size_t s
|
||||
std::size_t alignment) {
|
||||
AlignBuffer(alignment);
|
||||
std::memcpy(buffer_ptr, raw_pointer, size);
|
||||
GLintptr uploaded_offset = buffer_offset;
|
||||
const GLintptr uploaded_offset = buffer_offset;
|
||||
|
||||
buffer_ptr += size;
|
||||
buffer_offset += size;
|
||||
return uploaded_offset;
|
||||
}
|
||||
|
||||
std::tuple<u8*, GLintptr> OGLBufferCache::ReserveMemory(std::size_t size, std::size_t alignment) {
|
||||
AlignBuffer(alignment);
|
||||
u8* const uploaded_ptr = buffer_ptr;
|
||||
const GLintptr uploaded_offset = buffer_offset;
|
||||
|
||||
buffer_ptr += size;
|
||||
buffer_offset += size;
|
||||
return std::make_tuple(uploaded_ptr, uploaded_offset);
|
||||
}
|
||||
|
||||
void OGLBufferCache::Map(std::size_t max_size) {
|
||||
bool invalidate;
|
||||
std::tie(buffer_ptr, buffer_offset_base, invalidate) =
|
||||
@@ -74,6 +84,7 @@ void OGLBufferCache::Map(std::size_t max_size) {
|
||||
InvalidateAll();
|
||||
}
|
||||
}
|
||||
|
||||
void OGLBufferCache::Unmap() {
|
||||
stream_buffer.Unmap(buffer_offset - buffer_offset_base);
|
||||
}
|
||||
@@ -84,7 +95,7 @@ GLuint OGLBufferCache::GetHandle() const {
|
||||
|
||||
void OGLBufferCache::AlignBuffer(std::size_t alignment) {
|
||||
// Align the offset, not the mapped pointer
|
||||
GLintptr offset_aligned =
|
||||
const GLintptr offset_aligned =
|
||||
static_cast<GLintptr>(Common::AlignUp(static_cast<std::size_t>(buffer_offset), alignment));
|
||||
buffer_ptr += offset_aligned - buffer_offset;
|
||||
buffer_offset = offset_aligned;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
@@ -33,11 +34,17 @@ class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBuffer
|
||||
public:
|
||||
explicit OGLBufferCache(std::size_t size);
|
||||
|
||||
/// Uploads data from a guest GPU address. Returns host's buffer offset where it's been
|
||||
/// allocated.
|
||||
GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
|
||||
bool cache = true);
|
||||
|
||||
/// Uploads from a host memory. Returns host's buffer offset where it's been allocated.
|
||||
GLintptr UploadHostMemory(const void* raw_pointer, std::size_t size, std::size_t alignment = 4);
|
||||
|
||||
/// Reserves memory to be used by host's CPU. Returns mapped address and offset.
|
||||
std::tuple<u8*, GLintptr> ReserveMemory(std::size_t size, std::size_t alignment = 4);
|
||||
|
||||
void Map(std::size_t max_size);
|
||||
void Unmap();
|
||||
|
||||
|
||||
64
src/video_core/renderer_opengl/gl_primitive_assembler.cpp
Normal file
64
src/video_core/renderer_opengl/gl_primitive_assembler.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/renderer_opengl/gl_buffer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_primitive_assembler.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
constexpr u32 TRIANGLES_PER_QUAD = 6;
|
||||
constexpr std::array<u32, TRIANGLES_PER_QUAD> QUAD_MAP = {0, 1, 2, 0, 2, 3};
|
||||
|
||||
PrimitiveAssembler::PrimitiveAssembler(OGLBufferCache& buffer_cache) : buffer_cache(buffer_cache) {}
|
||||
|
||||
PrimitiveAssembler::~PrimitiveAssembler() = default;
|
||||
|
||||
std::size_t PrimitiveAssembler::CalculateQuadSize(u32 count) const {
|
||||
ASSERT_MSG(count % 4 == 0, "Quad count is expected to be a multiple of 4");
|
||||
return (count / 4) * TRIANGLES_PER_QUAD * sizeof(GLuint);
|
||||
}
|
||||
|
||||
GLintptr PrimitiveAssembler::MakeQuadArray(u32 first, u32 count) {
|
||||
const std::size_t size{CalculateQuadSize(count)};
|
||||
auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(size);
|
||||
|
||||
for (u32 primitive = 0; primitive < count / 4; ++primitive) {
|
||||
for (u32 i = 0; i < TRIANGLES_PER_QUAD; ++i) {
|
||||
const u32 index = first + primitive * 4 + QUAD_MAP[i];
|
||||
std::memcpy(dst_pointer, &index, sizeof(index));
|
||||
dst_pointer += sizeof(index);
|
||||
}
|
||||
}
|
||||
|
||||
return index_offset;
|
||||
}
|
||||
|
||||
GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size,
|
||||
u32 count) {
|
||||
const std::size_t map_size{CalculateQuadSize(count)};
|
||||
auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
|
||||
|
||||
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
|
||||
const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
|
||||
const u8* source{Memory::GetPointer(*cpu_addr)};
|
||||
|
||||
for (u32 primitive = 0; primitive < count / 4; ++primitive) {
|
||||
for (std::size_t i = 0; i < TRIANGLES_PER_QUAD; ++i) {
|
||||
const u32 index = primitive * 4 + QUAD_MAP[i];
|
||||
const u8* src_offset = source + (index * index_size);
|
||||
|
||||
std::memcpy(dst_pointer, src_offset, index_size);
|
||||
dst_pointer += index_size;
|
||||
}
|
||||
}
|
||||
|
||||
return index_offset;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
33
src/video_core/renderer_opengl/gl_primitive_assembler.h
Normal file
33
src/video_core/renderer_opengl/gl_primitive_assembler.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class OGLBufferCache;
|
||||
|
||||
class PrimitiveAssembler {
|
||||
public:
|
||||
explicit PrimitiveAssembler(OGLBufferCache& buffer_cache);
|
||||
~PrimitiveAssembler();
|
||||
|
||||
/// Calculates the size required by MakeQuadArray and MakeQuadIndexed.
|
||||
std::size_t CalculateQuadSize(u32 count) const;
|
||||
|
||||
GLintptr MakeQuadArray(u32 first, u32 count);
|
||||
|
||||
GLintptr MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size, u32 count);
|
||||
|
||||
private:
|
||||
OGLBufferCache& buffer_cache;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
@@ -42,6 +42,41 @@ MICROPROFILE_DEFINE(OpenGL_Framebuffer, "OpenGL", "Framebuffer Setup", MP_RGB(12
|
||||
MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
|
||||
MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192));
|
||||
MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
|
||||
MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255, 100, 100));
|
||||
|
||||
struct DrawParameters {
|
||||
GLenum primitive_mode;
|
||||
GLsizei count;
|
||||
GLint current_instance;
|
||||
bool use_indexed;
|
||||
|
||||
GLint vertex_first;
|
||||
|
||||
GLenum index_format;
|
||||
GLint base_vertex;
|
||||
GLintptr index_buffer_offset;
|
||||
|
||||
void DispatchDraw() const {
|
||||
if (use_indexed) {
|
||||
const auto index_buffer_ptr = reinterpret_cast<const void*>(index_buffer_offset);
|
||||
if (current_instance > 0) {
|
||||
glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, count, index_format,
|
||||
index_buffer_ptr, 1, base_vertex,
|
||||
current_instance);
|
||||
} else {
|
||||
glDrawElementsBaseVertex(primitive_mode, count, index_format, index_buffer_ptr,
|
||||
base_vertex);
|
||||
}
|
||||
} else {
|
||||
if (current_instance > 0) {
|
||||
glDrawArraysInstancedBaseInstance(primitive_mode, vertex_first, count, 1,
|
||||
current_instance);
|
||||
} else {
|
||||
glDrawArrays(primitive_mode, vertex_first, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
|
||||
: emu_window{window}, screen_info{info}, buffer_cache(STREAM_BUFFER_SIZE) {
|
||||
@@ -172,6 +207,53 @@ void RasterizerOpenGL::SetupVertexArrays() {
|
||||
}
|
||||
}
|
||||
|
||||
DrawParameters RasterizerOpenGL::SetupDraw() {
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
|
||||
|
||||
DrawParameters params{};
|
||||
params.current_instance = gpu.state.current_instance;
|
||||
|
||||
if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
|
||||
MICROPROFILE_SCOPE(OpenGL_PrimitiveAssembly);
|
||||
|
||||
params.use_indexed = true;
|
||||
params.primitive_mode = GL_TRIANGLES;
|
||||
|
||||
if (is_indexed) {
|
||||
params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
|
||||
params.count = (regs.index_array.count / 4) * 6;
|
||||
params.index_buffer_offset = primitive_assembler.MakeQuadIndexed(
|
||||
regs.index_array.IndexStart(), regs.index_array.FormatSizeInBytes(),
|
||||
regs.index_array.count);
|
||||
params.base_vertex = static_cast<GLint>(regs.vb_element_base);
|
||||
} else {
|
||||
// MakeQuadArray always generates u32 indexes
|
||||
params.index_format = GL_UNSIGNED_INT;
|
||||
params.count = (regs.vertex_buffer.count / 4) * 6;
|
||||
params.index_buffer_offset =
|
||||
primitive_assembler.MakeQuadArray(regs.vertex_buffer.first, params.count);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
params.use_indexed = is_indexed;
|
||||
params.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
|
||||
|
||||
if (is_indexed) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Index);
|
||||
params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
|
||||
params.count = regs.index_array.count;
|
||||
params.index_buffer_offset =
|
||||
buffer_cache.UploadMemory(regs.index_array.IndexStart(), CalculateIndexBufferSize());
|
||||
params.base_vertex = static_cast<GLint>(regs.vb_element_base);
|
||||
} else {
|
||||
params.count = regs.vertex_buffer.count;
|
||||
params.vertex_first = regs.vertex_buffer.first;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupShaders() {
|
||||
MICROPROFILE_SCOPE(OpenGL_Shader);
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
@@ -256,6 +338,13 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
return static_cast<std::size_t>(regs.index_array.count) *
|
||||
static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
|
||||
accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
|
||||
DrawArrays();
|
||||
@@ -459,16 +548,23 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
|
||||
// Draw the vertex batch
|
||||
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
|
||||
const u64 index_buffer_size{static_cast<u64>(regs.index_array.count) *
|
||||
static_cast<u64>(regs.index_array.FormatSizeInBytes())};
|
||||
|
||||
state.draw.vertex_buffer = buffer_cache.GetHandle();
|
||||
state.Apply();
|
||||
|
||||
std::size_t buffer_size = CalculateVertexArraysSize();
|
||||
|
||||
if (is_indexed) {
|
||||
buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + index_buffer_size;
|
||||
// Add space for index buffer (keeping in mind non-core primitives)
|
||||
switch (regs.draw.topology) {
|
||||
case Maxwell::PrimitiveTopology::Quads:
|
||||
buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) +
|
||||
primitive_assembler.CalculateQuadSize(regs.vertex_buffer.count);
|
||||
break;
|
||||
default:
|
||||
if (is_indexed) {
|
||||
buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + CalculateIndexBufferSize();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Uniform space for the 5 shader stages
|
||||
@@ -482,20 +578,7 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
buffer_cache.Map(buffer_size);
|
||||
|
||||
SetupVertexArrays();
|
||||
|
||||
// If indexed mode, copy the index buffer
|
||||
GLintptr index_buffer_offset = 0;
|
||||
if (is_indexed) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Index);
|
||||
|
||||
// Adjust the index buffer offset so it points to the first desired index.
|
||||
auto index_start = regs.index_array.StartAddress();
|
||||
index_start += static_cast<size_t>(regs.index_array.first) *
|
||||
static_cast<size_t>(regs.index_array.FormatSizeInBytes());
|
||||
|
||||
index_buffer_offset = buffer_cache.UploadMemory(index_start, index_buffer_size);
|
||||
}
|
||||
|
||||
DrawParameters params = SetupDraw();
|
||||
SetupShaders();
|
||||
|
||||
buffer_cache.Unmap();
|
||||
@@ -503,31 +586,8 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
shader_program_manager->ApplyTo(state);
|
||||
state.Apply();
|
||||
|
||||
const GLenum primitive_mode{MaxwellToGL::PrimitiveTopology(regs.draw.topology)};
|
||||
if (is_indexed) {
|
||||
const GLint base_vertex{static_cast<GLint>(regs.vb_element_base)};
|
||||
|
||||
if (gpu.state.current_instance > 0) {
|
||||
glDrawElementsInstancedBaseVertexBaseInstance(
|
||||
primitive_mode, regs.index_array.count,
|
||||
MaxwellToGL::IndexFormat(regs.index_array.format),
|
||||
reinterpret_cast<const void*>(index_buffer_offset), 1, base_vertex,
|
||||
gpu.state.current_instance);
|
||||
} else {
|
||||
glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
|
||||
MaxwellToGL::IndexFormat(regs.index_array.format),
|
||||
reinterpret_cast<const void*>(index_buffer_offset),
|
||||
base_vertex);
|
||||
}
|
||||
} else {
|
||||
if (gpu.state.current_instance > 0) {
|
||||
glDrawArraysInstancedBaseInstance(primitive_mode, regs.vertex_buffer.first,
|
||||
regs.vertex_buffer.count, 1,
|
||||
gpu.state.current_instance);
|
||||
} else {
|
||||
glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
|
||||
}
|
||||
}
|
||||
// Execute draw call
|
||||
params.DispatchDraw();
|
||||
|
||||
// Disable scissor test
|
||||
state.scissor.enabled = false;
|
||||
@@ -909,7 +969,10 @@ void RasterizerOpenGL::SyncTransformFeedback() {
|
||||
void RasterizerOpenGL::SyncPointState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
state.point.size = regs.point_size;
|
||||
// TODO(Rodrigo): Most games do not set a point size. I think this is a case of a
|
||||
// register carrying a default value. For now, if the point size is zero, assume it's
|
||||
// OpenGL's default (1).
|
||||
state.point.size = regs.point_size == 0 ? 1 : regs.point_size;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_opengl/gl_buffer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_primitive_assembler.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
@@ -38,6 +39,7 @@ class EmuWindow;
|
||||
namespace OpenGL {
|
||||
|
||||
struct ScreenInfo;
|
||||
struct DrawParameters;
|
||||
|
||||
class RasterizerOpenGL : public VideoCore::RasterizerInterface {
|
||||
public:
|
||||
@@ -192,12 +194,17 @@ private:
|
||||
static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
|
||||
OGLBufferCache buffer_cache;
|
||||
OGLFramebuffer framebuffer;
|
||||
PrimitiveAssembler primitive_assembler{buffer_cache};
|
||||
GLint uniform_buffer_alignment;
|
||||
|
||||
std::size_t CalculateVertexArraysSize() const;
|
||||
|
||||
std::size_t CalculateIndexBufferSize() const;
|
||||
|
||||
void SetupVertexArrays();
|
||||
|
||||
DrawParameters SetupDraw();
|
||||
|
||||
void SetupShaders();
|
||||
|
||||
enum class AccelDraw { Disabled, Arrays, Indexed };
|
||||
|
||||
@@ -21,9 +21,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
|
||||
ui->output_sink_combo_box->addItem(sink_detail.id);
|
||||
}
|
||||
|
||||
connect(ui->volume_slider, &QSlider::valueChanged, [this] {
|
||||
ui->volume_indicator->setText(tr("%1 %").arg(ui->volume_slider->sliderPosition()));
|
||||
});
|
||||
connect(ui->volume_slider, &QSlider::valueChanged, this,
|
||||
&ConfigureAudio::setVolumeIndicatorText);
|
||||
|
||||
this->setConfiguration();
|
||||
connect(ui->output_sink_combo_box,
|
||||
@@ -37,32 +36,48 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
|
||||
ConfigureAudio::~ConfigureAudio() = default;
|
||||
|
||||
void ConfigureAudio::setConfiguration() {
|
||||
setOutputSinkFromSinkID();
|
||||
|
||||
// The device list cannot be pre-populated (nor listed) until the output sink is known.
|
||||
updateAudioDevices(ui->output_sink_combo_box->currentIndex());
|
||||
|
||||
setAudioDeviceFromDeviceID();
|
||||
|
||||
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
|
||||
ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum());
|
||||
setVolumeIndicatorText(ui->volume_slider->sliderPosition());
|
||||
}
|
||||
|
||||
void ConfigureAudio::setOutputSinkFromSinkID() {
|
||||
int new_sink_index = 0;
|
||||
|
||||
const QString sink_id = QString::fromStdString(Settings::values.sink_id);
|
||||
for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
|
||||
if (ui->output_sink_combo_box->itemText(index).toStdString() == Settings::values.sink_id) {
|
||||
if (ui->output_sink_combo_box->itemText(index) == sink_id) {
|
||||
new_sink_index = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
|
||||
}
|
||||
|
||||
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
|
||||
|
||||
// The device list cannot be pre-populated (nor listed) until the output sink is known.
|
||||
updateAudioDevices(new_sink_index);
|
||||
|
||||
void ConfigureAudio::setAudioDeviceFromDeviceID() {
|
||||
int new_device_index = -1;
|
||||
|
||||
const QString device_id = QString::fromStdString(Settings::values.audio_device_id);
|
||||
for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
|
||||
if (ui->audio_device_combo_box->itemText(index).toStdString() ==
|
||||
Settings::values.audio_device_id) {
|
||||
if (ui->audio_device_combo_box->itemText(index) == device_id) {
|
||||
new_device_index = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ui->audio_device_combo_box->setCurrentIndex(new_device_index);
|
||||
|
||||
ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum());
|
||||
ui->volume_indicator->setText(tr("%1 %").arg(ui->volume_slider->sliderPosition()));
|
||||
ui->audio_device_combo_box->setCurrentIndex(new_device_index);
|
||||
}
|
||||
|
||||
void ConfigureAudio::setVolumeIndicatorText(int percentage) {
|
||||
ui->volume_indicator->setText(tr("%1%", "Volume percentage (e.g. 50%)").arg(percentage));
|
||||
}
|
||||
|
||||
void ConfigureAudio::applyConfiguration() {
|
||||
@@ -81,10 +96,10 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
|
||||
ui->audio_device_combo_box->clear();
|
||||
ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);
|
||||
|
||||
std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
|
||||
std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices();
|
||||
const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
|
||||
const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices();
|
||||
for (const auto& device : device_list) {
|
||||
ui->audio_device_combo_box->addItem(device.c_str());
|
||||
ui->audio_device_combo_box->addItem(QString::fromStdString(device));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,9 @@ public slots:
|
||||
|
||||
private:
|
||||
void setConfiguration();
|
||||
void setOutputSinkFromSinkID();
|
||||
void setAudioDeviceFromDeviceID();
|
||||
void setVolumeIndicatorText(int percentage);
|
||||
|
||||
std::unique_ptr<Ui::ConfigureAudio> ui;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
for (auto theme : UISettings::themes) {
|
||||
for (const auto& theme : UISettings::themes) {
|
||||
ui->theme_combobox->addItem(theme.first, theme.second);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,27 +8,7 @@
|
||||
#include "ui_configure_graphics.h"
|
||||
#include "yuzu/configuration/configure_graphics.h"
|
||||
|
||||
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::ConfigureGraphics) {
|
||||
|
||||
ui->setupUi(this);
|
||||
this->setConfiguration();
|
||||
|
||||
ui->frame_limit->setEnabled(Settings::values.use_frame_limit);
|
||||
connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit,
|
||||
&QSpinBox::setEnabled);
|
||||
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
|
||||
const QColor new_bg_color = QColorDialog::getColor(bg_color);
|
||||
if (!new_bg_color.isValid())
|
||||
return;
|
||||
bg_color = new_bg_color;
|
||||
ui->bg_button->setStyleSheet(
|
||||
QString("QPushButton { background-color: %1 }").arg(bg_color.name()));
|
||||
});
|
||||
}
|
||||
|
||||
ConfigureGraphics::~ConfigureGraphics() = default;
|
||||
|
||||
namespace {
|
||||
enum class Resolution : int {
|
||||
Auto,
|
||||
Scale1x,
|
||||
@@ -67,6 +47,28 @@ Resolution FromResolutionFactor(float factor) {
|
||||
}
|
||||
return Resolution::Auto;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::ConfigureGraphics) {
|
||||
|
||||
ui->setupUi(this);
|
||||
this->setConfiguration();
|
||||
|
||||
ui->frame_limit->setEnabled(Settings::values.use_frame_limit);
|
||||
connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit,
|
||||
&QSpinBox::setEnabled);
|
||||
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
|
||||
const QColor new_bg_color = QColorDialog::getColor(bg_color);
|
||||
if (!new_bg_color.isValid())
|
||||
return;
|
||||
bg_color = new_bg_color;
|
||||
ui->bg_button->setStyleSheet(
|
||||
QString("QPushButton { background-color: %1 }").arg(bg_color.name()));
|
||||
});
|
||||
}
|
||||
|
||||
ConfigureGraphics::~ConfigureGraphics() = default;
|
||||
|
||||
void ConfigureGraphics::setConfiguration() {
|
||||
ui->resolution_factor_combobox->setCurrentIndex(
|
||||
|
||||
@@ -119,7 +119,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> list;
|
||||
|
||||
constexpr std::size_t BaseRegister = 29;
|
||||
u64 base_pointer = thread.context.cpu_registers[BaseRegister];
|
||||
u64 base_pointer = thread.GetContext().cpu_registers[BaseRegister];
|
||||
|
||||
while (base_pointer != 0) {
|
||||
u64 lr = Memory::Read64(base_pointer + sizeof(u64));
|
||||
@@ -213,7 +213,7 @@ WaitTreeThread::~WaitTreeThread() = default;
|
||||
QString WaitTreeThread::GetText() const {
|
||||
const auto& thread = static_cast<const Kernel::Thread&>(object);
|
||||
QString status;
|
||||
switch (thread.status) {
|
||||
switch (thread.GetStatus()) {
|
||||
case Kernel::ThreadStatus::Running:
|
||||
status = tr("running");
|
||||
break;
|
||||
@@ -246,15 +246,17 @@ QString WaitTreeThread::GetText() const {
|
||||
status = tr("dead");
|
||||
break;
|
||||
}
|
||||
QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
|
||||
.arg(thread.context.pc, 8, 16, QLatin1Char('0'))
|
||||
.arg(thread.context.cpu_registers[30], 8, 16, QLatin1Char('0'));
|
||||
|
||||
const auto& context = thread.GetContext();
|
||||
const QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
|
||||
.arg(context.pc, 8, 16, QLatin1Char('0'))
|
||||
.arg(context.cpu_registers[30], 8, 16, QLatin1Char('0'));
|
||||
return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") ";
|
||||
}
|
||||
|
||||
QColor WaitTreeThread::GetColor() const {
|
||||
const auto& thread = static_cast<const Kernel::Thread&>(object);
|
||||
switch (thread.status) {
|
||||
switch (thread.GetStatus()) {
|
||||
case Kernel::ThreadStatus::Running:
|
||||
return QColor(Qt::GlobalColor::darkGreen);
|
||||
case Kernel::ThreadStatus::Ready:
|
||||
@@ -284,7 +286,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
|
||||
const auto& thread = static_cast<const Kernel::Thread&>(object);
|
||||
|
||||
QString processor;
|
||||
switch (thread.processor_id) {
|
||||
switch (thread.GetProcessorID()) {
|
||||
case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT:
|
||||
processor = tr("default");
|
||||
break;
|
||||
@@ -292,32 +294,35 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
|
||||
case Kernel::ThreadProcessorId::THREADPROCESSORID_1:
|
||||
case Kernel::ThreadProcessorId::THREADPROCESSORID_2:
|
||||
case Kernel::ThreadProcessorId::THREADPROCESSORID_3:
|
||||
processor = tr("core %1").arg(thread.processor_id);
|
||||
processor = tr("core %1").arg(thread.GetProcessorID());
|
||||
break;
|
||||
default:
|
||||
processor = tr("Unknown processor %1").arg(thread.processor_id);
|
||||
processor = tr("Unknown processor %1").arg(thread.GetProcessorID());
|
||||
break;
|
||||
}
|
||||
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.ideal_core)));
|
||||
list.push_back(
|
||||
std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.affinity_mask)));
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId())));
|
||||
std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.GetIdealCore())));
|
||||
list.push_back(
|
||||
std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.GetAffinityMask())));
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadID())));
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
|
||||
.arg(thread.current_priority)
|
||||
.arg(thread.nominal_priority)));
|
||||
.arg(thread.GetPriority())
|
||||
.arg(thread.GetNominalPriority())));
|
||||
list.push_back(std::make_unique<WaitTreeText>(
|
||||
tr("last running ticks = %1").arg(thread.last_running_ticks)));
|
||||
tr("last running ticks = %1").arg(thread.GetLastRunningTicks())));
|
||||
|
||||
if (thread.mutex_wait_address != 0)
|
||||
list.push_back(std::make_unique<WaitTreeMutexInfo>(thread.mutex_wait_address));
|
||||
else
|
||||
const VAddr mutex_wait_address = thread.GetMutexWaitAddress();
|
||||
if (mutex_wait_address != 0) {
|
||||
list.push_back(std::make_unique<WaitTreeMutexInfo>(mutex_wait_address));
|
||||
} else {
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));
|
||||
}
|
||||
|
||||
if (thread.status == Kernel::ThreadStatus::WaitSynchAny ||
|
||||
thread.status == Kernel::ThreadStatus::WaitSynchAll) {
|
||||
list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
|
||||
if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAny ||
|
||||
thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAll) {
|
||||
list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjects(),
|
||||
thread.IsSleepingOnWaitAll()));
|
||||
}
|
||||
|
||||
|
||||
@@ -60,14 +60,13 @@ QString FormatGameName(const std::string& physical_name) {
|
||||
QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) {
|
||||
QString out;
|
||||
for (const auto& kv : patch_manager.GetPatchVersionNames()) {
|
||||
if (!updatable && kv.first == FileSys::PatchType::Update)
|
||||
if (!updatable && kv.first == "Update")
|
||||
continue;
|
||||
|
||||
if (kv.second.empty()) {
|
||||
out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
|
||||
out.append(fmt::format("{}\n", kv.first).c_str());
|
||||
} else {
|
||||
out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
|
||||
.c_str());
|
||||
out.append(fmt::format("{} ({})\n", kv.first, kv.second).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,5 +6,11 @@
|
||||
|
||||
namespace UISettings {
|
||||
|
||||
const Themes themes{{
|
||||
{"Default", "default"},
|
||||
{"Dark", "qdarkstyle"},
|
||||
}};
|
||||
|
||||
Values values = {};
|
||||
}
|
||||
|
||||
} // namespace UISettings
|
||||
|
||||
@@ -15,9 +15,8 @@ namespace UISettings {
|
||||
using ContextualShortcut = std::pair<QString, int>;
|
||||
using Shortcut = std::pair<QString, ContextualShortcut>;
|
||||
|
||||
static const std::array<std::pair<QString, QString>, 2> themes = {
|
||||
{std::make_pair(QString("Default"), QString("default")),
|
||||
std::make_pair(QString("Dark"), QString("qdarkstyle"))}};
|
||||
using Themes = std::array<std::pair<const char*, const char*>, 2>;
|
||||
extern const Themes themes;
|
||||
|
||||
struct Values {
|
||||
QByteArray geometry;
|
||||
|
||||
Reference in New Issue
Block a user