summaryrefslogtreecommitdiff
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()
13if (ENABLE_QT) 13if (ENABLE_QT)
14 add_subdirectory(yuzu) 14 add_subdirectory(yuzu)
15endif() 15endif()
16if (ENABLE_WEB_SERVICE)
17 add_subdirectory(web_service)
18endif()
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})
29 if (BUILD_VERSION) 29 if (BUILD_VERSION)
30 # This leaves a trailing space on the last word, but we actually want that 30 # This leaves a trailing space on the last word, but we actually want that
31 # because of how it's styled in the title bar. 31 # because of how it's styled in the title bar.
32 set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ") 32 set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
33 else() 33 else()
34 set(BUILD_FULLNAME "") 34 set(BUILD_FULLNAME "")
35 endif() 35 endif()
@@ -41,6 +41,8 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU
41add_library(common STATIC 41add_library(common STATIC
42 alignment.h 42 alignment.h
43 assert.h 43 assert.h
44 detached_tasks.cpp
45 detached_tasks.h
44 bit_field.h 46 bit_field.h
45 bit_set.h 47 bit_set.h
46 cityhash.cpp 48 cityhash.cpp
@@ -87,6 +89,7 @@ add_library(common STATIC
87 timer.cpp 89 timer.cpp
88 timer.h 90 timer.h
89 vector_math.h 91 vector_math.h
92 web_result.h
90) 93)
91 94
92if(ARCHITECTURE_x86_64) 95if(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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <thread>
6#include "common/assert.h"
7#include "common/detached_tasks.h"
8
9namespace Common {
10
11DetachedTasks* DetachedTasks::instance = nullptr;
12
13DetachedTasks::DetachedTasks() {
14 ASSERT(instance == nullptr);
15 instance = this;
16}
17
18void DetachedTasks::WaitForAllTasks() {
19 std::unique_lock<std::mutex> lock(mutex);
20 cv.wait(lock, [this]() { return count == 0; });
21}
22
23DetachedTasks::~DetachedTasks() {
24 std::unique_lock<std::mutex> lock(mutex);
25 ASSERT(count == 0);
26 instance = nullptr;
27}
28
29void DetachedTasks::AddTask(std::function<void()> task) {
30 std::unique_lock<std::mutex> lock(instance->mutex);
31 ++instance->count;
32 std::thread([task{std::move(task)}]() {
33 task();
34 std::unique_lock<std::mutex> lock(instance->mutex);
35 --instance->count;
36 std::notify_all_at_thread_exit(instance->cv, std::move(lock));
37 })
38 .detach();
39}
40
41} // 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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <condition_variable>
8#include <functional>
9
10namespace Common {
11
12/**
13 * A background manager which ensures that all detached task is finished before program exits.
14 *
15 * Some tasks, telemetry submission for example, prefer executing asynchronously and don't care
16 * about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if
17 * the task is launched just before the program exits (which is a common case for telemetry), so we
18 * need to block on these tasks on program exit.
19 *
20 * To make detached task safe, a single DetachedTasks object should be placed in the main(), and
21 * call WaitForAllTasks() after all program execution but before global/static variable destruction.
22 * Any potentially unsafe detached task should be executed via DetachedTasks::AddTask.
23 */
24class DetachedTasks {
25public:
26 DetachedTasks();
27 ~DetachedTasks();
28 void WaitForAllTasks();
29
30 static void AddTask(std::function<void()> task);
31
32private:
33 static DetachedTasks* instance;
34
35 std::condition_variable cv;
36 std::mutex mutex;
37 int count = 0;
38};
39
40} // 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:
285template <typename T> 285template <typename T>
286void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) { 286void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
287#ifdef _MSC_VER 287#ifdef _MSC_VER
288 fstream.open(Common::UTF8ToTStr(filename).c_str(), openmode); 288 fstream.open(Common::UTF8ToUTF16W(filename).c_str(), openmode);
289#else 289#else
290 fstream.open(filename.c_str(), openmode); 290 fstream.open(filename.c_str(), openmode);
291#endif 291#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) {
31} 31}
32 32
33void PrintMessage(const Entry& entry) { 33void PrintMessage(const Entry& entry) {
34 auto str = FormatLogMessage(entry) + '\n'; 34 const auto str = FormatLogMessage(entry).append(1, '\n');
35 fputs(str.c_str(), stderr); 35 fputs(str.c_str(), stderr);
36} 36}
37 37
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";
20constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1"; 20constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
21constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2"; 21constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
22 22
23/// A placeholder for empty param packages to avoid empty strings
24/// (they may be recognized as "not set" by some frontend libraries like qt)
25constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
26
23ParamPackage::ParamPackage(const std::string& serialized) { 27ParamPackage::ParamPackage(const std::string& serialized) {
28 if (serialized == EMPTY_PLACEHOLDER) {
29 return;
30 }
31
24 std::vector<std::string> pairs; 32 std::vector<std::string> pairs;
25 Common::SplitString(serialized, PARAM_SEPARATOR, pairs); 33 Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
26 34
@@ -46,7 +54,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d
46 54
47std::string ParamPackage::Serialize() const { 55std::string ParamPackage::Serialize() const {
48 if (data.empty()) 56 if (data.empty())
49 return ""; 57 return EMPTY_PLACEHOLDER;
50 58
51 std::string result; 59 std::string result;
52 60
@@ -120,4 +128,12 @@ bool ParamPackage::Has(const std::string& key) const {
120 return data.find(key) != data.end(); 128 return data.find(key) != data.end();
121} 129}
122 130
131void ParamPackage::Erase(const std::string& key) {
132 data.erase(key);
133}
134
135void ParamPackage::Clear() {
136 data.clear();
137}
138
123} // namespace Common 139} // 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:
32 void Set(const std::string& key, int value); 32 void Set(const std::string& key, int value);
33 void Set(const std::string& key, float value); 33 void Set(const std::string& key, float value);
34 bool Has(const std::string& key) const; 34 bool Has(const std::string& key) const;
35 void Erase(const std::string& key);
36 void Clear();
35 37
36private: 38private:
37 DataType data; 39 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 @@
5#include <algorithm> 5#include <algorithm>
6#include <cctype> 6#include <cctype>
7#include <cerrno> 7#include <cerrno>
8#include <codecvt>
8#include <cstdio> 9#include <cstdio>
9#include <cstdlib> 10#include <cstdlib>
10#include <cstring> 11#include <cstring>
@@ -13,11 +14,7 @@
13#include "common/string_util.h" 14#include "common/string_util.h"
14 15
15#ifdef _WIN32 16#ifdef _WIN32
16#include <codecvt>
17#include <windows.h> 17#include <windows.h>
18#include "common/common_funcs.h"
19#else
20#include <iconv.h>
21#endif 18#endif
22 19
23namespace Common { 20namespace Common {
@@ -195,11 +192,9 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
195 return result; 192 return result;
196} 193}
197 194
198#ifdef _WIN32
199
200std::string UTF16ToUTF8(const std::u16string& input) { 195std::string UTF16ToUTF8(const std::u16string& input) {
201#if _MSC_VER >= 1900 196#ifdef _MSC_VER
202 // Workaround for missing char16_t/char32_t instantiations in MSVC2015 197 // Workaround for missing char16_t/char32_t instantiations in MSVC2017
203 std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert; 198 std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
204 std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend()); 199 std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend());
205 return convert.to_bytes(tmp_buffer); 200 return convert.to_bytes(tmp_buffer);
@@ -210,8 +205,8 @@ std::string UTF16ToUTF8(const std::u16string& input) {
210} 205}
211 206
212std::u16string UTF8ToUTF16(const std::string& input) { 207std::u16string UTF8ToUTF16(const std::string& input) {
213#if _MSC_VER >= 1900 208#ifdef _MSC_VER
214 // Workaround for missing char16_t/char32_t instantiations in MSVC2015 209 // Workaround for missing char16_t/char32_t instantiations in MSVC2017
215 std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert; 210 std::wstring_convert<std::codecvt_utf8_utf16<__int16>, __int16> convert;
216 auto tmp_buffer = convert.from_bytes(input); 211 auto tmp_buffer = convert.from_bytes(input);
217 return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend()); 212 return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend());
@@ -221,6 +216,7 @@ std::u16string UTF8ToUTF16(const std::string& input) {
221#endif 216#endif
222} 217}
223 218
219#ifdef _WIN32
224static std::wstring CPToUTF16(u32 code_page, const std::string& input) { 220static std::wstring CPToUTF16(u32 code_page, const std::string& input) {
225 const auto size = 221 const auto size =
226 MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0); 222 MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
@@ -261,124 +257,6 @@ std::wstring UTF8ToUTF16W(const std::string& input) {
261 return CPToUTF16(CP_UTF8, input); 257 return CPToUTF16(CP_UTF8, input);
262} 258}
263 259
264std::string SHIFTJISToUTF8(const std::string& input) {
265 return UTF16ToUTF8(CPToUTF16(932, input));
266}
267
268std::string CP1252ToUTF8(const std::string& input) {
269 return UTF16ToUTF8(CPToUTF16(1252, input));
270}
271
272#else
273
274template <typename T>
275static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& input) {
276 iconv_t const conv_desc = iconv_open("UTF-8", fromcode);
277 if ((iconv_t)(-1) == conv_desc) {
278 LOG_ERROR(Common, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno));
279 iconv_close(conv_desc);
280 return {};
281 }
282
283 const std::size_t in_bytes = sizeof(T) * input.size();
284 // Multiply by 4, which is the max number of bytes to encode a codepoint
285 const std::size_t out_buffer_size = 4 * in_bytes;
286
287 std::string out_buffer(out_buffer_size, '\0');
288
289 auto src_buffer = &input[0];
290 std::size_t src_bytes = in_bytes;
291 auto dst_buffer = &out_buffer[0];
292 std::size_t dst_bytes = out_buffer.size();
293
294 while (0 != src_bytes) {
295 std::size_t const iconv_result =
296 iconv(conv_desc, (char**)(&src_buffer), &src_bytes, &dst_buffer, &dst_bytes);
297
298 if (static_cast<std::size_t>(-1) == iconv_result) {
299 if (EILSEQ == errno || EINVAL == errno) {
300 // Try to skip the bad character
301 if (0 != src_bytes) {
302 --src_bytes;
303 ++src_buffer;
304 }
305 } else {
306 LOG_ERROR(Common, "iconv failure [{}]: {}", fromcode, strerror(errno));
307 break;
308 }
309 }
310 }
311
312 std::string result;
313 out_buffer.resize(out_buffer_size - dst_bytes);
314 out_buffer.swap(result);
315
316 iconv_close(conv_desc);
317
318 return result;
319}
320
321std::u16string UTF8ToUTF16(const std::string& input) {
322 iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8");
323 if ((iconv_t)(-1) == conv_desc) {
324 LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: {}", strerror(errno));
325 iconv_close(conv_desc);
326 return {};
327 }
328
329 const std::size_t in_bytes = sizeof(char) * input.size();
330 // Multiply by 4, which is the max number of bytes to encode a codepoint
331 const std::size_t out_buffer_size = 4 * sizeof(char16_t) * in_bytes;
332
333 std::u16string out_buffer(out_buffer_size, char16_t{});
334
335 char* src_buffer = const_cast<char*>(&input[0]);
336 std::size_t src_bytes = in_bytes;
337 char* dst_buffer = (char*)(&out_buffer[0]);
338 std::size_t dst_bytes = out_buffer.size();
339
340 while (0 != src_bytes) {
341 std::size_t const iconv_result =
342 iconv(conv_desc, &src_buffer, &src_bytes, &dst_buffer, &dst_bytes);
343
344 if (static_cast<std::size_t>(-1) == iconv_result) {
345 if (EILSEQ == errno || EINVAL == errno) {
346 // Try to skip the bad character
347 if (0 != src_bytes) {
348 --src_bytes;
349 ++src_buffer;
350 }
351 } else {
352 LOG_ERROR(Common, "iconv failure [UTF-8]: {}", strerror(errno));
353 break;
354 }
355 }
356 }
357
358 std::u16string result;
359 out_buffer.resize(out_buffer_size - dst_bytes);
360 out_buffer.swap(result);
361
362 iconv_close(conv_desc);
363
364 return result;
365}
366
367std::string UTF16ToUTF8(const std::u16string& input) {
368 return CodeToUTF8("UTF-16LE", input);
369}
370
371std::string CP1252ToUTF8(const std::string& input) {
372 // return CodeToUTF8("CP1252//TRANSLIT", input);
373 // return CodeToUTF8("CP1252//IGNORE", input);
374 return CodeToUTF8("CP1252", input);
375}
376
377std::string SHIFTJISToUTF8(const std::string& input) {
378 // return CodeToUTF8("CP932", input);
379 return CodeToUTF8("SJIS", input);
380}
381
382#endif 260#endif
383 261
384std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len) { 262std::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
72std::string UTF16ToUTF8(const std::u16string& input); 72std::string UTF16ToUTF8(const std::u16string& input);
73std::u16string UTF8ToUTF16(const std::string& input); 73std::u16string UTF8ToUTF16(const std::string& input);
74 74
75std::string CP1252ToUTF8(const std::string& str);
76std::string SHIFTJISToUTF8(const std::string& str);
77
78#ifdef _WIN32 75#ifdef _WIN32
79std::string UTF16ToUTF8(const std::wstring& input); 76std::string UTF16ToUTF8(const std::wstring& input);
80std::wstring UTF8ToUTF16W(const std::string& str); 77std::wstring UTF8ToUTF16W(const std::string& str);
81 78
82#ifdef _UNICODE
83inline std::string TStrToUTF8(const std::wstring& str) {
84 return UTF16ToUTF8(str);
85}
86
87inline std::wstring UTF8ToTStr(const std::string& str) {
88 return UTF8ToUTF16W(str);
89}
90#else
91inline std::string TStrToUTF8(const std::string& str) {
92 return str;
93}
94
95inline std::string UTF8ToTStr(const std::string& str) {
96 return str;
97}
98#endif
99
100#endif 79#endif
101 80
102/** 81/**
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 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <string>
8
9namespace Common {
10struct WebResult {
11 enum class Code : u32 {
12 Success,
13 InvalidURL,
14 CredentialsMissing,
15 LibError,
16 HttpError,
17 WrongContent,
18 NoWebservice,
19 };
20 Code result_code;
21 std::string result_string;
22 std::string returned_data;
23};
24} // 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
34 file_sys/errors.h 34 file_sys/errors.h
35 file_sys/fsmitm_romfsbuild.cpp 35 file_sys/fsmitm_romfsbuild.cpp
36 file_sys/fsmitm_romfsbuild.h 36 file_sys/fsmitm_romfsbuild.h
37 file_sys/ips_layer.cpp
38 file_sys/ips_layer.h
37 file_sys/mode.h 39 file_sys/mode.h
38 file_sys/nca_metadata.cpp 40 file_sys/nca_metadata.cpp
39 file_sys/nca_metadata.h 41 file_sys/nca_metadata.h
@@ -394,6 +396,10 @@ create_target_directory_groups(core)
394 396
395target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 397target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
396target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives) 398target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
399if (ENABLE_WEB_SERVICE)
400 add_definitions(-DENABLE_WEB_SERVICE)
401 target_link_libraries(core PUBLIC json-headers web_service)
402endif()
397 403
398if (ARCHITECTURE_x86_64) 404if (ARCHITECTURE_x86_64)
399 target_sources(core PRIVATE 405 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:
86 parent.jit->HaltExecution(); 86 parent.jit->HaltExecution();
87 parent.SetPC(pc); 87 parent.SetPC(pc);
88 Kernel::Thread* thread = Kernel::GetCurrentThread(); 88 Kernel::Thread* thread = Kernel::GetCurrentThread();
89 parent.SaveContext(thread->context); 89 parent.SaveContext(thread->GetContext());
90 GDBStub::Break(); 90 GDBStub::Break();
91 GDBStub::SendTrap(thread, 5); 91 GDBStub::SendTrap(thread, 5);
92 return; 92 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) {
195 uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address); 195 uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
196 } 196 }
197 Kernel::Thread* thread = Kernel::GetCurrentThread(); 197 Kernel::Thread* thread = Kernel::GetCurrentThread();
198 SaveContext(thread->context); 198 SaveContext(thread->GetContext());
199 if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) { 199 if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) {
200 last_bkpt_hit = false; 200 last_bkpt_hit = false;
201 GDBStub::Break(); 201 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 {
20 20
21constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"}; 21constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"};
22 22
23XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { 23XCI::XCI(VirtualFile file_)
24 : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
25 partitions(0x4) {
24 if (file->ReadObject(&header) != sizeof(GamecardHeader)) { 26 if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
25 status = Loader::ResultStatus::ErrorBadXCIHeader; 27 status = Loader::ResultStatus::ErrorBadXCIHeader;
26 return; 28 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 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/assert.h"
6#include "common/swap.h"
7#include "core/file_sys/ips_layer.h"
8#include "core/file_sys/vfs_vector.h"
9
10namespace FileSys {
11
12enum class IPSFileType {
13 IPS,
14 IPS32,
15 Error,
16};
17
18static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
19 if (magic.size() != 5)
20 return IPSFileType::Error;
21 if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'})
22 return IPSFileType::IPS;
23 if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'})
24 return IPSFileType::IPS32;
25 return IPSFileType::Error;
26}
27
28VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
29 if (in == nullptr || ips == nullptr)
30 return nullptr;
31
32 const auto type = IdentifyMagic(ips->ReadBytes(0x5));
33 if (type == IPSFileType::Error)
34 return nullptr;
35
36 auto in_data = in->ReadAllBytes();
37
38 std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
39 u64 offset = 5; // After header
40 while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
41 offset += temp.size();
42 if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} ||
43 type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) {
44 break;
45 }
46
47 u32 real_offset{};
48 if (type == IPSFileType::IPS32)
49 real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
50 else
51 real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
52
53 u16 data_size{};
54 if (ips->ReadObject(&data_size, offset) != sizeof(u16))
55 return nullptr;
56 data_size = Common::swap16(data_size);
57 offset += sizeof(u16);
58
59 if (data_size == 0) { // RLE
60 u16 rle_size{};
61 if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
62 return nullptr;
63 rle_size = Common::swap16(data_size);
64 offset += sizeof(u16);
65
66 const auto data = ips->ReadByte(offset++);
67 if (data == boost::none)
68 return nullptr;
69
70 if (real_offset + rle_size > in_data.size())
71 rle_size = in_data.size() - real_offset;
72 std::memset(in_data.data() + real_offset, data.get(), rle_size);
73 } else { // Standard Patch
74 auto read = data_size;
75 if (real_offset + read > in_data.size())
76 read = in_data.size() - real_offset;
77 if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
78 return nullptr;
79 offset += data_size;
80 }
81 }
82
83 if (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'})
84 return nullptr;
85 return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
86}
87
88} // 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 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8
9#include "core/file_sys/vfs.h"
10
11namespace FileSys {
12
13VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips);
14
15} // 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 @@
5#include <algorithm> 5#include <algorithm>
6#include <array> 6#include <array>
7#include <cstddef> 7#include <cstddef>
8#include <cstring>
8 9
10#include "common/hex_util.h"
9#include "common/logging/log.h" 11#include "common/logging/log.h"
10#include "core/file_sys/content_archive.h" 12#include "core/file_sys/content_archive.h"
11#include "core/file_sys/control_metadata.h" 13#include "core/file_sys/control_metadata.h"
14#include "core/file_sys/ips_layer.h"
12#include "core/file_sys/patch_manager.h" 15#include "core/file_sys/patch_manager.h"
13#include "core/file_sys/registered_cache.h" 16#include "core/file_sys/registered_cache.h"
14#include "core/file_sys/romfs.h" 17#include "core/file_sys/romfs.h"
15#include "core/file_sys/vfs_layered.h" 18#include "core/file_sys/vfs_layered.h"
19#include "core/file_sys/vfs_vector.h"
16#include "core/hle/service/filesystem/filesystem.h" 20#include "core/hle/service/filesystem/filesystem.h"
17#include "core/loader/loader.h" 21#include "core/loader/loader.h"
18 22
@@ -21,6 +25,14 @@ namespace FileSys {
21constexpr u64 SINGLE_BYTE_MODULUS = 0x100; 25constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
22constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; 26constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
23 27
28struct NSOBuildHeader {
29 u32_le magic;
30 INSERT_PADDING_BYTES(0x3C);
31 std::array<u8, 0x20> build_id;
32 INSERT_PADDING_BYTES(0xA0);
33};
34static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
35
24std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { 36std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
25 std::array<u8, sizeof(u32)> bytes{}; 37 std::array<u8, sizeof(u32)> bytes{};
26 bytes[0] = version % SINGLE_BYTE_MODULUS; 38 bytes[0] = version % SINGLE_BYTE_MODULUS;
@@ -34,16 +46,6 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
34 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); 46 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
35} 47}
36 48
37constexpr std::array<const char*, 3> PATCH_TYPE_NAMES{
38 "Update",
39 "LayeredFS",
40 "DLC",
41};
42
43std::string FormatPatchTypeName(PatchType type) {
44 return PATCH_TYPE_NAMES.at(static_cast<std::size_t>(type));
45}
46
47PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} 49PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
48 50
49PatchManager::~PatchManager() = default; 51PatchManager::~PatchManager() = default;
@@ -71,6 +73,79 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
71 return exefs; 73 return exefs;
72} 74}
73 75
76static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs,
77 const std::string& build_id) {
78 std::vector<VirtualFile> ips;
79 ips.reserve(patch_dirs.size());
80 for (const auto& subdir : patch_dirs) {
81 auto exefs_dir = subdir->GetSubdirectory("exefs");
82 if (exefs_dir != nullptr) {
83 for (const auto& file : exefs_dir->GetFiles()) {
84 if (file->GetExtension() != "ips")
85 continue;
86 auto name = file->GetName();
87 const auto p1 = name.substr(0, name.find('.'));
88 const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
89
90 if (build_id == this_build_id)
91 ips.push_back(file);
92 }
93 }
94 }
95
96 return ips;
97}
98
99std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
100 if (nso.size() < 0x100)
101 return nso;
102
103 NSOBuildHeader header;
104 std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
105
106 if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
107 return nso;
108
109 const auto build_id_raw = Common::HexArrayToString(header.build_id);
110 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
111
112 LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
113
114 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
115 auto patch_dirs = load_dir->GetSubdirectories();
116 std::sort(patch_dirs.begin(), patch_dirs.end(),
117 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
118 const auto ips = CollectIPSPatches(patch_dirs, build_id);
119
120 auto out = nso;
121 for (const auto& ips_file : ips) {
122 LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"",
123 ips_file->GetContainingDirectory()->GetParentDirectory()->GetName());
124 const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file);
125 if (patched != nullptr)
126 out = patched->ReadAllBytes();
127 }
128
129 if (out.size() < 0x100)
130 return nso;
131 std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
132 return out;
133}
134
135bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
136 const auto build_id_raw = Common::HexArrayToString(build_id_);
137 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
138
139 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
140
141 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
142 auto patch_dirs = load_dir->GetSubdirectories();
143 std::sort(patch_dirs.begin(), patch_dirs.end(),
144 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
145
146 return !CollectIPSPatches(patch_dirs, build_id).empty();
147}
148
74static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { 149static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
75 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 150 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
76 if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) { 151 if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
@@ -138,8 +213,19 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
138 return romfs; 213 return romfs;
139} 214}
140 215
141std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const { 216static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
142 std::map<PatchType, std::string> out; 217 if (to.empty())
218 to += with;
219 else
220 to += ", " + with;
221}
222
223static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
224 return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
225}
226
227std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const {
228 std::map<std::string, std::string, std::less<>> out;
143 const auto installed = Service::FileSystem::GetUnionContents(); 229 const auto installed = Service::FileSystem::GetUnionContents();
144 230
145 // Game Updates 231 // Game Updates
@@ -148,23 +234,36 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
148 auto [nacp, discard_icon_file] = update.GetControlMetadata(); 234 auto [nacp, discard_icon_file] = update.GetControlMetadata();
149 235
150 if (nacp != nullptr) { 236 if (nacp != nullptr) {
151 out[PatchType::Update] = nacp->GetVersionString(); 237 out.insert_or_assign("Update", nacp->GetVersionString());
152 } else { 238 } else {
153 if (installed->HasEntry(update_tid, ContentRecordType::Program)) { 239 if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
154 const auto meta_ver = installed->GetEntryVersion(update_tid); 240 const auto meta_ver = installed->GetEntryVersion(update_tid);
155 if (meta_ver == boost::none || meta_ver.get() == 0) { 241 if (meta_ver == boost::none || meta_ver.get() == 0) {
156 out[PatchType::Update] = ""; 242 out.insert_or_assign("Update", "");
157 } else { 243 } else {
158 out[PatchType::Update] = 244 out.insert_or_assign(
159 FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements); 245 "Update",
246 FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
160 } 247 }
161 } 248 }
162 } 249 }
163 250
164 // LayeredFS 251 // General Mods (LayeredFS and IPS)
165 const auto lfs_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 252 const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
166 if (lfs_dir != nullptr && lfs_dir->GetSize() > 0) 253 if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
167 out.insert_or_assign(PatchType::LayeredFS, ""); 254 for (const auto& mod : mod_dir->GetSubdirectories()) {
255 std::string types;
256 if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs")))
257 AppendCommaIfNotEmpty(types, "IPS");
258 if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
259 AppendCommaIfNotEmpty(types, "LayeredFS");
260
261 if (types.empty())
262 continue;
263
264 out.insert_or_assign(mod->GetName(), types);
265 }
266 }
168 267
169 // DLC 268 // DLC
170 const auto dlc_entries = installed->ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); 269 const auto dlc_entries = installed->ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
@@ -186,7 +285,7 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
186 285
187 list += fmt::format("{}", dlc_match.back().title_id & 0x7FF); 286 list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
188 287
189 out.insert_or_assign(PatchType::DLC, std::move(list)); 288 out.insert_or_assign("DLC", std::move(list));
190 } 289 }
191 290
192 return out; 291 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 {
24std::string FormatTitleVersion(u32 version, 24std::string FormatTitleVersion(u32 version,
25 TitleVersionFormat format = TitleVersionFormat::ThreeElements); 25 TitleVersionFormat format = TitleVersionFormat::ThreeElements);
26 26
27enum class PatchType {
28 Update,
29 LayeredFS,
30 DLC,
31};
32
33std::string FormatPatchTypeName(PatchType type);
34
35// A centralized class to manage patches to games. 27// A centralized class to manage patches to games.
36class PatchManager { 28class PatchManager {
37public: 29public:
@@ -42,6 +34,14 @@ public:
42 // - Game Updates 34 // - Game Updates
43 VirtualDir PatchExeFS(VirtualDir exefs) const; 35 VirtualDir PatchExeFS(VirtualDir exefs) const;
44 36
37 // Currently tracked NSO patches:
38 // - IPS
39 std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
40
41 // Checks to see if PatchNSO() will have any effect given the NSO's build ID.
42 // Used to prevent expensive copies in NSO loader.
43 bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
44
45 // Currently tracked RomFS patches: 45 // Currently tracked RomFS patches:
46 // - Game Updates 46 // - Game Updates
47 // - LayeredFS 47 // - LayeredFS
@@ -49,8 +49,8 @@ public:
49 ContentRecordType type = ContentRecordType::Program) const; 49 ContentRecordType type = ContentRecordType::Program) const;
50 50
51 // Returns a vector of pairs between patch names and patch versions. 51 // Returns a vector of pairs between patch names and patch versions.
52 // i.e. Update v80 will return {Update, 80} 52 // i.e. Update 3.2.2 will return {"Update", "3.2.2"}
53 std::map<PatchType, std::string> GetPatchVersionNames() const; 53 std::map<std::string, std::string, std::less<>> GetPatchVersionNames() const;
54 54
55 // Given title_id of the program, attempts to get the control data of the update and parse it, 55 // Given title_id of the program, attempts to get the control data of the update and parse it,
56 // falling back to the base control data. 56 // 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 @@
18#include "core/loader/loader.h" 18#include "core/loader/loader.h"
19 19
20namespace FileSys { 20namespace FileSys {
21namespace {
22void SetTicketKeys(const std::vector<VirtualFile>& files) {
23 Core::Crypto::KeyManager keys;
24
25 for (const auto& ticket_file : files) {
26 if (ticket_file == nullptr) {
27 continue;
28 }
29
30 if (ticket_file->GetExtension() != "tik") {
31 continue;
32 }
33
34 if (ticket_file->GetSize() <
35 Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
36 continue;
37 }
38
39 Core::Crypto::Key128 key{};
40 ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
41
42 // We get the name without the extension in order to create the rights ID.
43 std::string name_only(ticket_file->GetName());
44 name_only.erase(name_only.size() - 4);
45
46 const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
47 u128 rights_id;
48 std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
49 keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
50 }
51}
52} // Anonymous namespace
53
21NSP::NSP(VirtualFile file_) 54NSP::NSP(VirtualFile file_)
22 : file(std::move(file_)), status{Loader::ResultStatus::Success}, 55 : file(std::move(file_)), status{Loader::ResultStatus::Success},
23 pfs(std::make_shared<PartitionFilesystem>(file)) { 56 pfs(std::make_shared<PartitionFilesystem>(file)) {
@@ -26,83 +59,16 @@ NSP::NSP(VirtualFile file_)
26 return; 59 return;
27 } 60 }
28 61
62 const auto files = pfs->GetFiles();
63
29 if (IsDirectoryExeFS(pfs)) { 64 if (IsDirectoryExeFS(pfs)) {
30 extracted = true; 65 extracted = true;
31 exefs = pfs; 66 InitializeExeFSAndRomFS(files);
32
33 const auto& files = pfs->GetFiles();
34 const auto romfs_iter =
35 std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
36 return file->GetName().find(".romfs") != std::string::npos;
37 });
38 if (romfs_iter != files.end())
39 romfs = *romfs_iter;
40 return; 67 return;
41 } 68 }
42 69
43 extracted = false; 70 SetTicketKeys(files);
44 const auto files = pfs->GetFiles(); 71 ReadNCAs(files);
45
46 Core::Crypto::KeyManager keys;
47 for (const auto& ticket_file : files) {
48 if (ticket_file->GetExtension() == "tik") {
49 if (ticket_file == nullptr ||
50 ticket_file->GetSize() <
51 Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
52 continue;
53 }
54
55 Core::Crypto::Key128 key{};
56 ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
57 std::string_view name_only(ticket_file->GetName());
58 name_only.remove_suffix(4);
59 const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
60 u128 rights_id;
61 std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
62 keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
63 }
64 }
65
66 for (const auto& outer_file : files) {
67 if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") {
68 const auto nca = std::make_shared<NCA>(outer_file);
69 if (nca->GetStatus() != Loader::ResultStatus::Success) {
70 program_status[nca->GetTitleId()] = nca->GetStatus();
71 continue;
72 }
73
74 const auto section0 = nca->GetSubdirectories()[0];
75
76 for (const auto& inner_file : section0->GetFiles()) {
77 if (inner_file->GetExtension() != "cnmt")
78 continue;
79
80 const CNMT cnmt(inner_file);
81 auto& ncas_title = ncas[cnmt.GetTitleID()];
82
83 ncas_title[ContentRecordType::Meta] = nca;
84 for (const auto& rec : cnmt.GetContentRecords()) {
85 const auto id_string = Common::HexArrayToString(rec.nca_id, false);
86 const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
87 if (next_file == nullptr) {
88 LOG_WARNING(Service_FS,
89 "NCA with ID {}.nca is listed in content metadata, but cannot "
90 "be found in PFS. NSP appears to be corrupted.",
91 id_string);
92 continue;
93 }
94
95 auto next_nca = std::make_shared<NCA>(next_file);
96 if (next_nca->GetType() == NCAContentType::Program)
97 program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
98 if (next_nca->GetStatus() == Loader::ResultStatus::Success)
99 ncas_title[rec.type] = std::move(next_nca);
100 }
101
102 break;
103 }
104 }
105 }
106} 72}
107 73
108NSP::~NSP() = default; 74NSP::~NSP() = default;
@@ -242,4 +208,63 @@ VirtualDir NSP::GetParentDirectory() const {
242bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { 208bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
243 return false; 209 return false;
244} 210}
211
212void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
213 exefs = pfs;
214
215 const auto romfs_iter = std::find_if(files.begin(), files.end(), [](const VirtualFile& file) {
216 return file->GetName().rfind(".romfs") != std::string::npos;
217 });
218
219 if (romfs_iter == files.end()) {
220 return;
221 }
222
223 romfs = *romfs_iter;
224}
225
226void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
227 for (const auto& outer_file : files) {
228 if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
229 continue;
230 }
231
232 const auto nca = std::make_shared<NCA>(outer_file);
233 if (nca->GetStatus() != Loader::ResultStatus::Success) {
234 program_status[nca->GetTitleId()] = nca->GetStatus();
235 continue;
236 }
237
238 const auto section0 = nca->GetSubdirectories()[0];
239
240 for (const auto& inner_file : section0->GetFiles()) {
241 if (inner_file->GetExtension() != "cnmt")
242 continue;
243
244 const CNMT cnmt(inner_file);
245 auto& ncas_title = ncas[cnmt.GetTitleID()];
246
247 ncas_title[ContentRecordType::Meta] = nca;
248 for (const auto& rec : cnmt.GetContentRecords()) {
249 const auto id_string = Common::HexArrayToString(rec.nca_id, false);
250 const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
251 if (next_file == nullptr) {
252 LOG_WARNING(Service_FS,
253 "NCA with ID {}.nca is listed in content metadata, but cannot "
254 "be found in PFS. NSP appears to be corrupted.",
255 id_string);
256 continue;
257 }
258
259 auto next_nca = std::make_shared<NCA>(next_file);
260 if (next_nca->GetType() == NCAContentType::Program)
261 program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
262 if (next_nca->GetStatus() == Loader::ResultStatus::Success)
263 ncas_title[rec.type] = std::move(next_nca);
264 }
265
266 break;
267 }
268 }
269}
245} // namespace FileSys 270} // 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:
59 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; 59 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
60 60
61private: 61private:
62 void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
63 void ReadNCAs(const std::vector<VirtualFile>& files);
64
62 VirtualFile file; 65 VirtualFile file;
63 66
64 bool extracted; 67 bool extracted = false;
65 Loader::ResultStatus status; 68 Loader::ResultStatus status;
66 std::map<u64, Loader::ResultStatus> program_status; 69 std::map<u64, Loader::ResultStatus> program_status;
67 70
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) {
209 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { 209 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
210 const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList(); 210 const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
211 for (auto& thread : threads) { 211 for (auto& thread : threads) {
212 if (thread->GetThreadId() == static_cast<u32>(id)) { 212 if (thread->GetThreadID() == static_cast<u32>(id)) {
213 current_core = core; 213 current_core = core;
214 return thread.get(); 214 return thread.get();
215 } 215 }
@@ -223,16 +223,18 @@ static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) {
223 return 0; 223 return 0;
224 } 224 }
225 225
226 const auto& thread_context = thread->GetContext();
227
226 if (id < SP_REGISTER) { 228 if (id < SP_REGISTER) {
227 return thread->context.cpu_registers[id]; 229 return thread_context.cpu_registers[id];
228 } else if (id == SP_REGISTER) { 230 } else if (id == SP_REGISTER) {
229 return thread->context.sp; 231 return thread_context.sp;
230 } else if (id == PC_REGISTER) { 232 } else if (id == PC_REGISTER) {
231 return thread->context.pc; 233 return thread_context.pc;
232 } else if (id == PSTATE_REGISTER) { 234 } else if (id == PSTATE_REGISTER) {
233 return thread->context.pstate; 235 return thread_context.pstate;
234 } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { 236 } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
235 return thread->context.vector_registers[id - UC_ARM64_REG_Q0][0]; 237 return thread_context.vector_registers[id - UC_ARM64_REG_Q0][0];
236 } else { 238 } else {
237 return 0; 239 return 0;
238 } 240 }
@@ -243,16 +245,18 @@ static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr)
243 return; 245 return;
244 } 246 }
245 247
248 auto& thread_context = thread->GetContext();
249
246 if (id < SP_REGISTER) { 250 if (id < SP_REGISTER) {
247 thread->context.cpu_registers[id] = val; 251 thread_context.cpu_registers[id] = val;
248 } else if (id == SP_REGISTER) { 252 } else if (id == SP_REGISTER) {
249 thread->context.sp = val; 253 thread_context.sp = val;
250 } else if (id == PC_REGISTER) { 254 } else if (id == PC_REGISTER) {
251 thread->context.pc = val; 255 thread_context.pc = val;
252 } else if (id == PSTATE_REGISTER) { 256 } else if (id == PSTATE_REGISTER) {
253 thread->context.pstate = static_cast<u32>(val); 257 thread_context.pstate = static_cast<u32>(val);
254 } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { 258 } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
255 thread->context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val; 259 thread_context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
256 } 260 }
257} 261}
258 262
@@ -595,7 +599,7 @@ static void HandleQuery() {
595 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { 599 for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
596 const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList(); 600 const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
597 for (const auto& thread : threads) { 601 for (const auto& thread : threads) {
598 val += fmt::format("{:x}", thread->GetThreadId()); 602 val += fmt::format("{:x}", thread->GetThreadID());
599 val += ","; 603 val += ",";
600 } 604 }
601 } 605 }
@@ -612,7 +616,7 @@ static void HandleQuery() {
612 for (const auto& thread : threads) { 616 for (const auto& thread : threads) {
613 buffer += 617 buffer +=
614 fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*", 618 fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
615 thread->GetThreadId(), core, thread->GetThreadId()); 619 thread->GetThreadID(), core, thread->GetThreadID());
616 } 620 }
617 } 621 }
618 buffer += "</threads>"; 622 buffer += "</threads>";
@@ -693,7 +697,7 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
693 } 697 }
694 698
695 if (thread) { 699 if (thread) {
696 buffer += fmt::format(";thread:{:x};", thread->GetThreadId()); 700 buffer += fmt::format(";thread:{:x};", thread->GetThreadID());
697 } 701 }
698 702
699 SendReply(buffer.c_str()); 703 SendReply(buffer.c_str());
@@ -857,7 +861,9 @@ static void WriteRegister() {
857 } 861 }
858 862
859 // Update Unicorn context skipping scheduler, no running threads at this point 863 // Update Unicorn context skipping scheduler, no running threads at this point
860 Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context); 864 Core::System::GetInstance()
865 .ArmInterface(current_core)
866 .LoadContext(current_thread->GetContext());
861 867
862 SendReply("OK"); 868 SendReply("OK");
863} 869}
@@ -886,7 +892,9 @@ static void WriteRegisters() {
886 } 892 }
887 893
888 // Update Unicorn context skipping scheduler, no running threads at this point 894 // Update Unicorn context skipping scheduler, no running threads at this point
889 Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context); 895 Core::System::GetInstance()
896 .ArmInterface(current_core)
897 .LoadContext(current_thread->GetContext());
890 898
891 SendReply("OK"); 899 SendReply("OK");
892} 900}
@@ -960,7 +968,9 @@ static void Step() {
960 if (command_length > 1) { 968 if (command_length > 1) {
961 RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread); 969 RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread);
962 // Update Unicorn context skipping scheduler, no running threads at this point 970 // Update Unicorn context skipping scheduler, no running threads at this point
963 Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context); 971 Core::System::GetInstance()
972 .ArmInterface(current_core)
973 .LoadContext(current_thread->GetContext());
964 } 974 }
965 step_loop = true; 975 step_loop = true;
966 halt_loop = true; 976 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 {
23// Performs actual address waiting logic. 23// Performs actual address waiting logic.
24static ResultCode WaitForAddress(VAddr address, s64 timeout) { 24static ResultCode WaitForAddress(VAddr address, s64 timeout) {
25 SharedPtr<Thread> current_thread = GetCurrentThread(); 25 SharedPtr<Thread> current_thread = GetCurrentThread();
26 current_thread->arb_wait_address = address; 26 current_thread->SetArbiterWaitAddress(address);
27 current_thread->status = ThreadStatus::WaitArb; 27 current_thread->SetStatus(ThreadStatus::WaitArb);
28 current_thread->wakeup_callback = nullptr; 28 current_thread->InvalidateWakeupCallback();
29 29
30 current_thread->WakeAfterDelay(timeout); 30 current_thread->WakeAfterDelay(timeout);
31 31
32 Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule(); 32 Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
33 return RESULT_TIMEOUT; 33 return RESULT_TIMEOUT;
34} 34}
35 35
@@ -39,10 +39,10 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
39 std::vector<SharedPtr<Thread>>& waiting_threads, 39 std::vector<SharedPtr<Thread>>& waiting_threads,
40 VAddr arb_addr) { 40 VAddr arb_addr) {
41 const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); 41 const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
42 auto& thread_list = scheduler->GetThreadList(); 42 const auto& thread_list = scheduler->GetThreadList();
43 43
44 for (auto& thread : thread_list) { 44 for (const auto& thread : thread_list) {
45 if (thread->arb_wait_address == arb_addr) 45 if (thread->GetArbiterWaitAddress() == arb_addr)
46 waiting_threads.push_back(thread); 46 waiting_threads.push_back(thread);
47 } 47 }
48 }; 48 };
@@ -57,7 +57,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
57 // Sort them by priority, such that the highest priority ones come first. 57 // Sort them by priority, such that the highest priority ones come first.
58 std::sort(threads.begin(), threads.end(), 58 std::sort(threads.begin(), threads.end(),
59 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) { 59 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
60 return lhs->current_priority < rhs->current_priority; 60 return lhs->GetPriority() < rhs->GetPriority();
61 }); 61 });
62 62
63 return threads; 63 return threads;
@@ -73,9 +73,9 @@ static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num
73 73
74 // Signal the waiting threads. 74 // Signal the waiting threads.
75 for (std::size_t i = 0; i < last; i++) { 75 for (std::size_t i = 0; i < last; i++) {
76 ASSERT(waiting_threads[i]->status == ThreadStatus::WaitArb); 76 ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
77 waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS); 77 waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
78 waiting_threads[i]->arb_wait_address = 0; 78 waiting_threads[i]->SetArbiterWaitAddress(0);
79 waiting_threads[i]->ResumeFromWait(); 79 waiting_threads[i]->ResumeFromWait();
80 } 80 }
81} 81}
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 {
17ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {} 17ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {}
18ClientPort::~ClientPort() = default; 18ClientPort::~ClientPort() = default;
19 19
20SharedPtr<ServerPort> ClientPort::GetServerPort() const {
21 return server_port;
22}
23
20ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { 24ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
21 // Note: Threads do not wait for the server endpoint to call 25 // Note: Threads do not wait for the server endpoint to call
22 // AcceptSession before returning from this call. 26 // 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:
30 return HANDLE_TYPE; 30 return HANDLE_TYPE;
31 } 31 }
32 32
33 SharedPtr<ServerPort> GetServerPort() const;
34
33 /** 35 /**
34 * Creates a new Session pair, adds the created ServerSession to the associated ServerPort's 36 * Creates a new Session pair, adds the created ServerSession to the associated ServerPort's
35 * list of pending sessions, and signals the ServerPort, causing any threads 37 * 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,
42 Kernel::SharedPtr<Kernel::Event> event) { 42 Kernel::SharedPtr<Kernel::Event> event) {
43 43
44 // Put the client thread to sleep until the wait event is signaled or the timeout expires. 44 // Put the client thread to sleep until the wait event is signaled or the timeout expires.
45 thread->wakeup_callback = [context = *this, callback]( 45 thread->SetWakeupCallback([context = *this, callback](
46 ThreadWakeupReason reason, SharedPtr<Thread> thread, 46 ThreadWakeupReason reason, SharedPtr<Thread> thread,
47 SharedPtr<WaitObject> object, std::size_t index) mutable -> bool { 47 SharedPtr<WaitObject> object, std::size_t index) mutable -> bool {
48 ASSERT(thread->status == ThreadStatus::WaitHLEEvent); 48 ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent);
49 callback(thread, context, reason); 49 callback(thread, context, reason);
50 context.WriteToOutgoingCommandBuffer(*thread); 50 context.WriteToOutgoingCommandBuffer(*thread);
51 return true; 51 return true;
52 }; 52 });
53 53
54 if (!event) { 54 if (!event) {
55 // Create event if not provided 55 // Create event if not provided
@@ -59,8 +59,8 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
59 } 59 }
60 60
61 event->Clear(); 61 event->Clear();
62 thread->status = ThreadStatus::WaitHLEEvent; 62 thread->SetStatus(ThreadStatus::WaitHLEEvent);
63 thread->wait_objects = {event}; 63 thread->SetWaitObjects({event});
64 event->AddWaitingThread(thread); 64 event->AddWaitingThread(thread);
65 65
66 if (timeout > 0) { 66 if (timeout > 0) {
@@ -209,7 +209,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
209 209
210ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) { 210ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) {
211 std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf; 211 std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
212 Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), 212 Memory::ReadBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
213 dst_cmdbuf.size() * sizeof(u32)); 213 dst_cmdbuf.size() * sizeof(u32));
214 214
215 // The header was already built in the internal command buffer. Attempt to parse it to verify 215 // 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)
268 } 268 }
269 269
270 // Copy the translated command buffer back into the thread's command buffer area. 270 // Copy the translated command buffer back into the thread's command buffer area.
271 Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), 271 Memory::WriteBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
272 dst_cmdbuf.size() * sizeof(u32)); 272 dst_cmdbuf.size() * sizeof(u32));
273 273
274 return RESULT_SUCCESS; 274 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_
46 46
47 bool resume = true; 47 bool resume = true;
48 48
49 if (thread->status == ThreadStatus::WaitSynchAny || 49 if (thread->GetStatus() == ThreadStatus::WaitSynchAny ||
50 thread->status == ThreadStatus::WaitSynchAll || 50 thread->GetStatus() == ThreadStatus::WaitSynchAll ||
51 thread->status == ThreadStatus::WaitHLEEvent) { 51 thread->GetStatus() == ThreadStatus::WaitHLEEvent) {
52 // Remove the thread from each of its waiting objects' waitlists 52 // Remove the thread from each of its waiting objects' waitlists
53 for (auto& object : thread->wait_objects) { 53 for (const auto& object : thread->GetWaitObjects()) {
54 object->RemoveWaitingThread(thread.get()); 54 object->RemoveWaitingThread(thread.get());
55 } 55 }
56 thread->wait_objects.clear(); 56 thread->ClearWaitObjects();
57 57
58 // Invoke the wakeup callback before clearing the wait objects 58 // Invoke the wakeup callback before clearing the wait objects
59 if (thread->wakeup_callback) { 59 if (thread->HasWakeupCallback()) {
60 resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0); 60 resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
61 } 61 }
62 } 62 }
63 63
64 if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 || 64 if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
65 thread->wait_handle) { 65 thread->GetWaitHandle() != 0) {
66 ASSERT(thread->status == ThreadStatus::WaitMutex); 66 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
67 thread->mutex_wait_address = 0; 67 thread->SetMutexWaitAddress(0);
68 thread->condvar_wait_address = 0; 68 thread->SetCondVarWaitAddress(0);
69 thread->wait_handle = 0; 69 thread->SetWaitHandle(0);
70 70
71 auto lock_owner = thread->lock_owner; 71 auto* const lock_owner = thread->GetLockOwner();
72 // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance 72 // Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
73 // and don't have a lock owner unless SignalProcessWideKey was called first and the thread 73 // and don't have a lock owner unless SignalProcessWideKey was called first and the thread
74 // wasn't awakened due to the mutex already being acquired. 74 // wasn't awakened due to the mutex already being acquired.
75 if (lock_owner) { 75 if (lock_owner != nullptr) {
76 lock_owner->RemoveMutexWaiter(thread); 76 lock_owner->RemoveMutexWaiter(thread);
77 } 77 }
78 } 78 }
79 79
80 if (thread->arb_wait_address != 0) { 80 if (thread->GetArbiterWaitAddress() != 0) {
81 ASSERT(thread->status == ThreadStatus::WaitArb); 81 ASSERT(thread->GetStatus() == ThreadStatus::WaitArb);
82 thread->arb_wait_address = 0; 82 thread->SetArbiterWaitAddress(0);
83 } 83 }
84 84
85 if (resume) { 85 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(
28 SharedPtr<Thread> highest_priority_thread; 28 SharedPtr<Thread> highest_priority_thread;
29 u32 num_waiters = 0; 29 u32 num_waiters = 0;
30 30
31 for (auto& thread : current_thread->wait_mutex_threads) { 31 for (const auto& thread : current_thread->GetMutexWaitingThreads()) {
32 if (thread->mutex_wait_address != mutex_addr) 32 if (thread->GetMutexWaitAddress() != mutex_addr)
33 continue; 33 continue;
34 34
35 ASSERT(thread->status == ThreadStatus::WaitMutex); 35 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
36 36
37 ++num_waiters; 37 ++num_waiters;
38 if (highest_priority_thread == nullptr || 38 if (highest_priority_thread == nullptr ||
@@ -47,12 +47,12 @@ static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
47/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner. 47/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
48static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread, 48static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
49 SharedPtr<Thread> new_owner) { 49 SharedPtr<Thread> new_owner) {
50 auto threads = current_thread->wait_mutex_threads; 50 const auto threads = current_thread->GetMutexWaitingThreads();
51 for (auto& thread : threads) { 51 for (const auto& thread : threads) {
52 if (thread->mutex_wait_address != mutex_addr) 52 if (thread->GetMutexWaitAddress() != mutex_addr)
53 continue; 53 continue;
54 54
55 ASSERT(thread->lock_owner == current_thread); 55 ASSERT(thread->GetLockOwner() == current_thread);
56 current_thread->RemoveMutexWaiter(thread); 56 current_thread->RemoveMutexWaiter(thread);
57 if (new_owner != thread) 57 if (new_owner != thread)
58 new_owner->AddMutexWaiter(thread); 58 new_owner->AddMutexWaiter(thread);
@@ -84,11 +84,11 @@ ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle ho
84 return ERR_INVALID_HANDLE; 84 return ERR_INVALID_HANDLE;
85 85
86 // Wait until the mutex is released 86 // Wait until the mutex is released
87 GetCurrentThread()->mutex_wait_address = address; 87 GetCurrentThread()->SetMutexWaitAddress(address);
88 GetCurrentThread()->wait_handle = requesting_thread_handle; 88 GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
89 89
90 GetCurrentThread()->status = ThreadStatus::WaitMutex; 90 GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
91 GetCurrentThread()->wakeup_callback = nullptr; 91 GetCurrentThread()->InvalidateWakeupCallback();
92 92
93 // Update the lock holder thread's priority to prevent priority inversion. 93 // Update the lock holder thread's priority to prevent priority inversion.
94 holding_thread->AddMutexWaiter(GetCurrentThread()); 94 holding_thread->AddMutexWaiter(GetCurrentThread());
@@ -115,7 +115,7 @@ ResultCode Mutex::Release(VAddr address) {
115 // Transfer the ownership of the mutex from the previous owner to the new one. 115 // Transfer the ownership of the mutex from the previous owner to the new one.
116 TransferMutexOwnership(address, GetCurrentThread(), thread); 116 TransferMutexOwnership(address, GetCurrentThread(), thread);
117 117
118 u32 mutex_value = thread->wait_handle; 118 u32 mutex_value = thread->GetWaitHandle();
119 119
120 if (num_waiters >= 2) { 120 if (num_waiters >= 2) {
121 // Notify the guest that there are still some threads waiting for the mutex 121 // Notify the guest that there are still some threads waiting for the mutex
@@ -125,13 +125,13 @@ ResultCode Mutex::Release(VAddr address) {
125 // Grant the mutex to the next waiting thread and resume it. 125 // Grant the mutex to the next waiting thread and resume it.
126 Memory::Write32(address, mutex_value); 126 Memory::Write32(address, mutex_value);
127 127
128 ASSERT(thread->status == ThreadStatus::WaitMutex); 128 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
129 thread->ResumeFromWait(); 129 thread->ResumeFromWait();
130 130
131 thread->lock_owner = nullptr; 131 thread->SetLockOwner(nullptr);
132 thread->condvar_wait_address = 0; 132 thread->SetCondVarWaitAddress(0);
133 thread->mutex_wait_address = 0; 133 thread->SetMutexWaitAddress(0);
134 thread->wait_handle = 0; 134 thread->SetWaitHandle(0);
135 135
136 return RESULT_SUCCESS; 136 return RESULT_SUCCESS;
137} 137}
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() {
144 144
145 const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) { 145 const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) {
146 for (auto& thread : thread_list) { 146 for (auto& thread : thread_list) {
147 if (thread->owner_process != this) 147 if (thread->GetOwnerProcess() != this)
148 continue; 148 continue;
149 149
150 if (thread == GetCurrentThread()) 150 if (thread == GetCurrentThread())
151 continue; 151 continue;
152 152
153 // TODO(Subv): When are the other running/ready threads terminated? 153 // TODO(Subv): When are the other running/ready threads terminated?
154 ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny || 154 ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitSynchAny ||
155 thread->status == ThreadStatus::WaitSynchAll, 155 thread->GetStatus() == ThreadStatus::WaitSynchAll,
156 "Exiting processes with non-waiting threads is currently unimplemented"); 156 "Exiting processes with non-waiting threads is currently unimplemented");
157 157
158 thread->Stop(); 158 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() {
38 Thread* next = nullptr; 38 Thread* next = nullptr;
39 Thread* thread = GetCurrentThread(); 39 Thread* thread = GetCurrentThread();
40 40
41 if (thread && thread->status == ThreadStatus::Running) { 41 if (thread && thread->GetStatus() == ThreadStatus::Running) {
42 // We have to do better than the current thread. 42 // We have to do better than the current thread.
43 // This call returns null when that's not possible. 43 // This call returns null when that's not possible.
44 next = ready_queue.pop_first_better(thread->current_priority); 44 next = ready_queue.pop_first_better(thread->GetPriority());
45 if (!next) { 45 if (!next) {
46 // Otherwise just keep going with the current thread 46 // Otherwise just keep going with the current thread
47 next = thread; 47 next = thread;
@@ -58,22 +58,21 @@ void Scheduler::SwitchContext(Thread* new_thread) {
58 58
59 // Save context for previous thread 59 // Save context for previous thread
60 if (previous_thread) { 60 if (previous_thread) {
61 previous_thread->last_running_ticks = CoreTiming::GetTicks(); 61 cpu_core.SaveContext(previous_thread->GetContext());
62 cpu_core.SaveContext(previous_thread->context);
63 // Save the TPIDR_EL0 system register in case it was modified. 62 // Save the TPIDR_EL0 system register in case it was modified.
64 previous_thread->tpidr_el0 = cpu_core.GetTPIDR_EL0(); 63 previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
65 64
66 if (previous_thread->status == ThreadStatus::Running) { 65 if (previous_thread->GetStatus() == ThreadStatus::Running) {
67 // This is only the case when a reschedule is triggered without the current thread 66 // This is only the case when a reschedule is triggered without the current thread
68 // yielding execution (i.e. an event triggered, system core time-sliced, etc) 67 // yielding execution (i.e. an event triggered, system core time-sliced, etc)
69 ready_queue.push_front(previous_thread->current_priority, previous_thread); 68 ready_queue.push_front(previous_thread->GetPriority(), previous_thread);
70 previous_thread->status = ThreadStatus::Ready; 69 previous_thread->SetStatus(ThreadStatus::Ready);
71 } 70 }
72 } 71 }
73 72
74 // Load context of new thread 73 // Load context of new thread
75 if (new_thread) { 74 if (new_thread) {
76 ASSERT_MSG(new_thread->status == ThreadStatus::Ready, 75 ASSERT_MSG(new_thread->GetStatus() == ThreadStatus::Ready,
77 "Thread must be ready to become running."); 76 "Thread must be ready to become running.");
78 77
79 // Cancel any outstanding wakeup events for this thread 78 // Cancel any outstanding wakeup events for this thread
@@ -83,15 +82,16 @@ void Scheduler::SwitchContext(Thread* new_thread) {
83 82
84 current_thread = new_thread; 83 current_thread = new_thread;
85 84
86 ready_queue.remove(new_thread->current_priority, new_thread); 85 ready_queue.remove(new_thread->GetPriority(), new_thread);
87 new_thread->status = ThreadStatus::Running; 86 new_thread->SetStatus(ThreadStatus::Running);
88 87
89 if (previous_process != current_thread->owner_process) { 88 const auto thread_owner_process = current_thread->GetOwnerProcess();
90 Core::CurrentProcess() = current_thread->owner_process; 89 if (previous_process != thread_owner_process) {
90 Core::CurrentProcess() = thread_owner_process;
91 SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table); 91 SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
92 } 92 }
93 93
94 cpu_core.LoadContext(new_thread->context); 94 cpu_core.LoadContext(new_thread->GetContext());
95 cpu_core.SetTlsAddress(new_thread->GetTLSAddress()); 95 cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
96 cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0()); 96 cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
97 cpu_core.ClearExclusiveState(); 97 cpu_core.ClearExclusiveState();
@@ -136,14 +136,14 @@ void Scheduler::RemoveThread(Thread* thread) {
136void Scheduler::ScheduleThread(Thread* thread, u32 priority) { 136void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
137 std::lock_guard<std::mutex> lock(scheduler_mutex); 137 std::lock_guard<std::mutex> lock(scheduler_mutex);
138 138
139 ASSERT(thread->status == ThreadStatus::Ready); 139 ASSERT(thread->GetStatus() == ThreadStatus::Ready);
140 ready_queue.push_back(priority, thread); 140 ready_queue.push_back(priority, thread);
141} 141}
142 142
143void Scheduler::UnscheduleThread(Thread* thread, u32 priority) { 143void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
144 std::lock_guard<std::mutex> lock(scheduler_mutex); 144 std::lock_guard<std::mutex> lock(scheduler_mutex);
145 145
146 ASSERT(thread->status == ThreadStatus::Ready); 146 ASSERT(thread->GetStatus() == ThreadStatus::Ready);
147 ready_queue.remove(priority, thread); 147 ready_queue.remove(priority, thread);
148} 148}
149 149
@@ -151,8 +151,8 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
151 std::lock_guard<std::mutex> lock(scheduler_mutex); 151 std::lock_guard<std::mutex> lock(scheduler_mutex);
152 152
153 // If thread was ready, adjust queues 153 // If thread was ready, adjust queues
154 if (thread->status == ThreadStatus::Ready) 154 if (thread->GetStatus() == ThreadStatus::Ready)
155 ready_queue.move(thread, thread->current_priority, priority); 155 ready_queue.move(thread, thread->GetPriority(), priority);
156 else 156 else
157 ready_queue.prepare(priority); 157 ready_queue.prepare(priority);
158} 158}
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 @@
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/hle/kernel/object.h" 12#include "core/hle/kernel/object.h"
13#include "core/hle/kernel/wait_object.h" 13#include "core/hle/kernel/wait_object.h"
14#include "core/hle/result.h"
14 15
15namespace Kernel { 16namespace Kernel {
16 17
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) {
120 result = hle_handler->HandleSyncRequest(context); 120 result = hle_handler->HandleSyncRequest(context);
121 } 121 }
122 122
123 if (thread->status == ThreadStatus::Running) { 123 if (thread->GetStatus() == ThreadStatus::Running) {
124 // Put the thread to sleep until the server replies, it will be awoken in 124 // Put the thread to sleep until the server replies, it will be awoken in
125 // svcReplyAndReceive for LLE servers. 125 // svcReplyAndReceive for LLE servers.
126 thread->status = ThreadStatus::WaitIPC; 126 thread->SetStatus(ThreadStatus::WaitIPC);
127 127
128 if (hle_handler != nullptr) { 128 if (hle_handler != nullptr) {
129 // For HLE services, we put the request threads to sleep for a short duration to 129 // 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) {
156 return ERR_INVALID_HANDLE; 156 return ERR_INVALID_HANDLE;
157 } 157 }
158 158
159 *thread_id = thread->GetThreadId(); 159 *thread_id = thread->GetThreadID();
160 return RESULT_SUCCESS; 160 return RESULT_SUCCESS;
161} 161}
162 162
@@ -177,7 +177,7 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
177/// Default thread wakeup callback for WaitSynchronization 177/// Default thread wakeup callback for WaitSynchronization
178static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread, 178static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
179 SharedPtr<WaitObject> object, std::size_t index) { 179 SharedPtr<WaitObject> object, std::size_t index) {
180 ASSERT(thread->status == ThreadStatus::WaitSynchAny); 180 ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
181 181
182 if (reason == ThreadWakeupReason::Timeout) { 182 if (reason == ThreadWakeupReason::Timeout) {
183 thread->SetWaitSynchronizationResult(RESULT_TIMEOUT); 183 thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
@@ -204,10 +204,10 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
204 if (handle_count > MaxHandles) 204 if (handle_count > MaxHandles)
205 return ResultCode(ErrorModule::Kernel, ErrCodes::TooLarge); 205 return ResultCode(ErrorModule::Kernel, ErrCodes::TooLarge);
206 206
207 auto thread = GetCurrentThread(); 207 auto* const thread = GetCurrentThread();
208 208
209 using ObjectPtr = SharedPtr<WaitObject>; 209 using ObjectPtr = Thread::ThreadWaitObjects::value_type;
210 std::vector<ObjectPtr> objects(handle_count); 210 Thread::ThreadWaitObjects objects(handle_count);
211 auto& kernel = Core::System::GetInstance().Kernel(); 211 auto& kernel = Core::System::GetInstance().Kernel();
212 212
213 for (u64 i = 0; i < handle_count; ++i) { 213 for (u64 i = 0; i < handle_count; ++i) {
@@ -244,14 +244,14 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
244 for (auto& object : objects) 244 for (auto& object : objects)
245 object->AddWaitingThread(thread); 245 object->AddWaitingThread(thread);
246 246
247 thread->wait_objects = std::move(objects); 247 thread->SetWaitObjects(std::move(objects));
248 thread->status = ThreadStatus::WaitSynchAny; 248 thread->SetStatus(ThreadStatus::WaitSynchAny);
249 249
250 // Create an event to wake the thread up after the specified nanosecond delay has passed 250 // Create an event to wake the thread up after the specified nanosecond delay has passed
251 thread->WakeAfterDelay(nano_seconds); 251 thread->WakeAfterDelay(nano_seconds);
252 thread->wakeup_callback = DefaultThreadWakeupCallback; 252 thread->SetWakeupCallback(DefaultThreadWakeupCallback);
253 253
254 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 254 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
255 255
256 return RESULT_TIMEOUT; 256 return RESULT_TIMEOUT;
257} 257}
@@ -266,7 +266,7 @@ static ResultCode CancelSynchronization(Handle thread_handle) {
266 return ERR_INVALID_HANDLE; 266 return ERR_INVALID_HANDLE;
267 } 267 }
268 268
269 ASSERT(thread->status == ThreadStatus::WaitSynchAny); 269 ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny);
270 thread->SetWaitSynchronizationResult( 270 thread->SetWaitSynchronizationResult(
271 ResultCode(ErrorModule::Kernel, ErrCodes::SynchronizationCanceled)); 271 ResultCode(ErrorModule::Kernel, ErrCodes::SynchronizationCanceled));
272 thread->ResumeFromWait(); 272 thread->ResumeFromWait();
@@ -425,7 +425,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
425 } 425 }
426 426
427 const auto current_process = Core::CurrentProcess(); 427 const auto current_process = Core::CurrentProcess();
428 if (thread->owner_process != current_process) { 428 if (thread->GetOwnerProcess() != current_process) {
429 return ERR_INVALID_HANDLE; 429 return ERR_INVALID_HANDLE;
430 } 430 }
431 431
@@ -433,7 +433,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
433 return ERR_ALREADY_REGISTERED; 433 return ERR_ALREADY_REGISTERED;
434 } 434 }
435 435
436 Core::ARM_Interface::ThreadContext ctx = thread->context; 436 Core::ARM_Interface::ThreadContext ctx = thread->GetContext();
437 // Mask away mode bits, interrupt bits, IL bit, and other reserved bits. 437 // Mask away mode bits, interrupt bits, IL bit, and other reserved bits.
438 ctx.pstate &= 0xFF0FFE20; 438 ctx.pstate &= 0xFF0FFE20;
439 439
@@ -479,14 +479,14 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
479 479
480 thread->SetPriority(priority); 480 thread->SetPriority(priority);
481 481
482 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 482 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
483 return RESULT_SUCCESS; 483 return RESULT_SUCCESS;
484} 484}
485 485
486/// Get which CPU core is executing the current thread 486/// Get which CPU core is executing the current thread
487static u32 GetCurrentProcessorNumber() { 487static u32 GetCurrentProcessorNumber() {
488 LOG_TRACE(Kernel_SVC, "called"); 488 LOG_TRACE(Kernel_SVC, "called");
489 return GetCurrentThread()->processor_id; 489 return GetCurrentThread()->GetProcessorID();
490} 490}
491 491
492static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size, 492static 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
622 CASCADE_RESULT(SharedPtr<Thread> thread, 622 CASCADE_RESULT(SharedPtr<Thread> thread,
623 Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top, 623 Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
624 Core::CurrentProcess())); 624 Core::CurrentProcess()));
625 CASCADE_RESULT(thread->guest_handle, kernel.HandleTable().Create(thread)); 625 const auto new_guest_handle = kernel.HandleTable().Create(thread);
626 *out_handle = thread->guest_handle; 626 if (new_guest_handle.Failed()) {
627 return new_guest_handle.Code();
628 }
629 thread->SetGuestHandle(*new_guest_handle);
630 *out_handle = *new_guest_handle;
627 631
628 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 632 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
629 633
630 LOG_TRACE(Kernel_SVC, 634 LOG_TRACE(Kernel_SVC,
631 "called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, " 635 "called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
@@ -645,10 +649,10 @@ static ResultCode StartThread(Handle thread_handle) {
645 return ERR_INVALID_HANDLE; 649 return ERR_INVALID_HANDLE;
646 } 650 }
647 651
648 ASSERT(thread->status == ThreadStatus::Dormant); 652 ASSERT(thread->GetStatus() == ThreadStatus::Dormant);
649 653
650 thread->ResumeFromWait(); 654 thread->ResumeFromWait();
651 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 655 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
652 656
653 return RESULT_SUCCESS; 657 return RESULT_SUCCESS;
654} 658}
@@ -694,17 +698,17 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
694 CASCADE_CODE(Mutex::Release(mutex_addr)); 698 CASCADE_CODE(Mutex::Release(mutex_addr));
695 699
696 SharedPtr<Thread> current_thread = GetCurrentThread(); 700 SharedPtr<Thread> current_thread = GetCurrentThread();
697 current_thread->condvar_wait_address = condition_variable_addr; 701 current_thread->SetCondVarWaitAddress(condition_variable_addr);
698 current_thread->mutex_wait_address = mutex_addr; 702 current_thread->SetMutexWaitAddress(mutex_addr);
699 current_thread->wait_handle = thread_handle; 703 current_thread->SetWaitHandle(thread_handle);
700 current_thread->status = ThreadStatus::WaitMutex; 704 current_thread->SetStatus(ThreadStatus::WaitMutex);
701 current_thread->wakeup_callback = nullptr; 705 current_thread->InvalidateWakeupCallback();
702 706
703 current_thread->WakeAfterDelay(nano_seconds); 707 current_thread->WakeAfterDelay(nano_seconds);
704 708
705 // Note: Deliberately don't attempt to inherit the lock owner's priority. 709 // Note: Deliberately don't attempt to inherit the lock owner's priority.
706 710
707 Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule(); 711 Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
708 return RESULT_SUCCESS; 712 return RESULT_SUCCESS;
709} 713}
710 714
@@ -713,14 +717,14 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
713 LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}", 717 LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}",
714 condition_variable_addr, target); 718 condition_variable_addr, target);
715 719
716 auto RetrieveWaitingThreads = [](std::size_t core_index, 720 const auto RetrieveWaitingThreads = [](std::size_t core_index,
717 std::vector<SharedPtr<Thread>>& waiting_threads, 721 std::vector<SharedPtr<Thread>>& waiting_threads,
718 VAddr condvar_addr) { 722 VAddr condvar_addr) {
719 const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); 723 const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
720 auto& thread_list = scheduler->GetThreadList(); 724 const auto& thread_list = scheduler->GetThreadList();
721 725
722 for (auto& thread : thread_list) { 726 for (const auto& thread : thread_list) {
723 if (thread->condvar_wait_address == condvar_addr) 727 if (thread->GetCondVarWaitAddress() == condvar_addr)
724 waiting_threads.push_back(thread); 728 waiting_threads.push_back(thread);
725 } 729 }
726 }; 730 };
@@ -734,7 +738,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
734 // Sort them by priority, such that the highest priority ones come first. 738 // Sort them by priority, such that the highest priority ones come first.
735 std::sort(waiting_threads.begin(), waiting_threads.end(), 739 std::sort(waiting_threads.begin(), waiting_threads.end(),
736 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) { 740 [](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
737 return lhs->current_priority < rhs->current_priority; 741 return lhs->GetPriority() < rhs->GetPriority();
738 }); 742 });
739 743
740 // Only process up to 'target' threads, unless 'target' is -1, in which case process 744 // 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
750 for (std::size_t index = 0; index < last; ++index) { 754 for (std::size_t index = 0; index < last; ++index) {
751 auto& thread = waiting_threads[index]; 755 auto& thread = waiting_threads[index];
752 756
753 ASSERT(thread->condvar_wait_address == condition_variable_addr); 757 ASSERT(thread->GetCondVarWaitAddress() == condition_variable_addr);
754 758
755 std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex(); 759 std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex();
756 760
@@ -759,42 +763,43 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
759 // Atomically read the value of the mutex. 763 // Atomically read the value of the mutex.
760 u32 mutex_val = 0; 764 u32 mutex_val = 0;
761 do { 765 do {
762 monitor.SetExclusive(current_core, thread->mutex_wait_address); 766 monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
763 767
764 // If the mutex is not yet acquired, acquire it. 768 // If the mutex is not yet acquired, acquire it.
765 mutex_val = Memory::Read32(thread->mutex_wait_address); 769 mutex_val = Memory::Read32(thread->GetMutexWaitAddress());
766 770
767 if (mutex_val != 0) { 771 if (mutex_val != 0) {
768 monitor.ClearExclusive(); 772 monitor.ClearExclusive();
769 break; 773 break;
770 } 774 }
771 } while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address, 775 } while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
772 thread->wait_handle)); 776 thread->GetWaitHandle()));
773 777
774 if (mutex_val == 0) { 778 if (mutex_val == 0) {
775 // We were able to acquire the mutex, resume this thread. 779 // We were able to acquire the mutex, resume this thread.
776 ASSERT(thread->status == ThreadStatus::WaitMutex); 780 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
777 thread->ResumeFromWait(); 781 thread->ResumeFromWait();
778 782
779 auto lock_owner = thread->lock_owner; 783 auto* const lock_owner = thread->GetLockOwner();
780 if (lock_owner) 784 if (lock_owner != nullptr) {
781 lock_owner->RemoveMutexWaiter(thread); 785 lock_owner->RemoveMutexWaiter(thread);
786 }
782 787
783 thread->lock_owner = nullptr; 788 thread->SetLockOwner(nullptr);
784 thread->mutex_wait_address = 0; 789 thread->SetMutexWaitAddress(0);
785 thread->condvar_wait_address = 0; 790 thread->SetCondVarWaitAddress(0);
786 thread->wait_handle = 0; 791 thread->SetWaitHandle(0);
787 } else { 792 } else {
788 // Atomically signal that the mutex now has a waiting thread. 793 // Atomically signal that the mutex now has a waiting thread.
789 do { 794 do {
790 monitor.SetExclusive(current_core, thread->mutex_wait_address); 795 monitor.SetExclusive(current_core, thread->GetMutexWaitAddress());
791 796
792 // Ensure that the mutex value is still what we expect. 797 // Ensure that the mutex value is still what we expect.
793 u32 value = Memory::Read32(thread->mutex_wait_address); 798 u32 value = Memory::Read32(thread->GetMutexWaitAddress());
794 // TODO(Subv): When this happens, the kernel just clears the exclusive state and 799 // TODO(Subv): When this happens, the kernel just clears the exclusive state and
795 // retries the initial read for this thread. 800 // retries the initial read for this thread.
796 ASSERT_MSG(mutex_val == value, "Unhandled synchronization primitive case"); 801 ASSERT_MSG(mutex_val == value, "Unhandled synchronization primitive case");
797 } while (!monitor.ExclusiveWrite32(current_core, thread->mutex_wait_address, 802 } while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
798 mutex_val | Mutex::MutexHasWaitersFlag)); 803 mutex_val | Mutex::MutexHasWaitersFlag));
799 804
800 // The mutex is already owned by some other thread, make this thread wait on it. 805 // 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
802 Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask); 807 Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
803 auto owner = kernel.HandleTable().Get<Thread>(owner_handle); 808 auto owner = kernel.HandleTable().Get<Thread>(owner_handle);
804 ASSERT(owner); 809 ASSERT(owner);
805 ASSERT(thread->status == ThreadStatus::WaitMutex); 810 ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
806 thread->wakeup_callback = nullptr; 811 thread->InvalidateWakeupCallback();
807 812
808 owner->AddMutexWaiter(thread); 813 owner->AddMutexWaiter(thread);
809 814
810 Core::System::GetInstance().CpuCore(thread->processor_id).PrepareReschedule(); 815 Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
811 } 816 }
812 } 817 }
813 818
@@ -913,8 +918,8 @@ static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask)
913 return ERR_INVALID_HANDLE; 918 return ERR_INVALID_HANDLE;
914 } 919 }
915 920
916 *core = thread->ideal_core; 921 *core = thread->GetIdealCore();
917 *mask = thread->affinity_mask; 922 *mask = thread->GetAffinityMask();
918 923
919 return RESULT_SUCCESS; 924 return RESULT_SUCCESS;
920} 925}
@@ -930,11 +935,13 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
930 } 935 }
931 936
932 if (core == static_cast<u32>(THREADPROCESSORID_DEFAULT)) { 937 if (core == static_cast<u32>(THREADPROCESSORID_DEFAULT)) {
933 ASSERT(thread->owner_process->GetDefaultProcessorID() != 938 const u8 default_processor_id = thread->GetOwnerProcess()->GetDefaultProcessorID();
934 static_cast<u8>(THREADPROCESSORID_DEFAULT)); 939
940 ASSERT(default_processor_id != static_cast<u8>(THREADPROCESSORID_DEFAULT));
941
935 // Set the target CPU to the one specified in the process' exheader. 942 // Set the target CPU to the one specified in the process' exheader.
936 core = thread->owner_process->GetDefaultProcessorID(); 943 core = default_processor_id;
937 mask = 1ull << core; 944 mask = 1ULL << core;
938 } 945 }
939 946
940 if (mask == 0) { 947 if (mask == 0) {
@@ -945,7 +952,7 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
945 static constexpr u32 OnlyChangeMask = static_cast<u32>(-3); 952 static constexpr u32 OnlyChangeMask = static_cast<u32>(-3);
946 953
947 if (core == OnlyChangeMask) { 954 if (core == OnlyChangeMask) {
948 core = thread->ideal_core; 955 core = thread->GetIdealCore();
949 } else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) { 956 } else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) {
950 return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId); 957 return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
951 } 958 }
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() {
70 70
71void WaitCurrentThread_Sleep() { 71void WaitCurrentThread_Sleep() {
72 Thread* thread = GetCurrentThread(); 72 Thread* thread = GetCurrentThread();
73 thread->status = ThreadStatus::WaitSleep; 73 thread->SetStatus(ThreadStatus::WaitSleep);
74} 74}
75 75
76void ExitCurrentThread() { 76void ExitCurrentThread() {
@@ -169,7 +169,7 @@ void Thread::ResumeFromWait() {
169 next_scheduler->ScheduleThread(this, current_priority); 169 next_scheduler->ScheduleThread(this, current_priority);
170 170
171 // Change thread's scheduler 171 // Change thread's scheduler
172 scheduler = next_scheduler; 172 scheduler = next_scheduler.get();
173 173
174 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); 174 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
175} 175}
@@ -233,7 +233,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
233 thread->name = std::move(name); 233 thread->name = std::move(name);
234 thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap(); 234 thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
235 thread->owner_process = owner_process; 235 thread->owner_process = owner_process;
236 thread->scheduler = Core::System::GetInstance().Scheduler(processor_id); 236 thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get();
237 thread->scheduler->AddThread(thread, priority); 237 thread->scheduler->AddThread(thread, priority);
238 thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread); 238 thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
239 239
@@ -269,9 +269,9 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri
269 SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); 269 SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
270 270
271 // Register 1 must be a handle to the main thread 271 // Register 1 must be a handle to the main thread
272 thread->guest_handle = kernel.HandleTable().Create(thread).Unwrap(); 272 const Handle guest_handle = kernel.HandleTable().Create(thread).Unwrap();
273 273 thread->SetGuestHandle(guest_handle);
274 thread->context.cpu_registers[1] = thread->guest_handle; 274 thread->GetContext().cpu_registers[1] = guest_handle;
275 275
276 // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires 276 // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
277 thread->ResumeFromWait(); 277 thread->ResumeFromWait();
@@ -299,6 +299,18 @@ VAddr Thread::GetCommandBufferAddress() const {
299 return GetTLSAddress() + CommandHeaderOffset; 299 return GetTLSAddress() + CommandHeaderOffset;
300} 300}
301 301
302void Thread::SetStatus(ThreadStatus new_status) {
303 if (new_status == status) {
304 return;
305 }
306
307 if (status == ThreadStatus::Running) {
308 last_running_ticks = CoreTiming::GetTicks();
309 }
310
311 status = new_status;
312}
313
302void Thread::AddMutexWaiter(SharedPtr<Thread> thread) { 314void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
303 if (thread->lock_owner == this) { 315 if (thread->lock_owner == this) {
304 // If the thread is already waiting for this thread to release the mutex, ensure that the 316 // 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) {
388 next_scheduler->ScheduleThread(this, current_priority); 400 next_scheduler->ScheduleThread(this, current_priority);
389 401
390 // Change thread's scheduler 402 // Change thread's scheduler
391 scheduler = next_scheduler; 403 scheduler = next_scheduler.get();
392 404
393 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); 405 Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
394} 406}
395 407
408bool Thread::AllWaitObjectsReady() {
409 return std::none_of(
410 wait_objects.begin(), wait_objects.end(),
411 [this](const SharedPtr<WaitObject>& object) { return object->ShouldWait(this); });
412}
413
414bool Thread::InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
415 SharedPtr<WaitObject> object, std::size_t index) {
416 ASSERT(wakeup_callback);
417 return wakeup_callback(reason, std::move(thread), std::move(object), index);
418}
419
396//////////////////////////////////////////////////////////////////////////////////////////////////// 420////////////////////////////////////////////////////////////////////////////////////////////////////
397 421
398/** 422/**
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:
65 using TLSMemory = std::vector<u8>; 65 using TLSMemory = std::vector<u8>;
66 using TLSMemoryPtr = std::shared_ptr<TLSMemory>; 66 using TLSMemoryPtr = std::shared_ptr<TLSMemory>;
67 67
68 using MutexWaitingThreads = std::vector<SharedPtr<Thread>>;
69
70 using ThreadContext = Core::ARM_Interface::ThreadContext;
71
72 using ThreadWaitObjects = std::vector<SharedPtr<WaitObject>>;
73
74 using WakeupCallback = std::function<bool(ThreadWakeupReason reason, SharedPtr<Thread> thread,
75 SharedPtr<WaitObject> object, std::size_t index)>;
76
68 /** 77 /**
69 * Creates and returns a new thread. The new thread is immediately scheduled 78 * Creates and returns a new thread. The new thread is immediately scheduled
70 * @param kernel The kernel instance this thread will be created under. 79 * @param kernel The kernel instance this thread will be created under.
@@ -106,6 +115,14 @@ public:
106 } 115 }
107 116
108 /** 117 /**
118 * Gets the thread's nominal priority.
119 * @return The current thread's nominal priority.
120 */
121 u32 GetNominalPriority() const {
122 return nominal_priority;
123 }
124
125 /**
109 * Sets the thread's current priority 126 * Sets the thread's current priority
110 * @param priority The new priority 127 * @param priority The new priority
111 */ 128 */
@@ -133,7 +150,7 @@ public:
133 * Gets the thread's thread ID 150 * Gets the thread's thread ID
134 * @return The thread's ID 151 * @return The thread's ID
135 */ 152 */
136 u32 GetThreadId() const { 153 u32 GetThreadID() const {
137 return thread_id; 154 return thread_id;
138 } 155 }
139 156
@@ -203,6 +220,11 @@ public:
203 return tpidr_el0; 220 return tpidr_el0;
204 } 221 }
205 222
223 /// Sets the value of the TPIDR_EL0 Read/Write system register for this thread.
224 void SetTPIDR_EL0(u64 value) {
225 tpidr_el0 = value;
226 }
227
206 /* 228 /*
207 * Returns the address of the current thread's command buffer, located in the TLS. 229 * Returns the address of the current thread's command buffer, located in the TLS.
208 * @returns VAddr of the thread's command buffer. 230 * @returns VAddr of the thread's command buffer.
@@ -218,69 +240,193 @@ public:
218 return status == ThreadStatus::WaitSynchAll; 240 return status == ThreadStatus::WaitSynchAll;
219 } 241 }
220 242
221 Core::ARM_Interface::ThreadContext context; 243 ThreadContext& GetContext() {
244 return context;
245 }
246
247 const ThreadContext& GetContext() const {
248 return context;
249 }
250
251 ThreadStatus GetStatus() const {
252 return status;
253 }
254
255 void SetStatus(ThreadStatus new_status);
256
257 u64 GetLastRunningTicks() const {
258 return last_running_ticks;
259 }
260
261 s32 GetProcessorID() const {
262 return processor_id;
263 }
264
265 SharedPtr<Process>& GetOwnerProcess() {
266 return owner_process;
267 }
268
269 const SharedPtr<Process>& GetOwnerProcess() const {
270 return owner_process;
271 }
272
273 const ThreadWaitObjects& GetWaitObjects() const {
274 return wait_objects;
275 }
276
277 void SetWaitObjects(ThreadWaitObjects objects) {
278 wait_objects = std::move(objects);
279 }
280
281 void ClearWaitObjects() {
282 wait_objects.clear();
283 }
284
285 /// Determines whether all the objects this thread is waiting on are ready.
286 bool AllWaitObjectsReady();
287
288 const MutexWaitingThreads& GetMutexWaitingThreads() const {
289 return wait_mutex_threads;
290 }
291
292 Thread* GetLockOwner() const {
293 return lock_owner.get();
294 }
295
296 void SetLockOwner(SharedPtr<Thread> owner) {
297 lock_owner = std::move(owner);
298 }
299
300 VAddr GetCondVarWaitAddress() const {
301 return condvar_wait_address;
302 }
303
304 void SetCondVarWaitAddress(VAddr address) {
305 condvar_wait_address = address;
306 }
307
308 VAddr GetMutexWaitAddress() const {
309 return mutex_wait_address;
310 }
222 311
223 u32 thread_id; 312 void SetMutexWaitAddress(VAddr address) {
313 mutex_wait_address = address;
314 }
224 315
225 ThreadStatus status; 316 Handle GetWaitHandle() const {
226 VAddr entry_point; 317 return wait_handle;
227 VAddr stack_top; 318 }
228 319
229 u32 nominal_priority; ///< Nominal thread priority, as set by the emulated application 320 void SetWaitHandle(Handle handle) {
230 u32 current_priority; ///< Current thread priority, can be temporarily changed 321 wait_handle = handle;
322 }
231 323
232 u64 last_running_ticks; ///< CPU tick when thread was last running 324 VAddr GetArbiterWaitAddress() const {
325 return arb_wait_address;
326 }
233 327
234 s32 processor_id; 328 void SetArbiterWaitAddress(VAddr address) {
329 arb_wait_address = address;
330 }
235 331
236 VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread 332 void SetGuestHandle(Handle handle) {
237 u64 tpidr_el0; ///< TPIDR_EL0 read/write system register. 333 guest_handle = handle;
334 }
238 335
239 SharedPtr<Process> owner_process; ///< Process that owns this thread 336 bool HasWakeupCallback() const {
337 return wakeup_callback != nullptr;
338 }
339
340 void SetWakeupCallback(WakeupCallback callback) {
341 wakeup_callback = std::move(callback);
342 }
343
344 void InvalidateWakeupCallback() {
345 SetWakeupCallback(nullptr);
346 }
347
348 /**
349 * Invokes the thread's wakeup callback.
350 *
351 * @pre A valid wakeup callback has been set. Violating this precondition
352 * will cause an assertion to trigger.
353 */
354 bool InvokeWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread,
355 SharedPtr<WaitObject> object, std::size_t index);
356
357 u32 GetIdealCore() const {
358 return ideal_core;
359 }
360
361 u64 GetAffinityMask() const {
362 return affinity_mask;
363 }
364
365private:
366 explicit Thread(KernelCore& kernel);
367 ~Thread() override;
368
369 Core::ARM_Interface::ThreadContext context{};
370
371 u32 thread_id = 0;
372
373 ThreadStatus status = ThreadStatus::Dormant;
374
375 VAddr entry_point = 0;
376 VAddr stack_top = 0;
377
378 u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
379 u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
380
381 u64 last_running_ticks = 0; ///< CPU tick when thread was last running
382
383 s32 processor_id = 0;
384
385 VAddr tls_address = 0; ///< Virtual address of the Thread Local Storage of the thread
386 u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register.
387
388 /// Process that owns this thread
389 SharedPtr<Process> owner_process;
240 390
241 /// Objects that the thread is waiting on, in the same order as they were 391 /// Objects that the thread is waiting on, in the same order as they were
242 // passed to WaitSynchronization1/N. 392 /// passed to WaitSynchronization1/N.
243 std::vector<SharedPtr<WaitObject>> wait_objects; 393 ThreadWaitObjects wait_objects;
244 394
245 /// List of threads that are waiting for a mutex that is held by this thread. 395 /// List of threads that are waiting for a mutex that is held by this thread.
246 std::vector<SharedPtr<Thread>> wait_mutex_threads; 396 MutexWaitingThreads wait_mutex_threads;
247 397
248 /// Thread that owns the lock that this thread is waiting for. 398 /// Thread that owns the lock that this thread is waiting for.
249 SharedPtr<Thread> lock_owner; 399 SharedPtr<Thread> lock_owner;
250 400
251 // If waiting on a ConditionVariable, this is the ConditionVariable address 401 /// If waiting on a ConditionVariable, this is the ConditionVariable address
252 VAddr condvar_wait_address; 402 VAddr condvar_wait_address = 0;
253 VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address 403 /// If waiting on a Mutex, this is the mutex address
254 Handle wait_handle; ///< The handle used to wait for the mutex. 404 VAddr mutex_wait_address = 0;
405 /// The handle used to wait for the mutex.
406 Handle wait_handle = 0;
255 407
256 // If waiting for an AddressArbiter, this is the address being waited on. 408 /// If waiting for an AddressArbiter, this is the address being waited on.
257 VAddr arb_wait_address{0}; 409 VAddr arb_wait_address{0};
258 410
259 std::string name;
260
261 /// Handle used by guest emulated application to access this thread 411 /// Handle used by guest emulated application to access this thread
262 Handle guest_handle; 412 Handle guest_handle = 0;
263 413
264 /// Handle used as userdata to reference this object when inserting into the CoreTiming queue. 414 /// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
265 Handle callback_handle; 415 Handle callback_handle = 0;
266 416
267 using WakeupCallback = bool(ThreadWakeupReason reason, SharedPtr<Thread> thread, 417 /// Callback that will be invoked when the thread is resumed from a waiting state. If the thread
268 SharedPtr<WaitObject> object, std::size_t index); 418 /// was waiting via WaitSynchronizationN then the object will be the last object that became
269 // Callback that will be invoked when the thread is resumed from a waiting state. If the thread 419 /// available. In case of a timeout, the object will be nullptr.
270 // was waiting via WaitSynchronizationN then the object will be the last object that became 420 WakeupCallback wakeup_callback;
271 // available. In case of a timeout, the object will be nullptr.
272 std::function<WakeupCallback> wakeup_callback;
273 421
274 std::shared_ptr<Scheduler> scheduler; 422 Scheduler* scheduler = nullptr;
275 423
276 u32 ideal_core{0xFFFFFFFF}; 424 u32 ideal_core{0xFFFFFFFF};
277 u64 affinity_mask{0x1}; 425 u64 affinity_mask{0x1};
278 426
279private:
280 explicit Thread(KernelCore& kernel);
281 ~Thread() override;
282
283 TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>(); 427 TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>();
428
429 std::string name;
284}; 430};
285 431
286/** 432/**
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() {
35 u32 candidate_priority = THREADPRIO_LOWEST + 1; 35 u32 candidate_priority = THREADPRIO_LOWEST + 1;
36 36
37 for (const auto& thread : waiting_threads) { 37 for (const auto& thread : waiting_threads) {
38 const ThreadStatus thread_status = thread->GetStatus();
39
38 // The list of waiting threads must not contain threads that are not waiting to be awakened. 40 // The list of waiting threads must not contain threads that are not waiting to be awakened.
39 ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny || 41 ASSERT_MSG(thread_status == ThreadStatus::WaitSynchAny ||
40 thread->status == ThreadStatus::WaitSynchAll || 42 thread_status == ThreadStatus::WaitSynchAll ||
41 thread->status == ThreadStatus::WaitHLEEvent, 43 thread_status == ThreadStatus::WaitHLEEvent,
42 "Inconsistent thread statuses in waiting_threads"); 44 "Inconsistent thread statuses in waiting_threads");
43 45
44 if (thread->current_priority >= candidate_priority) 46 if (thread->GetPriority() >= candidate_priority)
45 continue; 47 continue;
46 48
47 if (ShouldWait(thread.get())) 49 if (ShouldWait(thread.get()))
@@ -50,16 +52,13 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
50 // A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or 52 // A thread is ready to run if it's either in ThreadStatus::WaitSynchAny or
51 // in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready. 53 // in ThreadStatus::WaitSynchAll and the rest of the objects it is waiting on are ready.
52 bool ready_to_run = true; 54 bool ready_to_run = true;
53 if (thread->status == ThreadStatus::WaitSynchAll) { 55 if (thread_status == ThreadStatus::WaitSynchAll) {
54 ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(), 56 ready_to_run = thread->AllWaitObjectsReady();
55 [&thread](const SharedPtr<WaitObject>& object) {
56 return object->ShouldWait(thread.get());
57 });
58 } 57 }
59 58
60 if (ready_to_run) { 59 if (ready_to_run) {
61 candidate = thread.get(); 60 candidate = thread.get();
62 candidate_priority = thread->current_priority; 61 candidate_priority = thread->GetPriority();
63 } 62 }
64 } 63 }
65 64
@@ -75,24 +74,24 @@ void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) {
75 if (!thread->IsSleepingOnWaitAll()) { 74 if (!thread->IsSleepingOnWaitAll()) {
76 Acquire(thread.get()); 75 Acquire(thread.get());
77 } else { 76 } else {
78 for (auto& object : thread->wait_objects) { 77 for (const auto& object : thread->GetWaitObjects()) {
79 ASSERT(!object->ShouldWait(thread.get())); 78 ASSERT(!object->ShouldWait(thread.get()));
80 object->Acquire(thread.get()); 79 object->Acquire(thread.get());
81 } 80 }
82 } 81 }
83 82
84 std::size_t index = thread->GetWaitObjectIndex(this); 83 const std::size_t index = thread->GetWaitObjectIndex(this);
85 84
86 for (auto& object : thread->wait_objects) 85 for (const auto& object : thread->GetWaitObjects())
87 object->RemoveWaitingThread(thread.get()); 86 object->RemoveWaitingThread(thread.get());
88 thread->wait_objects.clear(); 87 thread->ClearWaitObjects();
89 88
90 thread->CancelWakeupTimer(); 89 thread->CancelWakeupTimer();
91 90
92 bool resume = true; 91 bool resume = true;
93 92
94 if (thread->wakeup_callback) 93 if (thread->HasWakeupCallback())
95 resume = thread->wakeup_callback(ThreadWakeupReason::Signal, thread, this, index); 94 resume = thread->InvokeWakeupCallback(ThreadWakeupReason::Signal, thread, this, index);
96 95
97 if (resume) 96 if (resume)
98 thread->ResumeFromWait(); 97 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) {
84 out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF)); 84 out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
85 } 85 }
86 86
87 if (out.size() <= offset) { 87 if (out.size() < offset) {
88 IPC::ResponseBuilder rb{ctx, 2}; 88 IPC::ResponseBuilder rb{ctx, 2};
89 // TODO(DarkLordZach): Find the correct error code. 89 // TODO(DarkLordZach): Find the correct error code.
90 rb.Push(ResultCode(-1)); 90 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") {
468 {80, nullptr, "OpenSaveDataMetaFile"}, 468 {80, nullptr, "OpenSaveDataMetaFile"},
469 {81, nullptr, "OpenSaveDataTransferManager"}, 469 {81, nullptr, "OpenSaveDataTransferManager"},
470 {82, nullptr, "OpenSaveDataTransferManagerVersion2"}, 470 {82, nullptr, "OpenSaveDataTransferManagerVersion2"},
471 {83, nullptr, "OpenSaveDataTransferProhibiterForCloudBackUp"},
471 {100, nullptr, "OpenImageDirectoryFileSystem"}, 472 {100, nullptr, "OpenImageDirectoryFileSystem"},
472 {110, nullptr, "OpenContentStorageFileSystem"}, 473 {110, nullptr, "OpenContentStorageFileSystem"},
473 {200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"}, 474 {200, &FSP_SRV::OpenDataStorageByCurrentProcess, "OpenDataStorageByCurrentProcess"},
@@ -495,6 +496,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
495 {613, nullptr, "VerifySaveDataFileSystemBySaveDataSpaceId"}, 496 {613, nullptr, "VerifySaveDataFileSystemBySaveDataSpaceId"},
496 {614, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId"}, 497 {614, nullptr, "CorruptSaveDataFileSystemBySaveDataSpaceId"},
497 {615, nullptr, "QuerySaveDataInternalStorageTotalSize"}, 498 {615, nullptr, "QuerySaveDataInternalStorageTotalSize"},
499 {616, nullptr, "GetSaveDataCommitId"},
498 {620, nullptr, "SetSdCardEncryptionSeed"}, 500 {620, nullptr, "SetSdCardEncryptionSeed"},
499 {630, nullptr, "SetSdCardAccessibility"}, 501 {630, nullptr, "SetSdCardAccessibility"},
500 {631, nullptr, "IsSdCardAccessible"}, 502 {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:
21 {0, nullptr, "Unknown1"}, 21 {0, nullptr, "Unknown1"},
22 {1, nullptr, "Unknown2"}, 22 {1, nullptr, "Unknown2"},
23 {2, nullptr, "Unknown3"}, 23 {2, nullptr, "Unknown3"},
24 {3, nullptr, "Unknown4"}, 24 {3, nullptr, "GetCurrentBacklightLevel"},
25 {4, nullptr, "Unknown5"}, 25 {4, nullptr, "Unknown4"},
26 {5, nullptr, "Unknown6"}, 26 {5, nullptr, "GetAlsComputedBacklightLevel"},
27 {6, nullptr, "TurnOffBacklight"}, 27 {6, nullptr, "TurnOffBacklight"},
28 {7, nullptr, "TurnOnBacklight"}, 28 {7, nullptr, "TurnOnBacklight"},
29 {8, nullptr, "GetBacklightStatus"}, 29 {8, nullptr, "GetBacklightStatus"},
30 {9, nullptr, "Unknown7"}, 30 {9, nullptr, "Unknown5"},
31 {10, nullptr, "Unknown8"}, 31 {10, nullptr, "Unknown6"},
32 {11, nullptr, "Unknown9"}, 32 {11, nullptr, "Unknown7"},
33 {12, nullptr, "Unknown10"}, 33 {12, nullptr, "Unknown8"},
34 {13, nullptr, "Unknown11"}, 34 {13, nullptr, "Unknown9"},
35 {14, nullptr, "Unknown12"}, 35 {14, nullptr, "Unknown10"},
36 {15, nullptr, "Unknown13"}, 36 {15, nullptr, "GetAutoBrightnessSetting"},
37 {16, nullptr, "ReadRawLightSensor"}, 37 {16, nullptr, "ReadRawLightSensor"},
38 {17, nullptr, "Unknown14"}, 38 {17, nullptr, "Unknown11"},
39 {18, nullptr, "Unknown15"}, 39 {18, nullptr, "Unknown12"},
40 {19, nullptr, "Unknown16"}, 40 {19, nullptr, "Unknown13"},
41 {20, nullptr, "Unknown17"}, 41 {20, nullptr, "Unknown14"},
42 {21, nullptr, "Unknown18"}, 42 {21, nullptr, "Unknown15"},
43 {22, nullptr, "Unknown19"}, 43 {22, nullptr, "Unknown16"},
44 {23, nullptr, "Unknown20"}, 44 {23, nullptr, "Unknown17"},
45 {24, nullptr, "Unknown21"}, 45 {24, nullptr, "Unknown18"},
46 {25, nullptr, "Unknown22"}, 46 {25, nullptr, "Unknown19"},
47 {26, &LBL::EnableVrMode, "EnableVrMode"}, 47 {26, &LBL::EnableVrMode, "EnableVrMode"},
48 {27, &LBL::DisableVrMode, "DisableVrMode"}, 48 {27, &LBL::DisableVrMode, "DisableVrMode"},
49 {28, &LBL::GetVrMode, "GetVrMode"}, 49 {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 @@
6 6
7#include <memory> 7#include <memory>
8#include <string> 8#include <string>
9#include <type_traits>
9#include <unordered_map> 10#include <unordered_map>
10 11
12#include "core/hle/kernel/client_port.h"
11#include "core/hle/kernel/object.h" 13#include "core/hle/kernel/object.h"
14#include "core/hle/kernel/server_port.h"
12#include "core/hle/result.h" 15#include "core/hle/result.h"
13#include "core/hle/service/service.h" 16#include "core/hle/service/service.h"
14 17
@@ -48,6 +51,22 @@ public:
48 ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name); 51 ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name);
49 ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name); 52 ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name);
50 53
54 template <typename T>
55 std::shared_ptr<T> GetService(const std::string& service_name) const {
56 static_assert(std::is_base_of_v<Kernel::SessionRequestHandler, T>,
57 "Not a base of ServiceFrameworkBase");
58 auto service = registered_services.find(service_name);
59 if (service == registered_services.end()) {
60 LOG_DEBUG(Service, "Can't find service: {}", service_name);
61 return nullptr;
62 }
63 auto port = service->second->GetServerPort();
64 if (port == nullptr) {
65 return nullptr;
66 }
67 return std::static_pointer_cast<T>(port->hle_handler);
68 }
69
51 void InvokeControlRequest(Kernel::HLERequestContext& context); 70 void InvokeControlRequest(Kernel::HLERequestContext& context);
52 71
53private: 72private:
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)
130 } 130 }
131 131
132 process.LoadFromMetadata(metadata); 132 process.LoadFromMetadata(metadata);
133 const FileSys::PatchManager pm(metadata.GetTitleID());
133 134
134 // Load NSO modules 135 // Load NSO modules
135 const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); 136 const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
@@ -139,7 +140,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
139 const FileSys::VirtualFile module_file = dir->GetFile(module); 140 const FileSys::VirtualFile module_file = dir->GetFile(module);
140 if (module_file != nullptr) { 141 if (module_file != nullptr) {
141 const VAddr load_addr = next_load_addr; 142 const VAddr load_addr = next_load_addr;
142 next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr); 143 next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr, pm);
143 LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); 144 LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
144 // Register module with GDBStub 145 // Register module with GDBStub
145 GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false); 146 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 @@
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/swap.h" 11#include "common/swap.h"
12#include "core/core.h" 12#include "core/core.h"
13#include "core/file_sys/patch_manager.h"
13#include "core/gdbstub/gdbstub.h" 14#include "core/gdbstub/gdbstub.h"
14#include "core/hle/kernel/kernel.h" 15#include "core/hle/kernel/kernel.h"
15#include "core/hle/kernel/process.h" 16#include "core/hle/kernel/process.h"
@@ -36,8 +37,7 @@ struct NsoHeader {
36 INSERT_PADDING_WORDS(1); 37 INSERT_PADDING_WORDS(1);
37 u8 flags; 38 u8 flags;
38 std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order) 39 std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
39 u32_le bss_size; 40 std::array<u8, 0x20> build_id;
40 INSERT_PADDING_BYTES(0x1c);
41 std::array<u32_le, 3> segments_compressed_size; 41 std::array<u32_le, 3> segments_compressed_size;
42 42
43 bool IsSegmentCompressed(size_t segment_num) const { 43 bool IsSegmentCompressed(size_t segment_num) const {
@@ -93,7 +93,8 @@ static constexpr u32 PageAlignSize(u32 size) {
93 return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; 93 return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
94} 94}
95 95
96VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) { 96VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
97 boost::optional<FileSys::PatchManager> pm) {
97 if (file == nullptr) 98 if (file == nullptr)
98 return {}; 99 return {};
99 100
@@ -142,6 +143,17 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base) {
142 const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)}; 143 const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
143 program_image.resize(image_size); 144 program_image.resize(image_size);
144 145
146 // Apply patches if necessary
147 if (pm != boost::none && pm->HasNSOPatch(nso_header.build_id)) {
148 std::vector<u8> pi_header(program_image.size() + 0x100);
149 std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
150 std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());
151
152 pi_header = pm->PatchNSO(pi_header);
153
154 std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size());
155 }
156
145 // Load codeset for current process 157 // Load codeset for current process
146 codeset->name = file->GetName(); 158 codeset->name = file->GetName();
147 codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); 159 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 @@
5#pragma once 5#pragma once
6 6
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "core/file_sys/patch_manager.h"
8#include "core/loader/linker.h" 9#include "core/loader/linker.h"
9#include "core/loader/loader.h" 10#include "core/loader/loader.h"
10 11
@@ -26,7 +27,8 @@ public:
26 return IdentifyType(file); 27 return IdentifyType(file);
27 } 28 }
28 29
29 static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base); 30 static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base,
31 boost::optional<FileSys::PatchManager> pm = boost::none);
30 32
31 ResultStatus Load(Kernel::Process& process) override; 33 ResultStatus Load(Kernel::Process& process) override;
32}; 34};
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 {
155 // Debugging 155 // Debugging
156 bool use_gdbstub; 156 bool use_gdbstub;
157 u16 gdbstub_port; 157 u16 gdbstub_port;
158
159 // WebService
160 bool enable_telemetry;
161 std::string web_api_url;
162 std::string yuzu_username;
163 std::string yuzu_token;
158} extern values; 164} extern values;
159 165
160void Apply(); 166void 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 @@
6#include "common/common_types.h" 6#include "common/common_types.h"
7#include "common/file_util.h" 7#include "common/file_util.h"
8 8
9#include <mbedtls/ctr_drbg.h>
10#include <mbedtls/entropy.h>
9#include "core/core.h" 11#include "core/core.h"
10#include "core/file_sys/control_metadata.h" 12#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/patch_manager.h" 13#include "core/file_sys/patch_manager.h"
@@ -13,10 +15,31 @@
13#include "core/settings.h" 15#include "core/settings.h"
14#include "core/telemetry_session.h" 16#include "core/telemetry_session.h"
15 17
18#ifdef ENABLE_WEB_SERVICE
19#include "web_service/telemetry_json.h"
20#include "web_service/verify_login.h"
21#endif
22
16namespace Core { 23namespace Core {
17 24
18static u64 GenerateTelemetryId() { 25static u64 GenerateTelemetryId() {
19 u64 telemetry_id{}; 26 u64 telemetry_id{};
27
28 mbedtls_entropy_context entropy;
29 mbedtls_entropy_init(&entropy);
30 mbedtls_ctr_drbg_context ctr_drbg;
31 std::string personalization = "yuzu Telemetry ID";
32
33 mbedtls_ctr_drbg_init(&ctr_drbg);
34 ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
35 reinterpret_cast<const unsigned char*>(personalization.c_str()),
36 personalization.size()) == 0);
37 ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id),
38 sizeof(u64)) == 0);
39
40 mbedtls_ctr_drbg_free(&ctr_drbg);
41 mbedtls_entropy_free(&entropy);
42
20 return telemetry_id; 43 return telemetry_id;
21} 44}
22 45
@@ -25,14 +48,21 @@ u64 GetTelemetryId() {
25 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + 48 const std::string filename{FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) +
26 "telemetry_id"}; 49 "telemetry_id"};
27 50
28 if (FileUtil::Exists(filename)) { 51 bool generate_new_id = !FileUtil::Exists(filename);
52 if (!generate_new_id) {
29 FileUtil::IOFile file(filename, "rb"); 53 FileUtil::IOFile file(filename, "rb");
30 if (!file.IsOpen()) { 54 if (!file.IsOpen()) {
31 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 55 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
32 return {}; 56 return {};
33 } 57 }
34 file.ReadBytes(&telemetry_id, sizeof(u64)); 58 file.ReadBytes(&telemetry_id, sizeof(u64));
35 } else { 59 if (telemetry_id == 0) {
60 LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id);
61 generate_new_id = true;
62 }
63 }
64
65 if (generate_new_id) {
36 FileUtil::IOFile file(filename, "wb"); 66 FileUtil::IOFile file(filename, "wb");
37 if (!file.IsOpen()) { 67 if (!file.IsOpen()) {
38 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename); 68 LOG_ERROR(Core, "failed to open telemetry_id: {}", filename);
@@ -59,23 +89,20 @@ u64 RegenerateTelemetryId() {
59 return new_telemetry_id; 89 return new_telemetry_id;
60} 90}
61 91
62std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func) { 92bool VerifyLogin(const std::string& username, const std::string& token) {
63#ifdef ENABLE_WEB_SERVICE 93#ifdef ENABLE_WEB_SERVICE
64 return WebService::VerifyLogin(username, token, Settings::values.verify_endpoint_url, func); 94 return WebService::VerifyLogin(Settings::values.web_api_url, username, token);
65#else 95#else
66 return std::async(std::launch::async, [func{std::move(func)}]() { 96 return false;
67 func();
68 return false;
69 });
70#endif 97#endif
71} 98}
72 99
73TelemetrySession::TelemetrySession() { 100TelemetrySession::TelemetrySession() {
74#ifdef ENABLE_WEB_SERVICE 101#ifdef ENABLE_WEB_SERVICE
75 if (Settings::values.enable_telemetry) { 102 if (Settings::values.enable_telemetry) {
76 backend = std::make_unique<WebService::TelemetryJson>( 103 backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url,
77 Settings::values.telemetry_endpoint_url, Settings::values.yuzu_username, 104 Settings::values.yuzu_username,
78 Settings::values.yuzu_token); 105 Settings::values.yuzu_token);
79 } else { 106 } else {
80 backend = std::make_unique<Telemetry::NullVisitor>(); 107 backend = std::make_unique<Telemetry::NullVisitor>();
81 } 108 }
@@ -94,7 +121,8 @@ TelemetrySession::TelemetrySession() {
94 u64 program_id{}; 121 u64 program_id{};
95 const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)}; 122 const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)};
96 if (res == Loader::ResultStatus::Success) { 123 if (res == Loader::ResultStatus::Success) {
97 AddField(Telemetry::FieldType::Session, "ProgramId", program_id); 124 const std::string formatted_program_id{fmt::format("{:016X}", program_id)};
125 AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id);
98 126
99 std::string name; 127 std::string name;
100 System::GetInstance().GetAppLoader().ReadTitle(name); 128 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 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <future>
8#include <memory> 7#include <memory>
9#include "common/telemetry.h" 8#include "common/telemetry.h"
10 9
@@ -31,6 +30,8 @@ public:
31 field_collection.AddField(type, name, std::move(value)); 30 field_collection.AddField(type, name, std::move(value));
32 } 31 }
33 32
33 static void FinalizeAsyncJob();
34
34private: 35private:
35 Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session 36 Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
36 std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields 37 std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
@@ -55,6 +56,6 @@ u64 RegenerateTelemetryId();
55 * @param func A function that gets exectued when the verification is finished 56 * @param func A function that gets exectued when the verification is finished
56 * @returns Future with bool indicating whether the verification succeeded 57 * @returns Future with bool indicating whether the verification succeeded
57 */ 58 */
58std::future<bool> VerifyLogin(std::string username, std::string token, std::function<void()> func); 59bool VerifyLogin(const std::string& username, const std::string& token);
59 60
60} // namespace Core 61} // 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
27 renderer_base.h 27 renderer_base.h
28 renderer_opengl/gl_buffer_cache.cpp 28 renderer_opengl/gl_buffer_cache.cpp
29 renderer_opengl/gl_buffer_cache.h 29 renderer_opengl/gl_buffer_cache.h
30 renderer_opengl/gl_primitive_assembler.cpp
31 renderer_opengl/gl_primitive_assembler.h
30 renderer_opengl/gl_rasterizer.cpp 32 renderer_opengl/gl_rasterizer.cpp
31 renderer_opengl/gl_rasterizer.h 33 renderer_opengl/gl_rasterizer.h
32 renderer_opengl/gl_rasterizer_cache.cpp 34 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:
744 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(end_addr_high) << 32) | 744 return static_cast<GPUVAddr>((static_cast<GPUVAddr>(end_addr_high) << 32) |
745 end_addr_low); 745 end_addr_low);
746 } 746 }
747
748 /// Adjust the index buffer offset so it points to the first desired index.
749 GPUVAddr IndexStart() const {
750 return StartAddress() + static_cast<size_t>(first) *
751 static_cast<size_t>(FormatSizeInBytes());
752 }
747 } index_array; 753 } index_array;
748 754
749 INSERT_PADDING_WORDS(0x7); 755 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
34 } 34 }
35 35
36 AlignBuffer(alignment); 36 AlignBuffer(alignment);
37 GLintptr uploaded_offset = buffer_offset; 37 const GLintptr uploaded_offset = buffer_offset;
38 38
39 Memory::ReadBlock(*cpu_addr, buffer_ptr, size); 39 Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
40 40
@@ -57,13 +57,23 @@ GLintptr OGLBufferCache::UploadHostMemory(const void* raw_pointer, std::size_t s
57 std::size_t alignment) { 57 std::size_t alignment) {
58 AlignBuffer(alignment); 58 AlignBuffer(alignment);
59 std::memcpy(buffer_ptr, raw_pointer, size); 59 std::memcpy(buffer_ptr, raw_pointer, size);
60 GLintptr uploaded_offset = buffer_offset; 60 const GLintptr uploaded_offset = buffer_offset;
61 61
62 buffer_ptr += size; 62 buffer_ptr += size;
63 buffer_offset += size; 63 buffer_offset += size;
64 return uploaded_offset; 64 return uploaded_offset;
65} 65}
66 66
67std::tuple<u8*, GLintptr> OGLBufferCache::ReserveMemory(std::size_t size, std::size_t alignment) {
68 AlignBuffer(alignment);
69 u8* const uploaded_ptr = buffer_ptr;
70 const GLintptr uploaded_offset = buffer_offset;
71
72 buffer_ptr += size;
73 buffer_offset += size;
74 return std::make_tuple(uploaded_ptr, uploaded_offset);
75}
76
67void OGLBufferCache::Map(std::size_t max_size) { 77void OGLBufferCache::Map(std::size_t max_size) {
68 bool invalidate; 78 bool invalidate;
69 std::tie(buffer_ptr, buffer_offset_base, invalidate) = 79 std::tie(buffer_ptr, buffer_offset_base, invalidate) =
@@ -74,6 +84,7 @@ void OGLBufferCache::Map(std::size_t max_size) {
74 InvalidateAll(); 84 InvalidateAll();
75 } 85 }
76} 86}
87
77void OGLBufferCache::Unmap() { 88void OGLBufferCache::Unmap() {
78 stream_buffer.Unmap(buffer_offset - buffer_offset_base); 89 stream_buffer.Unmap(buffer_offset - buffer_offset_base);
79} 90}
@@ -84,7 +95,7 @@ GLuint OGLBufferCache::GetHandle() const {
84 95
85void OGLBufferCache::AlignBuffer(std::size_t alignment) { 96void OGLBufferCache::AlignBuffer(std::size_t alignment) {
86 // Align the offset, not the mapped pointer 97 // Align the offset, not the mapped pointer
87 GLintptr offset_aligned = 98 const GLintptr offset_aligned =
88 static_cast<GLintptr>(Common::AlignUp(static_cast<std::size_t>(buffer_offset), alignment)); 99 static_cast<GLintptr>(Common::AlignUp(static_cast<std::size_t>(buffer_offset), alignment));
89 buffer_ptr += offset_aligned - buffer_offset; 100 buffer_ptr += offset_aligned - buffer_offset;
90 buffer_offset = offset_aligned; 101 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 @@
6 6
7#include <cstddef> 7#include <cstddef>
8#include <memory> 8#include <memory>
9#include <tuple>
9 10
10#include "common/common_types.h" 11#include "common/common_types.h"
11#include "video_core/rasterizer_cache.h" 12#include "video_core/rasterizer_cache.h"
@@ -33,11 +34,17 @@ class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBuffer
33public: 34public:
34 explicit OGLBufferCache(std::size_t size); 35 explicit OGLBufferCache(std::size_t size);
35 36
37 /// Uploads data from a guest GPU address. Returns host's buffer offset where it's been
38 /// allocated.
36 GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4, 39 GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
37 bool cache = true); 40 bool cache = true);
38 41
42 /// Uploads from a host memory. Returns host's buffer offset where it's been allocated.
39 GLintptr UploadHostMemory(const void* raw_pointer, std::size_t size, std::size_t alignment = 4); 43 GLintptr UploadHostMemory(const void* raw_pointer, std::size_t size, std::size_t alignment = 4);
40 44
45 /// Reserves memory to be used by host's CPU. Returns mapped address and offset.
46 std::tuple<u8*, GLintptr> ReserveMemory(std::size_t size, std::size_t alignment = 4);
47
41 void Map(std::size_t max_size); 48 void Map(std::size_t max_size);
42 void Unmap(); 49 void Unmap();
43 50
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 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include "common/assert.h"
8#include "common/common_types.h"
9#include "core/memory.h"
10#include "video_core/renderer_opengl/gl_buffer_cache.h"
11#include "video_core/renderer_opengl/gl_primitive_assembler.h"
12
13namespace OpenGL {
14
15constexpr u32 TRIANGLES_PER_QUAD = 6;
16constexpr std::array<u32, TRIANGLES_PER_QUAD> QUAD_MAP = {0, 1, 2, 0, 2, 3};
17
18PrimitiveAssembler::PrimitiveAssembler(OGLBufferCache& buffer_cache) : buffer_cache(buffer_cache) {}
19
20PrimitiveAssembler::~PrimitiveAssembler() = default;
21
22std::size_t PrimitiveAssembler::CalculateQuadSize(u32 count) const {
23 ASSERT_MSG(count % 4 == 0, "Quad count is expected to be a multiple of 4");
24 return (count / 4) * TRIANGLES_PER_QUAD * sizeof(GLuint);
25}
26
27GLintptr PrimitiveAssembler::MakeQuadArray(u32 first, u32 count) {
28 const std::size_t size{CalculateQuadSize(count)};
29 auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(size);
30
31 for (u32 primitive = 0; primitive < count / 4; ++primitive) {
32 for (u32 i = 0; i < TRIANGLES_PER_QUAD; ++i) {
33 const u32 index = first + primitive * 4 + QUAD_MAP[i];
34 std::memcpy(dst_pointer, &index, sizeof(index));
35 dst_pointer += sizeof(index);
36 }
37 }
38
39 return index_offset;
40}
41
42GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size,
43 u32 count) {
44 const std::size_t map_size{CalculateQuadSize(count)};
45 auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
46
47 auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
48 const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
49 const u8* source{Memory::GetPointer(*cpu_addr)};
50
51 for (u32 primitive = 0; primitive < count / 4; ++primitive) {
52 for (std::size_t i = 0; i < TRIANGLES_PER_QUAD; ++i) {
53 const u32 index = primitive * 4 + QUAD_MAP[i];
54 const u8* src_offset = source + (index * index_size);
55
56 std::memcpy(dst_pointer, src_offset, index_size);
57 dst_pointer += index_size;
58 }
59 }
60
61 return index_offset;
62}
63
64} // 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 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <vector>
8#include <glad/glad.h>
9
10#include "common/common_types.h"
11#include "video_core/memory_manager.h"
12
13namespace OpenGL {
14
15class OGLBufferCache;
16
17class PrimitiveAssembler {
18public:
19 explicit PrimitiveAssembler(OGLBufferCache& buffer_cache);
20 ~PrimitiveAssembler();
21
22 /// Calculates the size required by MakeQuadArray and MakeQuadIndexed.
23 std::size_t CalculateQuadSize(u32 count) const;
24
25 GLintptr MakeQuadArray(u32 first, u32 count);
26
27 GLintptr MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size, u32 count);
28
29private:
30 OGLBufferCache& buffer_cache;
31};
32
33} // 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
42MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); 42MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
43MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192)); 43MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192));
44MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); 44MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
45MICROPROFILE_DEFINE(OpenGL_PrimitiveAssembly, "OpenGL", "Prim Asmbl", MP_RGB(255, 100, 100));
46
47struct DrawParameters {
48 GLenum primitive_mode;
49 GLsizei count;
50 GLint current_instance;
51 bool use_indexed;
52
53 GLint vertex_first;
54
55 GLenum index_format;
56 GLint base_vertex;
57 GLintptr index_buffer_offset;
58
59 void DispatchDraw() const {
60 if (use_indexed) {
61 const auto index_buffer_ptr = reinterpret_cast<const void*>(index_buffer_offset);
62 if (current_instance > 0) {
63 glDrawElementsInstancedBaseVertexBaseInstance(primitive_mode, count, index_format,
64 index_buffer_ptr, 1, base_vertex,
65 current_instance);
66 } else {
67 glDrawElementsBaseVertex(primitive_mode, count, index_format, index_buffer_ptr,
68 base_vertex);
69 }
70 } else {
71 if (current_instance > 0) {
72 glDrawArraysInstancedBaseInstance(primitive_mode, vertex_first, count, 1,
73 current_instance);
74 } else {
75 glDrawArrays(primitive_mode, vertex_first, count);
76 }
77 }
78 }
79};
45 80
46RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info) 81RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
47 : emu_window{window}, screen_info{info}, buffer_cache(STREAM_BUFFER_SIZE) { 82 : emu_window{window}, screen_info{info}, buffer_cache(STREAM_BUFFER_SIZE) {
@@ -172,6 +207,54 @@ void RasterizerOpenGL::SetupVertexArrays() {
172 } 207 }
173} 208}
174 209
210DrawParameters RasterizerOpenGL::SetupDraw() {
211 const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
212 const auto& regs = gpu.regs;
213 const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
214
215 DrawParameters params{};
216 params.current_instance = gpu.state.current_instance;
217
218 if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
219 MICROPROFILE_SCOPE(OpenGL_PrimitiveAssembly);
220
221 params.use_indexed = true;
222 params.primitive_mode = GL_TRIANGLES;
223
224 if (is_indexed) {
225 params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
226 params.count = (regs.index_array.count / 4) * 6;
227 params.index_buffer_offset = primitive_assembler.MakeQuadIndexed(
228 regs.index_array.IndexStart(), regs.index_array.FormatSizeInBytes(),
229 regs.index_array.count);
230 params.base_vertex = static_cast<GLint>(regs.vb_element_base);
231 } else {
232 // MakeQuadArray always generates u32 indexes
233 params.index_format = GL_UNSIGNED_INT;
234 params.count = (regs.vertex_buffer.count / 4) * 6;
235 params.index_buffer_offset =
236 primitive_assembler.MakeQuadArray(regs.vertex_buffer.first, params.count);
237 }
238 return params;
239 }
240
241 params.use_indexed = is_indexed;
242 params.primitive_mode = MaxwellToGL::PrimitiveTopology(regs.draw.topology);
243
244 if (is_indexed) {
245 MICROPROFILE_SCOPE(OpenGL_Index);
246 params.index_format = MaxwellToGL::IndexFormat(regs.index_array.format);
247 params.count = regs.index_array.count;
248 params.index_buffer_offset =
249 buffer_cache.UploadMemory(regs.index_array.IndexStart(), CalculateIndexBufferSize());
250 params.base_vertex = static_cast<GLint>(regs.vb_element_base);
251 } else {
252 params.count = regs.vertex_buffer.count;
253 params.vertex_first = regs.vertex_buffer.first;
254 }
255 return params;
256}
257
175void RasterizerOpenGL::SetupShaders() { 258void RasterizerOpenGL::SetupShaders() {
176 MICROPROFILE_SCOPE(OpenGL_Shader); 259 MICROPROFILE_SCOPE(OpenGL_Shader);
177 const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); 260 const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
@@ -256,6 +339,13 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
256 return size; 339 return size;
257} 340}
258 341
342std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
343 const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
344
345 return static_cast<std::size_t>(regs.index_array.count) *
346 static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
347}
348
259bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) { 349bool RasterizerOpenGL::AccelerateDrawBatch(bool is_indexed) {
260 accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays; 350 accelerate_draw = is_indexed ? AccelDraw::Indexed : AccelDraw::Arrays;
261 DrawArrays(); 351 DrawArrays();
@@ -459,16 +549,23 @@ void RasterizerOpenGL::DrawArrays() {
459 549
460 // Draw the vertex batch 550 // Draw the vertex batch
461 const bool is_indexed = accelerate_draw == AccelDraw::Indexed; 551 const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
462 const u64 index_buffer_size{static_cast<u64>(regs.index_array.count) *
463 static_cast<u64>(regs.index_array.FormatSizeInBytes())};
464 552
465 state.draw.vertex_buffer = buffer_cache.GetHandle(); 553 state.draw.vertex_buffer = buffer_cache.GetHandle();
466 state.Apply(); 554 state.Apply();
467 555
468 std::size_t buffer_size = CalculateVertexArraysSize(); 556 std::size_t buffer_size = CalculateVertexArraysSize();
469 557
470 if (is_indexed) { 558 // Add space for index buffer (keeping in mind non-core primitives)
471 buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + index_buffer_size; 559 switch (regs.draw.topology) {
560 case Maxwell::PrimitiveTopology::Quads:
561 buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) +
562 primitive_assembler.CalculateQuadSize(regs.vertex_buffer.count);
563 break;
564 default:
565 if (is_indexed) {
566 buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + CalculateIndexBufferSize();
567 }
568 break;
472 } 569 }
473 570
474 // Uniform space for the 5 shader stages 571 // Uniform space for the 5 shader stages
@@ -482,20 +579,7 @@ void RasterizerOpenGL::DrawArrays() {
482 buffer_cache.Map(buffer_size); 579 buffer_cache.Map(buffer_size);
483 580
484 SetupVertexArrays(); 581 SetupVertexArrays();
485 582 DrawParameters params = SetupDraw();
486 // If indexed mode, copy the index buffer
487 GLintptr index_buffer_offset = 0;
488 if (is_indexed) {
489 MICROPROFILE_SCOPE(OpenGL_Index);
490
491 // Adjust the index buffer offset so it points to the first desired index.
492 auto index_start = regs.index_array.StartAddress();
493 index_start += static_cast<size_t>(regs.index_array.first) *
494 static_cast<size_t>(regs.index_array.FormatSizeInBytes());
495
496 index_buffer_offset = buffer_cache.UploadMemory(index_start, index_buffer_size);
497 }
498
499 SetupShaders(); 583 SetupShaders();
500 584
501 buffer_cache.Unmap(); 585 buffer_cache.Unmap();
@@ -503,31 +587,8 @@ void RasterizerOpenGL::DrawArrays() {
503 shader_program_manager->ApplyTo(state); 587 shader_program_manager->ApplyTo(state);
504 state.Apply(); 588 state.Apply();
505 589
506 const GLenum primitive_mode{MaxwellToGL::PrimitiveTopology(regs.draw.topology)}; 590 // Execute draw call
507 if (is_indexed) { 591 params.DispatchDraw();
508 const GLint base_vertex{static_cast<GLint>(regs.vb_element_base)};
509
510 if (gpu.state.current_instance > 0) {
511 glDrawElementsInstancedBaseVertexBaseInstance(
512 primitive_mode, regs.index_array.count,
513 MaxwellToGL::IndexFormat(regs.index_array.format),
514 reinterpret_cast<const void*>(index_buffer_offset), 1, base_vertex,
515 gpu.state.current_instance);
516 } else {
517 glDrawElementsBaseVertex(primitive_mode, regs.index_array.count,
518 MaxwellToGL::IndexFormat(regs.index_array.format),
519 reinterpret_cast<const void*>(index_buffer_offset),
520 base_vertex);
521 }
522 } else {
523 if (gpu.state.current_instance > 0) {
524 glDrawArraysInstancedBaseInstance(primitive_mode, regs.vertex_buffer.first,
525 regs.vertex_buffer.count, 1,
526 gpu.state.current_instance);
527 } else {
528 glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count);
529 }
530 }
531 592
532 // Disable scissor test 593 // Disable scissor test
533 state.scissor.enabled = false; 594 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 @@
23#include "video_core/rasterizer_cache.h" 23#include "video_core/rasterizer_cache.h"
24#include "video_core/rasterizer_interface.h" 24#include "video_core/rasterizer_interface.h"
25#include "video_core/renderer_opengl/gl_buffer_cache.h" 25#include "video_core/renderer_opengl/gl_buffer_cache.h"
26#include "video_core/renderer_opengl/gl_primitive_assembler.h"
26#include "video_core/renderer_opengl/gl_rasterizer_cache.h" 27#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
27#include "video_core/renderer_opengl/gl_resource_manager.h" 28#include "video_core/renderer_opengl/gl_resource_manager.h"
28#include "video_core/renderer_opengl/gl_shader_cache.h" 29#include "video_core/renderer_opengl/gl_shader_cache.h"
@@ -38,6 +39,7 @@ class EmuWindow;
38namespace OpenGL { 39namespace OpenGL {
39 40
40struct ScreenInfo; 41struct ScreenInfo;
42struct DrawParameters;
41 43
42class RasterizerOpenGL : public VideoCore::RasterizerInterface { 44class RasterizerOpenGL : public VideoCore::RasterizerInterface {
43public: 45public:
@@ -194,12 +196,17 @@ private:
194 static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024; 196 static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
195 OGLBufferCache buffer_cache; 197 OGLBufferCache buffer_cache;
196 OGLFramebuffer framebuffer; 198 OGLFramebuffer framebuffer;
199 PrimitiveAssembler primitive_assembler{buffer_cache};
197 GLint uniform_buffer_alignment; 200 GLint uniform_buffer_alignment;
198 201
199 std::size_t CalculateVertexArraysSize() const; 202 std::size_t CalculateVertexArraysSize() const;
200 203
204 std::size_t CalculateIndexBufferSize() const;
205
201 void SetupVertexArrays(); 206 void SetupVertexArrays();
202 207
208 DrawParameters SetupDraw();
209
203 void SetupShaders(); 210 void SetupShaders();
204 211
205 enum class AccelDraw { Disabled, Arrays, Indexed }; 212 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 @@
1add_library(web_service STATIC
2 telemetry_json.cpp
3 telemetry_json.h
4 verify_login.cpp
5 verify_login.h
6 web_backend.cpp
7 web_backend.h
8)
9
10create_target_directory_groups(web_service)
11
12get_directory_property(OPENSSL_LIBS
13 DIRECTORY ${CMAKE_SOURCE_DIR}/externals/libressl
14 DEFINITION OPENSSL_LIBS)
15target_compile_definitions(web_service PUBLIC -DCPPHTTPLIB_OPENSSL_SUPPORT)
16target_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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <thread>
6#include "common/assert.h"
7#include "common/detached_tasks.h"
8#include "web_service/telemetry_json.h"
9#include "web_service/web_backend.h"
10
11namespace WebService {
12
13TelemetryJson::TelemetryJson(const std::string& host, const std::string& username,
14 const std::string& token)
15 : host(std::move(host)), username(std::move(username)), token(std::move(token)) {}
16TelemetryJson::~TelemetryJson() = default;
17
18template <class T>
19void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) {
20 sections[static_cast<u8>(type)][name] = value;
21}
22
23void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) {
24 TopSection()[name] = sections[static_cast<unsigned>(type)];
25}
26
27void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
28 Serialize(field.GetType(), field.GetName(), field.GetValue());
29}
30
31void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
32 Serialize(field.GetType(), field.GetName(), field.GetValue());
33}
34
35void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
36 Serialize(field.GetType(), field.GetName(), field.GetValue());
37}
38
39void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
40 Serialize(field.GetType(), field.GetName(), field.GetValue());
41}
42
43void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
44 Serialize(field.GetType(), field.GetName(), field.GetValue());
45}
46
47void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
48 Serialize(field.GetType(), field.GetName(), field.GetValue());
49}
50
51void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
52 Serialize(field.GetType(), field.GetName(), field.GetValue());
53}
54
55void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
56 Serialize(field.GetType(), field.GetName(), field.GetValue());
57}
58
59void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
60 Serialize(field.GetType(), field.GetName(), field.GetValue());
61}
62
63void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
64 Serialize(field.GetType(), field.GetName(), field.GetValue());
65}
66
67void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
68 Serialize(field.GetType(), field.GetName(), field.GetValue());
69}
70
71void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
72 Serialize(field.GetType(), field.GetName(), field.GetValue());
73}
74
75void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
76 Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
77}
78
79void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
80 Serialize(field.GetType(), field.GetName(), field.GetValue().count());
81}
82
83void TelemetryJson::Complete() {
84 SerializeSection(Telemetry::FieldType::App, "App");
85 SerializeSection(Telemetry::FieldType::Session, "Session");
86 SerializeSection(Telemetry::FieldType::Performance, "Performance");
87 SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
88 SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
89 SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
90
91 auto content = TopSection().dump();
92 // Send the telemetry async but don't handle the errors since they were written to the log
93 Common::DetachedTasks::AddTask(
94 [host{this->host}, username{this->username}, token{this->token}, content]() {
95 Client{host, username, token}.PostJson("/telemetry", content, true);
96 });
97}
98
99} // 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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <string>
9#include <json.hpp>
10#include "common/telemetry.h"
11#include "common/web_result.h"
12
13namespace WebService {
14
15/**
16 * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
17 * yuzu web service
18 */
19class TelemetryJson : public Telemetry::VisitorInterface {
20public:
21 TelemetryJson(const std::string& host, const std::string& username, const std::string& token);
22 ~TelemetryJson();
23
24 void Visit(const Telemetry::Field<bool>& field) override;
25 void Visit(const Telemetry::Field<double>& field) override;
26 void Visit(const Telemetry::Field<float>& field) override;
27 void Visit(const Telemetry::Field<u8>& field) override;
28 void Visit(const Telemetry::Field<u16>& field) override;
29 void Visit(const Telemetry::Field<u32>& field) override;
30 void Visit(const Telemetry::Field<u64>& field) override;
31 void Visit(const Telemetry::Field<s8>& field) override;
32 void Visit(const Telemetry::Field<s16>& field) override;
33 void Visit(const Telemetry::Field<s32>& field) override;
34 void Visit(const Telemetry::Field<s64>& field) override;
35 void Visit(const Telemetry::Field<std::string>& field) override;
36 void Visit(const Telemetry::Field<const char*>& field) override;
37 void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override;
38
39 void Complete() override;
40
41private:
42 nlohmann::json& TopSection() {
43 return sections[static_cast<u8>(Telemetry::FieldType::None)];
44 }
45
46 template <class T>
47 void Serialize(Telemetry::FieldType type, const std::string& name, T value);
48
49 void SerializeSection(Telemetry::FieldType type, const std::string& name);
50
51 nlohmann::json output;
52 std::array<nlohmann::json, 7> sections;
53 std::string host;
54 std::string username;
55 std::string token;
56};
57
58} // 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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <json.hpp>
6#include "web_service/verify_login.h"
7#include "web_service/web_backend.h"
8
9namespace WebService {
10
11bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token) {
12 Client client(host, username, token);
13 auto reply = client.GetJson("/profile", false).returned_data;
14 if (reply.empty()) {
15 return false;
16 }
17 nlohmann::json json = nlohmann::json::parse(reply);
18 const auto iter = json.find("username");
19
20 if (iter == json.end()) {
21 return username.empty();
22 }
23
24 return username == *iter;
25}
26
27} // 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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <future>
9#include <string>
10
11namespace WebService {
12
13/**
14 * Checks if username and token is valid
15 * @param host the web API URL
16 * @param username yuzu username to use for authentication.
17 * @param token yuzu token to use for authentication.
18 * @returns a bool indicating whether the verification succeeded
19 */
20bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token);
21
22} // 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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstdlib>
6#include <string>
7#include <thread>
8#include <LUrlParser.h>
9#include "common/logging/log.h"
10#include "common/web_result.h"
11#include "core/settings.h"
12#include "web_service/web_backend.h"
13
14namespace WebService {
15
16constexpr std::array<const char, 1> API_VERSION{'1'};
17
18constexpr u32 HTTP_PORT = 80;
19constexpr u32 HTTPS_PORT = 443;
20
21constexpr u32 TIMEOUT_SECONDS = 30;
22
23Client::JWTCache Client::jwt_cache{};
24
25Client::Client(const std::string& host, const std::string& username, const std::string& token)
26 : host(host), username(username), token(token) {
27 std::lock_guard<std::mutex> lock(jwt_cache.mutex);
28 if (username == jwt_cache.username && token == jwt_cache.token) {
29 jwt = jwt_cache.jwt;
30 }
31}
32
33Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
34 const std::string& data, const std::string& jwt,
35 const std::string& username, const std::string& token) {
36 if (cli == nullptr) {
37 auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
38 int port;
39 if (parsedUrl.m_Scheme == "http") {
40 if (!parsedUrl.GetPort(&port)) {
41 port = HTTP_PORT;
42 }
43 cli =
44 std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
45 } else if (parsedUrl.m_Scheme == "https") {
46 if (!parsedUrl.GetPort(&port)) {
47 port = HTTPS_PORT;
48 }
49 cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
50 TIMEOUT_SECONDS);
51 } else {
52 LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
53 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
54 }
55 }
56 if (cli == nullptr) {
57 LOG_ERROR(WebService, "Invalid URL {}", host + path);
58 return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
59 }
60
61 httplib::Headers params;
62 if (!jwt.empty()) {
63 params = {
64 {std::string("Authorization"), fmt::format("Bearer {}", jwt)},
65 };
66 } else if (!username.empty()) {
67 params = {
68 {std::string("x-username"), username},
69 {std::string("x-token"), token},
70 };
71 }
72
73 params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end()));
74 if (method != "GET") {
75 params.emplace(std::string("Content-Type"), std::string("application/json"));
76 };
77
78 httplib::Request request;
79 request.method = method;
80 request.path = path;
81 request.headers = params;
82 request.body = data;
83
84 httplib::Response response;
85
86 if (!cli->send(request, response)) {
87 LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
88 return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
89 }
90
91 if (response.status >= 400) {
92 LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
93 response.status);
94 return Common::WebResult{Common::WebResult::Code::HttpError,
95 std::to_string(response.status)};
96 }
97
98 auto content_type = response.headers.find("content-type");
99
100 if (content_type == response.headers.end()) {
101 LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
102 return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
103 }
104
105 if (content_type->second.find("application/json") == std::string::npos &&
106 content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
107 LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
108 content_type->second);
109 return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
110 }
111 return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
112}
113
114void Client::UpdateJWT() {
115 if (!username.empty() && !token.empty()) {
116 auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
117 if (result.result_code != Common::WebResult::Code::Success) {
118 LOG_ERROR(WebService, "UpdateJWT failed");
119 } else {
120 std::lock_guard<std::mutex> lock(jwt_cache.mutex);
121 jwt_cache.username = username;
122 jwt_cache.token = token;
123 jwt_cache.jwt = jwt = result.returned_data;
124 }
125 }
126}
127
128Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
129 const std::string& data, bool allow_anonymous) {
130 if (jwt.empty()) {
131 UpdateJWT();
132 }
133
134 if (jwt.empty() && !allow_anonymous) {
135 LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
136 return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
137 }
138
139 auto result = GenericJson(method, path, data, jwt);
140 if (result.result_string == "401") {
141 // Try again with new JWT
142 UpdateJWT();
143 result = GenericJson(method, path, data, jwt);
144 }
145
146 return result;
147}
148
149} // 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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <mutex>
9#include <string>
10#include <tuple>
11#include <httplib.h>
12#include "common/common_types.h"
13#include "common/web_result.h"
14
15namespace httplib {
16class Client;
17}
18
19namespace WebService {
20
21class Client {
22public:
23 Client(const std::string& host, const std::string& username, const std::string& token);
24
25 /**
26 * Posts JSON to the specified path.
27 * @param path the URL segment after the host address.
28 * @param data String of JSON data to use for the body of the POST request.
29 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
30 * @return the result of the request.
31 */
32 Common::WebResult PostJson(const std::string& path, const std::string& data,
33 bool allow_anonymous) {
34 return GenericJson("POST", path, data, allow_anonymous);
35 }
36
37 /**
38 * Gets JSON from the specified path.
39 * @param path the URL segment after the host address.
40 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
41 * @return the result of the request.
42 */
43 Common::WebResult GetJson(const std::string& path, bool allow_anonymous) {
44 return GenericJson("GET", path, "", allow_anonymous);
45 }
46
47 /**
48 * Deletes JSON to the specified path.
49 * @param path the URL segment after the host address.
50 * @param data String of JSON data to use for the body of the DELETE request.
51 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
52 * @return the result of the request.
53 */
54 Common::WebResult DeleteJson(const std::string& path, const std::string& data,
55 bool allow_anonymous) {
56 return GenericJson("DELETE", path, data, allow_anonymous);
57 }
58
59private:
60 /// A generic function handles POST, GET and DELETE request together
61 Common::WebResult GenericJson(const std::string& method, const std::string& path,
62 const std::string& data, bool allow_anonymous);
63
64 /**
65 * A generic function with explicit authentication method specified
66 * JWT is used if the jwt parameter is not empty
67 * username + token is used if jwt is empty but username and token are not empty
68 * anonymous if all of jwt, username and token are empty
69 */
70 Common::WebResult GenericJson(const std::string& method, const std::string& path,
71 const std::string& data, const std::string& jwt = "",
72 const std::string& username = "", const std::string& token = "");
73
74 // Retrieve a new JWT from given username and token
75 void UpdateJWT();
76
77 std::string host;
78 std::string username;
79 std::string token;
80 std::string jwt;
81 std::unique_ptr<httplib::Client> cli;
82
83 struct JWTCache {
84 std::mutex mutex;
85 std::string username;
86 std::string token;
87 std::string jwt;
88 };
89 static JWTCache jwt_cache;
90};
91
92} // 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
29 configuration/configure_input.h 29 configuration/configure_input.h
30 configuration/configure_system.cpp 30 configuration/configure_system.cpp
31 configuration/configure_system.h 31 configuration/configure_system.h
32 configuration/configure_web.cpp
33 configuration/configure_web.h
32 debugger/graphics/graphics_breakpoint_observer.cpp 34 debugger/graphics/graphics_breakpoint_observer.cpp
33 debugger/graphics/graphics_breakpoint_observer.h 35 debugger/graphics/graphics_breakpoint_observer.h
34 debugger/graphics/graphics_breakpoints.cpp 36 debugger/graphics/graphics_breakpoints.cpp
@@ -42,6 +44,7 @@ add_executable(yuzu
42 debugger/profiler.h 44 debugger/profiler.h
43 debugger/wait_tree.cpp 45 debugger/wait_tree.cpp
44 debugger/wait_tree.h 46 debugger/wait_tree.h
47 discord.h
45 game_list.cpp 48 game_list.cpp
46 game_list.h 49 game_list.h
47 game_list_p.h 50 game_list_p.h
@@ -57,6 +60,8 @@ add_executable(yuzu
57 util/spinbox.h 60 util/spinbox.h
58 util/util.cpp 61 util/util.cpp
59 util/util.h 62 util/util.h
63 compatdb.cpp
64 compatdb.h
60 yuzu.rc 65 yuzu.rc
61) 66)
62 67
@@ -70,8 +75,10 @@ set(UIS
70 configuration/configure_graphics.ui 75 configuration/configure_graphics.ui
71 configuration/configure_input.ui 76 configuration/configure_input.ui
72 configuration/configure_system.ui 77 configuration/configure_system.ui
78 configuration/configure_web.ui
73 hotkeys.ui 79 hotkeys.ui
74 main.ui 80 main.ui
81 compatdb.ui
75) 82)
76 83
77file(GLOB COMPAT_LIST 84file(GLOB COMPAT_LIST
@@ -113,6 +120,19 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)
113target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets) 120target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
114target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) 121target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
115 122
123if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
124 add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING)
125endif()
126
127if (USE_DISCORD_PRESENCE)
128 target_sources(yuzu PUBLIC
129 discord_impl.cpp
130 discord_impl.h
131 )
132 target_link_libraries(yuzu PRIVATE discord-rpc)
133 target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
134endif()
135
116if(UNIX AND NOT APPLE) 136if(UNIX AND NOT APPLE)
117 install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") 137 install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
118endif() 138endif()
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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QButtonGroup>
6#include <QMessageBox>
7#include <QPushButton>
8#include "common/logging/log.h"
9#include "common/telemetry.h"
10#include "core/core.h"
11#include "core/telemetry_session.h"
12#include "ui_compatdb.h"
13#include "yuzu/compatdb.h"
14
15CompatDB::CompatDB(QWidget* parent)
16 : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
17 ui{std::make_unique<Ui::CompatDB>()} {
18 ui->setupUi(this);
19 connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext);
20 connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext);
21 connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext);
22 connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext);
23 connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext);
24 connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext);
25 connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit);
26}
27
28CompatDB::~CompatDB() = default;
29
30enum class CompatDBPage {
31 Intro = 0,
32 Selection = 1,
33 Final = 2,
34};
35
36void CompatDB::Submit() {
37 QButtonGroup* compatibility = new QButtonGroup(this);
38 compatibility->addButton(ui->radioButton_Perfect, 0);
39 compatibility->addButton(ui->radioButton_Great, 1);
40 compatibility->addButton(ui->radioButton_Okay, 2);
41 compatibility->addButton(ui->radioButton_Bad, 3);
42 compatibility->addButton(ui->radioButton_IntroMenu, 4);
43 compatibility->addButton(ui->radioButton_WontBoot, 5);
44 switch ((static_cast<CompatDBPage>(currentId()))) {
45 case CompatDBPage::Selection:
46 if (compatibility->checkedId() == -1) {
47 button(NextButton)->setEnabled(false);
48 }
49 break;
50 case CompatDBPage::Final:
51 LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
52 Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility",
53 compatibility->checkedId());
54 // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
55 // workaround
56 button(QWizard::CancelButton)->setVisible(false);
57 break;
58 default:
59 LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
60 }
61}
62
63void CompatDB::EnableNext() {
64 button(NextButton)->setEnabled(true);
65}
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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QWizard>
9
10namespace Ui {
11class CompatDB;
12}
13
14class CompatDB : public QWizard {
15 Q_OBJECT
16
17public:
18 explicit CompatDB(QWidget* parent = nullptr);
19 ~CompatDB();
20
21private:
22 std::unique_ptr<Ui::CompatDB> ui;
23
24 void Submit();
25 void EnableNext();
26};
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 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>CompatDB</class>
4 <widget class="QWizard" name="CompatDB">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>600</width>
10 <height>482</height>
11 </rect>
12 </property>
13 <property name="minimumSize">
14 <size>
15 <width>500</width>
16 <height>410</height>
17 </size>
18 </property>
19 <property name="windowTitle">
20 <string>Report Compatibility</string>
21 </property>
22 <property name="options">
23 <set>QWizard::DisabledBackButtonOnLastPage|QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage</set>
24 </property>
25 <widget class="QWizardPage" name="wizard_Info">
26 <property name="title">
27 <string>Report Game Compatibility</string>
28 </property>
29 <attribute name="pageId">
30 <string notr="true">0</string>
31 </attribute>
32 <layout class="QVBoxLayout" name="verticalLayout">
33 <item>
34 <widget class="QLabel" name="lbl_Spiel">
35 <property name="text">
36 <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>
37 </property>
38 <property name="wordWrap">
39 <bool>true</bool>
40 </property>
41 <property name="openExternalLinks">
42 <bool>true</bool>
43 </property>
44 </widget>
45 </item>
46 <item>
47 <spacer name="verticalSpacer_2">
48 <property name="orientation">
49 <enum>Qt::Vertical</enum>
50 </property>
51 <property name="sizeHint" stdset="0">
52 <size>
53 <width>20</width>
54 <height>0</height>
55 </size>
56 </property>
57 </spacer>
58 </item>
59 </layout>
60 </widget>
61 <widget class="QWizardPage" name="wizard_Report">
62 <property name="title">
63 <string>Report Game Compatibility</string>
64 </property>
65 <attribute name="pageId">
66 <string notr="true">1</string>
67 </attribute>
68 <layout class="QFormLayout" name="formLayout">
69 <item row="2" column="0">
70 <widget class="QRadioButton" name="radioButton_Perfect">
71 <property name="text">
72 <string>Perfect</string>
73 </property>
74 </widget>
75 </item>
76 <item row="2" column="1">
77 <widget class="QLabel" name="lbl_Perfect">
78 <property name="text">
79 <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>
80 </property>
81 <property name="wordWrap">
82 <bool>true</bool>
83 </property>
84 </widget>
85 </item>
86 <item row="4" column="0">
87 <widget class="QRadioButton" name="radioButton_Great">
88 <property name="text">
89 <string>Great </string>
90 </property>
91 </widget>
92 </item>
93 <item row="4" column="1">
94 <widget class="QLabel" name="lbl_Great">
95 <property name="text">
96 <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>
97 </property>
98 <property name="wordWrap">
99 <bool>true</bool>
100 </property>
101 </widget>
102 </item>
103 <item row="5" column="0">
104 <widget class="QRadioButton" name="radioButton_Okay">
105 <property name="text">
106 <string>Okay</string>
107 </property>
108 </widget>
109 </item>
110 <item row="5" column="1">
111 <widget class="QLabel" name="lbl_Okay">
112 <property name="text">
113 <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>
114 </property>
115 <property name="wordWrap">
116 <bool>true</bool>
117 </property>
118 </widget>
119 </item>
120 <item row="6" column="0">
121 <widget class="QRadioButton" name="radioButton_Bad">
122 <property name="text">
123 <string>Bad</string>
124 </property>
125 </widget>
126 </item>
127 <item row="6" column="1">
128 <widget class="QLabel" name="lbl_Bad">
129 <property name="text">
130 <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>
131 </property>
132 <property name="wordWrap">
133 <bool>true</bool>
134 </property>
135 </widget>
136 </item>
137 <item row="7" column="0">
138 <widget class="QRadioButton" name="radioButton_IntroMenu">
139 <property name="text">
140 <string>Intro/Menu</string>
141 </property>
142 </widget>
143 </item>
144 <item row="7" column="1">
145 <widget class="QLabel" name="lbl_IntroMenu">
146 <property name="text">
147 <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>
148 </property>
149 <property name="wordWrap">
150 <bool>true</bool>
151 </property>
152 </widget>
153 </item>
154 <item row="8" column="0">
155 <widget class="QRadioButton" name="radioButton_WontBoot">
156 <property name="text">
157 <string>Won't Boot</string>
158 </property>
159 <property name="checkable">
160 <bool>true</bool>
161 </property>
162 <property name="checked">
163 <bool>false</bool>
164 </property>
165 </widget>
166 </item>
167 <item row="8" column="1">
168 <widget class="QLabel" name="lbl_WontBoot">
169 <property name="text">
170 <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>
171 </property>
172 </widget>
173 </item>
174 <item row="0" column="0" colspan="2">
175 <widget class="QLabel" name="lbl_Independent">
176 <property name="font">
177 <font>
178 <pointsize>10</pointsize>
179 </font>
180 </property>
181 <property name="text">
182 <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>
183 </property>
184 <property name="wordWrap">
185 <bool>true</bool>
186 </property>
187 </widget>
188 </item>
189 <item row="1" column="0" colspan="2">
190 <spacer name="verticalSpacer">
191 <property name="orientation">
192 <enum>Qt::Vertical</enum>
193 </property>
194 <property name="sizeHint" stdset="0">
195 <size>
196 <width>20</width>
197 <height>0</height>
198 </size>
199 </property>
200 </spacer>
201 </item>
202 </layout>
203 </widget>
204 <widget class="QWizardPage" name="wizard_ThankYou">
205 <property name="title">
206 <string>Thank you for your submission!</string>
207 </property>
208 <attribute name="pageId">
209 <string notr="true">2</string>
210 </attribute>
211 </widget>
212 </widget>
213 <resources/>
214 <connections/>
215</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() {
136 Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); 136 Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
137 qt_config->endGroup(); 137 qt_config->endGroup();
138 138
139 qt_config->beginGroup("WebService");
140 Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
141 Settings::values.web_api_url =
142 qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
143 Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString();
144 Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
145 qt_config->endGroup();
146
139 qt_config->beginGroup("UI"); 147 qt_config->beginGroup("UI");
140 UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); 148 UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
149 UISettings::values.enable_discord_presence =
150 qt_config->value("enable_discord_presence", true).toBool();
141 151
142 qt_config->beginGroup("UIGameList"); 152 qt_config->beginGroup("UIGameList");
143 UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); 153 UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
@@ -261,8 +271,16 @@ void Config::SaveValues() {
261 qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); 271 qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
262 qt_config->endGroup(); 272 qt_config->endGroup();
263 273
274 qt_config->beginGroup("WebService");
275 qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
276 qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url));
277 qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
278 qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
279 qt_config->endGroup();
280
264 qt_config->beginGroup("UI"); 281 qt_config->beginGroup("UI");
265 qt_config->setValue("theme", UISettings::values.theme); 282 qt_config->setValue("theme", UISettings::values.theme);
283 qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
266 284
267 qt_config->beginGroup("UIGameList"); 285 qt_config->beginGroup("UIGameList");
268 qt_config->setValue("show_unknown", UISettings::values.show_unknown); 286 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 @@
54 <string>Debug</string> 54 <string>Debug</string>
55 </attribute> 55 </attribute>
56 </widget> 56 </widget>
57 <widget class="ConfigureWeb" name="webTab">
58 <attribute name="title">
59 <string>Web</string>
60 </attribute>
61 </widget>
57 </widget> 62 </widget>
58 </item> 63 </item>
59 <item> 64 <item>
@@ -108,6 +113,12 @@
108 <header>configuration/configure_graphics.h</header> 113 <header>configuration/configure_graphics.h</header>
109 <container>1</container> 114 <container>1</container>
110 </customwidget> 115 </customwidget>
116 <customwidget>
117 <class>ConfigureWeb</class>
118 <extends>QWidget</extends>
119 <header>configuration/configure_web.h</header>
120 <container>1</container>
121 </customwidget>
111 </customwidgets> 122 </customwidgets>
112 <resources/> 123 <resources/>
113 <connections> 124 <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)
21 ui->output_sink_combo_box->addItem(sink_detail.id); 21 ui->output_sink_combo_box->addItem(sink_detail.id);
22 } 22 }
23 23
24 connect(ui->volume_slider, &QSlider::valueChanged, [this] { 24 connect(ui->volume_slider, &QSlider::valueChanged, this,
25 ui->volume_indicator->setText(tr("%1 %").arg(ui->volume_slider->sliderPosition())); 25 &ConfigureAudio::setVolumeIndicatorText);
26 });
27 26
28 this->setConfiguration(); 27 this->setConfiguration();
29 connect(ui->output_sink_combo_box, 28 connect(ui->output_sink_combo_box,
@@ -37,32 +36,48 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
37ConfigureAudio::~ConfigureAudio() = default; 36ConfigureAudio::~ConfigureAudio() = default;
38 37
39void ConfigureAudio::setConfiguration() { 38void ConfigureAudio::setConfiguration() {
39 setOutputSinkFromSinkID();
40
41 // The device list cannot be pre-populated (nor listed) until the output sink is known.
42 updateAudioDevices(ui->output_sink_combo_box->currentIndex());
43
44 setAudioDeviceFromDeviceID();
45
46 ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
47 ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum());
48 setVolumeIndicatorText(ui->volume_slider->sliderPosition());
49}
50
51void ConfigureAudio::setOutputSinkFromSinkID() {
40 int new_sink_index = 0; 52 int new_sink_index = 0;
53
54 const QString sink_id = QString::fromStdString(Settings::values.sink_id);
41 for (int index = 0; index < ui->output_sink_combo_box->count(); index++) { 55 for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
42 if (ui->output_sink_combo_box->itemText(index).toStdString() == Settings::values.sink_id) { 56 if (ui->output_sink_combo_box->itemText(index) == sink_id) {
43 new_sink_index = index; 57 new_sink_index = index;
44 break; 58 break;
45 } 59 }
46 } 60 }
47 ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
48 61
49 ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); 62 ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
50 63}
51 // The device list cannot be pre-populated (nor listed) until the output sink is known.
52 updateAudioDevices(new_sink_index);
53 64
65void ConfigureAudio::setAudioDeviceFromDeviceID() {
54 int new_device_index = -1; 66 int new_device_index = -1;
67
68 const QString device_id = QString::fromStdString(Settings::values.audio_device_id);
55 for (int index = 0; index < ui->audio_device_combo_box->count(); index++) { 69 for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
56 if (ui->audio_device_combo_box->itemText(index).toStdString() == 70 if (ui->audio_device_combo_box->itemText(index) == device_id) {
57 Settings::values.audio_device_id) {
58 new_device_index = index; 71 new_device_index = index;
59 break; 72 break;
60 } 73 }
61 } 74 }
75
62 ui->audio_device_combo_box->setCurrentIndex(new_device_index); 76 ui->audio_device_combo_box->setCurrentIndex(new_device_index);
77}
63 78
64 ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum()); 79void ConfigureAudio::setVolumeIndicatorText(int percentage) {
65 ui->volume_indicator->setText(tr("%1 %").arg(ui->volume_slider->sliderPosition())); 80 ui->volume_indicator->setText(tr("%1%", "Volume percentage (e.g. 50%)").arg(percentage));
66} 81}
67 82
68void ConfigureAudio::applyConfiguration() { 83void ConfigureAudio::applyConfiguration() {
@@ -81,10 +96,10 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
81 ui->audio_device_combo_box->clear(); 96 ui->audio_device_combo_box->clear();
82 ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); 97 ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);
83 98
84 std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); 99 const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
85 std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices(); 100 const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices();
86 for (const auto& device : device_list) { 101 for (const auto& device : device_list) {
87 ui->audio_device_combo_box->addItem(device.c_str()); 102 ui->audio_device_combo_box->addItem(QString::fromStdString(device));
88 } 103 }
89} 104}
90 105
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:
26 26
27private: 27private:
28 void setConfiguration(); 28 void setConfiguration();
29 void setOutputSinkFromSinkID();
30 void setAudioDeviceFromDeviceID();
31 void setVolumeIndicatorText(int percentage);
29 32
30 std::unique_ptr<Ui::ConfigureAudio> ui; 33 std::unique_ptr<Ui::ConfigureAudio> ui;
31}; 34};
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() {
27 ui->graphicsTab->applyConfiguration(); 27 ui->graphicsTab->applyConfiguration();
28 ui->audioTab->applyConfiguration(); 28 ui->audioTab->applyConfiguration();
29 ui->debugTab->applyConfiguration(); 29 ui->debugTab->applyConfiguration();
30 ui->webTab->applyConfiguration();
30 Settings::Apply(); 31 Settings::Apply();
31} 32}
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)
13 13
14 ui->setupUi(this); 14 ui->setupUi(this);
15 15
16 for (auto theme : UISettings::themes) { 16 for (const auto& theme : UISettings::themes) {
17 ui->theme_combobox->addItem(theme.first, theme.second); 17 ui->theme_combobox->addItem(theme.first, theme.second);
18 } 18 }
19 19
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 @@
8#include "ui_configure_graphics.h" 8#include "ui_configure_graphics.h"
9#include "yuzu/configuration/configure_graphics.h" 9#include "yuzu/configuration/configure_graphics.h"
10 10
11ConfigureGraphics::ConfigureGraphics(QWidget* parent) 11namespace {
12 : QWidget(parent), ui(new Ui::ConfigureGraphics) {
13
14 ui->setupUi(this);
15 this->setConfiguration();
16
17 ui->frame_limit->setEnabled(Settings::values.use_frame_limit);
18 connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit,
19 &QSpinBox::setEnabled);
20 connect(ui->bg_button, &QPushButton::clicked, this, [this] {
21 const QColor new_bg_color = QColorDialog::getColor(bg_color);
22 if (!new_bg_color.isValid())
23 return;
24 bg_color = new_bg_color;
25 ui->bg_button->setStyleSheet(
26 QString("QPushButton { background-color: %1 }").arg(bg_color.name()));
27 });
28}
29
30ConfigureGraphics::~ConfigureGraphics() = default;
31
32enum class Resolution : int { 12enum class Resolution : int {
33 Auto, 13 Auto,
34 Scale1x, 14 Scale1x,
@@ -67,6 +47,28 @@ Resolution FromResolutionFactor(float factor) {
67 } 47 }
68 return Resolution::Auto; 48 return Resolution::Auto;
69} 49}
50} // Anonymous namespace
51
52ConfigureGraphics::ConfigureGraphics(QWidget* parent)
53 : QWidget(parent), ui(new Ui::ConfigureGraphics) {
54
55 ui->setupUi(this);
56 this->setConfiguration();
57
58 ui->frame_limit->setEnabled(Settings::values.use_frame_limit);
59 connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit,
60 &QSpinBox::setEnabled);
61 connect(ui->bg_button, &QPushButton::clicked, this, [this] {
62 const QColor new_bg_color = QColorDialog::getColor(bg_color);
63 if (!new_bg_color.isValid())
64 return;
65 bg_color = new_bg_color;
66 ui->bg_button->setStyleSheet(
67 QString("QPushButton { background-color: %1 }").arg(bg_color.name()));
68 });
69}
70
71ConfigureGraphics::~ConfigureGraphics() = default;
70 72
71void ConfigureGraphics::setConfiguration() { 73void ConfigureGraphics::setConfiguration() {
72 ui->resolution_factor_combobox->setCurrentIndex( 74 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 @@
5#include <algorithm> 5#include <algorithm>
6#include <memory> 6#include <memory>
7#include <utility> 7#include <utility>
8#include <QMenu>
8#include <QMessageBox> 9#include <QMessageBox>
9#include <QTimer> 10#include <QTimer>
10#include "common/param_package.h" 11#include "common/param_package.h"
@@ -128,33 +129,68 @@ ConfigureInput::ConfigureInput(QWidget* parent)
128 analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; 129 analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
129 130
130 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { 131 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
131 if (button_map[button_id]) 132 if (!button_map[button_id])
132 connect(button_map[button_id], &QPushButton::released, [=]() { 133 continue;
133 handleClick( 134 button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
134 button_map[button_id], 135 connect(button_map[button_id], &QPushButton::released, [=]() {
135 [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, 136 handleClick(
136 InputCommon::Polling::DeviceType::Button); 137 button_map[button_id],
137 }); 138 [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
139 InputCommon::Polling::DeviceType::Button);
140 });
141 connect(button_map[button_id], &QPushButton::customContextMenuRequested,
142 [=](const QPoint& menu_location) {
143 QMenu context_menu;
144 context_menu.addAction(tr("Clear"), [&] {
145 buttons_param[button_id].Clear();
146 button_map[button_id]->setText(tr("[not set]"));
147 });
148 context_menu.addAction(tr("Restore Default"), [&] {
149 buttons_param[button_id] = Common::ParamPackage{
150 InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
151 button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
152 });
153 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
154 });
138 } 155 }
139 156
140 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { 157 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
141 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { 158 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
142 if (analog_map_buttons[analog_id][sub_button_id] != nullptr) { 159 if (!analog_map_buttons[analog_id][sub_button_id])
143 connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, 160 continue;
144 [=]() { 161 analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
145 handleClick(analog_map_buttons[analog_id][sub_button_id], 162 Qt::CustomContextMenu);
146 [=](const Common::ParamPackage& params) { 163 connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() {
147 SetAnalogButton(params, analogs_param[analog_id], 164 handleClick(analog_map_buttons[analog_id][sub_button_id],
148 analog_sub_buttons[sub_button_id]); 165 [=](const Common::ParamPackage& params) {
149 }, 166 SetAnalogButton(params, analogs_param[analog_id],
150 InputCommon::Polling::DeviceType::Button); 167 analog_sub_buttons[sub_button_id]);
168 },
169 InputCommon::Polling::DeviceType::Button);
170 });
171 connect(analog_map_buttons[analog_id][sub_button_id],
172 &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
173 QMenu context_menu;
174 context_menu.addAction(tr("Clear"), [&] {
175 analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
176 analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
151 }); 177 });
152 } 178 context_menu.addAction(tr("Restore Default"), [&] {
179 Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
180 Config::default_analogs[analog_id][sub_button_id])};
181 SetAnalogButton(params, analogs_param[analog_id],
182 analog_sub_buttons[sub_button_id]);
183 analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
184 analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
185 });
186 context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
187 menu_location));
188 });
153 } 189 }
154 connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { 190 connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {
155 QMessageBox::information( 191 QMessageBox::information(this, tr("Information"),
156 this, "Information", 192 tr("After pressing OK, first move your joystick horizontally, "
157 "After pressing OK, first move your joystick horizontally, and then vertically."); 193 "and then vertically."));
158 handleClick( 194 handleClick(
159 analog_map_stick[analog_id], 195 analog_map_stick[analog_id],
160 [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, 196 [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
@@ -162,6 +198,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
162 }); 198 });
163 } 199 }
164 200
201 connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
165 connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); 202 connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
166 203
167 timeout_timer->setSingleShot(true); 204 timeout_timer->setSingleShot(true);
@@ -215,7 +252,21 @@ void ConfigureInput::restoreDefaults() {
215 } 252 }
216 } 253 }
217 updateButtonLabels(); 254 updateButtonLabels();
218 applyConfiguration(); 255}
256
257void ConfigureInput::ClearAll() {
258 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
259 if (button_map[button_id] && button_map[button_id]->isEnabled())
260 buttons_param[button_id].Clear();
261 }
262 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
263 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
264 if (analog_map_buttons[analog_id][sub_button_id] &&
265 analog_map_buttons[analog_id][sub_button_id]->isEnabled())
266 analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
267 }
268 }
269 updateButtonLabels();
219} 270}
220 271
221void ConfigureInput::updateButtonLabels() { 272void 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:
72 void loadConfiguration(); 72 void loadConfiguration();
73 /// Restore all buttons to their default values. 73 /// Restore all buttons to their default values.
74 void restoreDefaults(); 74 void restoreDefaults();
75 /// Clear all input configuration
76 void ClearAll();
77
75 /// Update UI to reflect current configuration. 78 /// Update UI to reflect current configuration.
76 void updateButtonLabels(); 79 void updateButtonLabels();
77 80
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>
695 </spacer> 695 </spacer>
696 </item> 696 </item>
697 <item> 697 <item>
698 <widget class="QPushButton" name="buttonClearAll">
699 <property name="sizePolicy">
700 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
701 <horstretch>0</horstretch>
702 <verstretch>0</verstretch>
703 </sizepolicy>
704 </property>
705 <property name="sizeIncrement">
706 <size>
707 <width>0</width>
708 <height>0</height>
709 </size>
710 </property>
711 <property name="baseSize">
712 <size>
713 <width>0</width>
714 <height>0</height>
715 </size>
716 </property>
717 <property name="layoutDirection">
718 <enum>Qt::LeftToRight</enum>
719 </property>
720 <property name="text">
721 <string>Clear All</string>
722 </property>
723 </widget>
724 </item>
725 <item>
698 <widget class="QPushButton" name="buttonRestoreDefaults"> 726 <widget class="QPushButton" name="buttonRestoreDefaults">
699 <property name="sizePolicy"> 727 <property name="sizePolicy">
700 <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> 728 <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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QIcon>
6#include <QMessageBox>
7#include <QtConcurrent/QtConcurrentRun>
8#include "core/settings.h"
9#include "core/telemetry_session.h"
10#include "ui_configure_web.h"
11#include "yuzu/configuration/configure_web.h"
12#include "yuzu/ui_settings.h"
13
14ConfigureWeb::ConfigureWeb(QWidget* parent)
15 : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
16 ui->setupUi(this);
17 connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
18 &ConfigureWeb::RefreshTelemetryID);
19 connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
20 connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified);
21
22#ifndef USE_DISCORD_PRESENCE
23 ui->discord_group->setVisible(false);
24#endif
25 this->setConfiguration();
26}
27
28ConfigureWeb::~ConfigureWeb() = default;
29
30void ConfigureWeb::setConfiguration() {
31 ui->web_credentials_disclaimer->setWordWrap(true);
32 ui->telemetry_learn_more->setOpenExternalLinks(true);
33 ui->telemetry_learn_more->setText(
34 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'><span style=\"text-decoration: "
35 "underline; color:#039be5;\">Learn more</span></a>"));
36
37 ui->web_signup_link->setOpenExternalLinks(true);
38 ui->web_signup_link->setText(
39 tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; "
40 "color:#039be5;\">Sign up</span></a>"));
41 ui->web_token_info_link->setOpenExternalLinks(true);
42 ui->web_token_info_link->setText(
43 tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: "
44 "underline; color:#039be5;\">What is my token?</span></a>"));
45
46 ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
47 ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username));
48 ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token));
49 // Connect after setting the values, to avoid calling OnLoginChanged now
50 connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
51 connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
52 ui->label_telemetry_id->setText(
53 tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
54 user_verified = true;
55
56 ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence);
57}
58
59void ConfigureWeb::applyConfiguration() {
60 Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
61 UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked();
62 if (user_verified) {
63 Settings::values.yuzu_username = ui->edit_username->text().toStdString();
64 Settings::values.yuzu_token = ui->edit_token->text().toStdString();
65 } else {
66 QMessageBox::warning(this, tr("Username and token not verified"),
67 tr("Username and token were not verified. The changes to your "
68 "username and/or token have not been saved."));
69 }
70}
71
72void ConfigureWeb::RefreshTelemetryID() {
73 const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
74 ui->label_telemetry_id->setText(
75 tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
76}
77
78void ConfigureWeb::OnLoginChanged() {
79 if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
80 user_verified = true;
81 ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
82 ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
83 } else {
84 user_verified = false;
85 ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
86 ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
87 }
88}
89
90void ConfigureWeb::VerifyLogin() {
91 ui->button_verify_login->setDisabled(true);
92 ui->button_verify_login->setText(tr("Verifying"));
93 verify_watcher.setFuture(
94 QtConcurrent::run([this, username = ui->edit_username->text().toStdString(),
95 token = ui->edit_token->text().toStdString()]() {
96 return Core::VerifyLogin(username, token);
97 }));
98}
99
100void ConfigureWeb::OnLoginVerified() {
101 ui->button_verify_login->setEnabled(true);
102 ui->button_verify_login->setText(tr("Verify"));
103 if (verify_watcher.result()) {
104 user_verified = true;
105 ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
106 ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16));
107 } else {
108 ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
109 ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16));
110 QMessageBox::critical(
111 this, tr("Verification failed"),
112 tr("Verification failed. Check that you have entered your username and token "
113 "correctly, and that your internet connection is working."));
114 }
115}
116
117void ConfigureWeb::retranslateUi() {
118 ui->retranslateUi(this);
119}
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 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QFutureWatcher>
9#include <QWidget>
10
11namespace Ui {
12class ConfigureWeb;
13}
14
15class ConfigureWeb : public QWidget {
16 Q_OBJECT
17
18public:
19 explicit ConfigureWeb(QWidget* parent = nullptr);
20 ~ConfigureWeb();
21
22 void applyConfiguration();
23 void retranslateUi();
24
25public slots:
26 void RefreshTelemetryID();
27 void OnLoginChanged();
28 void VerifyLogin();
29 void OnLoginVerified();
30
31private:
32 void setConfiguration();
33
34 bool user_verified = true;
35 QFutureWatcher<bool> verify_watcher;
36
37 std::unique_ptr<Ui::ConfigureWeb> ui;
38};
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 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureWeb</class>
4 <widget class="QWidget" name="ConfigureWeb">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>926</width>
10 <height>561</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Form</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3">
19 <item>
20 <widget class="QGroupBox" name="groupBoxWebConfig">
21 <property name="title">
22 <string>yuzu Web Service</string>
23 </property>
24 <layout class="QVBoxLayout" name="verticalLayoutYuzuWebService">
25 <item>
26 <widget class="QLabel" name="web_credentials_disclaimer">
27 <property name="text">
28 <string>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</string>
29 </property>
30 </widget>
31 </item>
32 <item>
33 <layout class="QGridLayout" name="gridLayoutYuzuUsername">
34 <item row="2" column="3">
35 <widget class="QPushButton" name="button_verify_login">
36 <property name="sizePolicy">
37 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
38 <horstretch>0</horstretch>
39 <verstretch>0</verstretch>
40 </sizepolicy>
41 </property>
42 <property name="layoutDirection">
43 <enum>Qt::RightToLeft</enum>
44 </property>
45 <property name="text">
46 <string>Verify</string>
47 </property>
48 </widget>
49 </item>
50 <item row="2" column="0">
51 <widget class="QLabel" name="web_signup_link">
52 <property name="text">
53 <string>Sign up</string>
54 </property>
55 </widget>
56 </item>
57 <item row="0" column="1" colspan="3">
58 <widget class="QLineEdit" name="edit_username">
59 <property name="maxLength">
60 <number>36</number>
61 </property>
62 </widget>
63 </item>
64 <item row="1" column="0">
65 <widget class="QLabel" name="label_token">
66 <property name="text">
67 <string>Token: </string>
68 </property>
69 </widget>
70 </item>
71 <item row="1" column="4">
72 <widget class="QLabel" name="label_token_verified">
73 </widget>
74 </item>
75 <item row="0" column="0">
76 <widget class="QLabel" name="label_username">
77 <property name="text">
78 <string>Username: </string>
79 </property>
80 </widget>
81 </item>
82 <item row="0" column="4">
83 <widget class="QLabel" name="label_username_verified">
84 </widget>
85 </item>
86 <item row="1" column="1" colspan="3">
87 <widget class="QLineEdit" name="edit_token">
88 <property name="maxLength">
89 <number>36</number>
90 </property>
91 <property name="echoMode">
92 <enum>QLineEdit::Password</enum>
93 </property>
94 </widget>
95 </item>
96 <item row="2" column="1">
97 <widget class="QLabel" name="web_token_info_link">
98 <property name="text">
99 <string>What is my token?</string>
100 </property>
101 </widget>
102 </item>
103 <item row="2" column="2">
104 <spacer name="horizontalSpacer">
105 <property name="orientation">
106 <enum>Qt::Horizontal</enum>
107 </property>
108 <property name="sizeHint" stdset="0">
109 <size>
110 <width>40</width>
111 <height>20</height>
112 </size>
113 </property>
114 </spacer>
115 </item>
116 </layout>
117 </item>
118 </layout>
119 </widget>
120 </item>
121 <item>
122 <widget class="QGroupBox" name="groupBox">
123 <property name="title">
124 <string>Telemetry</string>
125 </property>
126 <layout class="QVBoxLayout" name="verticalLayout_2">
127 <item>
128 <widget class="QCheckBox" name="toggle_telemetry">
129 <property name="text">
130 <string>Share anonymous usage data with the yuzu team</string>
131 </property>
132 </widget>
133 </item>
134 <item>
135 <widget class="QLabel" name="telemetry_learn_more">
136 <property name="text">
137 <string>Learn more</string>
138 </property>
139 </widget>
140 </item>
141 <item>
142 <layout class="QGridLayout" name="gridLayoutTelemetryId">
143 <item row="0" column="0">
144 <widget class="QLabel" name="label_telemetry_id">
145 <property name="text">
146 <string>Telemetry ID:</string>
147 </property>
148 </widget>
149 </item>
150 <item row="0" column="1">
151 <widget class="QPushButton" name="button_regenerate_telemetry_id">
152 <property name="sizePolicy">
153 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
154 <horstretch>0</horstretch>
155 <verstretch>0</verstretch>
156 </sizepolicy>
157 </property>
158 <property name="layoutDirection">
159 <enum>Qt::RightToLeft</enum>
160 </property>
161 <property name="text">
162 <string>Regenerate</string>
163 </property>
164 </widget>
165 </item>
166 </layout>
167 </item>
168 </layout>
169 </widget>
170 </item>
171 </layout>
172 </item>
173 <item>
174 <widget class="QGroupBox" name="discord_group">
175 <property name="title">
176 <string>Discord Presence</string>
177 </property>
178 <layout class="QVBoxLayout" name="verticalLayout_21">
179 <item>
180 <widget class="QCheckBox" name="toggle_discordrpc">
181 <property name="text">
182 <string>Show Current Game in your Discord Status</string>
183 </property>
184 </widget>
185 </item>
186 </layout>
187 </widget>
188 </item>
189 <item>
190 <spacer name="verticalSpacer">
191 <property name="orientation">
192 <enum>Qt::Vertical</enum>
193 </property>
194 <property name="sizeHint" stdset="0">
195 <size>
196 <width>20</width>
197 <height>40</height>
198 </size>
199 </property>
200 </spacer>
201 </item>
202 </layout>
203 </widget>
204 <resources/>
205 <connections/>
206</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
119 std::vector<std::unique_ptr<WaitTreeItem>> list; 119 std::vector<std::unique_ptr<WaitTreeItem>> list;
120 120
121 constexpr std::size_t BaseRegister = 29; 121 constexpr std::size_t BaseRegister = 29;
122 u64 base_pointer = thread.context.cpu_registers[BaseRegister]; 122 u64 base_pointer = thread.GetContext().cpu_registers[BaseRegister];
123 123
124 while (base_pointer != 0) { 124 while (base_pointer != 0) {
125 u64 lr = Memory::Read64(base_pointer + sizeof(u64)); 125 u64 lr = Memory::Read64(base_pointer + sizeof(u64));
@@ -213,7 +213,7 @@ WaitTreeThread::~WaitTreeThread() = default;
213QString WaitTreeThread::GetText() const { 213QString WaitTreeThread::GetText() const {
214 const auto& thread = static_cast<const Kernel::Thread&>(object); 214 const auto& thread = static_cast<const Kernel::Thread&>(object);
215 QString status; 215 QString status;
216 switch (thread.status) { 216 switch (thread.GetStatus()) {
217 case Kernel::ThreadStatus::Running: 217 case Kernel::ThreadStatus::Running:
218 status = tr("running"); 218 status = tr("running");
219 break; 219 break;
@@ -246,15 +246,17 @@ QString WaitTreeThread::GetText() const {
246 status = tr("dead"); 246 status = tr("dead");
247 break; 247 break;
248 } 248 }
249 QString pc_info = tr(" PC = 0x%1 LR = 0x%2") 249
250 .arg(thread.context.pc, 8, 16, QLatin1Char('0')) 250 const auto& context = thread.GetContext();
251 .arg(thread.context.cpu_registers[30], 8, 16, QLatin1Char('0')); 251 const QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
252 .arg(context.pc, 8, 16, QLatin1Char('0'))
253 .arg(context.cpu_registers[30], 8, 16, QLatin1Char('0'));
252 return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") "; 254 return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") ";
253} 255}
254 256
255QColor WaitTreeThread::GetColor() const { 257QColor WaitTreeThread::GetColor() const {
256 const auto& thread = static_cast<const Kernel::Thread&>(object); 258 const auto& thread = static_cast<const Kernel::Thread&>(object);
257 switch (thread.status) { 259 switch (thread.GetStatus()) {
258 case Kernel::ThreadStatus::Running: 260 case Kernel::ThreadStatus::Running:
259 return QColor(Qt::GlobalColor::darkGreen); 261 return QColor(Qt::GlobalColor::darkGreen);
260 case Kernel::ThreadStatus::Ready: 262 case Kernel::ThreadStatus::Ready:
@@ -284,7 +286,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
284 const auto& thread = static_cast<const Kernel::Thread&>(object); 286 const auto& thread = static_cast<const Kernel::Thread&>(object);
285 287
286 QString processor; 288 QString processor;
287 switch (thread.processor_id) { 289 switch (thread.GetProcessorID()) {
288 case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT: 290 case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT:
289 processor = tr("default"); 291 processor = tr("default");
290 break; 292 break;
@@ -292,32 +294,35 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
292 case Kernel::ThreadProcessorId::THREADPROCESSORID_1: 294 case Kernel::ThreadProcessorId::THREADPROCESSORID_1:
293 case Kernel::ThreadProcessorId::THREADPROCESSORID_2: 295 case Kernel::ThreadProcessorId::THREADPROCESSORID_2:
294 case Kernel::ThreadProcessorId::THREADPROCESSORID_3: 296 case Kernel::ThreadProcessorId::THREADPROCESSORID_3:
295 processor = tr("core %1").arg(thread.processor_id); 297 processor = tr("core %1").arg(thread.GetProcessorID());
296 break; 298 break;
297 default: 299 default:
298 processor = tr("Unknown processor %1").arg(thread.processor_id); 300 processor = tr("Unknown processor %1").arg(thread.GetProcessorID());
299 break; 301 break;
300 } 302 }
301 303
302 list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); 304 list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
303 list.push_back(std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.ideal_core)));
304 list.push_back( 305 list.push_back(
305 std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.affinity_mask))); 306 std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.GetIdealCore())));
306 list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId()))); 307 list.push_back(
308 std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.GetAffinityMask())));
309 list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadID())));
307 list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") 310 list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
308 .arg(thread.current_priority) 311 .arg(thread.GetPriority())
309 .arg(thread.nominal_priority))); 312 .arg(thread.GetNominalPriority())));
310 list.push_back(std::make_unique<WaitTreeText>( 313 list.push_back(std::make_unique<WaitTreeText>(
311 tr("last running ticks = %1").arg(thread.last_running_ticks))); 314 tr("last running ticks = %1").arg(thread.GetLastRunningTicks())));
312 315
313 if (thread.mutex_wait_address != 0) 316 const VAddr mutex_wait_address = thread.GetMutexWaitAddress();
314 list.push_back(std::make_unique<WaitTreeMutexInfo>(thread.mutex_wait_address)); 317 if (mutex_wait_address != 0) {
315 else 318 list.push_back(std::make_unique<WaitTreeMutexInfo>(mutex_wait_address));
319 } else {
316 list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex"))); 320 list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));
321 }
317 322
318 if (thread.status == Kernel::ThreadStatus::WaitSynchAny || 323 if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAny ||
319 thread.status == Kernel::ThreadStatus::WaitSynchAll) { 324 thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAll) {
320 list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, 325 list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjects(),
321 thread.IsSleepingOnWaitAll())); 326 thread.IsSleepingOnWaitAll()));
322 } 327 }
323 328
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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7namespace DiscordRPC {
8
9class DiscordInterface {
10public:
11 virtual ~DiscordInterface() = default;
12
13 virtual void Pause() = 0;
14 virtual void Update() = 0;
15};
16
17class NullImpl : public DiscordInterface {
18public:
19 ~NullImpl() = default;
20
21 void Pause() override {}
22 void Update() override {}
23};
24
25} // 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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <string>
7#include <discord_rpc.h>
8#include "common/common_types.h"
9#include "core/core.h"
10#include "core/loader/loader.h"
11#include "yuzu/discord_impl.h"
12#include "yuzu/ui_settings.h"
13
14namespace DiscordRPC {
15
16DiscordImpl::DiscordImpl() {
17 DiscordEventHandlers handlers{};
18
19 // The number is the client ID for yuzu, it's used for images and the
20 // application name
21 Discord_Initialize("471872241299226636", &handlers, 1, nullptr);
22}
23
24DiscordImpl::~DiscordImpl() {
25 Discord_ClearPresence();
26 Discord_Shutdown();
27}
28
29void DiscordImpl::Pause() {
30 Discord_ClearPresence();
31}
32
33void DiscordImpl::Update() {
34 s64 start_time = std::chrono::duration_cast<std::chrono::seconds>(
35 std::chrono::system_clock::now().time_since_epoch())
36 .count();
37 std::string title;
38 if (Core::System::GetInstance().IsPoweredOn())
39 Core::System::GetInstance().GetAppLoader().ReadTitle(title);
40 DiscordRichPresence presence{};
41 presence.largeImageKey = "yuzu_logo";
42 presence.largeImageText = "yuzu is an emulator for the Nintendo Switch";
43 if (Core::System::GetInstance().IsPoweredOn()) {
44 presence.state = title.c_str();
45 presence.details = "Currently in game";
46 } else {
47 presence.details = "Not in game";
48 }
49 presence.startTimestamp = start_time;
50 Discord_UpdatePresence(&presence);
51}
52} // 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 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "yuzu/discord.h"
8
9namespace DiscordRPC {
10
11class DiscordImpl : public DiscordInterface {
12public:
13 DiscordImpl();
14 ~DiscordImpl() override;
15
16 void Pause() override;
17 void Update() override;
18};
19
20} // 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) {
60QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) { 60QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) {
61 QString out; 61 QString out;
62 for (const auto& kv : patch_manager.GetPatchVersionNames()) { 62 for (const auto& kv : patch_manager.GetPatchVersionNames()) {
63 if (!updatable && kv.first == FileSys::PatchType::Update) 63 if (!updatable && kv.first == "Update")
64 continue; 64 continue;
65 65
66 if (kv.second.empty()) { 66 if (kv.second.empty()) {
67 out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str()); 67 out.append(fmt::format("{}\n", kv.first).c_str());
68 } else { 68 } else {
69 out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second) 69 out.append(fmt::format("{} ({})\n", kv.first, kv.second).c_str());
70 .c_str());
71 } 70 }
72 } 71 }
73 72
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
35#include <QtWidgets> 35#include <QtWidgets>
36#include <fmt/format.h> 36#include <fmt/format.h>
37#include "common/common_paths.h" 37#include "common/common_paths.h"
38#include "common/detached_tasks.h"
38#include "common/file_util.h" 39#include "common/file_util.h"
39#include "common/logging/backend.h" 40#include "common/logging/backend.h"
40#include "common/logging/filter.h" 41#include "common/logging/filter.h"
@@ -65,6 +66,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
65#include "video_core/debug_utils/debug_utils.h" 66#include "video_core/debug_utils/debug_utils.h"
66#include "yuzu/about_dialog.h" 67#include "yuzu/about_dialog.h"
67#include "yuzu/bootmanager.h" 68#include "yuzu/bootmanager.h"
69#include "yuzu/compatdb.h"
68#include "yuzu/compatibility_list.h" 70#include "yuzu/compatibility_list.h"
69#include "yuzu/configuration/config.h" 71#include "yuzu/configuration/config.h"
70#include "yuzu/configuration/configure_dialog.h" 72#include "yuzu/configuration/configure_dialog.h"
@@ -73,12 +75,17 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
73#include "yuzu/debugger/graphics/graphics_surface.h" 75#include "yuzu/debugger/graphics/graphics_surface.h"
74#include "yuzu/debugger/profiler.h" 76#include "yuzu/debugger/profiler.h"
75#include "yuzu/debugger/wait_tree.h" 77#include "yuzu/debugger/wait_tree.h"
78#include "yuzu/discord.h"
76#include "yuzu/game_list.h" 79#include "yuzu/game_list.h"
77#include "yuzu/game_list_p.h" 80#include "yuzu/game_list_p.h"
78#include "yuzu/hotkeys.h" 81#include "yuzu/hotkeys.h"
79#include "yuzu/main.h" 82#include "yuzu/main.h"
80#include "yuzu/ui_settings.h" 83#include "yuzu/ui_settings.h"
81 84
85#ifdef USE_DISCORD_PRESENCE
86#include "yuzu/discord_impl.h"
87#endif
88
82#ifdef QT_STATICPLUGIN 89#ifdef QT_STATICPLUGIN
83Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); 90Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
84#endif 91#endif
@@ -102,23 +109,22 @@ enum class CalloutFlag : uint32_t {
102 DRDDeprecation = 0x2, 109 DRDDeprecation = 0x2,
103}; 110};
104 111
105static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { 112void GMainWindow::ShowTelemetryCallout() {
106 if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) { 113 if (UISettings::values.callout_flags & static_cast<uint32_t>(CalloutFlag::Telemetry)) {
107 return; 114 return;
108 } 115 }
109 116
110 UISettings::values.callout_flags |= static_cast<uint32_t>(flag); 117 UISettings::values.callout_flags |= static_cast<uint32_t>(CalloutFlag::Telemetry);
111 118 const QString telemetry_message =
112 QMessageBox msg; 119 tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
113 msg.setText(message); 120 "data is collected</a> to help improve yuzu. "
114 msg.setStandardButtons(QMessageBox::Ok); 121 "<br/><br/>Would you like to share your usage data with us?");
115 msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 122 if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) {
116 msg.setStyleSheet("QLabel{min-width: 900px;}"); 123 Settings::values.enable_telemetry = false;
117 msg.exec(); 124 Settings::Apply();
125 }
118} 126}
119 127
120void GMainWindow::ShowCallouts() {}
121
122const int GMainWindow::max_recent_files_item; 128const int GMainWindow::max_recent_files_item;
123 129
124static void InitializeLogging() { 130static void InitializeLogging() {
@@ -145,6 +151,9 @@ GMainWindow::GMainWindow()
145 default_theme_paths = QIcon::themeSearchPaths(); 151 default_theme_paths = QIcon::themeSearchPaths();
146 UpdateUITheme(); 152 UpdateUITheme();
147 153
154 SetDiscordEnabled(UISettings::values.enable_discord_presence);
155 discord_rpc->Update();
156
148 InitializeWidgets(); 157 InitializeWidgets();
149 InitializeDebugWidgets(); 158 InitializeDebugWidgets();
150 InitializeRecentFileMenuActions(); 159 InitializeRecentFileMenuActions();
@@ -168,7 +177,7 @@ GMainWindow::GMainWindow()
168 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 177 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
169 178
170 // Show one-time "callout" messages to the user 179 // Show one-time "callout" messages to the user
171 ShowCallouts(); 180 ShowTelemetryCallout();
172 181
173 QStringList args = QApplication::arguments(); 182 QStringList args = QApplication::arguments();
174 if (args.length() >= 2) { 183 if (args.length() >= 2) {
@@ -183,6 +192,9 @@ GMainWindow::~GMainWindow() {
183} 192}
184 193
185void GMainWindow::InitializeWidgets() { 194void GMainWindow::InitializeWidgets() {
195#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
196 ui.action_Report_Compatibility->setVisible(true);
197#endif
186 render_window = new GRenderWindow(this, emu_thread.get()); 198 render_window = new GRenderWindow(this, emu_thread.get());
187 render_window->hide(); 199 render_window->hide();
188 200
@@ -411,6 +423,8 @@ void GMainWindow::ConnectMenuEvents() {
411 connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame); 423 connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
412 connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame); 424 connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame);
413 connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); 425 connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
426 connect(ui.action_Report_Compatibility, &QAction::triggered, this,
427 &GMainWindow::OnMenuReportCompatibility);
414 connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); 428 connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
415 connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); 429 connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
416 430
@@ -647,6 +661,7 @@ void GMainWindow::BootGame(const QString& filename) {
647} 661}
648 662
649void GMainWindow::ShutdownGame() { 663void GMainWindow::ShutdownGame() {
664 discord_rpc->Pause();
650 emu_thread->RequestStop(); 665 emu_thread->RequestStop();
651 666
652 emit EmulationStopping(); 667 emit EmulationStopping();
@@ -655,6 +670,8 @@ void GMainWindow::ShutdownGame() {
655 emu_thread->wait(); 670 emu_thread->wait();
656 emu_thread = nullptr; 671 emu_thread = nullptr;
657 672
673 discord_rpc->Update();
674
658 // The emulation is stopped, so closing the window or not does not matter anymore 675 // The emulation is stopped, so closing the window or not does not matter anymore
659 disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); 676 disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
660 677
@@ -664,6 +681,7 @@ void GMainWindow::ShutdownGame() {
664 ui.action_Pause->setEnabled(false); 681 ui.action_Pause->setEnabled(false);
665 ui.action_Stop->setEnabled(false); 682 ui.action_Stop->setEnabled(false);
666 ui.action_Restart->setEnabled(false); 683 ui.action_Restart->setEnabled(false);
684 ui.action_Report_Compatibility->setEnabled(false);
667 render_window->hide(); 685 render_window->hide();
668 game_list->show(); 686 game_list->show();
669 game_list->setFilterFocus(); 687 game_list->setFilterFocus();
@@ -1147,6 +1165,9 @@ void GMainWindow::OnStartGame() {
1147 ui.action_Pause->setEnabled(true); 1165 ui.action_Pause->setEnabled(true);
1148 ui.action_Stop->setEnabled(true); 1166 ui.action_Stop->setEnabled(true);
1149 ui.action_Restart->setEnabled(true); 1167 ui.action_Restart->setEnabled(true);
1168 ui.action_Report_Compatibility->setEnabled(true);
1169
1170 discord_rpc->Update();
1150} 1171}
1151 1172
1152void GMainWindow::OnPauseGame() { 1173void GMainWindow::OnPauseGame() {
@@ -1161,6 +1182,20 @@ void GMainWindow::OnStopGame() {
1161 ShutdownGame(); 1182 ShutdownGame();
1162} 1183}
1163 1184
1185void GMainWindow::OnMenuReportCompatibility() {
1186 if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) {
1187 CompatDB compatdb{this};
1188 compatdb.exec();
1189 } else {
1190 QMessageBox::critical(
1191 this, tr("Missing yuzu Account"),
1192 tr("In order to submit a game compatibility test case, you must link your yuzu "
1193 "account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration "
1194 "&gt; "
1195 "Web."));
1196 }
1197}
1198
1164void GMainWindow::ToggleFullscreen() { 1199void GMainWindow::ToggleFullscreen() {
1165 if (!emulation_running) { 1200 if (!emulation_running) {
1166 return; 1201 return;
@@ -1224,11 +1259,14 @@ void GMainWindow::ToggleWindowMode() {
1224void GMainWindow::OnConfigure() { 1259void GMainWindow::OnConfigure() {
1225 ConfigureDialog configureDialog(this, hotkey_registry); 1260 ConfigureDialog configureDialog(this, hotkey_registry);
1226 auto old_theme = UISettings::values.theme; 1261 auto old_theme = UISettings::values.theme;
1262 const bool old_discord_presence = UISettings::values.enable_discord_presence;
1227 auto result = configureDialog.exec(); 1263 auto result = configureDialog.exec();
1228 if (result == QDialog::Accepted) { 1264 if (result == QDialog::Accepted) {
1229 configureDialog.applyConfiguration(); 1265 configureDialog.applyConfiguration();
1230 if (UISettings::values.theme != old_theme) 1266 if (UISettings::values.theme != old_theme)
1231 UpdateUITheme(); 1267 UpdateUITheme();
1268 if (UISettings::values.enable_discord_presence != old_discord_presence)
1269 SetDiscordEnabled(UISettings::values.enable_discord_presence);
1232 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 1270 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
1233 config->Save(); 1271 config->Save();
1234 } 1272 }
@@ -1443,11 +1481,25 @@ void GMainWindow::UpdateUITheme() {
1443 emit UpdateThemedIcons(); 1481 emit UpdateThemedIcons();
1444} 1482}
1445 1483
1484void GMainWindow::SetDiscordEnabled(bool state) {
1485#ifdef USE_DISCORD_PRESENCE
1486 if (state) {
1487 discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>();
1488 } else {
1489 discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
1490 }
1491#else
1492 discord_rpc = std::make_unique<DiscordRPC::NullImpl>();
1493#endif
1494 discord_rpc->Update();
1495}
1496
1446#ifdef main 1497#ifdef main
1447#undef main 1498#undef main
1448#endif 1499#endif
1449 1500
1450int main(int argc, char* argv[]) { 1501int main(int argc, char* argv[]) {
1502 Common::DetachedTasks detached_tasks;
1451 MicroProfileOnThreadCreate("Frontend"); 1503 MicroProfileOnThreadCreate("Frontend");
1452 SCOPE_EXIT({ MicroProfileShutdown(); }); 1504 SCOPE_EXIT({ MicroProfileShutdown(); });
1453 1505
@@ -1465,5 +1517,7 @@ int main(int argc, char* argv[]) {
1465 GMainWindow main_window; 1517 GMainWindow main_window;
1466 // After settings have been loaded by GMainWindow, apply the filter 1518 // After settings have been loaded by GMainWindow, apply the filter
1467 main_window.show(); 1519 main_window.show();
1468 return app.exec(); 1520 int result = app.exec();
1521 detached_tasks.WaitForAllTasks();
1522 return result;
1469} 1523}
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 {
41 SDMC, 41 SDMC,
42}; 42};
43 43
44namespace DiscordRPC {
45class DiscordInterface;
46}
47
44class GMainWindow : public QMainWindow { 48class GMainWindow : public QMainWindow {
45 Q_OBJECT 49 Q_OBJECT
46 50
@@ -61,6 +65,8 @@ public:
61 GMainWindow(); 65 GMainWindow();
62 ~GMainWindow() override; 66 ~GMainWindow() override;
63 67
68 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
69
64signals: 70signals:
65 71
66 /** 72 /**
@@ -99,7 +105,8 @@ private:
99 void BootGame(const QString& filename); 105 void BootGame(const QString& filename);
100 void ShutdownGame(); 106 void ShutdownGame();
101 107
102 void ShowCallouts(); 108 void ShowTelemetryCallout();
109 void SetDiscordEnabled(bool state);
103 110
104 /** 111 /**
105 * Stores the filename in the recently loaded files list. 112 * Stores the filename in the recently loaded files list.
@@ -135,6 +142,7 @@ private slots:
135 void OnStartGame(); 142 void OnStartGame();
136 void OnPauseGame(); 143 void OnPauseGame();
137 void OnStopGame(); 144 void OnStopGame();
145 void OnMenuReportCompatibility();
138 /// Called whenever a user selects a game in the game list widget. 146 /// Called whenever a user selects a game in the game list widget.
139 void OnGameListLoadFile(QString game_path); 147 void OnGameListLoadFile(QString game_path);
140 void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); 148 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 @@
45 <x>0</x> 45 <x>0</x>
46 <y>0</y> 46 <y>0</y>
47 <width>1081</width> 47 <width>1081</width>
48 <height>19</height> 48 <height>21</height>
49 </rect> 49 </rect>
50 </property> 50 </property>
51 <widget class="QMenu" name="menu_File"> 51 <widget class="QMenu" name="menu_File">
@@ -101,6 +101,8 @@
101 <property name="title"> 101 <property name="title">
102 <string>&amp;Help</string> 102 <string>&amp;Help</string>
103 </property> 103 </property>
104 <addaction name="action_Report_Compatibility"/>
105 <addaction name="separator"/>
104 <addaction name="action_About"/> 106 <addaction name="action_About"/>
105 </widget> 107 </widget>
106 <addaction name="menu_File"/> 108 <addaction name="menu_File"/>
@@ -239,6 +241,18 @@
239 <string>Restart</string> 241 <string>Restart</string>
240 </property> 242 </property>
241 </action> 243 </action>
244 <action name="action_Report_Compatibility">
245 <property name="enabled">
246 <bool>false</bool>
247 </property>
248 <property name="text">
249 <string>Report Compatibility</string>
250 </property>
251 <property name="visible">
252 <bool>false</bool>
253 </property>
254 </action>
242 </widget> 255 </widget>
243 <resources/> 256 <resources/>
257 <connections/>
244</ui> 258</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 @@
6 6
7namespace UISettings { 7namespace UISettings {
8 8
9const Themes themes{{
10 {"Default", "default"},
11 {"Dark", "qdarkstyle"},
12}};
13
9Values values = {}; 14Values values = {};
10} 15
16} // 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 {
15using ContextualShortcut = std::pair<QString, int>; 15using ContextualShortcut = std::pair<QString, int>;
16using Shortcut = std::pair<QString, ContextualShortcut>; 16using Shortcut = std::pair<QString, ContextualShortcut>;
17 17
18static const std::array<std::pair<QString, QString>, 2> themes = { 18using Themes = std::array<std::pair<const char*, const char*>, 2>;
19 {std::make_pair(QString("Default"), QString("default")), 19extern const Themes themes;
20 std::make_pair(QString("Dark"), QString("qdarkstyle"))}};
21 20
22struct Values { 21struct Values {
23 QByteArray geometry; 22 QByteArray geometry;
@@ -39,6 +38,9 @@ struct Values {
39 bool confirm_before_closing; 38 bool confirm_before_closing;
40 bool first_start; 39 bool first_start;
41 40
41 // Discord RPC
42 bool enable_discord_presence;
43
42 QString roms_path; 44 QString roms_path;
43 QString symbols_path; 45 QString symbols_path;
44 QString gamedir; 46 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() {
138 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); 138 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
139 Settings::values.gdbstub_port = 139 Settings::values.gdbstub_port =
140 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); 140 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
141
142 // Web Service
143 Settings::values.enable_telemetry =
144 sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
145 Settings::values.web_api_url =
146 sdl2_config->Get("WebService", "web_api_url", "https://api.yuzu-emu.org");
147 Settings::values.yuzu_username = sdl2_config->Get("WebService", "yuzu_username", "");
148 Settings::values.yuzu_token = sdl2_config->Get("WebService", "yuzu_token", "");
141} 149}
142 150
143void Config::Reload() { 151void 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
202# Whether or not to enable telemetry 202# Whether or not to enable telemetry
203# 0: No, 1 (default): Yes 203# 0: No, 1 (default): Yes
204enable_telemetry = 204enable_telemetry =
205# Endpoint URL for submitting telemetry data 205# URL for Web API
206telemetry_endpoint_url = 206web_api_url = https://api.yuzu-emu.org
207# Endpoint URL to verify the username and token
208verify_endpoint_url =
209# Username and token for yuzu Web Service 207# Username and token for yuzu Web Service
210# See https://services.citra-emu.org/ for more info 208# See https://profile.yuzu-emu.org/ for more info
211yuzu_username = 209yuzu_username =
212yuzu_token = 210yuzu_token =
213)"; 211)";
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 @@
10#include <fmt/ostream.h> 10#include <fmt/ostream.h>
11 11
12#include "common/common_paths.h" 12#include "common/common_paths.h"
13#include "common/detached_tasks.h"
13#include "common/file_util.h" 14#include "common/file_util.h"
14#include "common/logging/backend.h" 15#include "common/logging/backend.h"
15#include "common/logging/filter.h" 16#include "common/logging/filter.h"
@@ -78,6 +79,7 @@ static void InitializeLogging() {
78 79
79/// Application entry point 80/// Application entry point
80int main(int argc, char** argv) { 81int main(int argc, char** argv) {
82 Common::DetachedTasks detached_tasks;
81 Config config; 83 Config config;
82 84
83 int option_index = 0; 85 int option_index = 0;
@@ -213,5 +215,6 @@ int main(int argc, char** argv) {
213 system.RunLoop(); 215 system.RunLoop();
214 } 216 }
215 217
218 detached_tasks.WaitForAllTasks();
216 return 0; 219 return 0;
217} 220}