summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/common/CMakeLists.txt5
-rw-r--r--src/common/detached_tasks.cpp41
-rw-r--r--src/common/detached_tasks.h40
-rw-r--r--src/common/file_util.h2
-rw-r--r--src/common/logging/text_formatter.cpp2
-rw-r--r--src/common/param_package.cpp18
-rw-r--r--src/common/param_package.h2
-rw-r--r--src/common/string_util.cpp134
-rw-r--r--src/common/string_util.h21
-rw-r--r--src/common/web_result.h24
-rw-r--r--src/core/CMakeLists.txt6
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp2
-rw-r--r--src/core/arm/unicorn/arm_unicorn.cpp2
-rw-r--r--src/core/file_sys/card_image.cpp4
-rw-r--r--src/core/file_sys/ips_layer.cpp88
-rw-r--r--src/core/file_sys/ips_layer.h15
-rw-r--r--src/core/file_sys/patch_manager.cpp141
-rw-r--r--src/core/file_sys/patch_manager.h20
-rw-r--r--src/core/file_sys/submission_package.cpp169
-rw-r--r--src/core/file_sys/submission_package.h5
-rw-r--r--src/core/gdbstub/gdbstub.cpp44
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp20
-rw-r--r--src/core/hle/kernel/client_port.cpp4
-rw-r--r--src/core/hle/kernel/client_port.h2
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp14
-rw-r--r--src/core/hle/kernel/kernel.cpp36
-rw-r--r--src/core/hle/kernel/mutex.cpp34
-rw-r--r--src/core/hle/kernel/process.cpp6
-rw-r--r--src/core/hle/kernel/scheduler.cpp36
-rw-r--r--src/core/hle/kernel/server_port.h1
-rw-r--r--src/core/hle/kernel/server_session.cpp4
-rw-r--r--src/core/hle/kernel/svc.cpp121
-rw-r--r--src/core/hle/kernel/thread.cpp38
-rw-r--r--src/core/hle/kernel/thread.h218
-rw-r--r--src/core/hle/kernel/wait_object.cpp31
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp2
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp2
-rw-r--r--src/core/hle/service/lbl/lbl.cpp38
-rw-r--r--src/core/hle/service/sm/sm.h19
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp3
-rw-r--r--src/core/loader/nso.cpp18
-rw-r--r--src/core/loader/nso.h4
-rw-r--r--src/core/settings.h6
-rw-r--r--src/core/telemetry_session.cpp52
-rw-r--r--src/core/telemetry_session.h5
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/engines/maxwell_3d.h6
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp17
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h7
-rw-r--r--src/video_core/renderer_opengl/gl_primitive_assembler.cpp64
-rw-r--r--src/video_core/renderer_opengl/gl_primitive_assembler.h33
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp147
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h7
-rw-r--r--src/web_service/CMakeLists.txt16
-rw-r--r--src/web_service/telemetry_json.cpp99
-rw-r--r--src/web_service/telemetry_json.h58
-rw-r--r--src/web_service/verify_login.cpp27
-rw-r--r--src/web_service/verify_login.h22
-rw-r--r--src/web_service/web_backend.cpp149
-rw-r--r--src/web_service/web_backend.h92
-rw-r--r--src/yuzu/CMakeLists.txt20
-rw-r--r--src/yuzu/compatdb.cpp65
-rw-r--r--src/yuzu/compatdb.h26
-rw-r--r--src/yuzu/compatdb.ui215
-rw-r--r--src/yuzu/configuration/config.cpp18
-rw-r--r--src/yuzu/configuration/configure.ui11
-rw-r--r--src/yuzu/configuration/configure_audio.cpp47
-rw-r--r--src/yuzu/configuration/configure_audio.h3
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp1
-rw-r--r--src/yuzu/configuration/configure_general.cpp2
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp44
-rw-r--r--src/yuzu/configuration/configure_input.cpp93
-rw-r--r--src/yuzu/configuration/configure_input.h3
-rw-r--r--src/yuzu/configuration/configure_input.ui28
-rw-r--r--src/yuzu/configuration/configure_web.cpp119
-rw-r--r--src/yuzu/configuration/configure_web.h38
-rw-r--r--src/yuzu/configuration/configure_web.ui206
-rw-r--r--src/yuzu/debugger/wait_tree.cpp47
-rw-r--r--src/yuzu/discord.h25
-rw-r--r--src/yuzu/discord_impl.cpp52
-rw-r--r--src/yuzu/discord_impl.h20
-rw-r--r--src/yuzu/game_list_worker.cpp7
-rw-r--r--src/yuzu/main.cpp82
-rw-r--r--src/yuzu/main.h10
-rw-r--r--src/yuzu/main.ui16
-rw-r--r--src/yuzu/ui_settings.cpp8
-rw-r--r--src/yuzu/ui_settings.h8
-rw-r--r--src/yuzu_cmd/config.cpp8
-rw-r--r--src/yuzu_cmd/default_ini.h8
-rw-r--r--src/yuzu_cmd/yuzu.cpp3
91 files changed, 2819 insertions, 662 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a88551fbc..f69d00a2b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,3 +13,6 @@ endif()
if (ENABLE_QT)
add_subdirectory(yuzu)
endif()
+if (ENABLE_WEB_SERVICE)
+ add_subdirectory(web_service)
+endif()
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 6a3f1fe08..d0e506689 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -29,7 +29,7 @@ if ($ENV{CI})
if (BUILD_VERSION)
# This leaves a trailing space on the last word, but we actually want that
# because of how it's styled in the title bar.
- set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ")
+ set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
else()
set(BUILD_FULLNAME "")
endif()
@@ -41,6 +41,8 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU
add_library(common STATIC
alignment.h
assert.h
+ detached_tasks.cpp
+ detached_tasks.h
bit_field.h
bit_set.h
cityhash.cpp
@@ -87,6 +89,7 @@ add_library(common STATIC
timer.cpp
timer.h
vector_math.h
+ web_result.h
)
if(ARCHITECTURE_x86_64)
diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp
new file mode 100644
index 000000000..a347d9e02
--- /dev/null
+++ b/src/common/detached_tasks.cpp
@@ -0,0 +1,41 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <thread>
+#include "common/assert.h"
+#include "common/detached_tasks.h"
+
+namespace Common {
+
+DetachedTasks* DetachedTasks::instance = nullptr;
+
+DetachedTasks::DetachedTasks() {
+ ASSERT(instance == nullptr);
+ instance = this;
+}
+
+void DetachedTasks::WaitForAllTasks() {
+ std::unique_lock<std::mutex> lock(mutex);
+ cv.wait(lock, [this]() { return count == 0; });
+}
+
+DetachedTasks::~DetachedTasks() {
+ std::unique_lock<std::mutex> lock(mutex);
+ ASSERT(count == 0);
+ instance = nullptr;
+}
+
+void DetachedTasks::AddTask(std::function<void()> task) {
+ std::unique_lock<std::mutex> lock(instance->mutex);
+ ++instance->count;
+ std::thread([task{std::move(task)}]() {
+ task();
+ std::unique_lock<std::mutex> lock(instance->mutex);
+ --instance->count;
+ std::notify_all_at_thread_exit(instance->cv, std::move(lock));
+ })
+ .detach();
+}
+
+} // namespace Common
diff --git a/src/common/detached_tasks.h b/src/common/detached_tasks.h
new file mode 100644
index 000000000..5dd8fc27b
--- /dev/null
+++ b/src/common/detached_tasks.h
@@ -0,0 +1,40 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <condition_variable>
+#include <functional>
+
+namespace Common {
+
+/**
+ * A background manager which ensures that all detached task is finished before program exits.
+ *
+ * Some tasks, telemetry submission for example, prefer executing asynchronously and don't care
+ * about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if
+ * the task is launched just before the program exits (which is a common case for telemetry), so we
+ * need to block on these tasks on program exit.
+ *
+ * To make detached task safe, a single DetachedTasks object should be placed in the main(), and
+ * call WaitForAllTasks() after all program execution but before global/static variable destruction.
+ * Any potentially unsafe detached task should be executed via DetachedTasks::AddTask.
+ */
+class DetachedTasks {
+public:
+ DetachedTasks();
+ ~DetachedTasks();
+ void WaitForAllTasks();
+
+ static void AddTask(std::function<void()> task);
+
+private:
+ static DetachedTasks* instance;
+
+ std::condition_variable cv;
+ std::mutex mutex;
+ int count = 0;
+};
+
+} // namespace Common
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 3d8fe6264..571503d2a 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -285,7 +285,7 @@ private:
template <typename T>
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
#ifdef _MSC_VER
- fstream.open(Common::UTF8ToTStr(filename).c_str(), openmode);
+ fstream.open(Common::UTF8ToUTF16W(filename).c_str(), openmode);
#else
fstream.open(filename.c_str(), openmode);
#endif
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index 05437c137..6a0605c63 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -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);
}
diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp
index 9526ca0c6..b916b4866 100644
--- a/src/common/param_package.cpp
+++ b/src/common/param_package.cpp
@@ -20,7 +20,15 @@ constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
+/// A placeholder for empty param packages to avoid empty strings
+/// (they may be recognized as "not set" by some frontend libraries like qt)
+constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
+
ParamPackage::ParamPackage(const std::string& serialized) {
+ if (serialized == EMPTY_PLACEHOLDER) {
+ return;
+ }
+
std::vector<std::string> pairs;
Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
@@ -46,7 +54,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d
std::string ParamPackage::Serialize() const {
if (data.empty())
- return "";
+ return EMPTY_PLACEHOLDER;
std::string result;
@@ -120,4 +128,12 @@ bool ParamPackage::Has(const std::string& key) const {
return data.find(key) != data.end();
}
+void ParamPackage::Erase(const std::string& key) {
+ data.erase(key);
+}
+
+void ParamPackage::Clear() {
+ data.clear();
+}
+
} // namespace Common
diff --git a/src/common/param_package.h b/src/common/param_package.h
index 7842cd4ef..6a0a9b656 100644
--- a/src/common/param_package.h
+++ b/src/common/param_package.h
@@ -32,6 +32,8 @@ public:
void Set(const std::string& key, int value);
void Set(const std::string& key, float value);
bool Has(const std::string& key) const;
+ void Erase(const std::string& key);
+ void Clear();
private:
DataType data;
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index c9a5425a7..731d1db34 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -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) {
diff --git a/src/common/string_util.h b/src/common/string_util.h
index dcca6bc38..32bf6a19c 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -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
/**
diff --git a/src/common/web_result.h b/src/common/web_result.h
new file mode 100644
index 000000000..969926674
--- /dev/null
+++ b/src/common/web_result.h
@@ -0,0 +1,24 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+namespace Common {
+struct WebResult {
+ enum class Code : u32 {
+ Success,
+ InvalidURL,
+ CredentialsMissing,
+ LibError,
+ HttpError,
+ WrongContent,
+ NoWebservice,
+ };
+ Code result_code;
+ std::string result_string;
+ std::string returned_data;
+};
+} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 23fd6e920..e4a676e91 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -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
@@ -394,6 +396,10 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
+if (ENABLE_WEB_SERVICE)
+ add_definitions(-DENABLE_WEB_SERVICE)
+ target_link_libraries(core PUBLIC json-headers web_service)
+endif()
if (ARCHITECTURE_x86_64)
target_sources(core PRIVATE
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 05cc84458..7e978cf7a 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -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;
diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp
index e218a0b15..ded4dd359 100644
--- a/src/core/arm/unicorn/arm_unicorn.cpp
+++ b/src/core/arm/unicorn/arm_unicorn.cpp
@@ -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();
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index edfc1bbd4..8f5142a07 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -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;
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
new file mode 100644
index 000000000..df933ee36
--- /dev/null
+++ b/src/core/file_sys/ips_layer.cpp
@@ -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
diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h
new file mode 100644
index 000000000..81c163494
--- /dev/null
+++ b/src/core/file_sys/ips_layer.h
@@ -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
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 57b7741f8..539698f6e 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -5,14 +5,18 @@
#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"
@@ -21,6 +25,14 @@ 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{};
bytes[0] = version % SINGLE_BYTE_MODULUS;
@@ -34,16 +46,6 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
}
-constexpr std::array<const char*, 3> PATCH_TYPE_NAMES{
- "Update",
- "LayeredFS",
- "DLC",
-};
-
-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;
@@ -71,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) {
@@ -138,8 +213,19 @@ 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
@@ -148,23 +234,36 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
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));
}
}
}
- // LayeredFS
- 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);
@@ -186,7 +285,7 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
- out.insert_or_assign(PatchType::DLC, std::move(list));
+ out.insert_or_assign("DLC", std::move(list));
}
return out;
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 3a2a9d212..6a864ec43 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -24,14 +24,6 @@ enum class TitleVersionFormat : u8 {
std::string FormatTitleVersion(u32 version,
TitleVersionFormat format = TitleVersionFormat::ThreeElements);
-enum class PatchType {
- Update,
- LayeredFS,
- DLC,
-};
-
-std::string FormatPatchTypeName(PatchType type);
-
// A centralized class to manage patches to games.
class PatchManager {
public:
@@ -42,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
@@ -49,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.
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 11264878d..09bf077cd 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -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
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index e85a2b76e..da3dc5e9f 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -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;
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index 5bc947010..e961ef121 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -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;
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index 93577591f..ebf193930 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -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();
}
}
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index 873d6c516..d4c91d529 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -17,6 +17,10 @@ namespace Kernel {
ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {}
ClientPort::~ClientPort() = default;
+SharedPtr<ServerPort> ClientPort::GetServerPort() const {
+ return server_port;
+}
+
ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
// Note: Threads do not wait for the server endpoint to call
// AcceptSession before returning from this call.
diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h
index f3dfebbb1..6cd607206 100644
--- a/src/core/hle/kernel/client_port.h
+++ b/src/core/hle/kernel/client_port.h
@@ -30,6 +30,8 @@ public:
return HANDLE_TYPE;
}
+ SharedPtr<ServerPort> GetServerPort() const;
+
/**
* Creates a new Session pair, adds the created ServerSession to the associated ServerPort's
* list of pending sessions, and signals the ServerPort, causing any threads
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 72fb9d250..edad5f1b1 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -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;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 3e0800a71..98eb74298 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -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) {
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index 81675eac5..dd541ffcc 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -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;
}
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index dc9fc8470..fb0027a71 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -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();
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 1e82cfffb..cfd6e1bad 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -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);
}
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index 62fb51349..e52f8245f 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -11,6 +11,7 @@
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/wait_object.h"
+#include "core/hle/result.h"
namespace Kernel {
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index aba0cab96..1ece691c7 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -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
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 1cdaa740a..6c4af7e47 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -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);
}
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index b5c16cfbb..8e514cf9a 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -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);
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
/**
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 4250144c3..c6ffbd28c 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -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.
@@ -106,6 +115,14 @@ public:
}
/**
+ * 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;
+ }
+
+ const ThreadContext& GetContext() const {
+ return context;
+ }
+
+ ThreadStatus GetStatus() const {
+ return status;
+ }
+
+ void SetStatus(ThreadStatus new_status);
+
+ u64 GetLastRunningTicks() const {
+ return last_running_ticks;
+ }
+
+ s32 GetProcessorID() const {
+ return processor_id;
+ }
+
+ SharedPtr<Process>& GetOwnerProcess() {
+ return owner_process;
+ }
+
+ const SharedPtr<Process>& GetOwnerProcess() const {
+ return owner_process;
+ }
+
+ const ThreadWaitObjects& GetWaitObjects() const {
+ return wait_objects;
+ }
+
+ void SetWaitObjects(ThreadWaitObjects objects) {
+ wait_objects = std::move(objects);
+ }
+
+ void ClearWaitObjects() {
+ wait_objects.clear();
+ }
+
+ /// Determines whether all the objects this thread is waiting on are ready.
+ bool AllWaitObjectsReady();
+
+ const MutexWaitingThreads& GetMutexWaitingThreads() const {
+ return wait_mutex_threads;
+ }
+
+ Thread* GetLockOwner() const {
+ return lock_owner.get();
+ }
+
+ void SetLockOwner(SharedPtr<Thread> owner) {
+ lock_owner = std::move(owner);
+ }
+
+ VAddr GetCondVarWaitAddress() const {
+ return condvar_wait_address;
+ }
+
+ void SetCondVarWaitAddress(VAddr address) {
+ condvar_wait_address = address;
+ }
+
+ VAddr GetMutexWaitAddress() const {
+ return mutex_wait_address;
+ }
- u32 thread_id;
+ void SetMutexWaitAddress(VAddr address) {
+ mutex_wait_address = address;
+ }
- ThreadStatus status;
- VAddr entry_point;
- VAddr stack_top;
+ Handle GetWaitHandle() const {
+ return wait_handle;
+ }
- u32 nominal_priority; ///< Nominal thread priority, as set by the emulated application
- u32 current_priority; ///< Current thread priority, can be temporarily changed
+ void SetWaitHandle(Handle handle) {
+ wait_handle = handle;
+ }
- u64 last_running_ticks; ///< CPU tick when thread was last running
+ VAddr GetArbiterWaitAddress() const {
+ return arb_wait_address;
+ }
- s32 processor_id;
+ void SetArbiterWaitAddress(VAddr address) {
+ arb_wait_address = address;
+ }
- VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
- u64 tpidr_el0; ///< TPIDR_EL0 read/write system register.
+ void SetGuestHandle(Handle handle) {
+ guest_handle = handle;
+ }
- SharedPtr<Process> owner_process; ///< Process that owns this thread
+ 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.
- std::vector<SharedPtr<WaitObject>> wait_objects;
+ /// passed to WaitSynchronization1/N.
+ ThreadWaitObjects wait_objects;
/// List of threads that are waiting for a mutex that is held by this thread.
- std::vector<SharedPtr<Thread>> wait_mutex_threads;
+ 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;
- 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.
+ /// 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.
+ /// If waiting for an AddressArbiter, this is the address being waited on.
VAddr arb_wait_address{0};
- std::string name;
-
/// Handle used by guest emulated application to access this thread
- Handle guest_handle;
+ Handle guest_handle = 0;
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
- Handle callback_handle;
+ Handle callback_handle = 0;
- 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;
+ /// 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;
- std::shared_ptr<Scheduler> scheduler;
+ Scheduler* scheduler = nullptr;
u32 ideal_core{0xFFFFFFFF};
u64 affinity_mask{0x1};
-private:
- explicit Thread(KernelCore& kernel);
- ~Thread() override;
-
TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>();
+
+ std::string name;
};
/**
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index b190ceb98..530ee6af7 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -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();
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index cfc28fa0c..79580bcd9 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -84,7 +84,7 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
}
- if (out.size() <= offset) {
+ if (out.size() < offset) {
IPC::ResponseBuilder rb{ctx, 2};
// TODO(DarkLordZach): Find the correct error code.
rb.Push(ResultCode(-1));
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index cabaf5a55..d5dced429 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -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"},
diff --git a/src/core/hle/service/lbl/lbl.cpp b/src/core/hle/service/lbl/lbl.cpp
index 8fc8b1057..7321584e1 100644
--- a/src/core/hle/service/lbl/lbl.cpp
+++ b/src/core/hle/service/lbl/lbl.cpp
@@ -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"},
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index da2c51082..4f8145dda 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -6,9 +6,12 @@
#include <memory>
#include <string>
+#include <type_traits>
#include <unordered_map>
+#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/object.h"
+#include "core/hle/kernel/server_port.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h"
@@ -48,6 +51,22 @@ public:
ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name);
ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name);
+ template <typename T>
+ std::shared_ptr<T> GetService(const std::string& service_name) const {
+ static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
+ "Not a base of ServiceFrameworkBase");
+ auto service = registered_services.find(service_name);
+ if (service == registered_services.end()) {
+ LOG_DEBUG(Service, "Can't find service: {}", service_name);
+ return nullptr;
+ }
+ auto port = service->second->GetServerPort();
+ if (port == nullptr) {
+ return nullptr;
+ }
+ return std::static_pointer_cast<T>(port->hle_handler);
+ }
+
void InvokeControlRequest(Kernel::HLERequestContext& context);
private:
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index c1824b9c3..9a86e5824 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -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);
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index cbe2a3e53..2186b02af 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -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));
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 7f142405b..05353d4d9 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -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;
};
diff --git a/src/core/settings.h b/src/core/settings.h
index 0318d019c..1808f5937 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -155,6 +155,12 @@ struct Values {
// Debugging
bool use_gdbstub;
u16 gdbstub_port;
+
+ // WebService
+ bool enable_telemetry;
+ std::string web_api_url;
+ std::string yuzu_username;
+ std::string yuzu_token;
} extern values;
void Apply();
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index b0df154ca..f29fff1e7 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -6,6 +6,8 @@
#include "common/common_types.h"
#include "common/file_util.h"
+#include <mbedtls/ctr_drbg.h>
+#include <mbedtls/entropy.h>
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -13,10 +15,31 @@
#include "core/settings.h"
#include "core/telemetry_session.h"
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/telemetry_json.h"
+#include "web_service/verify_login.h"
+#endif
+
namespace Core {
static u64 GenerateTelemetryId() {
u64 telemetry_id{};
+
+ mbedtls_entropy_context entropy;
+ mbedtls_entropy_init(&entropy);
+ mbedtls_ctr_drbg_context ctr_drbg;
+ std::string personalization = "yuzu Telemetry ID";
+
+ mbedtls_ctr_drbg_init(&ctr_drbg);
+ ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
+ reinterpret_cast<const unsigned char*>(personalization.c_str()),
+ personalization.size()) == 0);
+ ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id),
+ sizeof(u64)) == 0);
+
+ mbedtls_ctr_drbg_free(&ctr_drbg);
+ mbedtls_entropy_free(&entropy);
+
return telemetry_id;
}
@@ -25,14 +48,21 @@ u64 GetTelemetryId() {
const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
"telemetry_id"};
- if (FileUtil::Exists(filename)) {
+ bool generate_new_id = !FileUtil::Exists(filename);
+ if (!generate_new_id) {
FileUtil::IOFile file(filename, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
return {};
}
file.ReadBytes(&telemetry_id, sizeof(u64));
- } else {
+ if (telemetry_id == 0) {
+ LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id);
+ generate_new_id = true;
+ }
+ }
+
+ if (generate_new_id) {
FileUtil::IOFile file(filename, "wb");
if (!file.IsOpen()) {
LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
@@ -59,23 +89,20 @@ u64 RegenerateTelemetryId() {
return new_telemetry_id;
}
-std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func) {
+bool VerifyLogin(const std::string& username, const std::string& token) {
#ifdef ENABLE_WEB_SERVICE
- return WebService::VerifyLogin(username, token, Settings::values.verify_endpoint_url, func);
+ return WebService::VerifyLogin(Settings::values.web_api_url, username, token);
#else
- return std::async(std::launch::async, [func{std::move(func)}]() {
- func();
- return false;
- });
+ return false;
#endif
}
TelemetrySession::TelemetrySession() {
#ifdef ENABLE_WEB_SERVICE
if (Settings::values.enable_telemetry) {
- backend = std::make_unique<WebService::TelemetryJson>(
- Settings::values.telemetry_endpoint_url, Settings::values.yuzu_username,
- Settings::values.yuzu_token);
+ backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url,
+ Settings::values.yuzu_username,
+ Settings::values.yuzu_token);
} else {
backend = std::make_unique<Telemetry::NullVisitor>();
}
@@ -94,7 +121,8 @@ TelemetrySession::TelemetrySession() {
u64 program_id{};
const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)};
if (res == Loader::ResultStatus::Success) {
- AddField(Telemetry::FieldType::Session, "ProgramId", program_id);
+ const std::string formatted_program_id{fmt::format("{:016X}", program_id)};
+ AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id);
std::string name;
System::GetInstance().GetAppLoader().ReadTitle(name);
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index dbc4f8bd4..cec271df0 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -4,7 +4,6 @@
#pragma once
-#include <future>
#include <memory>
#include "common/telemetry.h"
@@ -31,6 +30,8 @@ public:
field_collection.AddField(type, name, std::move(value));
}
+ static void FinalizeAsyncJob();
+
private:
Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
@@ -55,6 +56,6 @@ u64 RegenerateTelemetryId();
* @param func A function that gets exectued when the verification is finished
* @returns Future with bool indicating whether the verification succeeded
*/
-std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func);
+bool VerifyLogin(const std::string& username, const std::string& token);
} // namespace Core
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index f5ae57039..09ecc5bad 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -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
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 9f5581045..4290da33f 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -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);
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 578aca789..c142095c5 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -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;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 6c18461f4..965976334 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -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();
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
new file mode 100644
index 000000000..ee1d9601b
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
@@ -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 \ No newline at end of file
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.h b/src/video_core/renderer_opengl/gl_primitive_assembler.h
new file mode 100644
index 000000000..a8cb88eb5
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_primitive_assembler.h
@@ -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 \ No newline at end of file
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index cc07b4721..edcd5e451 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -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,54 @@ 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;
+ }
+ return params;
+}
+
void RasterizerOpenGL::SetupShaders() {
MICROPROFILE_SCOPE(OpenGL_Shader);
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
@@ -256,6 +339,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 +549,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 +579,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 +587,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;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 987c4f4fb..dc31a2dbc 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -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:
@@ -194,12 +196,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 };
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
new file mode 100644
index 000000000..1c83e9c34
--- /dev/null
+++ b/src/web_service/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_library(web_service STATIC
+ telemetry_json.cpp
+ telemetry_json.h
+ verify_login.cpp
+ verify_login.h
+ web_backend.cpp
+ web_backend.h
+)
+
+create_target_directory_groups(web_service)
+
+get_directory_property(OPENSSL_LIBS
+ DIRECTORY ${CMAKE_SOURCE_DIR}/externals/libressl
+ DEFINITION OPENSSL_LIBS)
+target_compile_definitions(web_service PUBLIC -DCPPHTTPLIB_OPENSSL_SUPPORT)
+target_link_libraries(web_service PRIVATE common json-headers ${OPENSSL_LIBS} httplib lurlparser)
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
new file mode 100644
index 000000000..033ea1ea4
--- /dev/null
+++ b/src/web_service/telemetry_json.cpp
@@ -0,0 +1,99 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <thread>
+#include "common/assert.h"
+#include "common/detached_tasks.h"
+#include "web_service/telemetry_json.h"
+#include "web_service/web_backend.h"
+
+namespace WebService {
+
+TelemetryJson::TelemetryJson(const std::string& host, const std::string& username,
+ const std::string& token)
+ : host(std::move(host)), username(std::move(username)), token(std::move(token)) {}
+TelemetryJson::~TelemetryJson() = default;
+
+template <class T>
+void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) {
+ sections[static_cast<u8>(type)][name] = value;
+}
+
+void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) {
+ TopSection()[name] = sections[static_cast<unsigned>(type)];
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
+ Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue().count());
+}
+
+void TelemetryJson::Complete() {
+ SerializeSection(Telemetry::FieldType::App, "App");
+ SerializeSection(Telemetry::FieldType::Session, "Session");
+ SerializeSection(Telemetry::FieldType::Performance, "Performance");
+ SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
+ SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
+ SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
+
+ auto content = TopSection().dump();
+ // Send the telemetry async but don't handle the errors since they were written to the log
+ Common::DetachedTasks::AddTask(
+ [host{this->host}, username{this->username}, token{this->token}, content]() {
+ Client{host, username, token}.PostJson("/telemetry", content, true);
+ });
+}
+
+} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
new file mode 100644
index 000000000..0fe6f9a3e
--- /dev/null
+++ b/src/web_service/telemetry_json.h
@@ -0,0 +1,58 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <string>
+#include <json.hpp>
+#include "common/telemetry.h"
+#include "common/web_result.h"
+
+namespace WebService {
+
+/**
+ * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
+ * yuzu web service
+ */
+class TelemetryJson : public Telemetry::VisitorInterface {
+public:
+ TelemetryJson(const std::string& host, const std::string& username, const std::string& token);
+ ~TelemetryJson();
+
+ void Visit(const Telemetry::Field<bool>& field) override;
+ void Visit(const Telemetry::Field<double>& field) override;
+ void Visit(const Telemetry::Field<float>& field) override;
+ void Visit(const Telemetry::Field<u8>& field) override;
+ void Visit(const Telemetry::Field<u16>& field) override;
+ void Visit(const Telemetry::Field<u32>& field) override;
+ void Visit(const Telemetry::Field<u64>& field) override;
+ void Visit(const Telemetry::Field<s8>& field) override;
+ void Visit(const Telemetry::Field<s16>& field) override;
+ void Visit(const Telemetry::Field<s32>& field) override;
+ void Visit(const Telemetry::Field<s64>& field) override;
+ void Visit(const Telemetry::Field<std::string>& field) override;
+ void Visit(const Telemetry::Field<const char*>& field) override;
+ void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override;
+
+ void Complete() override;
+
+private:
+ nlohmann::json& TopSection() {
+ return sections[static_cast<u8>(Telemetry::FieldType::None)];
+ }
+
+ template <class T>
+ void Serialize(Telemetry::FieldType type, const std::string& name, T value);
+
+ void SerializeSection(Telemetry::FieldType type, const std::string& name);
+
+ nlohmann::json output;
+ std::array<nlohmann::json, 7> sections;
+ std::string host;
+ std::string username;
+ std::string token;
+};
+
+} // namespace WebService
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
new file mode 100644
index 000000000..124aa3863
--- /dev/null
+++ b/src/web_service/verify_login.cpp
@@ -0,0 +1,27 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <json.hpp>
+#include "web_service/verify_login.h"
+#include "web_service/web_backend.h"
+
+namespace WebService {
+
+bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token) {
+ Client client(host, username, token);
+ auto reply = client.GetJson("/profile", false).returned_data;
+ if (reply.empty()) {
+ return false;
+ }
+ nlohmann::json json = nlohmann::json::parse(reply);
+ const auto iter = json.find("username");
+
+ if (iter == json.end()) {
+ return username.empty();
+ }
+
+ return username == *iter;
+}
+
+} // namespace WebService
diff --git a/src/web_service/verify_login.h b/src/web_service/verify_login.h
new file mode 100644
index 000000000..39db32dbb
--- /dev/null
+++ b/src/web_service/verify_login.h
@@ -0,0 +1,22 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <future>
+#include <string>
+
+namespace WebService {
+
+/**
+ * Checks if username and token is valid
+ * @param host the web API URL
+ * @param username yuzu username to use for authentication.
+ * @param token yuzu token to use for authentication.
+ * @returns a bool indicating whether the verification succeeded
+ */
+bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token);
+
+} // namespace WebService
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
new file mode 100644
index 000000000..787b0fbcb
--- /dev/null
+++ b/src/web_service/web_backend.cpp
@@ -0,0 +1,149 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstdlib>
+#include <string>
+#include <thread>
+#include <LUrlParser.h>
+#include "common/logging/log.h"
+#include "common/web_result.h"
+#include "core/settings.h"
+#include "web_service/web_backend.h"
+
+namespace WebService {
+
+constexpr std::array<const char, 1> API_VERSION{'1'};
+
+constexpr u32 HTTP_PORT = 80;
+constexpr u32 HTTPS_PORT = 443;
+
+constexpr u32 TIMEOUT_SECONDS = 30;
+
+Client::JWTCache Client::jwt_cache{};
+
+Client::Client(const std::string& host, const std::string& username, const std::string& token)
+ : host(host), username(username), token(token) {
+ std::lock_guard<std::mutex> lock(jwt_cache.mutex);
+ if (username == jwt_cache.username && token == jwt_cache.token) {
+ jwt = jwt_cache.jwt;
+ }
+}
+
+Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
+ const std::string& data, const std::string& jwt,
+ const std::string& username, const std::string& token) {
+ if (cli == nullptr) {
+ auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
+ int port;
+ if (parsedUrl.m_Scheme == "http") {
+ if (!parsedUrl.GetPort(&port)) {
+ port = HTTP_PORT;
+ }
+ cli =
+ std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
+ } else if (parsedUrl.m_Scheme == "https") {
+ if (!parsedUrl.GetPort(&port)) {
+ port = HTTPS_PORT;
+ }
+ cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
+ TIMEOUT_SECONDS);
+ } else {
+ LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
+ return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
+ }
+ }
+ if (cli == nullptr) {
+ LOG_ERROR(WebService, "Invalid URL {}", host + path);
+ return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
+ }
+
+ httplib::Headers params;
+ if (!jwt.empty()) {
+ params = {
+ {std::string("Authorization"), fmt::format("Bearer {}", jwt)},
+ };
+ } else if (!username.empty()) {
+ params = {
+ {std::string("x-username"), username},
+ {std::string("x-token"), token},
+ };
+ }
+
+ params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end()));
+ if (method != "GET") {
+ params.emplace(std::string("Content-Type"), std::string("application/json"));
+ };
+
+ httplib::Request request;
+ request.method = method;
+ request.path = path;
+ request.headers = params;
+ request.body = data;
+
+ httplib::Response response;
+
+ if (!cli->send(request, response)) {
+ LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
+ return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
+ }
+
+ if (response.status >= 400) {
+ LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
+ response.status);
+ return Common::WebResult{Common::WebResult::Code::HttpError,
+ std::to_string(response.status)};
+ }
+
+ auto content_type = response.headers.find("content-type");
+
+ if (content_type == response.headers.end()) {
+ LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
+ return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
+ }
+
+ if (content_type->second.find("application/json") == std::string::npos &&
+ content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
+ LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
+ content_type->second);
+ return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
+ }
+ return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
+}
+
+void Client::UpdateJWT() {
+ if (!username.empty() && !token.empty()) {
+ auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
+ if (result.result_code != Common::WebResult::Code::Success) {
+ LOG_ERROR(WebService, "UpdateJWT failed");
+ } else {
+ std::lock_guard<std::mutex> lock(jwt_cache.mutex);
+ jwt_cache.username = username;
+ jwt_cache.token = token;
+ jwt_cache.jwt = jwt = result.returned_data;
+ }
+ }
+}
+
+Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
+ const std::string& data, bool allow_anonymous) {
+ if (jwt.empty()) {
+ UpdateJWT();
+ }
+
+ if (jwt.empty() && !allow_anonymous) {
+ LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
+ return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
+ }
+
+ auto result = GenericJson(method, path, data, jwt);
+ if (result.result_string == "401") {
+ // Try again with new JWT
+ UpdateJWT();
+ result = GenericJson(method, path, data, jwt);
+ }
+
+ return result;
+}
+
+} // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
new file mode 100644
index 000000000..d75fbcc15
--- /dev/null
+++ b/src/web_service/web_backend.h
@@ -0,0 +1,92 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <string>
+#include <tuple>
+#include <httplib.h>
+#include "common/common_types.h"
+#include "common/web_result.h"
+
+namespace httplib {
+class Client;
+}
+
+namespace WebService {
+
+class Client {
+public:
+ Client(const std::string& host, const std::string& username, const std::string& token);
+
+ /**
+ * Posts JSON to the specified path.
+ * @param path the URL segment after the host address.
+ * @param data String of JSON data to use for the body of the POST request.
+ * @param allow_anonymous If true, allow anonymous unauthenticated requests.
+ * @return the result of the request.
+ */
+ Common::WebResult PostJson(const std::string& path, const std::string& data,
+ bool allow_anonymous) {
+ return GenericJson("POST", path, data, allow_anonymous);
+ }
+
+ /**
+ * Gets JSON from the specified path.
+ * @param path the URL segment after the host address.
+ * @param allow_anonymous If true, allow anonymous unauthenticated requests.
+ * @return the result of the request.
+ */
+ Common::WebResult GetJson(const std::string& path, bool allow_anonymous) {
+ return GenericJson("GET", path, "", allow_anonymous);
+ }
+
+ /**
+ * Deletes JSON to the specified path.
+ * @param path the URL segment after the host address.
+ * @param data String of JSON data to use for the body of the DELETE request.
+ * @param allow_anonymous If true, allow anonymous unauthenticated requests.
+ * @return the result of the request.
+ */
+ Common::WebResult DeleteJson(const std::string& path, const std::string& data,
+ bool allow_anonymous) {
+ return GenericJson("DELETE", path, data, allow_anonymous);
+ }
+
+private:
+ /// A generic function handles POST, GET and DELETE request together
+ Common::WebResult GenericJson(const std::string& method, const std::string& path,
+ const std::string& data, bool allow_anonymous);
+
+ /**
+ * A generic function with explicit authentication method specified
+ * JWT is used if the jwt parameter is not empty
+ * username + token is used if jwt is empty but username and token are not empty
+ * anonymous if all of jwt, username and token are empty
+ */
+ Common::WebResult GenericJson(const std::string& method, const std::string& path,
+ const std::string& data, const std::string& jwt = "",
+ const std::string& username = "", const std::string& token = "");
+
+ // Retrieve a new JWT from given username and token
+ void UpdateJWT();
+
+ std::string host;
+ std::string username;
+ std::string token;
+ std::string jwt;
+ std::unique_ptr<httplib::Client> cli;
+
+ struct JWTCache {
+ std::mutex mutex;
+ std::string username;
+ std::string token;
+ std::string jwt;
+ };
+ static JWTCache jwt_cache;
+};
+
+} // namespace WebService
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index f48b69809..04464ad5e 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -29,6 +29,8 @@ add_executable(yuzu
configuration/configure_input.h
configuration/configure_system.cpp
configuration/configure_system.h
+ configuration/configure_web.cpp
+ configuration/configure_web.h
debugger/graphics/graphics_breakpoint_observer.cpp
debugger/graphics/graphics_breakpoint_observer.h
debugger/graphics/graphics_breakpoints.cpp
@@ -42,6 +44,7 @@ add_executable(yuzu
debugger/profiler.h
debugger/wait_tree.cpp
debugger/wait_tree.h
+ discord.h
game_list.cpp
game_list.h
game_list_p.h
@@ -57,6 +60,8 @@ add_executable(yuzu
util/spinbox.h
util/util.cpp
util/util.h
+ compatdb.cpp
+ compatdb.h
yuzu.rc
)
@@ -70,8 +75,10 @@ set(UIS
configuration/configure_graphics.ui
configuration/configure_input.ui
configuration/configure_system.ui
+ configuration/configure_web.ui
hotkeys.ui
main.ui
+ compatdb.ui
)
file(GLOB COMPAT_LIST
@@ -113,6 +120,19 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)
target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
+if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
+ add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING)
+endif()
+
+if (USE_DISCORD_PRESENCE)
+ target_sources(yuzu PUBLIC
+ discord_impl.cpp
+ discord_impl.h
+ )
+ target_link_libraries(yuzu PRIVATE discord-rpc)
+ target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
+endif()
+
if(UNIX AND NOT APPLE)
install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
new file mode 100644
index 000000000..91e754274
--- /dev/null
+++ b/src/yuzu/compatdb.cpp
@@ -0,0 +1,65 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QButtonGroup>
+#include <QMessageBox>
+#include <QPushButton>
+#include "common/logging/log.h"
+#include "common/telemetry.h"
+#include "core/core.h"
+#include "core/telemetry_session.h"
+#include "ui_compatdb.h"
+#include "yuzu/compatdb.h"
+
+CompatDB::CompatDB(QWidget* parent)
+ : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
+ ui{std::make_unique<Ui::CompatDB>()} {
+ ui->setupUi(this);
+ connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext);
+ connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit);
+}
+
+CompatDB::~CompatDB() = default;
+
+enum class CompatDBPage {
+ Intro = 0,
+ Selection = 1,
+ Final = 2,
+};
+
+void CompatDB::Submit() {
+ QButtonGroup* compatibility = new QButtonGroup(this);
+ compatibility->addButton(ui->radioButton_Perfect, 0);
+ compatibility->addButton(ui->radioButton_Great, 1);
+ compatibility->addButton(ui->radioButton_Okay, 2);
+ compatibility->addButton(ui->radioButton_Bad, 3);
+ compatibility->addButton(ui->radioButton_IntroMenu, 4);
+ compatibility->addButton(ui->radioButton_WontBoot, 5);
+ switch ((static_cast<CompatDBPage>(currentId()))) {
+ case CompatDBPage::Selection:
+ if (compatibility->checkedId() == -1) {
+ button(NextButton)->setEnabled(false);
+ }
+ break;
+ case CompatDBPage::Final:
+ LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
+ Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility",
+ compatibility->checkedId());
+ // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
+ // workaround
+ button(QWizard::CancelButton)->setVisible(false);
+ break;
+ default:
+ LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
+ }
+}
+
+void CompatDB::EnableNext() {
+ button(NextButton)->setEnabled(true);
+}
diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h
new file mode 100644
index 000000000..ca0dd11d6
--- /dev/null
+++ b/src/yuzu/compatdb.h
@@ -0,0 +1,26 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QWizard>
+
+namespace Ui {
+class CompatDB;
+}
+
+class CompatDB : public QWizard {
+ Q_OBJECT
+
+public:
+ explicit CompatDB(QWidget* parent = nullptr);
+ ~CompatDB();
+
+private:
+ std::unique_ptr<Ui::CompatDB> ui;
+
+ void Submit();
+ void EnableNext();
+};
diff --git a/src/yuzu/compatdb.ui b/src/yuzu/compatdb.ui
new file mode 100644
index 000000000..fed402176
--- /dev/null
+++ b/src/yuzu/compatdb.ui
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CompatDB</class>
+ <widget class="QWizard" name="CompatDB">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>600</width>
+ <height>482</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>500</width>
+ <height>410</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Report Compatibility</string>
+ </property>
+ <property name="options">
+ <set>QWizard::DisabledBackButtonOnLastPage|QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage</set>
+ </property>
+ <widget class="QWizardPage" name="wizard_Info">
+ <property name="title">
+ <string>Report Game Compatibility</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">0</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="lbl_Spiel">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWizardPage" name="wizard_Report">
+ <property name="title">
+ <string>Report Game Compatibility</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">1</string>
+ </attribute>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="2" column="0">
+ <widget class="QRadioButton" name="radioButton_Perfect">
+ <property name="text">
+ <string>Perfect</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="lbl_Perfect">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QRadioButton" name="radioButton_Great">
+ <property name="text">
+ <string>Great </string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLabel" name="lbl_Great">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QRadioButton" name="radioButton_Okay">
+ <property name="text">
+ <string>Okay</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLabel" name="lbl_Okay">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QRadioButton" name="radioButton_Bad">
+ <property name="text">
+ <string>Bad</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLabel" name="lbl_Bad">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QRadioButton" name="radioButton_IntroMenu">
+ <property name="text">
+ <string>Intro/Menu</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QLabel" name="lbl_IntroMenu">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QRadioButton" name="radioButton_WontBoot">
+ <property name="text">
+ <string>Won't Boot</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="1">
+ <widget class="QLabel" name="lbl_WontBoot">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="lbl_Independent">
+ <property name="font">
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWizardPage" name="wizard_ThankYou">
+ <property name="title">
+ <string>Thank you for your submission!</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">2</string>
+ </attribute>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index d229225b4..650dd03c0 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -136,8 +136,18 @@ void Config::ReadValues() {
Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
qt_config->endGroup();
+ qt_config->beginGroup("WebService");
+ Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
+ Settings::values.web_api_url =
+ qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
+ Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString();
+ Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
+ qt_config->endGroup();
+
qt_config->beginGroup("UI");
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
+ UISettings::values.enable_discord_presence =
+ qt_config->value("enable_discord_presence", true).toBool();
qt_config->beginGroup("UIGameList");
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
@@ -261,8 +271,16 @@ void Config::SaveValues() {
qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
qt_config->endGroup();
+ qt_config->beginGroup("WebService");
+ qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
+ qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url));
+ qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
+ qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
+ qt_config->endGroup();
+
qt_config->beginGroup("UI");
qt_config->setValue("theme", UISettings::values.theme);
+ qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
qt_config->beginGroup("UIGameList");
qt_config->setValue("show_unknown", UISettings::values.show_unknown);
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 20f120134..9b297df28 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -54,6 +54,11 @@
<string>Debug</string>
</attribute>
</widget>
+ <widget class="ConfigureWeb" name="webTab">
+ <attribute name="title">
+ <string>Web</string>
+ </attribute>
+ </widget>
</widget>
</item>
<item>
@@ -108,6 +113,12 @@
<header>configuration/configure_graphics.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>ConfigureWeb</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_web.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 6ea59f2a3..eb1da0f9e 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -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);
+ ui->output_sink_combo_box->setCurrentIndex(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()));
+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));
}
}
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
index 4f0af4163..207f9dfb3 100644
--- a/src/yuzu/configuration/configure_audio.h
+++ b/src/yuzu/configuration/configure_audio.h
@@ -26,6 +26,9 @@ public slots:
private:
void setConfiguration();
+ void setOutputSinkFromSinkID();
+ void setAudioDeviceFromDeviceID();
+ void setVolumeIndicatorText(int percentage);
std::unique_ptr<Ui::ConfigureAudio> ui;
};
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index daa4cc0d9..3905423e9 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -27,5 +27,6 @@ void ConfigureDialog::applyConfiguration() {
ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration();
ui->debugTab->applyConfiguration();
+ ui->webTab->applyConfiguration();
Settings::Apply();
}
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 9292d9a42..f5db9e55b 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -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);
}
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 839d58f59..cd1549462 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -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(
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index d29abb74b..94789c064 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -5,6 +5,7 @@
#include <algorithm>
#include <memory>
#include <utility>
+#include <QMenu>
#include <QMessageBox>
#include <QTimer>
#include "common/param_package.h"
@@ -128,33 +129,68 @@ ConfigureInput::ConfigureInput(QWidget* parent)
analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
- if (button_map[button_id])
- connect(button_map[button_id], &QPushButton::released, [=]() {
- handleClick(
- button_map[button_id],
- [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
- InputCommon::Polling::DeviceType::Button);
- });
+ if (!button_map[button_id])
+ continue;
+ button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(button_map[button_id], &QPushButton::released, [=]() {
+ handleClick(
+ button_map[button_id],
+ [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
+ InputCommon::Polling::DeviceType::Button);
+ });
+ connect(button_map[button_id], &QPushButton::customContextMenuRequested,
+ [=](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ buttons_param[button_id].Clear();
+ button_map[button_id]->setText(tr("[not set]"));
+ });
+ context_menu.addAction(tr("Restore Default"), [&] {
+ buttons_param[button_id] = Common::ParamPackage{
+ InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+ button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
+ });
+ context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
+ });
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
- if (analog_map_buttons[analog_id][sub_button_id] != nullptr) {
- connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released,
- [=]() {
- handleClick(analog_map_buttons[analog_id][sub_button_id],
- [=](const Common::ParamPackage& params) {
- SetAnalogButton(params, analogs_param[analog_id],
- analog_sub_buttons[sub_button_id]);
- },
- InputCommon::Polling::DeviceType::Button);
+ if (!analog_map_buttons[analog_id][sub_button_id])
+ continue;
+ analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
+ Qt::CustomContextMenu);
+ connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() {
+ handleClick(analog_map_buttons[analog_id][sub_button_id],
+ [=](const Common::ParamPackage& params) {
+ SetAnalogButton(params, analogs_param[analog_id],
+ analog_sub_buttons[sub_button_id]);
+ },
+ InputCommon::Polling::DeviceType::Button);
+ });
+ connect(analog_map_buttons[analog_id][sub_button_id],
+ &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
+ analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
});
- }
+ context_menu.addAction(tr("Restore Default"), [&] {
+ Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
+ Config::default_analogs[analog_id][sub_button_id])};
+ SetAnalogButton(params, analogs_param[analog_id],
+ analog_sub_buttons[sub_button_id]);
+ analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
+ analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
+ });
+ context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
+ menu_location));
+ });
}
connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {
- QMessageBox::information(
- this, "Information",
- "After pressing OK, first move your joystick horizontally, and then vertically.");
+ QMessageBox::information(this, tr("Information"),
+ tr("After pressing OK, first move your joystick horizontally, "
+ "and then vertically."));
handleClick(
analog_map_stick[analog_id],
[=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
@@ -162,6 +198,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
});
}
+ connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
timeout_timer->setSingleShot(true);
@@ -215,7 +252,21 @@ void ConfigureInput::restoreDefaults() {
}
}
updateButtonLabels();
- applyConfiguration();
+}
+
+void ConfigureInput::ClearAll() {
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
+ if (button_map[button_id] && button_map[button_id]->isEnabled())
+ buttons_param[button_id].Clear();
+ }
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ if (analog_map_buttons[analog_id][sub_button_id] &&
+ analog_map_buttons[analog_id][sub_button_id]->isEnabled())
+ analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
+ }
+ }
+ updateButtonLabels();
}
void ConfigureInput::updateButtonLabels() {
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index a0bef86d5..d1198db81 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -72,6 +72,9 @@ private:
void loadConfiguration();
/// Restore all buttons to their default values.
void restoreDefaults();
+ /// Clear all input configuration
+ void ClearAll();
+
/// Update UI to reflect current configuration.
void updateButtonLabels();
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index 8bfa5df62..8a019a693 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -695,6 +695,34 @@ Capture:</string>
</spacer>
</item>
<item>
+ <widget class="QPushButton" name="buttonClearAll">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="sizeIncrement">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>Clear All</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="buttonRestoreDefaults">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
new file mode 100644
index 000000000..3c2ccb76f
--- /dev/null
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -0,0 +1,119 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QIcon>
+#include <QMessageBox>
+#include <QtConcurrent/QtConcurrentRun>
+#include "core/settings.h"
+#include "core/telemetry_session.h"
+#include "ui_configure_web.h"
+#include "yuzu/configuration/configure_web.h"
+#include "yuzu/ui_settings.h"
+
+ConfigureWeb::ConfigureWeb(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
+ ui->setupUi(this);
+ connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
+ &ConfigureWeb::RefreshTelemetryID);
+ connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
+ connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified);
+
+#ifndef USE_DISCORD_PRESENCE
+ ui->discord_group->setVisible(false);
+#endif
+ this->setConfiguration();
+}
+
+ConfigureWeb::~ConfigureWeb() = default;
+
+void ConfigureWeb::setConfiguration() {
+ ui->web_credentials_disclaimer->setWordWrap(true);
+ ui->telemetry_learn_more->setOpenExternalLinks(true);
+ ui->telemetry_learn_more->setText(
+ tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'><span style=\"text-decoration: "
+ "underline; color:#039be5;\">Learn more</span></a>"));
+
+ ui->web_signup_link->setOpenExternalLinks(true);
+ ui->web_signup_link->setText(
+ tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; "
+ "color:#039be5;\">Sign up</span></a>"));
+ ui->web_token_info_link->setOpenExternalLinks(true);
+ ui->web_token_info_link->setText(
+ tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: "
+ "underline; color:#039be5;\">What is my token?</span></a>"));
+
+ ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
+ ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username));
+ ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token));
+ // Connect after setting the values, to avoid calling OnLoginChanged now
+ connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
+ connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
+ ui->label_telemetry_id->setText(
+ tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
+ user_verified = true;
+
+ ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence);
+}
+
+void ConfigureWeb::applyConfiguration() {
+ Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
+ UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
+ if (user_verified) {
+ Settings::values.yuzu_username = ui->edit_username->text().toStdString();
+ Settings::values.yuzu_token = ui->edit_token->text().toStdString();
+ } else {
+ QMessageBox::warning(this, tr("Username and token not verified"),
+ tr("Username and token were not verified. The changes to your "
+ "username and/or token have not been saved."));
+ }
+}
+
+void ConfigureWeb::RefreshTelemetryID() {
+ const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
+ ui->label_telemetry_id->setText(
+ tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
+}
+
+void ConfigureWeb::OnLoginChanged() {
+ if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
+ user_verified = true;
+ ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+ ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+ } else {
+ user_verified = false;
+ ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+ ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+ }
+}
+
+void ConfigureWeb::VerifyLogin() {
+ ui->button_verify_login->setDisabled(true);
+ ui->button_verify_login->setText(tr("Verifying"));
+ verify_watcher.setFuture(
+ QtConcurrent::run([this, username = ui->edit_username->text().toStdString(),
+ token = ui->edit_token->text().toStdString()]() {
+ return Core::VerifyLogin(username, token);
+ }));
+}
+
+void ConfigureWeb::OnLoginVerified() {
+ ui->button_verify_login->setEnabled(true);
+ ui->button_verify_login->setText(tr("Verify"));
+ if (verify_watcher.result()) {
+ user_verified = true;
+ ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+ ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
+ } else {
+ ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+ ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
+ QMessageBox::critical(
+ this, tr("Verification failed"),
+ tr("Verification failed. Check that you have entered your username and token "
+ "correctly, and that your internet connection is working."));
+ }
+}
+
+void ConfigureWeb::retranslateUi() {
+ ui->retranslateUi(this);
+}
diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h
new file mode 100644
index 000000000..7741ab95d
--- /dev/null
+++ b/src/yuzu/configuration/configure_web.h
@@ -0,0 +1,38 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <QFutureWatcher>
+#include <QWidget>
+
+namespace Ui {
+class ConfigureWeb;
+}
+
+class ConfigureWeb : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureWeb(QWidget* parent = nullptr);
+ ~ConfigureWeb();
+
+ void applyConfiguration();
+ void retranslateUi();
+
+public slots:
+ void RefreshTelemetryID();
+ void OnLoginChanged();
+ void VerifyLogin();
+ void OnLoginVerified();
+
+private:
+ void setConfiguration();
+
+ bool user_verified = true;
+ QFutureWatcher<bool> verify_watcher;
+
+ std::unique_ptr<Ui::ConfigureWeb> ui;
+};
diff --git a/src/yuzu/configuration/configure_web.ui b/src/yuzu/configuration/configure_web.ui
new file mode 100644
index 000000000..2f4b9dd73
--- /dev/null
+++ b/src/yuzu/configuration/configure_web.ui
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureWeb</class>
+ <widget class="QWidget" name="ConfigureWeb">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>926</width>
+ <height>561</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="QGroupBox" name="groupBoxWebConfig">
+ <property name="title">
+ <string>yuzu Web Service</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayoutYuzuWebService">
+ <item>
+ <widget class="QLabel" name="web_credentials_disclaimer">
+ <property name="text">
+ <string>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayoutYuzuUsername">
+ <item row="2" column="3">
+ <widget class="QPushButton" name="button_verify_login">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::RightToLeft</enum>
+ </property>
+ <property name="text">
+ <string>Verify</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="web_signup_link">
+ <property name="text">
+ <string>Sign up</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="3">
+ <widget class="QLineEdit" name="edit_username">
+ <property name="maxLength">
+ <number>36</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_token">
+ <property name="text">
+ <string>Token: </string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QLabel" name="label_token_verified">
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_username">
+ <property name="text">
+ <string>Username: </string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="label_username_verified">
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="3">
+ <widget class="QLineEdit" name="edit_token">
+ <property name="maxLength">
+ <number>36</number>
+ </property>
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="web_token_info_link">
+ <property name="text">
+ <string>What is my token?</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Telemetry</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="toggle_telemetry">
+ <property name="text">
+ <string>Share anonymous usage data with the yuzu team</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="telemetry_learn_more">
+ <property name="text">
+ <string>Learn more</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayoutTelemetryId">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_telemetry_id">
+ <property name="text">
+ <string>Telemetry ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="button_regenerate_telemetry_id">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::RightToLeft</enum>
+ </property>
+ <property name="text">
+ <string>Regenerate</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="discord_group">
+ <property name="title">
+ <string>Discord Presence</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_21">
+ <item>
+ <widget class="QCheckBox" name="toggle_discordrpc">
+ <property name="text">
+ <string>Show Current Game in your Discord Status</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index a3b1fd357..4a09da685 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -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()));
}
diff --git a/src/yuzu/discord.h b/src/yuzu/discord.h
new file mode 100644
index 000000000..a867cc4d6
--- /dev/null
+++ b/src/yuzu/discord.h
@@ -0,0 +1,25 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace DiscordRPC {
+
+class DiscordInterface {
+public:
+ virtual ~DiscordInterface() = default;
+
+ virtual void Pause() = 0;
+ virtual void Update() = 0;
+};
+
+class NullImpl : public DiscordInterface {
+public:
+ ~NullImpl() = default;
+
+ void Pause() override {}
+ void Update() override {}
+};
+
+} // namespace DiscordRPC
diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp
new file mode 100644
index 000000000..9d87a41eb
--- /dev/null
+++ b/src/yuzu/discord_impl.cpp
@@ -0,0 +1,52 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <string>
+#include <discord_rpc.h>
+#include "common/common_types.h"
+#include "core/core.h"
+#include "core/loader/loader.h"
+#include "yuzu/discord_impl.h"
+#include "yuzu/ui_settings.h"
+
+namespace DiscordRPC {
+
+DiscordImpl::DiscordImpl() {
+ DiscordEventHandlers handlers{};
+
+ // The number is the client ID for yuzu, it's used for images and the
+ // application name
+ Discord_Initialize("471872241299226636", &handlers, 1, nullptr);
+}
+
+DiscordImpl::~DiscordImpl() {
+ Discord_ClearPresence();
+ Discord_Shutdown();
+}
+
+void DiscordImpl::Pause() {
+ Discord_ClearPresence();
+}
+
+void DiscordImpl::Update() {
+ s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+ std::string title;
+ if (Core::System::GetInstance().IsPoweredOn())
+ Core::System::GetInstance().GetAppLoader().ReadTitle(title);
+ DiscordRichPresence presence{};
+ presence.largeImageKey = "yuzu_logo";
+ presence.largeImageText = "yuzu is an emulator for the Nintendo Switch";
+ if (Core::System::GetInstance().IsPoweredOn()) {
+ presence.state = title.c_str();
+ presence.details = "Currently in game";
+ } else {
+ presence.details = "Not in game";
+ }
+ presence.startTimestamp = start_time;
+ Discord_UpdatePresence(&presence);
+}
+} // namespace DiscordRPC
diff --git a/src/yuzu/discord_impl.h b/src/yuzu/discord_impl.h
new file mode 100644
index 000000000..4bfda8cdf
--- /dev/null
+++ b/src/yuzu/discord_impl.h
@@ -0,0 +1,20 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "yuzu/discord.h"
+
+namespace DiscordRPC {
+
+class DiscordImpl : public DiscordInterface {
+public:
+ DiscordImpl();
+ ~DiscordImpl() override;
+
+ void Pause() override;
+ void Update() override;
+};
+
+} // namespace DiscordRPC
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index e228d61bd..1947bdb93 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -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());
}
}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 27015d02c..ad62a82d0 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -35,6 +35,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QtWidgets>
#include <fmt/format.h>
#include "common/common_paths.h"
+#include "common/detached_tasks.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
@@ -65,6 +66,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "video_core/debug_utils/debug_utils.h"
#include "yuzu/about_dialog.h"
#include "yuzu/bootmanager.h"
+#include "yuzu/compatdb.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
@@ -73,12 +75,17 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/debugger/graphics/graphics_surface.h"
#include "yuzu/debugger/profiler.h"
#include "yuzu/debugger/wait_tree.h"
+#include "yuzu/discord.h"
#include "yuzu/game_list.h"
#include "yuzu/game_list_p.h"
#include "yuzu/hotkeys.h"
#include "yuzu/main.h"
#include "yuzu/ui_settings.h"
+#ifdef USE_DISCORD_PRESENCE
+#include "yuzu/discord_impl.h"
+#endif
+
#ifdef QT_STATICPLUGIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#endif
@@ -102,23 +109,22 @@ enum class CalloutFlag : uint32_t {
DRDDeprecation = 0x2,
};
-static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
- if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) {
+void GMainWindow::ShowTelemetryCallout() {
+ if (UISettings::values.callout_flags & static_cast<uint32_t>(CalloutFlag::Telemetry)) {
return;
}
- UISettings::values.callout_flags |= static_cast<uint32_t>(flag);
-
- QMessageBox msg;
- msg.setText(message);
- msg.setStandardButtons(QMessageBox::Ok);
- msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
- msg.setStyleSheet("QLabel{min-width: 900px;}");
- msg.exec();
+ UISettings::values.callout_flags |= static_cast<uint32_t>(CalloutFlag::Telemetry);
+ const QString telemetry_message =
+ tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
+ "data is collected</a> to help improve yuzu. "
+ "<br/><br/>Would you like to share your usage data with us?");
+ if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) {
+ Settings::values.enable_telemetry = false;
+ Settings::Apply();
+ }
}
-void GMainWindow::ShowCallouts() {}
-
const int GMainWindow::max_recent_files_item;
static void InitializeLogging() {
@@ -145,6 +151,9 @@ GMainWindow::GMainWindow()
default_theme_paths = QIcon::themeSearchPaths();
UpdateUITheme();
+ SetDiscordEnabled(UISettings::values.enable_discord_presence);
+ discord_rpc->Update();
+
InitializeWidgets();
InitializeDebugWidgets();
InitializeRecentFileMenuActions();
@@ -168,7 +177,7 @@ GMainWindow::GMainWindow()
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
// Show one-time "callout" messages to the user
- ShowCallouts();
+ ShowTelemetryCallout();
QStringList args = QApplication::arguments();
if (args.length() >= 2) {
@@ -183,6 +192,9 @@ GMainWindow::~GMainWindow() {
}
void GMainWindow::InitializeWidgets() {
+#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
+ ui.action_Report_Compatibility->setVisible(true);
+#endif
render_window = new GRenderWindow(this, emu_thread.get());
render_window->hide();
@@ -411,6 +423,8 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame);
connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
+ connect(ui.action_Report_Compatibility, &QAction::triggered, this,
+ &GMainWindow::OnMenuReportCompatibility);
connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
@@ -647,6 +661,7 @@ void GMainWindow::BootGame(const QString& filename) {
}
void GMainWindow::ShutdownGame() {
+ discord_rpc->Pause();
emu_thread->RequestStop();
emit EmulationStopping();
@@ -655,6 +670,8 @@ void GMainWindow::ShutdownGame() {
emu_thread->wait();
emu_thread = nullptr;
+ discord_rpc->Update();
+
// The emulation is stopped, so closing the window or not does not matter anymore
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
@@ -664,6 +681,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(false);
ui.action_Restart->setEnabled(false);
+ ui.action_Report_Compatibility->setEnabled(false);
render_window->hide();
game_list->show();
game_list->setFilterFocus();
@@ -1147,6 +1165,9 @@ void GMainWindow::OnStartGame() {
ui.action_Pause->setEnabled(true);
ui.action_Stop->setEnabled(true);
ui.action_Restart->setEnabled(true);
+ ui.action_Report_Compatibility->setEnabled(true);
+
+ discord_rpc->Update();
}
void GMainWindow::OnPauseGame() {
@@ -1161,6 +1182,20 @@ void GMainWindow::OnStopGame() {
ShutdownGame();
}
+void GMainWindow::OnMenuReportCompatibility() {
+ if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) {
+ CompatDB compatdb{this};
+ compatdb.exec();
+ } else {
+ QMessageBox::critical(
+ this, tr("Missing yuzu Account"),
+ tr("In order to submit a game compatibility test case, you must link your yuzu "
+ "account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration "
+ "&gt; "
+ "Web."));
+ }
+}
+
void GMainWindow::ToggleFullscreen() {
if (!emulation_running) {
return;
@@ -1224,11 +1259,14 @@ void GMainWindow::ToggleWindowMode() {
void GMainWindow::OnConfigure() {
ConfigureDialog configureDialog(this, hotkey_registry);
auto old_theme = UISettings::values.theme;
+ const bool old_discord_presence = UISettings::values.enable_discord_presence;
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
if (UISettings::values.theme != old_theme)
UpdateUITheme();
+ if (UISettings::values.enable_discord_presence != old_discord_presence)
+ SetDiscordEnabled(UISettings::values.enable_discord_presence);
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
config->Save();
}
@@ -1443,11 +1481,25 @@ void GMainWindow::UpdateUITheme() {
emit UpdateThemedIcons();
}
+void GMainWindow::SetDiscordEnabled(bool state) {
+#ifdef USE_DISCORD_PRESENCE
+ if (state) {
+ discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>();
+ } else {
+ discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
+ }
+#else
+ discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
+#endif
+ discord_rpc->Update();
+}
+
#ifdef main
#undef main
#endif
int main(int argc, char* argv[]) {
+ Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
@@ -1465,5 +1517,7 @@ int main(int argc, char* argv[]) {
GMainWindow main_window;
// After settings have been loaded by GMainWindow, apply the filter
main_window.show();
- return app.exec();
+ int result = app.exec();
+ detached_tasks.WaitForAllTasks();
+ return result;
}
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 8ee9242b1..fe0e9a50a 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -41,6 +41,10 @@ enum class EmulatedDirectoryTarget {
SDMC,
};
+namespace DiscordRPC {
+class DiscordInterface;
+}
+
class GMainWindow : public QMainWindow {
Q_OBJECT
@@ -61,6 +65,8 @@ public:
GMainWindow();
~GMainWindow() override;
+ std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
+
signals:
/**
@@ -99,7 +105,8 @@ private:
void BootGame(const QString& filename);
void ShutdownGame();
- void ShowCallouts();
+ void ShowTelemetryCallout();
+ void SetDiscordEnabled(bool state);
/**
* Stores the filename in the recently loaded files list.
@@ -135,6 +142,7 @@ private slots:
void OnStartGame();
void OnPauseGame();
void OnStopGame();
+ void OnMenuReportCompatibility();
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 3879d4813..cb1664b21 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -45,7 +45,7 @@
<x>0</x>
<y>0</y>
<width>1081</width>
- <height>19</height>
+ <height>21</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
@@ -101,6 +101,8 @@
<property name="title">
<string>&amp;Help</string>
</property>
+ <addaction name="action_Report_Compatibility"/>
+ <addaction name="separator"/>
<addaction name="action_About"/>
</widget>
<addaction name="menu_File"/>
@@ -239,6 +241,18 @@
<string>Restart</string>
</property>
</action>
+ <action name="action_Report_Compatibility">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Report Compatibility</string>
+ </property>
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ </action>
</widget>
<resources/>
+ <connections/>
</ui>
diff --git a/src/yuzu/ui_settings.cpp b/src/yuzu/ui_settings.cpp
index 120b34990..a314493fc 100644
--- a/src/yuzu/ui_settings.cpp
+++ b/src/yuzu/ui_settings.cpp
@@ -6,5 +6,11 @@
namespace UISettings {
+const Themes themes{{
+ {"Default", "default"},
+ {"Dark", "qdarkstyle"},
+}};
+
Values values = {};
-}
+
+} // namespace UISettings
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 051494bc5..2e617d52a 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -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;
@@ -39,6 +38,9 @@ struct Values {
bool confirm_before_closing;
bool first_start;
+ // Discord RPC
+ bool enable_discord_presence;
+
QString roms_path;
QString symbols_path;
QString gamedir;
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index a478b0a56..9d934e220 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -138,6 +138,14 @@ void Config::ReadValues() {
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
Settings::values.gdbstub_port =
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
+
+ // Web Service
+ Settings::values.enable_telemetry =
+ sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
+ Settings::values.web_api_url =
+ sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org");
+ Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", "");
+ Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", "");
}
void Config::Reload() {
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index d35c441e9..762396e3b 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -202,12 +202,10 @@ gdbstub_port=24689
# Whether or not to enable telemetry
# 0: No, 1 (default): Yes
enable_telemetry =
-# Endpoint URL for submitting telemetry data
-telemetry_endpoint_url =
-# Endpoint URL to verify the username and token
-verify_endpoint_url =
+# URL for Web API
+web_api_url = https://api.yuzu-emu.org
# Username and token for yuzu Web Service
-# See https://services.citra-emu.org/ for more info
+# See https://profile.yuzu-emu.org/ for more info
yuzu_username =
yuzu_token =
)";
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index b2559b717..1d951ca3f 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -10,6 +10,7 @@
#include <fmt/ostream.h>
#include "common/common_paths.h"
+#include "common/detached_tasks.h"
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
@@ -78,6 +79,7 @@ static void InitializeLogging() {
/// Application entry point
int main(int argc, char** argv) {
+ Common::DetachedTasks detached_tasks;
Config config;
int option_index = 0;
@@ -213,5 +215,6 @@ int main(int argc, char** argv) {
system.RunLoop();
}
+ detached_tasks.WaitForAllTasks();
return 0;
}