Преглед на файлове

Merge branch 'yuzu-emu:master' into convert_legacy

Feng Chen преди 4 години
родител
ревизия
e49184e606
променени са 100 файла, в които са добавени 19200 реда и са изтрити 5754 реда
  1. 5 5
      CMakeLists.txt
  2. 1 1
      README.md
  3. 377 236
      dist/languages/ca.ts
  4. 381 240
      dist/languages/cs.ts
  5. 382 241
      dist/languages/da.ts
  6. 379 238
      dist/languages/de.ts
  7. 243 132
      dist/languages/es.ts
  8. 391 250
      dist/languages/fr.ts
  9. 379 238
      dist/languages/it.ts
  10. 381 240
      dist/languages/ja_JP.ts
  11. 382 241
      dist/languages/ko_KR.ts
  12. 377 236
      dist/languages/nb.ts
  13. 387 240
      dist/languages/nl.ts
  14. 379 238
      dist/languages/pl.ts
  15. 380 239
      dist/languages/pt_BR.ts
  16. 377 236
      dist/languages/pt_PT.ts
  17. 435 294
      dist/languages/ru_RU.ts
  18. 377 236
      dist/languages/sv.ts
  19. 420 276
      dist/languages/tr_TR.ts
  20. 6044 0
      dist/languages/vi_VN.ts
  21. 382 241
      dist/languages/zh_CN.ts
  22. 419 270
      dist/languages/zh_TW.ts
  23. 0 4
      externals/CMakeLists.txt
  24. 1 1
      externals/SDL
  25. 1 1
      externals/cubeb
  26. 0 18
      externals/find-modules/FindUnicorn.cmake
  27. 2 0
      src/CMakeLists.txt
  28. 4 0
      src/audio_core/delay_line.cpp
  29. 4 0
      src/audio_core/delay_line.h
  30. 1 0
      src/common/CMakeLists.txt
  31. 7 0
      src/common/bit_util.h
  32. 4 0
      src/common/host_memory.cpp
  33. 366 0
      src/common/input.h
  34. 1 0
      src/common/logging/filter.cpp
  35. 1 0
      src/common/logging/types.h
  36. 1 0
      src/common/settings.cpp
  37. 3 12
      src/common/settings.h
  38. 51 19
      src/common/settings_input.h
  39. 0 12
      src/common/x64/cpu_detect.cpp
  40. 18 18
      src/common/x64/native_clock.cpp
  41. 24 3
      src/core/CMakeLists.txt
  42. 20 0
      src/core/arm/dynarmic/arm_dynarmic_64.cpp
  43. 11 9
      src/core/core.cpp
  44. 10 3
      src/core/core.h
  45. 10 13
      src/core/cpu_manager.cpp
  46. 20 25
      src/core/frontend/applets/controller.cpp
  47. 4 4
      src/core/frontend/applets/controller.h
  48. 10 88
      src/core/frontend/emu_window.cpp
  49. 5 25
      src/core/frontend/emu_window.h
  50. 6 1
      src/core/frontend/framebuffer_layout.cpp
  51. 1 10
      src/core/frontend/framebuffer_layout.h
  52. 0 217
      src/core/frontend/input.h
  53. 232 0
      src/core/hid/emulated_console.cpp
  54. 190 0
      src/core/hid/emulated_console.h
  55. 1139 0
      src/core/hid/emulated_controller.cpp
  56. 411 0
      src/core/hid/emulated_controller.h
  57. 459 0
      src/core/hid/emulated_devices.cpp
  58. 210 0
      src/core/hid/emulated_devices.h
  59. 214 0
      src/core/hid/hid_core.cpp
  60. 82 0
      src/core/hid/hid_core.h
  61. 635 0
      src/core/hid/hid_types.h
  62. 383 0
      src/core/hid/input_converter.cpp
  63. 96 0
      src/core/hid/input_converter.h
  64. 13 14
      src/core/hid/input_interpreter.cpp
  65. 11 43
      src/core/hid/input_interpreter.h
  66. 15 42
      src/core/hid/motion_input.cpp
  67. 20 7
      src/core/hid/motion_input.h
  68. 2 0
      src/core/hle/kernel/init/init_slab_setup.cpp
  69. 43 49
      src/core/hle/kernel/k_address_arbiter.cpp
  70. 4 0
      src/core/hle/kernel/k_auto_object.h
  71. 3 2
      src/core/hle/kernel/k_class_token.cpp
  72. 146 0
      src/core/hle/kernel/k_code_memory.cpp
  73. 66 0
      src/core/hle/kernel/k_code_memory.h
  74. 115 130
      src/core/hle/kernel/k_condition_variable.cpp
  75. 1 1
      src/core/hle/kernel/k_condition_variable.h
  76. 6 0
      src/core/hle/kernel/k_handle_table.cpp
  77. 2 0
      src/core/hle/kernel/k_handle_table.h
  78. 80 0
      src/core/hle/kernel/k_light_condition_variable.cpp
  79. 5 53
      src/core/hle/kernel/k_light_condition_variable.h
  80. 39 33
      src/core/hle/kernel/k_light_lock.cpp
  81. 1 1
      src/core/hle/kernel/k_light_lock.h
  82. 20 0
      src/core/hle/kernel/k_memory_block.h
  83. 4 0
      src/core/hle/kernel/k_page_linked_list.h
  84. 121 2
      src/core/hle/kernel/k_page_table.cpp
  85. 9 1
      src/core/hle/kernel/k_page_table.h
  86. 24 6
      src/core/hle/kernel/k_process.cpp
  87. 1 0
      src/core/hle/kernel/k_process.h
  88. 60 41
      src/core/hle/kernel/k_scheduler.cpp
  89. 2 0
      src/core/hle/kernel/k_scheduler.h
  90. 10 0
      src/core/hle/kernel/k_scheduler_lock.h
  91. 1 0
      src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
  92. 1 2
      src/core/hle/kernel/k_server_session.cpp
  93. 78 73
      src/core/hle/kernel/k_synchronization_object.cpp
  94. 32 0
      src/core/hle/kernel/k_synchronization_object.h
  95. 154 92
      src/core/hle/kernel/k_thread.cpp
  96. 62 10
      src/core/hle/kernel/k_thread.h
  97. 49 0
      src/core/hle/kernel/k_thread_queue.cpp
  98. 14 60
      src/core/hle/kernel/k_thread_queue.h
  99. 62 41
      src/core/hle/kernel/kernel.cpp
  100. 9 0
      src/core/hle/kernel/kernel.h

+ 5 - 5
CMakeLists.txt

@@ -57,7 +57,7 @@ function(check_submodules_present)
         string(REGEX REPLACE "path *= *" "" module ${module})
         if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git")
             message(FATAL_ERROR "Git submodule ${module} not found. "
-                    "Please run: git submodule update --init --recursive")
+                    "Please run: \ngit submodule update --init --recursive")
         endif()
     endforeach()
 endfunction()
@@ -131,7 +131,7 @@ add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS)
 if (MSVC)
     add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++latest>)
 
-    # cubeb and boost still make use of deprecated result_of.
+    # boost still makes use of deprecated result_of.
     add_definitions(-D_HAS_DEPRECATED_RESULT_OF)
 else()
     set(CMAKE_CXX_STANDARD 20)
@@ -167,7 +167,7 @@ macro(yuzu_find_packages)
     set(REQUIRED_LIBS
     #    Cmake Pkg Prefix  Version     Conan Pkg
         "Catch2            2.13.7      catch2/2.13.7"
-        "fmt               8.0         fmt/8.0.0"
+        "fmt               8.0.1       fmt/8.0.1"
         "lz4               1.8         lz4/1.9.2"
         "nlohmann_json     3.8         nlohmann_json/3.8.0"
         "ZLIB              1.2         zlib/1.2.11"
@@ -370,7 +370,7 @@ if (ENABLE_SDL2)
     if (YUZU_USE_BUNDLED_SDL2)
         # Detect toolchain and platform
         if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
-            set(SDL2_VER "SDL2-2.0.16")
+            set(SDL2_VER "SDL2-2.0.18")
         else()
             message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
         endif()
@@ -390,7 +390,7 @@ if (ENABLE_SDL2)
     elseif (YUZU_USE_EXTERNAL_SDL2)
         message(STATUS "Using SDL2 from externals.")
     else()
-        find_package(SDL2 2.0.16 REQUIRED)
+        find_package(SDL2 2.0.18 REQUIRED)
 
         # Some installations don't set SDL2_LIBRARIES
         if("${SDL2_LIBRARIES}" STREQUAL "")

+ 1 - 1
README.md

@@ -17,7 +17,7 @@ It is written in C++ with portability in mind, and we actively maintain builds f
             alt="Azure Mainline CI Build Status">
     </a>
     <a href="https://discord.com/invite/u77vRWY">
-        <img src="https://img.shields.io/discord/398318088170242053?color=%237289DA&label=yuzu&logo=discord&logoColor=white"
+        <img src="https://img.shields.io/discord/398318088170242053?color=5865F2&label=yuzu&logo=discord&logoColor=white"
             alt="Discord">
     </a>
 </p>

Файловите разлики са ограничени, защото са твърде много
+ 377 - 236
dist/languages/ca.ts


Файловите разлики са ограничени, защото са твърде много
+ 381 - 240
dist/languages/cs.ts


Файловите разлики са ограничени, защото са твърде много
+ 382 - 241
dist/languages/da.ts


Файловите разлики са ограничени, защото са твърде много
+ 379 - 238
dist/languages/de.ts


Файловите разлики са ограничени, защото са твърде много
+ 243 - 132
dist/languages/es.ts


Файловите разлики са ограничени, защото са твърде много
+ 391 - 250
dist/languages/fr.ts


Файловите разлики са ограничени, защото са твърде много
+ 379 - 238
dist/languages/it.ts


Файловите разлики са ограничени, защото са твърде много
+ 381 - 240
dist/languages/ja_JP.ts


Файловите разлики са ограничени, защото са твърде много
+ 382 - 241
dist/languages/ko_KR.ts


Файловите разлики са ограничени, защото са твърде много
+ 377 - 236
dist/languages/nb.ts


Файловите разлики са ограничени, защото са твърде много
+ 387 - 240
dist/languages/nl.ts


Файловите разлики са ограничени, защото са твърде много
+ 379 - 238
dist/languages/pl.ts


Файловите разлики са ограничени, защото са твърде много
+ 380 - 239
dist/languages/pt_BR.ts


Файловите разлики са ограничени, защото са твърде много
+ 377 - 236
dist/languages/pt_PT.ts


Файловите разлики са ограничени, защото са твърде много
+ 435 - 294
dist/languages/ru_RU.ts


Файловите разлики са ограничени, защото са твърде много
+ 377 - 236
dist/languages/sv.ts


Файловите разлики са ограничени, защото са твърде много
+ 420 - 276
dist/languages/tr_TR.ts


+ 6044 - 0
dist/languages/vi_VN.ts

@@ -0,0 +1,6044 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS language="vi_VN" sourcelanguage="en_US" version="2.1">
+<context>
+    <name>AboutDialog</name>
+    <message>
+        <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+        <source>About yuzu</source>
+        <translation>Thông tin về yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/aboutdialog.ui" line="30"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/aboutdialog.ui" line="60"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/aboutdialog.ui" line="73"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 (%2)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 (%2)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/aboutdialog.ui" line="86"/>
+        <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu là một phần mềm giả lập thử nghiệm dưới dạng mã nguồn mở cho máy Nintendo Switch, được cấp phép theo giấy phép GPLv2.0.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Bạn không được phép sử dụng phần mềm này để chơi những trò chơi mà bạn sở hữu một cách bất hợp pháp.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/aboutdialog.ui" line="118"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Trang web&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Mã nguồn&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Đóng góp&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Giấy phép&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/aboutdialog.ui" line="134"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; là thương hiệu của Nintendo. yuzu không hề liên kết với Nintendo dưới bất kỳ hình thức nào.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+</context>
+<context>
+    <name>CalibrationConfigurationDialog</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="29"/>
+        <source>Communicating with the server...</source>
+        <translation>Đang giao tiếp với máy chủ...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="30"/>
+        <source>Cancel</source>
+        <translation>Huỷ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="48"/>
+        <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+        <translation>Hãy chạm vào góc trên cùng&lt;br&gt;bên trái trên touchpad của bạn.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="51"/>
+        <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+        <translation>Giờ hãy chạm vào góc dưới cùng&lt;br&gt;bên phải trên touchpad của bạn.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="54"/>
+        <source>Configuration completed!</source>
+        <translation>Đã hoàn thành quá trình thiết lập!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="61"/>
+        <source>OK</source>
+        <translation>OK</translation>
+    </message>
+</context>
+<context>
+    <name>CompatDB</name>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+        <source>Report Compatibility</source>
+        <translation>Báo cáo tương thích</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+        <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+        <source>Report Game Compatibility</source>
+        <translation>Báo cáo trò chơi tương thích</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+        <source>&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;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Nếu bạn chọn gửi bản kiểm tra vào &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;danh sách tương thích yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, Thông tin sau sẽ được thu thập và hiển thị lên trang web:&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;Thông tin phần cứng (CPU / GPU / Hệ điều hành)&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;Phiên bản yuzu bạn đang chạy&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;Tài khoản yuzu đang kết nối&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+        <source>Perfect</source>
+        <translation>Tốt nhất</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+        <source>&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;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Có thể chơi một cách mượt mà mà không có lỗi âm thanh hoặc đồ họa nào.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+        <source>Great </source>
+        <translation>Tốt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+        <source>&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;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Có thể chơi từ đầu đến cuối nhưng vẫn có một số lỗi nhỏ về đồ họa hoặc âm thanh. Có thể sẽ cần tới một vài tinh chỉnh nào đó.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+        <source>Okay</source>
+        <translation>Tạm ổn</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+        <source>&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;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Tạm chơi được nhưng có lỗi đồ họa hoặc âm thanh một cách đáng kể, tuy vậy vẫn có thể chơi hết từ đầu đến cuối với một số tinh chỉnh.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+        <source>Bad</source>
+        <translation>Không tốt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+        <source>&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;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Chơi được nhưng có nhiều lỗi đồ họa hoặc âm thanh. Không thể chơi tiếp ở một số nơi nào đó do lỗi dù có tinh chỉnh thế nào đi nữa.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+        <source>Intro/Menu</source>
+        <translation>Phần mở đầu/Menu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+        <source>&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;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Hoàn toàn không thể chơi được do có nhiều lỗi đồ họa hoặc âm thanh. Không thể chơi tiếp sau khi bấm nút bắt đầu trò chơi.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+        <source>Won&apos;t Boot</source>
+        <translation>Không hoạt động</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+        <source>&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;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Trò chơi sẽ thoát đột ngột khi khởi động.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+        <source>&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;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Không phụ thuộc vào tốc độ hay hiệu suất, trò chơi này chơi như thế nào từ đầu đến cuối trên phiên bản này của yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+        <source>Thank you for your submission!</source>
+        <translation>Cảm ơn bạn đã gửi đến cho chúng tôi!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.cpp" line="59"/>
+        <source>Submitting</source>
+        <translation>Đang gửi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.cpp" line="72"/>
+        <source>Communication error</source>
+        <translation>Đã xảy ra lỗi giao tiếp với máy chủ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.cpp" line="73"/>
+        <source>An error occurred while sending the Testcase</source>
+        <translation>Có lỗi xảy ra khi gửi Testcase</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/compatdb.cpp" line="75"/>
+        <source>Next</source>
+        <translation>Tiếp theo</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureAudio</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_audio.ui" line="14"/>
+        <location filename="../../src/yuzu/configuration/configure_audio.ui" line="20"/>
+        <source>Audio</source>
+        <translation>Âm thanh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_audio.ui" line="28"/>
+        <source>Output Engine:</source>
+        <translation>Đầu ra hệ thống:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_audio.ui" line="42"/>
+        <source>Audio Device:</source>
+        <translation>Thiết bị âm thanh:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_audio.ui" line="70"/>
+        <source>Use global volume</source>
+        <translation>Sử dụng âm lượng trong cài đặt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_audio.ui" line="75"/>
+        <source>Set volume:</source>
+        <translation>Âm lượng tuỳ chỉnh:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_audio.ui" line="83"/>
+        <source>Volume:</source>
+        <translation>Âm lượng:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_audio.ui" line="128"/>
+        <source>0 %</source>
+        <translation>0 %</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="97"/>
+        <source>%1%</source>
+        <comment>Volume percentage (e.g. 50%)</comment>
+        <translation>%1%</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureCpu</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+        <source>Form</source>
+        <translation>Mẫu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="17"/>
+        <source>CPU</source>
+        <translation>CPU</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="25"/>
+        <source>General</source>
+        <translation>Chung</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="34"/>
+        <source>Accuracy:</source>
+        <translation>Độ chính xác</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="42"/>
+        <source>Auto</source>
+        <translation>Tự động</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="47"/>
+        <source>Accurate</source>
+        <translation>Tuyệt đối</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="52"/>
+        <source>Unsafe</source>
+        <translation>Tương đối</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="63"/>
+        <source>We recommend setting accuracy to &quot;Auto&quot;.</source>
+        <translation>Chúng tôi khuyến khích sử dụng chế độ &quot;Tự động&quot;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="80"/>
+        <source>Unsafe CPU Optimization Settings</source>
+        <translation>Cài đặt tối ưu cho CPU ở chế độ tương đối</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="86"/>
+        <source>These settings reduce accuracy for speed.</source>
+        <translation>Những cài đặt sau giảm độ chính xác của giả lập để đổi lấy tốc độ.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="96"/>
+        <source>
+            &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+           </source>
+        <translation>
+          &lt;div&gt;Chức năng này tăng tốc độ giả lập bằng cách giảm độ chính xác của tập lệnh phép tính gộp cộng và nhân (FMA) trên các dòng CPU không hỗ trợ nó.&lt;/div&gt;
+</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="101"/>
+        <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+        <translation>Không dùng FMA (tăng hiệu suất cho các dòng CPU không hỗ trợ FMA)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="108"/>
+        <source>
+            &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+           </source>
+        <translation>
+            &lt;div&gt;Chức năng này cải thiện tốc độ của một số chức năng dấu phẩy động tương đối bằng cách sử dụng tính năng làm tròn kém chính xác hơn.&lt;/div&gt;
+</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="113"/>
+        <source>Faster FRSQRTE and FRECPE</source>
+        <translation>Chạy FRSQRTE và FRECPE nhanh hơn</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="120"/>
+        <source>
+            &lt;div&gt;This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.&lt;/div&gt;
+           </source>
+        <translation>
+            &lt;div&gt;Tùy chọn này cải thiện tốc độ của các hàm dấu phẩy động ASIMD 32 bit bằng cách chạy với các chế độ làm tròn không chính xác.&lt;/div&gt;
+           </translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="125"/>
+        <source>Faster ASIMD instructions (32 bits only)</source>
+        <translation>Các lệnh ASIMD nhanh hơn (chỉ áp dụng cho 32 bit)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="132"/>
+        <source>
+            &lt;div&gt;This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.&lt;/div&gt;
+           </source>
+        <translation>
+&lt;div&gt;Tùy chọn này sẽ cải thiện tốc độ bằng việc gỡ bỏ Kiểm tra NaN. Hãy nhớ là tùy chọn cũng sẽ giảm độ chính xác cho các dòng lệnh floating-point.&lt;/div&gt;
+</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="137"/>
+        <source>Inaccurate NaN handling</source>
+        <translation>Xử lí NaN gặp lỗi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="144"/>
+        <source>
+            &lt;div&gt;This option improves speed by eliminating a safety check before every memory read/write in guest. Disabling it may allow a game to read/write the emulator's memory.&lt;/div&gt;
+           </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="149"/>
+        <source>Disable address space checks</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="174"/>
+        <source>CPU settings are available only when game is not running.</source>
+        <translation>Cài đặt CPU chỉ có sẵn khi không chạy trò chơi.</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureCpuDebug</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+        <source>Form</source>
+        <translation>Mẫu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="17"/>
+        <source>CPU</source>
+        <translation>CPU</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="25"/>
+        <source>Toggle CPU Optimizations</source>
+        <translation>Tuỳ chọn cho tối ưu CPU</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;For debugging only.&lt;/span&gt;&lt;br/&gt;If you&apos;re not sure what these do, keep all of these enabled. &lt;br/&gt;These settings, when disabled, only take effect when CPU Debugging is enabled. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Chỉ dành cho việc gỡ lỗi.&lt;/span&gt;&lt;br/&gt;Nếu bạn không biết những sự lựa chọn này làm gì, hãy bật tất cả.&lt;br/&gt;Những cài đặt này, khi tắt, chỉ hiệu quả khi bật &quot;Gỡ lỗi CPU&quot;. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="41"/>
+        <source>
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+           </source>
+        <translation>
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;Sự tối ưu hóa này làm cho việc truy cập bộ nhớ của chương trình khách nhanh hơn.&lt;/div&gt;
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;Bật nó giúp PageTable::pointers có thể được truy cập trong mã được phát ra.&lt;/div&gt;
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;Tắt nó làm cho mọi truy cập vào bộ nhớ bắt buộc phải qua các function Memory::Read/Memory::Write.&lt;/div&gt;
+           </translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="48"/>
+        <source>Enable inline page tables</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="55"/>
+        <source>
+            &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+           </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+        <source>Enable block linking</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="67"/>
+        <source>
+            &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+           </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+        <source>Enable return stack buffer</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="79"/>
+        <source>
+            &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+           </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+        <source>Enable fast dispatcher</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="91"/>
+        <source>
+            &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+           </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+        <source>Enable context elimination</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="103"/>
+        <source>
+            &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+           </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+        <source>Enable constant propagation</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="115"/>
+        <source>
+            &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+           </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+        <source>Enable miscellaneous optimizations</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="127"/>
+        <source>
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+           </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="133"/>
+        <source>Enable misalignment check reduction</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="140"/>
+        <source>
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
+            &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to use Software MMU Emulation.&lt;/div&gt;
+           </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="147"/>
+        <source>Enable Host MMU Emulation</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="172"/>
+        <source>CPU settings are available only when game is not running.</source>
+        <translation>Cài đặt CPU chỉ có sẵn khi không chạy trò chơi.</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureDebug</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="9"/>
+        <source>Logging</source>
+        <translation>Sổ ghi chép</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="17"/>
+        <source>Global Log Filter</source>
+        <translation>Bộ lọc sổ ghi chép</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="29"/>
+        <source>Show Log in Console</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="36"/>
+        <source>Open Log Location</source>
+        <translation>Mở vị trí sổ ghi chép</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="46"/>
+        <source>When checked, the max size of the log increases from 100 MB to 1 GB</source>
+        <translation>Khi tích vào, dung lượng tối đa cho file log chuyển từ 100 MB lên 1 GB</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="49"/>
+        <source>Enable Extended Logging**</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="59"/>
+        <source>Homebrew</source>
+        <translation>Homebrew</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="67"/>
+        <source>Arguments String</source>
+        <translation>Chuỗi đối số</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="82"/>
+        <source>Graphics</source>
+        <translation>Đồ hoạ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="91"/>
+        <source>When checked, the graphics API enters a slower debugging mode</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="94"/>
+        <source>Enable Graphics Debugging</source>
+        <translation>Kích hoạt chế độ gỡ lỗi đồ hoạ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="101"/>
+        <source>When checked, it enables Nsight Aftermath crash dumps</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="104"/>
+        <source>Enable Nsight Aftermath</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="114"/>
+        <source>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="117"/>
+        <source>Disable Macro JIT</source>
+        <translation>Không dùng Macro JIT</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="124"/>
+        <source>When checked, yuzu will log statistics about the compiled pipeline cache</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="127"/>
+        <source>Enable Shader Feedback</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="134"/>
+        <source>When checked, it executes shaders without loop logic changes</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="137"/>
+        <source>Disable Loop safety checks</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="147"/>
+        <source>Debugging</source>
+        <translation>Vá lỗi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="153"/>
+        <source>Enable FS Access Log</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="160"/>
+        <source>Enable Verbose Reporting Services**</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="170"/>
+        <source>Advanced</source>
+        <translation>Nâng Cao</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="176"/>
+        <source>Kiosk (Quest) Mode</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="183"/>
+        <source>Enable CPU Debugging</source>
+        <translation>Bật Vá Lỗi CPU</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="190"/>
+        <source>Enable Debug Asserts</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="197"/>
+        <source>Enable Auto-Stub**</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="204"/>
+        <source>Enable all Controller Types</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug.ui" line="219"/>
+        <source>**This will be reset automatically when yuzu closes.</source>
+        <translation>**Sẽ tự động thiết lập lại khi tắt yuzu.</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureDebugController</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+        <source>Configure Debug Controller</source>
+        <translation>Thiết lập bộ điều khiển vá lỗi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+        <source>Clear</source>
+        <translation>Làm trống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+        <source>Defaults</source>
+        <translation>Quay về mặc định</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureDebugTab</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug_tab.ui" line="14"/>
+        <source>Form</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug_tab.ui" line="17"/>
+        <location filename="../../src/yuzu/configuration/configure_debug_tab.cpp" line="17"/>
+        <source>Debug</source>
+        <translation>Vá lỗi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_debug_tab.cpp" line="18"/>
+        <source>CPU</source>
+        <translation>CPU</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureDialog</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+        <source>yuzu Configuration</source>
+        <translation>Thiết lập yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="59"/>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="162"/>
+        <source>Audio</source>
+        <translation>Âm thanh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="60"/>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="160"/>
+        <source>CPU</source>
+        <translation>CPU</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="61"/>
+        <source>Debug</source>
+        <translation>Gỡ lỗi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="62"/>
+        <source>Filesystem</source>
+        <translation>Hệ thống tệp tin</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="63"/>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="156"/>
+        <source>General</source>
+        <translation>Chung</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="64"/>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="161"/>
+        <source>Graphics</source>
+        <translation>Đồ hoạ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="65"/>
+        <source>GraphicsAdvanced</source>
+        <translation>Đồ họa Nâng cao</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="66"/>
+        <source>Hotkeys</source>
+        <translation>Phím tắt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="67"/>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="163"/>
+        <source>Controls</source>
+        <translation>Phím</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="68"/>
+        <source>Profiles</source>
+        <translation>Hồ sơ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="69"/>
+        <source>Network</source>
+        <translation>Mạng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="70"/>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="158"/>
+        <source>System</source>
+        <translation>Hệ thống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="71"/>
+        <source>Game List</source>
+        <translation>Danh sách trò chơi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="72"/>
+        <source>Web</source>
+        <translation>Web</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureFilesystem</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+        <source>Form</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="17"/>
+        <source>Filesystem</source>
+        <translation>File hệ thống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="25"/>
+        <source>Storage Directories</source>
+        <translation>Thư mục lưu trữ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="31"/>
+        <source>NAND</source>
+        <translation>NAND</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="38"/>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="58"/>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="114"/>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="136"/>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="143"/>
+        <source>...</source>
+        <translation>...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="51"/>
+        <source>SD Card</source>
+        <translation>Thẻ nhớ SD</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="84"/>
+        <source>Gamecard</source>
+        <translation>Thẻ nhớ trò chơi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="90"/>
+        <source>Path</source>
+        <translation>Đường dẫn</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="100"/>
+        <source>Inserted</source>
+        <translation>Đã chèn vào</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="107"/>
+        <source>Current Game</source>
+        <translation>Game hiện tại</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="124"/>
+        <source>Patch Manager</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="152"/>
+        <source>Dump Decompressed NSOs</source>
+        <translation>Sao chép NSO đã giải nén</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="159"/>
+        <source>Dump ExeFS</source>
+        <translation>Sao chép ExeFS</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="168"/>
+        <source>Mod Load Root</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="175"/>
+        <source>Dump Root</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="201"/>
+        <source>Caching</source>
+        <translation>Bộ nhớ đệm</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="209"/>
+        <source>Cache Game List Metadata</source>
+        <translation>Lưu bộ nhớ đệm của danh sách trò chơi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="216"/>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="124"/>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="128"/>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="133"/>
+        <source>Reset Metadata Cache</source>
+        <translation>Khôi phục bộ nhớ đệm của metadata</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="86"/>
+        <source>Select Emulated NAND Directory...</source>
+        <translation>Chọn Thư Mục Giả Lập NAND...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="89"/>
+        <source>Select Emulated SD Directory...</source>
+        <translation>Chọn Thư Mục Giả Lập SD...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="92"/>
+        <source>Select Gamecard Path...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="95"/>
+        <source>Select Dump Directory...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="98"/>
+        <source>Select Mod Load Directory...</source>
+        <translation>Chọn Thư Mục Chứa Mod...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="125"/>
+        <source>The metadata cache is already empty.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="129"/>
+        <source>The operation completed successfully.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="134"/>
+        <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ConfigureGeneral</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+        <source>Form</source>
+        <translation>Mẫu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="17"/>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="25"/>
+        <source>General</source>
+        <translation>Chung</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="50"/>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="57"/>
+        <source>Use global framerate cap</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="62"/>
+        <source>Set framerate cap:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="70"/>
+        <source>Requires the use of the FPS Limiter Toggle hotkey to take effect.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="73"/>
+        <source>Framerate Cap</source>
+        <translation>Giới hạn Tốc độ khung hình</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="95"/>
+        <source>x</source>
+        <translation>x</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="116"/>
+        <source>Limit Speed Percent</source>
+        <translation>Giới hạn phần trăm tốc độ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="123"/>
+        <source>%</source>
+        <translation>%</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="141"/>
+        <source>Multicore CPU Emulation</source>
+        <translation>Giả lập CPU đa nhân</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="148"/>
+        <source>Confirm exit while emulation is running</source>
+        <translation>Xác nhận thoát trong khi đang chạy giả lập</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="155"/>
+        <source>Prompt for user on game boot</source>
+        <translation>Hiển thị cửa sổ chọn người dùng khi bắt đầu trò chơi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="162"/>
+        <source>Pause emulation when in background</source>
+        <translation>Tạm dừng giả lập khi chạy nền</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="169"/>
+        <source>Hide mouse on inactivity</source>
+        <translation>Ẩn con trỏ chuột khi không dùng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.ui" line="211"/>
+        <source>Reset All Settings</source>
+        <translation>Đặt lại mọi tùy chỉnh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.cpp" line="78"/>
+        <source>yuzu</source>
+        <translation>yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_general.cpp" line="79"/>
+        <source>This reset all settings and remove all per-game configurations. This will not delete game directories, profiles, or input profiles. Proceed?</source>
+        <translation>Quá trình này sẽ thiết lập lại toàn bộ tùy chỉnh và gỡ hết mọi cài đặt cho từng game riêng lẻ. Quá trình này không xóa đường dẫn tới thư mục game, hồ sơ, hay hồ sơ của thiết lập phím. Tiếp tục?</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureGraphics</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+        <source>Form</source>
+        <translation>Mẫu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="17"/>
+        <source>Graphics</source>
+        <translation>Đồ họa</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="25"/>
+        <source>API Settings</source>
+        <translation>Cài đặt API</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="64"/>
+        <source>Shader Backend:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="92"/>
+        <source>Device:</source>
+        <translation>Thiết bị đồ hoạ:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="120"/>
+        <source>API:</source>
+        <translation>API đồ hoạ:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="156"/>
+        <source>Graphics Settings</source>
+        <translation>Cài đặt đồ hoạ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="162"/>
+        <source>Use disk pipeline cache</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="169"/>
+        <source>Use asynchronous GPU emulation</source>
+        <translation>Dùng giả lập GPU không đồng bộ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+        <source>Accelerate ASTC texture decoding</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="198"/>
+        <source>NVDEC emulation:</source>
+        <translation>Giả lập NVDEC</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="206"/>
+        <source>No Video Output</source>
+        <translation>Không Video Đầu Ra</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="211"/>
+        <source>CPU Video Decoding</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="216"/>
+        <source>GPU Video Decoding (Default)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="242"/>
+        <source>Fullscreen Mode:</source>
+        <translation>Chế độ Toàn màn hình:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="250"/>
+        <source>Borderless Windowed</source>
+        <translation>Cửa sổ không viền</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="255"/>
+        <source>Exclusive Fullscreen</source>
+        <translation>Toàn màn hình</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="281"/>
+        <source>Aspect Ratio:</source>
+        <translation>Tỉ lệ khung hình:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="289"/>
+        <source>Default (16:9)</source>
+        <translation>Mặc định (16:9)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="294"/>
+        <source>Force 4:3</source>
+        <translation>Dùng 4:3</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="299"/>
+        <source>Force 21:9</source>
+        <translation>Dùng 21:9</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="304"/>
+        <source>Stretch to Window</source>
+        <translation>Kéo dãn đến cửa sổ phần mềm</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="330"/>
+        <source>Resolution:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="338"/>
+        <source>0.5X  (360p/540p) [EXPERIMENTAL]</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="343"/>
+        <source>0.75X (540p/810p) [EXPERIMENTAL]</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="348"/>
+        <source>1X (720p/1080p)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="353"/>
+        <source>2X (1440p/2160p)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="358"/>
+        <source>3X (2160p/3240p)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="363"/>
+        <source>4X (2880p/4320p)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="368"/>
+        <source>5X (3600p/5400p)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="373"/>
+        <source>6X (4320p/6480p)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="399"/>
+        <source>Window Adapting Filter:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="407"/>
+        <source>Nearest Neighbor</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="412"/>
+        <source>Bilinear</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="417"/>
+        <source>Bicubic</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="422"/>
+        <source>Gaussian</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="427"/>
+        <source>ScaleForce</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="432"/>
+        <source>AMD FidelityFX™️ Super Resolution [Vulkan Only]</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="458"/>
+        <source>Anti-Aliasing Method:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="466"/>
+        <source>None</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="471"/>
+        <source>FXAA</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="506"/>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="516"/>
+        <source>Use global background color</source>
+        <translation>Dùng màu nền theo cài đặt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="521"/>
+        <source>Set background color:</source>
+        <translation>Chọn màu nền:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="529"/>
+        <source>Background Color:</source>
+        <translation>Màu nền:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="34"/>
+        <source>GLASM (Assembly Shaders, NVIDIA Only)</source>
+        <translation>GLASM (Assembly Shaders, Chỉ Cho NVIDIA)</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureGraphicsAdvanced</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+        <source>Form</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="17"/>
+        <source>Advanced</source>
+        <translation>Nâng cao</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="25"/>
+        <source>Advanced Graphics Settings</source>
+        <translation>Cài đặt đồ hoạ nâng cao</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="46"/>
+        <source>Accuracy Level:</source>
+        <translation>Độ chính xác:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+        <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+        <translation>VSync tránh cho màn hình bị &quot;xước&quot;, tuy nhiên một số cạc đồ hoạ có hiệu năng chậm hơn khi VSync được kích hoạt. Bật chức năng này nếu nó không ảnh hưởng gì đến hiệu năng.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="78"/>
+        <source>Use VSync (OpenGL only)</source>
+        <translation>Dùng VSync (chỉ dành cho OpenGL)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+        <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+        <translation>Kích hoạt tính năng tạo shader không đồng bộ nhằm tránh cho trò chơi bị giật khi tạo shader. Tính năng này vẫn đang thử nghiệm.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="88"/>
+        <source>Use asynchronous shader building (Hack)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+        <source>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="98"/>
+        <source>Use Fast GPU Time (Hack)</source>
+        <translation>Tăng Tốc Thời Gian GPU (Hack)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="120"/>
+        <source>Anisotropic Filtering:</source>
+        <translation>Bộ lọc góc nghiêng:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="128"/>
+        <source>Automatic</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="133"/>
+        <source>Default</source>
+        <translation>Mặc định</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="138"/>
+        <source>2x</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="143"/>
+        <source>4x</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="148"/>
+        <source>8x</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="153"/>
+        <source>16x</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ConfigureHotkeys</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+        <source>Hotkey Settings</source>
+        <translation>Cài đặt phím nóng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="17"/>
+        <source>Hotkeys</source>
+        <translation>Phím tắt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="25"/>
+        <source>Double-click on a binding to change it.</source>
+        <translation>Đúp chuột vào phím đã được thiết lập để thay đổi.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="45"/>
+        <source>Clear All</source>
+        <translation>Bỏ Trống Hết</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="52"/>
+        <source>Restore Defaults</source>
+        <translation>Khôi phục về mặc định</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+        <source>Action</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+        <source>Hotkey</source>
+        <translation>Phím tắt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="74"/>
+        <source>Context</source>
+        <translation>Bối cảnh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="96"/>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="183"/>
+        <source>Conflicting Key Sequence</source>
+        <translation>Tổ hợp phím bị xung đột</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="97"/>
+        <source>The entered key sequence is already assigned to: %1</source>
+        <translation>Tổ hợp phím này đã gán với: %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="171"/>
+        <source>Restore Default</source>
+        <translation>Khôi phục về mặc định</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+        <source>Clear</source>
+        <translation>Xóa</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="184"/>
+        <source>The default key sequence is already assigned to: %1</source>
+        <translation>Tổ hợp phím này đã gán với: %1</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureInput</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+        <source>ConfigureInput</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+        <source>Player 1</source>
+        <translation>Người chơi 1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+        <source>Player 2</source>
+        <translation>Người chơi 2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+        <source>Player 3</source>
+        <translation>Người chơi 3</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+        <source>Player 4</source>
+        <translation>Người chơi 4</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+        <source>Player 5</source>
+        <translation>Người chơi 5</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+        <source>Player 6</source>
+        <translation>Người chơi 6</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+        <source>Player 7</source>
+        <translation>Người chơi 7</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+        <source>Player 8</source>
+        <translation>Người chơi 8</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+        <source>Advanced</source>
+        <translation>Nâng cao</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+        <source>Console Mode</source>
+        <translation>Console Mode</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+        <source>Docked</source>
+        <translation>Chế độ cắm TV</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+        <source>Undocked</source>
+        <translation>Chế độ cầm tay</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+        <source>Vibration</source>
+        <translation>Độ rung</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="215"/>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="261"/>
+        <source>Configure</source>
+        <translation>Thiết lập</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="225"/>
+        <source>Motion</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="296"/>
+        <source>Controllers</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="324"/>
+        <source>1</source>
+        <translation>1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="365"/>
+        <source>2</source>
+        <translation>2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="375"/>
+        <source>3</source>
+        <translation>3</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="385"/>
+        <source>4</source>
+        <translation>4</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="395"/>
+        <source>5</source>
+        <translation>5</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="405"/>
+        <source>6</source>
+        <translation>6</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="415"/>
+        <source>7</source>
+        <translation>7</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="425"/>
+        <source>8</source>
+        <translation>8</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="435"/>
+        <source>Connected</source>
+        <translation>Đã kết nối</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="494"/>
+        <source>Defaults</source>
+        <translation>Quay về mặc định</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input.ui" line="537"/>
+        <source>Clear</source>
+        <translation>Xóa</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureInputAdvanced</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+        <source>Configure Input</source>
+        <translation>Thiết lập đầu vào</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+        <source>Joycon Colors</source>
+        <translation>Màu tay cầm Joycon</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+        <source>Player 1</source>
+        <translation>Người chơi 1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+        <source>L Body</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+        <source>L Button</source>
+        <translation>Phím L</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+        <source>R Body</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+        <source>R Button</source>
+        <translation>Phím R</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+        <source>Player 2</source>
+        <translation>Người chơi 2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+        <source>Player 3</source>
+        <translation>Người chơi 3</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+        <source>Player 4</source>
+        <translation>Người chơi 4</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+        <source>Player 5</source>
+        <translation>Người chơi 5</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+        <source>Player 6</source>
+        <translation>Người chơi 6</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+        <source>Player 7</source>
+        <translation>Người chơi 7</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+        <source>Player 8</source>
+        <translation>Người chơi 8</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+        <source>Emulated Devices</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+        <source>Keyboard</source>
+        <translation>Bàn phím</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2558"/>
+        <source>Mouse</source>
+        <translation>Chuột</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2565"/>
+        <source>Touchscreen</source>
+        <translation>Màn hình cảm ứng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2588"/>
+        <source>Advanced</source>
+        <translation>Nâng cao</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+        <source>Debug Controller</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2702"/>
+        <source>Configure</source>
+        <translation>Thiết lập</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2612"/>
+        <source>Other</source>
+        <translation>Khác</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2624"/>
+        <source>Emulate Analog with Keyboard Input</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2631"/>
+        <source>Requires restarting yuzu</source>
+        <translation>Phải khởi động lại yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2640"/>
+        <source>Enable XInput 8 player support (disables web applet)</source>
+        <translation>Bật hỗ trợ XInput cho 8 người (tắt web applet)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2653"/>
+        <source>Enable UDP controllers (not needed for motion)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2666"/>
+        <source>Enable mouse panning</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2673"/>
+        <source>Mouse sensitivity</source>
+        <translation>Độ nhạy chuột</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2679"/>
+        <source>%</source>
+        <translation>%</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2695"/>
+        <source>Motion / Touch</source>
+        <translation>Chuyển động / Cảm ứng</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureInputPlayer</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+        <source>Configure Input</source>
+        <translation>Thiết lập đầu vào</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+        <source>Connect Controller</source>
+        <translation>Kết nối Tay cầm</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="100"/>
+        <source>Input Device</source>
+        <translation>Thiết bị Nhập</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="156"/>
+        <source>Profile</source>
+        <translation>Hồ sơ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="196"/>
+        <source>Save</source>
+        <translation>Lưu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="212"/>
+        <source>New</source>
+        <translation>Mới</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="228"/>
+        <source>Delete</source>
+        <translation>Xoá</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="291"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1190"/>
+        <source>Left Stick</source>
+        <translation>Cần trái</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="349"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="391"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="925"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="964"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2583"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2622"/>
+        <source>Up</source>
+        <translation>Lên</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="422"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="461"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="995"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1034"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2069"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2653"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2692"/>
+        <source>Left</source>
+        <translation>Trái</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="471"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="510"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1044"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1083"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2118"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2702"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2741"/>
+        <source>Right</source>
+        <translation>Phải</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="553"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="592"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1126"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1165"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2784"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2823"/>
+        <source>Down</source>
+        <translation>Xuống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="623"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="662"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2854"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2893"/>
+        <source>Pressed</source>
+        <translation>Nhấn</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="672"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="711"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2903"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2942"/>
+        <source>Modifier</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="721"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2952"/>
+        <source>Range</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="754"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2985"/>
+        <source>%</source>
+        <translation>%</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="797"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="3025"/>
+        <source>Deadzone: 0%</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="821"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="3049"/>
+        <source>Modifier Range: 0%</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="867"/>
+        <source>D-Pad</source>
+        <translation>D-Pad</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1251"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1290"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1195"/>
+        <source>L</source>
+        <translation>L</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1306"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1345"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1187"/>
+        <source>ZL</source>
+        <translation>ZL</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1426"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1465"/>
+        <source>Minus</source>
+        <translation>Trừ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1475"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1514"/>
+        <source>Capture</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1545"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1584"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1186"/>
+        <source>Plus</source>
+        <translation>Cộng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1594"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1633"/>
+        <source>Home</source>
+        <translation>Home</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1698"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1737"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1189"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1196"/>
+        <source>R</source>
+        <translation>R</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1753"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1792"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1188"/>
+        <source>ZR</source>
+        <translation>ZR</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1873"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1912"/>
+        <source>SL</source>
+        <translation>SL</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1922"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1961"/>
+        <source>SR</source>
+        <translation>SR</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2030"/>
+        <source>Motion 1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2079"/>
+        <source>Motion 2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2170"/>
+        <source>Face Buttons</source>
+        <translation>Nút chức năng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2228"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2267"/>
+        <source>X</source>
+        <translation>X</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2298"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2337"/>
+        <source>Y</source>
+        <translation>Y</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2347"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2386"/>
+        <source>A</source>
+        <translation>A</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2429"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2468"/>
+        <source>B</source>
+        <translation>B</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2516"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1191"/>
+        <source>Right Stick</source>
+        <translation>Cần phải</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="330"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="396"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="461"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="507"/>
+        <source>Clear</source>
+        <translation>Bỏ trống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="332"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="398"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="463"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="509"/>
+        <source>[not set]</source>
+        <translation>[không đặt]</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="335"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="512"/>
+        <source>Toggle button</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="341"/>
+        <source>Invert button</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="349"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="465"/>
+        <source>Invert axis</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="355"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+        <source>Set threshold</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="359"/>
+        <source>Choose a value between 0% and 100%</source>
+        <translation>Chọn một giá trị giữa 0% và 100%</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="436"/>
+        <source>Map Analog Stick</source>
+        <translation>Thiết lập Cần Điều Khiển</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="437"/>
+        <source>After pressing OK, first move your joystick horizontally, and then vertically.
+To invert the axes, first move your joystick vertically, and then horizontally.</source>
+        <translation>Sau khi bấm OK, di chuyển cần sang ngang, rồi sau đó sang dọc.
+Nếu muốn đảo ngược hướng cần điều khiển, di chuyển cần sang dọc trước, rồi sang ngang.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="537"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="891"/>
+        <source>Deadzone: %1%</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="546"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="896"/>
+        <source>Modifier Range: %1%</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="572"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="918"/>
+        <source>Pro Controller</source>
+        <translation>Tay cầm Pro Controller</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="924"/>
+        <source>Dual Joycons</source>
+        <translation>Joycon đôi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="930"/>
+        <source>Left Joycon</source>
+        <translation>Joycon Trái</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="936"/>
+        <source>Right Joycon</source>
+        <translation>Joycon Phải</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="942"/>
+        <source>Handheld</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="948"/>
+        <source>GameCube Controller</source>
+        <translation>Tay cầm GameCube</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="958"/>
+        <source>Poke Ball Plus</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="964"/>
+        <source>NES Controller</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="970"/>
+        <source>SNES Controller</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="976"/>
+        <source>N64 Controller</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="982"/>
+        <source>Sega Genesis</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1194"/>
+        <source>Start / Pause</source>
+        <translation>Bắt đầu / Tạm ngưng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1197"/>
+        <source>Z</source>
+        <translation>Z</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1198"/>
+        <source>Control Stick</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1199"/>
+        <source>C-Stick</source>
+        <translation>C-Stick</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1297"/>
+        <source>Shake!</source>
+        <translation>Lắc!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1299"/>
+        <source>[waiting]</source>
+        <translation>[Chờ]</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1381"/>
+        <source>New Profile</source>
+        <translation>Hồ sơ mới</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1381"/>
+        <source>Enter a profile name:</source>
+        <translation>Nhập tên hồ sơ:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1389"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1397"/>
+        <source>Create Input Profile</source>
+        <translation>Tạo Hồ Sơ Phím</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1390"/>
+        <source>The given profile name is not valid!</source>
+        <translation>Tên hồ sơ không hợp lệ!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1398"/>
+        <source>Failed to create the input profile &quot;%1&quot;</source>
+        <translation>Quá trình tạo hồ sơ phím &quot;%1&quot; thất bại</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1418"/>
+        <source>Delete Input Profile</source>
+        <translation>Xóa Hồ Sơ Phím</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1419"/>
+        <source>Failed to delete the input profile &quot;%1&quot;</source>
+        <translation>Quá trình xóa hồ sơ phím &quot;%1&quot; thất bại</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1441"/>
+        <source>Load Input Profile</source>
+        <translation>Nạp Hồ Sơ Phím</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1442"/>
+        <source>Failed to load the input profile &quot;%1&quot;</source>
+        <translation>Quá trình nạp hồ sơ phím &quot;%1&quot; thất bại</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1461"/>
+        <source>Save Input Profile</source>
+        <translation>Lưu Hồ Sơ Phím</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1462"/>
+        <source>Failed to save the input profile &quot;%1&quot;</source>
+        <translation>Quá trình lưu hồ sơ phím &quot;%1&quot; thất bại</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureInputProfileDialog</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="14"/>
+        <source>Create Input Profile</source>
+        <translation>Tạo Hồ Sơ Phím</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="40"/>
+        <source>Clear</source>
+        <translation>Bỏ trống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="47"/>
+        <source>Defaults</source>
+        <translation>Mặc định</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureMotionTouch</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+        <source>Configure Motion / Touch</source>
+        <translation>Tùy chỉnh Chuột / Cảm Ứng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="15"/>
+        <source>Touch</source>
+        <translation>Cảm Ứng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="23"/>
+        <source>UDP Calibration:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="30"/>
+        <source>(100, 50) - (1800, 850)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="46"/>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="73"/>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="245"/>
+        <source>Configure</source>
+        <translation>Cài đặt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="57"/>
+        <source>Touch from button profile:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="85"/>
+        <source>CemuhookUDP Config</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="91"/>
+        <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="134"/>
+        <source>Server:</source>
+        <translation>Server:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="161"/>
+        <source>Port:</source>
+        <translation>Cổng:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="188"/>
+        <source>Learn More</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="201"/>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="268"/>
+        <source>Test</source>
+        <translation>Thử nghiệm</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="214"/>
+        <source>Add Server</source>
+        <translation>Thêm Server</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+        <source>Remove Server</source>
+        <translation>Xóa Server</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="91"/>
+        <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+        <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Tìm hiểu thêm&lt;/span&gt;&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="171"/>
+        <source>%1:%2</source>
+        <translation>%1:%2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="176"/>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="180"/>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="184"/>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="190"/>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="196"/>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="290"/>
+        <source>yuzu</source>
+        <translation>yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="176"/>
+        <source>Port number has invalid characters</source>
+        <translation>Cổng có kí tự không hợp lệ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="180"/>
+        <source>Port has to be in range 0 and 65353</source>
+        <translation>Cổng phải từ 0 đến 65353</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="184"/>
+        <source>IP address is not valid</source>
+        <translation>Địa chỉ IP không hợp lệ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="190"/>
+        <source>This UDP server already exists</source>
+        <translation>Server UDP đã tồn tại</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="196"/>
+        <source>Unable to add more than 8 servers</source>
+        <translation>Không thể vượt quá 8 server</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="212"/>
+        <source>Testing</source>
+        <translation>Thử nghiệm</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="228"/>
+        <source>Configuring</source>
+        <translation>Cài đặt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="259"/>
+        <source>Test Successful</source>
+        <translation>Thử Nghiệm Thành Công</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="260"/>
+        <source>Successfully received data from the server.</source>
+        <translation>Nhận được dữ liệu từ server!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="262"/>
+        <source>Test Failed</source>
+        <translation>Thử Nghiệm Thất Bại</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="263"/>
+        <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+        <translation>Không thể nhận được dữ liệu hợp lệ từ server. &lt;br&gt;Hãy chắc chắn server được thiết lập chính xác, từ địa chỉ lẫn cổng phải  được thiết lập đúng.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="291"/>
+        <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ConfigureNetwork</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_network.ui" line="14"/>
+        <source>Form</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_network.ui" line="17"/>
+        <source>Network</source>
+        <translation>Mạng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_network.ui" line="25"/>
+        <source>General</source>
+        <translation>Tổng Quan</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_network.ui" line="34"/>
+        <source>Network Interface</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_network.cpp" line="17"/>
+        <source>None</source>
+        <translation>Trống</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigurePerGame</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="12"/>
+        <source>Dialog</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="26"/>
+        <source>Info</source>
+        <translation>Thông Tin</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="85"/>
+        <source>Name</source>
+        <translation>Tên</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="92"/>
+        <source>Title ID</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="129"/>
+        <source>Filename</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="156"/>
+        <source>Format</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="163"/>
+        <source>Version</source>
+        <translation>Phiên Bản</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="170"/>
+        <source>Size</source>
+        <translation>Dung Lượng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="177"/>
+        <source>Developer</source>
+        <translation>Nhà Phát Hành</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="64"/>
+        <source>Add-Ons</source>
+        <translation>Bổ Sung</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="65"/>
+        <source>General</source>
+        <translation>Tổng Quan</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="66"/>
+        <source>System</source>
+        <translation>Hệ Thống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="67"/>
+        <source>CPU</source>
+        <translation>CPU</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="68"/>
+        <source>Graphics</source>
+        <translation>Đồ Họa</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="69"/>
+        <source>Adv. Graphics</source>
+        <translation>Đồ Họa Nâng Cao</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="70"/>
+        <source>Audio</source>
+        <translation>Âm Thanh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="73"/>
+        <source>Properties</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="93"/>
+        <source>Use global configuration (%1)</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ConfigurePerGameAddons</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+        <source>Form</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="17"/>
+        <source>Add-Ons</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="48"/>
+        <source>Patch Name</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="49"/>
+        <source>Version</source>
+        <translation>Phiên Bản</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureProfileManager</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+        <source>Form</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="17"/>
+        <source>Profiles</source>
+        <translation>Hồ Sơ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="25"/>
+        <source>Profile Manager</source>
+        <translation>Quản Lý Hồ Sơ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="42"/>
+        <source>Current User</source>
+        <translation>Người Dùng Hiện Tại</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="80"/>
+        <source>Username</source>
+        <translation>Tên</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="110"/>
+        <source>Set Image</source>
+        <translation>Đặt Hình Ảnh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="130"/>
+        <source>Add</source>
+        <translation>Thêm</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="140"/>
+        <source>Rename</source>
+        <translation>Đổi Tên</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="150"/>
+        <source>Remove</source>
+        <translation>Gỡ Bỏ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="162"/>
+        <source>Profile management is available only when game is not running.</source>
+        <translation>Chỉ có thể quản lí hồ sơ khi game không chạy.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="55"/>
+        <source>%1
+%2</source>
+        <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+        <translation>%1
+%2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="73"/>
+        <source>Enter Username</source>
+        <translation>Điền Tên</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="136"/>
+        <source>Users</source>
+        <translation>Người Dùng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="198"/>
+        <source>Enter a username for the new user:</source>
+        <translation>Chọn tên cho người dùng mới</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="218"/>
+        <source>Enter a new username:</source>
+        <translation>Chọn một tên mới:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="243"/>
+        <source>Confirm Delete</source>
+        <translation>Xác nhận xóa</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="244"/>
+        <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+        <translation>Bạn đang xóa người dùng &quot;%1&quot;. Bạn chắc chứ?</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="271"/>
+        <source>Select User Image</source>
+        <translation>Chọn Ảnh cho Người Dùng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="272"/>
+        <source>JPEG Images (*.jpg *.jpeg)</source>
+        <translation>Ảnh JPEG (*.jpg *.jpeg)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="281"/>
+        <source>Error deleting image</source>
+        <translation>Lỗi khi xóa ảnh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="282"/>
+        <source>Error occurred attempting to overwrite previous image at: %1.</source>
+        <translation>Có lỗi khi ghi đè ảnh trước tại: %1.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="290"/>
+        <source>Error deleting file</source>
+        <translation>Lỗi xóa ảnh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="291"/>
+        <source>Unable to delete existing file: %1.</source>
+        <translation>Không thể xóa ảnh hiện tại: %1.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="298"/>
+        <source>Error creating user image directory</source>
+        <translation>Lỗi khi tạo thư mục chứa ảnh người dùng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="299"/>
+        <source>Unable to create directory %1 for storing user images.</source>
+        <translation>Không thể tạo thư mục %1 để chứa ảnh người dùng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="304"/>
+        <source>Error copying user image</source>
+        <translation>Lỗi chép ảnh người dùng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="305"/>
+        <source>Unable to copy image from %1 to %2</source>
+        <translation>Không thể chép ảnh từ %1 sang %2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="314"/>
+        <source>Error resizing user image</source>
+        <translation>Lỗi thu phóng ảnh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="315"/>
+        <source>Unable to resize image</source>
+        <translation>Không thể thu phóng ảnh</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureSystem</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+        <source>Form</source>
+        <translation>Mẫu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="17"/>
+        <source>System</source>
+        <translation>Hệ thống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="25"/>
+        <source>System Settings</source>
+        <translation>Cài đặt hệ thống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="33"/>
+        <source>Region:</source>
+        <translation>Vùng:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="41"/>
+        <source>Auto</source>
+        <translation>Tự động</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="46"/>
+        <source>Default</source>
+        <translation>Mặc định</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="51"/>
+        <source>CET</source>
+        <translation>CET</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="56"/>
+        <source>CST6CDT</source>
+        <translation>CST6CDT</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="61"/>
+        <source>Cuba</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="66"/>
+        <source>EET</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="71"/>
+        <source>Egypt</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="76"/>
+        <source>Eire</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="81"/>
+        <source>EST</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="86"/>
+        <source>EST5EDT</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="91"/>
+        <source>GB</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="96"/>
+        <source>GB-Eire</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="101"/>
+        <source>GMT</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="106"/>
+        <source>GMT+0</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="111"/>
+        <source>GMT-0</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="116"/>
+        <source>GMT0</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="121"/>
+        <source>Greenwich</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="126"/>
+        <source>Hongkong</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="131"/>
+        <source>HST</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="136"/>
+        <source>Iceland</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="141"/>
+        <source>Iran</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="146"/>
+        <source>Israel</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="151"/>
+        <source>Jamaica</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="156"/>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="275"/>
+        <source>Japan</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="161"/>
+        <source>Kwajalein</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="166"/>
+        <source>Libya</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="171"/>
+        <source>MET</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="176"/>
+        <source>MST</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="181"/>
+        <source>MST7MDT</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="186"/>
+        <source>Navajo</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="191"/>
+        <source>NZ</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="196"/>
+        <source>NZ-CHAT</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="201"/>
+        <source>Poland</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="206"/>
+        <source>Portugal</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="211"/>
+        <source>PRC</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="216"/>
+        <source>PST8PDT</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="221"/>
+        <source>ROC</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="226"/>
+        <source>ROK</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="231"/>
+        <source>Singapore</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="236"/>
+        <source>Turkey</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="241"/>
+        <source>UCT</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="246"/>
+        <source>Universal</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="251"/>
+        <source>UTC</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="256"/>
+        <source>W-SU</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="261"/>
+        <source>WET</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="266"/>
+        <source>Zulu</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="280"/>
+        <source>USA</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="285"/>
+        <source>Europe</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="290"/>
+        <source>Australia</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="295"/>
+        <source>China</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="300"/>
+        <source>Korea</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="305"/>
+        <source>Taiwan</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="313"/>
+        <source>Time Zone:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="320"/>
+        <source>Note: this can be overridden when region setting is auto-select</source>
+        <translation>Chú ý: cái này có thể ghi đè khi cài đặt quốc gia là chọn tự động</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="324"/>
+        <source>Japanese (日本語)</source>
+        <translation>Tiếng Nhật (日本語)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="329"/>
+        <source>English</source>
+        <translation>Tiếng Anh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="334"/>
+        <source>French (français)</source>
+        <translation>Tiếng Pháp (French)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="339"/>
+        <source>German (Deutsch)</source>
+        <translation>Tiếng Đức (German)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="344"/>
+        <source>Italian (italiano)</source>
+        <translation>Tiếng Ý (Italian)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="349"/>
+        <source>Spanish (español)</source>
+        <translation>Tiếng Tây Ban Nha (Spanish)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="354"/>
+        <source>Chinese</source>
+        <translation>Tiếng Trung</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="359"/>
+        <source>Korean (한국어)</source>
+        <translation>Tiếng Hàn (Korean)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="364"/>
+        <source>Dutch (Nederlands)</source>
+        <translation>Tiếng Hà Lan (Dutch)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="369"/>
+        <source>Portuguese (português)</source>
+        <translation>Tiếng Bồ Đào Nha (Portuguese)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="374"/>
+        <source>Russian (Русский)</source>
+        <translation>Tiếng Nga (Russian)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="379"/>
+        <source>Taiwanese</source>
+        <translation>Tiếng Đài Loan</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="384"/>
+        <source>British English</source>
+        <translation>Tiếng Anh UK</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="389"/>
+        <source>Canadian French</source>
+        <translation>Tiếng Pháp Canada</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="394"/>
+        <source>Latin American Spanish</source>
+        <translation>Tiếng Mỹ La-tinh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="399"/>
+        <source>Simplified Chinese</source>
+        <translation>Tiếng Trung giản thể</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="404"/>
+        <source>Traditional Chinese (正體中文)</source>
+        <translation>Tiếng Trung phồn thể (Traditional Chinese)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+        <source>Brazilian Portuguese (português do Brasil)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="417"/>
+        <source>Custom RTC</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="424"/>
+        <source>Language</source>
+        <translation>Ngôn ngữ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+        <source>RNG Seed</source>
+        <translation>Hạt giống RNG</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="439"/>
+        <source>Mono</source>
+        <translation>Mono</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="444"/>
+        <source>Stereo</source>
+        <translation>Stereo</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+        <source>Surround</source>
+        <translation>Surround</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="457"/>
+        <source>Console ID:</source>
+        <translation>ID bàn giao tiếp:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="464"/>
+        <source>Sound output mode</source>
+        <translation>Chế độ đầu ra âm thanh</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="478"/>
+        <source>d MMM yyyy h:mm:ss AP</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="515"/>
+        <source>Regenerate</source>
+        <translation>Tạo mới</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.ui" line="540"/>
+        <source>System settings are available only when game is not running.</source>
+        <translation>Cài đặt hệ thống chỉ khả dụng khi trò chơi không chạy.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.cpp" line="166"/>
+        <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+        <translation>Điều này sẽ thay thế Switch ảo hiện tại của bạn sang một cái mới. Switch ảo hiện tại của bạn sẽ không thể hồi phục lại. Điều này có thể gây ra tác dụng không mong muốn trong trò chơi. Điều này có thể gây thất bại, nếu bạn đang sử dụng thiết lập lưu trữ đã lỗi thời. Tiếp tục?</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.cpp" line="170"/>
+        <source>Warning</source>
+        <translation>Chú ý</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_system.cpp" line="178"/>
+        <source>Console ID: 0x%1</source>
+        <translation>ID của máy: 0x%1</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureTas</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="11"/>
+        <source>TAS</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="17"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;help page&lt;/span&gt;&lt;/a&gt; on the yuzu website.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="24"/>
+        <source>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (Configure -&gt; General -&gt; Hotkeys).</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="34"/>
+        <source>WARNING: This is an experimental feature.&lt;br/&gt;It will not play back scripts frame perfectly with the current, imperfect syncing method.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="51"/>
+        <source>Settings</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="57"/>
+        <source>Enable TAS features</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="64"/>
+        <source>Loop script</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="74"/>
+        <source>Pause execution during loads</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="88"/>
+        <source>Script Directory</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="94"/>
+        <source>Path</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.ui" line="101"/>
+        <source>...</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ConfigureTasDialog</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.cpp" line="20"/>
+        <source>TAS Configuration</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_tas.cpp" line="51"/>
+        <source>Select TAS Load Directory...</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ConfigureTouchFromButton</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+        <source>Configure Touchscreen Mappings</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+        <source>Mapping:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+        <source>New</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+        <source>Delete</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+        <source>Rename</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+        <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+        <source>Delete Point</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+        <source>Button</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+        <source>X</source>
+        <comment>X axis</comment>
+        <translation>X</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="80"/>
+        <source>Y</source>
+        <comment>Y axis</comment>
+        <translation>Y</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="197"/>
+        <source>New Profile</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="197"/>
+        <source>Enter the name for the new profile.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="208"/>
+        <source>Delete Profile</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="208"/>
+        <source>Delete profile %1?</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="221"/>
+        <source>Rename Profile</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="221"/>
+        <source>New name:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="230"/>
+        <source>[press key]</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ConfigureTouchscreenAdvanced</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+        <source>Configure Touchscreen</source>
+        <translation>Thiết lập màn hình cảm ứng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+        <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+        <translation>Chú ý: Cài đặt trong trang này có thể ảnh hưởng đến hoạt động bên trong giả lập màn hình cảm ứng của yuzu&apos;s. Thay đổi chúng có thể dẫn đến hành vi không mong muốn, chẳng hạn như một phần của màn hình cảm ứng sẽ không hoạt động. Bạn chỉ nên sử dụng trang này nếu bạn biết bạn đang làm gì.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+        <source>Touch Parameters</source>
+        <translation>Thông số cảm ứng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+        <source>Touch Diameter Y</source>
+        <translation>Đường kính cảm ứng Y</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="91"/>
+        <source>Touch Diameter X</source>
+        <translation>Đường kính cảm ứng X</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+        <source>Rotational Angle</source>
+        <translation>Góc quay</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="132"/>
+        <source>Restore Defaults</source>
+        <translation>Khôi phục về mặc định</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureUI</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="20"/>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="28"/>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="40"/>
+        <source>None</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="21"/>
+        <source>Small (32x32)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="22"/>
+        <source>Standard (64x64)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="23"/>
+        <source>Large (128x128)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="24"/>
+        <source>Full Size (256x256)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="29"/>
+        <source>Small (24x24)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="30"/>
+        <source>Standard (48x48)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="31"/>
+        <source>Large (72x72)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="36"/>
+        <source>Filename</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="37"/>
+        <source>Filetype</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="38"/>
+        <source>Title ID</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="39"/>
+        <source>Title Name</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ConfigureUi</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+        <source>Form</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="17"/>
+        <source>UI</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="23"/>
+        <source>General</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="31"/>
+        <source>Note: Changing language will apply your configuration.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="43"/>
+        <source>Interface language:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="57"/>
+        <source>Theme:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="74"/>
+        <source>Game List</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="82"/>
+        <source>Show Add-Ons Column</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="91"/>
+        <source>Game Icon Size:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="105"/>
+        <source>Folder Icon Size:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="119"/>
+        <source>Row 1 Text:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="133"/>
+        <source>Row 2 Text:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="150"/>
+        <source>Screenshots</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="158"/>
+        <source>Ask Where To Save Screenshots (Windows Only)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="167"/>
+        <source>Screenshots Path: </source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.ui" line="177"/>
+        <source>...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="93"/>
+        <source>Select Screenshots Path...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="173"/>
+        <source>&lt;System&gt;</source>
+        <translation>&lt;System&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="174"/>
+        <source>English</source>
+        <translation>Tiếng Anh</translation>
+    </message>
+</context>
+<context>
+    <name>ConfigureVibration</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="14"/>
+        <source>Configure Vibration</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="23"/>
+        <source>Vibration</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="56"/>
+        <source>Player 1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="89"/>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="141"/>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="193"/>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="245"/>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="315"/>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="367"/>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="419"/>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="471"/>
+        <source>%</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="108"/>
+        <source>Player 2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="160"/>
+        <source>Player 3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="212"/>
+        <source>Player 4</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="282"/>
+        <source>Player 5</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="334"/>
+        <source>Player 6</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="386"/>
+        <source>Player 7</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="438"/>
+        <source>Player 8</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="496"/>
+        <source>Settings</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="502"/>
+        <source>Enable Accurate Vibration</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ConfigureWeb</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+        <source>Form</source>
+        <translation>Mẫu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="17"/>
+        <source>Web</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="25"/>
+        <source>yuzu Web Service</source>
+        <translation>Dịch vụ Web yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="31"/>
+        <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+        <translation>Bằng cách cung cấp tên đăng nhập và mã thông báo của bạn, bạn đã chấp thuận sẽ cho phép yuzu thu thập dữ liệu đã sử dụng, trong đó có thể có thông tin nhận dạng người dùng.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="49"/>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="155"/>
+        <source>Verify</source>
+        <translation>Xác nhận</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="56"/>
+        <source>Sign up</source>
+        <translation>Đăng ký</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="66"/>
+        <source>Token: </source>
+        <translation>Mã thông báo: </translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="76"/>
+        <source>Username: </source>
+        <translation>Tên người dùng: </translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="93"/>
+        <source>What is my token?</source>
+        <translation>Mã thông báo của tôi là gì?</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="118"/>
+        <source>Telemetry</source>
+        <translation>Viễn trắc</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="124"/>
+        <source>Share anonymous usage data with the yuzu team</source>
+        <translation>Chia sẽ dữ liệu sử dụng ẩn danh với nhóm yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="131"/>
+        <source>Learn more</source>
+        <translation>Tìm hiểu thêm</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="140"/>
+        <source>Telemetry ID:</source>
+        <translation>ID Viễn trắc: </translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="156"/>
+        <source>Regenerate</source>
+        <translation>Tạo mới</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="170"/>
+        <source>Discord Presence</source>
+        <translation>Hiện diện trên Discord</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.ui" line="176"/>
+        <source>Show Current Game in your Discord Status</source>
+        <translation>Hiển thị trò chơi hiện tại lên thông tin Discord của bạn</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="69"/>
+        <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+        <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Tìm hiểu thêm&lt;/span&gt;&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="73"/>
+        <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+        <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Đăng ký&lt;/span&gt;&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="77"/>
+        <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+        <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Mã thông báo của tôi là gì?&lt;/span&gt;&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="81"/>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="126"/>
+        <source>Telemetry ID: 0x%1</source>
+        <translation>ID Viễn trắc: 0x%1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="92"/>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="166"/>
+        <source>Unspecified</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+        <source>Token not verified</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="119"/>
+        <source>Token was not verified. The change to your token has not been saved.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="145"/>
+        <source>Verifying...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="167"/>
+        <source>Verification failed</source>
+        <translation>Xác nhận không thành công</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_web.cpp" line="168"/>
+        <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>ControllerDialog</name>
+    <message>
+        <location filename="../../src/yuzu/debugger/controller.cpp" line="21"/>
+        <source>Controller P1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/controller.cpp" line="60"/>
+        <source>&amp;Controller P1</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>GMainWindow</name>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="176"/>
+        <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+        <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Dữ liệu ẩn danh được thu thập&lt;/a&gt;để hỗ trợ cải thiện yuzu. &lt;br/&gt;&lt;br/&gt;Bạn có muốn chia sẽ dữ liệu sử dụng cho chúng tôi?</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="179"/>
+        <source>Telemetry</source>
+        <translation>Viễn trắc</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="600"/>
+        <source>Loading Web Applet...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="647"/>
+        <location filename="../../src/yuzu/main.cpp" line="650"/>
+        <source>Disable Web Applet</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="651"/>
+        <source>Disabling the web applet will cause it to not be shown again for the rest of the emulated session. This can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet?</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="754"/>
+        <source>The amount of shaders currently being built</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="756"/>
+        <source>The current selected resolution scaling multiplier.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="759"/>
+        <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+        <translation>Tốc độ giả lập hiện tại. Giá trị cao hơn hoặc thấp hơn 100% chỉ ra giả lập sẽ chạy nhanh hơn hoặc chậm hơn trên máy Switch</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="762"/>
+        <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+        <translation>Có bao nhiêu khung hình trên mỗi giây mà trò chơi đang hiển thị. Điều này sẽ thay đổi từ trò chơi này đến trò chơi kia và khung cảnh này đến khung cảnh kia.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="766"/>
+        <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+        <translation>Thời gian mà giả lập lấy từ khung hình Switch, sẽ không kể đến giới hạn khung hình hoặc v-sync. Đối với tốc độ tối đa mà giả lập nhận được nhiều nhất là ở độ khoảng 16.67 ms.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="841"/>
+        <source>Invalid config detected</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="842"/>
+        <source>Handheld controller can&apos;t be used on docked mode. Pro controller will be selected.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="854"/>
+        <source>DOCK</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="888"/>
+        <source>VULKAN</source>
+        <translation>VULKAN</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="888"/>
+        <source>OPENGL</source>
+        <translation>OPENGL</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="951"/>
+        <source>&amp;Clear Recent Files</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1210"/>
+        <source>&amp;Continue</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1212"/>
+        <source>&amp;Pause</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1285"/>
+        <source>Warning Outdated Game Format</source>
+        <translation>Chú ý định dạng trò chơi đã lỗi thời</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1286"/>
+        <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+        <translation>Bạn đang sử dụng định dạng danh mục ROM giải mã cho trò chơi này, và đó là một định dạng lỗi thời đã được thay thế bởi những thứ khác như NCA, NAX, XCI, hoặc NSP. Danh mục ROM giải mã có thể thiếu biểu tượng, metadata, và hỗ trợ cập nhật.&lt;br&gt;&lt;br&gt;Để giải thích về các định dạng khác nhau của Switch mà yuzu hỗ trợ, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;vui lòng kiểm tra trên wiki của chúng tôi&lt;/a&gt;. Thông báo này sẽ không hiển thị lại lần sau.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1298"/>
+        <location filename="../../src/yuzu/main.cpp" line="1335"/>
+        <source>Error while loading ROM!</source>
+        <translation>Xảy ra lỗi khi đang nạp ROM!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1299"/>
+        <source>The ROM format is not supported.</source>
+        <translation>Định dạng ROM này không hỗ trợ.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1303"/>
+        <source>An error occurred initializing the video core.</source>
+        <translation>Đã xảy ra lỗi khi khởi tạo lõi video.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1304"/>
+        <source>yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.Ensure that you have the latest graphics drivers for your GPU.</source>
+        <translation>yuzu đã gặp lỗi trong quá trình đang chạy lõi video, vui lòng kiểm tra sổ ghi chép để biết thêm chi tiết. Để biết thêm thông tin về cách truy cập sổ ghi chép, vui lòng xem trang sau: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Làm sao để tải tệp tin sổ ghi chép lên&lt;/a&gt;.Hãy chắc rằng bạn đang sử dụng trình điều khiển đồ họa GPU mới nhất.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1322"/>
+        <source>Error while loading ROM! %1</source>
+        <comment>%1 signifies a numeric error code.</comment>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1325"/>
+        <source>%1&lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to redump your files.&lt;br&gt;You can refer to the yuzu wiki&lt;/a&gt; or the yuzu Discord&lt;/a&gt; for help.</source>
+        <comment>%1 signifies an error string.</comment>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1336"/>
+        <source>An unknown error occurred. Please see the log for more details.</source>
+        <translation>Đã xảy ra lỗi không xác định. Vui lòng kiểm tra sổ ghi chép để biết thêm chi tiết.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1460"/>
+        <source>(64-bit)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1460"/>
+        <source>(32-bit)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1461"/>
+        <source>%1 %2</source>
+        <comment>%1 is the title name. %2 indicates if the title is 64-bit or 32-bit</comment>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1605"/>
+        <source>Save Data</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1653"/>
+        <source>Mod Data</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1665"/>
+        <source>Error Opening %1 Folder</source>
+        <translation>Xảy ra lỗi khi mở %1 thư mục</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1666"/>
+        <location filename="../../src/yuzu/main.cpp" line="2072"/>
+        <source>Folder does not exist!</source>
+        <translation>Thư mục này không tồn tại!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1678"/>
+        <source>Error Opening Transferable Shader Cache</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1679"/>
+        <source>Failed to create the shader cache directory for this title.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1731"/>
+        <source>Contents</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1733"/>
+        <source>Update</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1735"/>
+        <source>DLC</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1742"/>
+        <source>Remove Entry</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1742"/>
+        <source>Remove Installed Game %1?</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1772"/>
+        <location filename="../../src/yuzu/main.cpp" line="1788"/>
+        <location filename="../../src/yuzu/main.cpp" line="1819"/>
+        <location filename="../../src/yuzu/main.cpp" line="1880"/>
+        <location filename="../../src/yuzu/main.cpp" line="1898"/>
+        <location filename="../../src/yuzu/main.cpp" line="1921"/>
+        <source>Successfully Removed</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1773"/>
+        <source>Successfully removed the installed base game.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1776"/>
+        <location filename="../../src/yuzu/main.cpp" line="1791"/>
+        <location filename="../../src/yuzu/main.cpp" line="1814"/>
+        <source>Error Removing %1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1777"/>
+        <source>The base game is not installed in the NAND and cannot be removed.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1789"/>
+        <source>Successfully removed the installed update.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1792"/>
+        <source>There is no update installed for this title.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1815"/>
+        <source>There are no DLC installed for this title.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1820"/>
+        <source>Successfully removed %1 installed DLC.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1828"/>
+        <source>Delete OpenGL Transferable Shader Cache?</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1830"/>
+        <source>Delete Vulkan Transferable Shader Cache?</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1832"/>
+        <source>Delete All Transferable Shader Caches?</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1834"/>
+        <source>Remove Custom Game Configuration?</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1840"/>
+        <source>Remove File</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1875"/>
+        <location filename="../../src/yuzu/main.cpp" line="1883"/>
+        <source>Error Removing Transferable Shader Cache</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1876"/>
+        <location filename="../../src/yuzu/main.cpp" line="1894"/>
+        <source>A shader cache for this title does not exist.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1881"/>
+        <source>Successfully removed the transferable shader cache.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1884"/>
+        <source>Failed to remove the transferable shader cache.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1893"/>
+        <location filename="../../src/yuzu/main.cpp" line="1901"/>
+        <source>Error Removing Transferable Shader Caches</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1899"/>
+        <source>Successfully removed the transferable shader caches.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1902"/>
+        <source>Failed to remove the transferable shader cache directory.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1915"/>
+        <location filename="../../src/yuzu/main.cpp" line="1924"/>
+        <source>Error Removing Custom Configuration</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1916"/>
+        <source>A custom configuration for this title does not exist.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1922"/>
+        <source>Successfully removed the custom game configuration.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1925"/>
+        <source>Failed to remove the custom game configuration.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1932"/>
+        <location filename="../../src/yuzu/main.cpp" line="2011"/>
+        <source>RomFS Extraction Failed!</source>
+        <translation>Khai thác RomFS không thành công!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1933"/>
+        <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+        <translation>Đã xảy ra lỗi khi sao chép tệp tin RomFS hoặc người dùng đã hủy bỏ hoạt động này.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1991"/>
+        <source>Full</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1991"/>
+        <source>Skeleton</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1993"/>
+        <source>Select RomFS Dump Mode</source>
+        <translation>Chọn chế độ kết xuất RomFS</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="1994"/>
+        <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+        <translation>Vui lòng chọn RomFS mà bạn muốn kết xuất như thế nào.&lt;br&gt;Đầy đủ sẽ sao chép toàn bộ tệp tin vào một danh mục mới trong khi &lt;br&gt;bộ xương chỉ tạo kết cấu danh mục.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2012"/>
+        <source>There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation &gt; Configure &gt; System &gt; Filesystem &gt; Dump Root</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2019"/>
+        <source>Extracting RomFS...</source>
+        <translation>Khai thác RomFS...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2019"/>
+        <location filename="../../src/yuzu/main.cpp" line="2199"/>
+        <source>Cancel</source>
+        <translation>Hủy bỏ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2026"/>
+        <source>RomFS Extraction Succeeded!</source>
+        <translation>Khai thác RomFS thành công!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2027"/>
+        <source>The operation completed successfully.</source>
+        <translation>Các hoạt động đã hoàn tất thành công.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2071"/>
+        <source>Error Opening %1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2080"/>
+        <source>Select Directory</source>
+        <translation>Chọn danh mục</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2107"/>
+        <source>Properties</source>
+        <translation>Thuộc tính</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2108"/>
+        <source>The game properties could not be loaded.</source>
+        <translation>Thuộc tính của trò chơi không thể nạp được.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2120"/>
+        <source>Switch Executable (%1);;All Files (*.*)</source>
+        <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+        <translation>Thực thi Switch (%1);;Tất cả tệp tin (*.*)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2124"/>
+        <source>Load File</source>
+        <translation>Nạp tệp tin</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2136"/>
+        <source>Open Extracted ROM Directory</source>
+        <translation>Mở danh mục ROM đã trích xuất</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2147"/>
+        <source>Invalid Directory Selected</source>
+        <translation>Danh mục đã chọn không hợp lệ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2148"/>
+        <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+        <translation>Danh mục mà bạn đã chọn không có chứa tệp tin &apos;main&apos;.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2158"/>
+        <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+        <translation>Những tệp tin Switch cài được (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2163"/>
+        <source>Install Files</source>
+        <translation type="unfinished"/>
+    </message>
+    <message numerus="yes">
+        <location filename="../../src/yuzu/main.cpp" line="2207"/>
+        <source>%n file(s) remaining</source>
+        <translation type="unfinished"><numerusform></numerusform></translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2209"/>
+        <source>Installing file &quot;%1&quot;...</source>
+        <translation>Đang cài đặt tệp tin &quot;%1&quot;...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2255"/>
+        <location filename="../../src/yuzu/main.cpp" line="2269"/>
+        <source>Install Results</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2256"/>
+        <source>To avoid possible conflicts, we discourage users from installing base games to the NAND.
+Please, only use this feature to install updates and DLC.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message numerus="yes">
+        <location filename="../../src/yuzu/main.cpp" line="2262"/>
+        <source>%n file(s) were newly installed
+</source>
+        <translation type="unfinished"><numerusform></numerusform></translation>
+    </message>
+    <message numerus="yes">
+        <location filename="../../src/yuzu/main.cpp" line="2265"/>
+        <source>%n file(s) were overwritten
+</source>
+        <translation type="unfinished"><numerusform></numerusform></translation>
+    </message>
+    <message numerus="yes">
+        <location filename="../../src/yuzu/main.cpp" line="2267"/>
+        <source>%n file(s) failed to install
+</source>
+        <translation type="unfinished"><numerusform></numerusform></translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2368"/>
+        <source>System Application</source>
+        <translation>Hệ thống ứng dụng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2369"/>
+        <source>System Archive</source>
+        <translation>Hệ thống lưu trữ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2370"/>
+        <source>System Application Update</source>
+        <translation>Cập nhật hệ thống ứng dụng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2371"/>
+        <source>Firmware Package (Type A)</source>
+        <translation>Gói phần mềm (Loại A)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2372"/>
+        <source>Firmware Package (Type B)</source>
+        <translation>Gói phần mềm (Loại B)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2373"/>
+        <source>Game</source>
+        <translation>Trò chơi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2374"/>
+        <source>Game Update</source>
+        <translation>Cập nhật trò chơi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2375"/>
+        <source>Game DLC</source>
+        <translation>Nội dung trò chơi có thể tải xuống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2376"/>
+        <source>Delta Title</source>
+        <translation>Tiêu đề Delta</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2379"/>
+        <source>Select NCA Install Type...</source>
+        <translation>Chọn loại NCA để cài đặt...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2380"/>
+        <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+        <translation>Vui lòng chọn loại tiêu đề mà bạn muốn cài đặt NCA này:
+(Trong hầu hết trường hợp, chọn mặc định &apos;Game&apos; là tốt nhất.)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2386"/>
+        <source>Failed to Install</source>
+        <translation>Cài đặt đã không thành công</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2387"/>
+        <source>The title type you selected for the NCA is invalid.</source>
+        <translation>Loại tiêu đề NCA mà bạn chọn nó không hợp lệ.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2422"/>
+        <source>File not found</source>
+        <translation>Không tìm thấy tệp tin</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2423"/>
+        <source>File &quot;%1&quot; not found</source>
+        <translation>Không tìm thấy &quot;%1&quot; tệp tin</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2492"/>
+        <source>OK</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2506"/>
+        <source>Missing yuzu Account</source>
+        <translation>Thiếu tài khoản yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2507"/>
+        <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+        <translation>Để gửi trường hợp thử nghiệm trò chơi tương thích, bạn phải liên kết tài khoản yuzu.&lt;br&gt;&lt;br/&gt;Để liên kết tải khoản yuzu của bạn, hãy đến Giả lập &amp;gt; Thiết lập &amp;gt; Web.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2517"/>
+        <source>Error opening URL</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2518"/>
+        <source>Unable to open the URL &quot;%1&quot;.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2777"/>
+        <source>TAS Recording</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2777"/>
+        <source>Overwrite file of player 1?</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2830"/>
+        <source>Amiibo File (%1);; All Files (*.*)</source>
+        <translation>Tệp tin Amiibo (%1);; Tất cả tệp tin (*.*)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2831"/>
+        <source>Load Amiibo</source>
+        <translation>Nạp dữ liệu Amiibo</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2849"/>
+        <source>Error opening Amiibo data file</source>
+        <translation>Xảy ra lỗi khi mở dữ liệu tệp tin Amiibo</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2850"/>
+        <source>Unable to open Amiibo file &quot;%1&quot; for reading.</source>
+        <translation>Không thể mở tệp tin Amiibo &quot;%1&quot; để đọc.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2858"/>
+        <source>Error reading Amiibo data file</source>
+        <translation>Xảy ra lỗi khi đọc dữ liệu tệp tin Amiibo</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2859"/>
+        <source>Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes.</source>
+        <translation>Hoàn toàn không thể đọc được dữ liệu Amiibo. Dự kiến byte sẽ đọc là %1, nhưng byte chỉ có thể đọc là %2.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2867"/>
+        <source>Error loading Amiibo data</source>
+        <translation>Xảy ra lỗi khi nạp dữ liệu Amiibo</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2868"/>
+        <source>Unable to load Amiibo data.</source>
+        <translation>Không thể nạp dữ liệu Amiibo.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2913"/>
+        <source>Capture Screenshot</source>
+        <translation>Chụp ảnh màn hình</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2914"/>
+        <source>PNG Image (*.png)</source>
+        <translation>Hình ảnh PNG (*.png)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2979"/>
+        <source>TAS state: Running %1/%2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2981"/>
+        <source>TAS state: Recording %1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2983"/>
+        <source>TAS state: Idle %1/%2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2985"/>
+        <source>TAS State: Invalid</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2999"/>
+        <source>&amp;Stop Running</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="2999"/>
+        <source>&amp;Start</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3000"/>
+        <source>Stop R&amp;ecording</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3000"/>
+        <source>R&amp;ecord</source>
+        <translation type="unfinished"/>
+    </message>
+    <message numerus="yes">
+        <location filename="../../src/yuzu/main.cpp" line="3024"/>
+        <source>Building: %n shader(s)</source>
+        <translation type="unfinished"><numerusform></numerusform></translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3033"/>
+        <source>Scale: %1x</source>
+        <comment>%1 is the resolution scaling factor</comment>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3036"/>
+        <source>Speed: %1% / %2%</source>
+        <translation>Tốc độ: %1% / %2%</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3040"/>
+        <source>Speed: %1%</source>
+        <translation>Tốc độ: %1%</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3044"/>
+        <source>Game: %1 FPS (Unlocked)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3046"/>
+        <source>Game: %1 FPS</source>
+        <translation>Trò chơi: %1 FPS</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3048"/>
+        <source>Frame: %1 ms</source>
+        <translation>Khung hình: %1 ms</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3059"/>
+        <source>GPU NORMAL</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3064"/>
+        <source>GPU HIGH</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3069"/>
+        <source>GPU EXTREME</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3074"/>
+        <source>GPU ERROR</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3084"/>
+        <source>NEAREST</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3087"/>
+        <location filename="../../src/yuzu/main.cpp" line="3102"/>
+        <source>BILINEAR</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3090"/>
+        <source>BICUBIC</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3093"/>
+        <source>GAUSSIAN</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3096"/>
+        <source>SCALEFORCE</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3099"/>
+        <source>FSR</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3111"/>
+        <location filename="../../src/yuzu/main.cpp" line="3117"/>
+        <source>NO AA</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3114"/>
+        <source>FXAA</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3175"/>
+        <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+        <translation>Trò chơi bạn muốn chạy yêu cầu một số tệp tin được sao chép từ thiết từ máy Switch của bạn trước khi bắt đầu chơi.&lt;br/&gt;&lt;br/&gt;Để biết thêm thông tin về cách sao chép những tệp tin đó, vui lòng tham khảo những wiki sau: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Sao chép dữ liệu hệ thống và font dùng chung từ máy Switch&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Bạn có muốn trở về danh sách trò chơi? Nếu bạn vẫn tiếp tục thì trò chơi có thể gặp sự cố, dữ liệu lưu tiến trình có thể bị lỗi, hoặc bạn sẽ gặp những lỗi khác.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3190"/>
+        <source>yuzu was unable to locate a Switch system archive. %1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3192"/>
+        <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3196"/>
+        <source>System Archive Not Found</source>
+        <translation>Không tìm thấy tệp tin hệ thống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3198"/>
+        <source>System Archive Missing</source>
+        <translation>Bị thiếu tệp tin hệ thống</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3204"/>
+        <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+        <translation>yuzu không thể tìm thấy vị trí font dùng chung của Switch. %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3205"/>
+        <source>Shared Fonts Not Found</source>
+        <translation>Không tìm thấy font dùng chung</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3207"/>
+        <source>Shared Font Missing</source>
+        <translation>Bị thiếu font dùng chung</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3213"/>
+        <source>Fatal Error</source>
+        <translation>Lỗi nghiêm trọng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3214"/>
+        <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+        <translation>yuzu đã xảy ra lỗi nghiêm trọng, vui lòng kiểm tra sổ ghi chép để biết thêm chi tiết. Để biết thêm thông tin về cách truy cập sổ ghi chép, vui lòng xem trang sau: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Làm sao để tải tệp tin sổ ghi chép lên&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Bạn có muốn trở về danh sách game? Tiếp tục có thể khiến giả lập gặp sự cố, gây hỏng dữ liệu đã lưu, hoặc gây các lỗi khác.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3223"/>
+        <source>Fatal Error encountered</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3246"/>
+        <source>Confirm Key Rederivation</source>
+        <translation>Xác nhận mã khóa Rederivation</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3247"/>
+        <source>You are about to force rederive all of your keys. 
+If you do not know what this means or what you are doing, 
+this is a potentially destructive action. 
+Please make sure this is what you want 
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+        <translation>Bạn đang muốn bắt buộc làm lại trích dẫn toàn bộ mã khóa của bạn. 
+Nếu bạn không biết cái này là gì hay bạn đang làm gì, 
+đây là hành động có khả năng phá hoại. 
+Hãy chắc rằng đây là điều bạn muốn 
+và phải tạo ra một bản sao lưu lại.
+
+Điều này sẽ xóa mã khóa tự động tạo trên tệp tin của bạn và chạy lại mô-đun mã khóa derivation.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3279"/>
+        <source>Missing fuses</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3282"/>
+        <source> - Missing BOOT0</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3285"/>
+        <source> - Missing BCPKG2-1-Normal-Main</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3288"/>
+        <source> - Missing PRODINFO</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3292"/>
+        <source>Derivation Components Missing</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3293"/>
+        <source>Encryption keys are missing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys, firmware and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3302"/>
+        <source>Deriving keys...
+This may take up to a minute depending 
+on your system&apos;s performance.</source>
+        <translation>Mã khóa xuất phát...
+Điều này có thể mất hơn vài phút tùy thuộc 
+vào hiệu suất hệ thống của bạn.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3304"/>
+        <source>Deriving Keys</source>
+        <translation>Mã khóa xuất phát</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3349"/>
+        <source>Select RomFS Dump Target</source>
+        <translation>Chọn thư mục để sao chép RomFS</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3350"/>
+        <source>Please select which RomFS you would like to dump.</source>
+        <translation>Vui lòng chọn RomFS mà bạn muốn sao chép.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3365"/>
+        <source>Are you sure you want to close yuzu?</source>
+        <translation>Bạn có chắc chắn muốn đóng yuzu?</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3366"/>
+        <location filename="../../src/yuzu/main.cpp" line="3443"/>
+        <location filename="../../src/yuzu/main.cpp" line="3456"/>
+        <source>yuzu</source>
+        <translation>yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3444"/>
+        <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+        <translation>Bạn có chắc rằng muốn dừng giả lập? Bất kì tiến trình nào chưa được lưu sẽ bị mất.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.cpp" line="3453"/>
+        <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+        <translation>Chương trình đang chạy đã yêu cầu yuzu không được thoát.
+
+Bạn có muốn bỏ qua yêu cầu đó và thoát luôn không?</translation>
+    </message>
+</context>
+<context>
+    <name>GRenderWindow</name>
+    <message>
+        <location filename="../../src/yuzu/bootmanager.cpp" line="976"/>
+        <source>OpenGL not available!</source>
+        <translation>Không có sẵn OpenGL!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/bootmanager.cpp" line="977"/>
+        <source>yuzu has not been compiled with OpenGL support.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/bootmanager.cpp" line="996"/>
+        <location filename="../../src/yuzu/bootmanager.cpp" line="1016"/>
+        <source>Error while initializing OpenGL!</source>
+        <translation>Đã xảy ra lỗi khi khởi tạo OpenGL!</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/bootmanager.cpp" line="997"/>
+        <source>Your GPU may not support OpenGL, or you do not have the latest graphics driver.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/bootmanager.cpp" line="1006"/>
+        <source>Error while initializing OpenGL 4.6!</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/bootmanager.cpp" line="1007"/>
+        <source>Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.&lt;br&gt;&lt;br&gt;GL Renderer:&lt;br&gt;%1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/bootmanager.cpp" line="1017"/>
+        <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;GL Renderer:&lt;br&gt;%1&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;%2</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>GameList</name>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="330"/>
+        <source>Name</source>
+        <translation>Tên</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="331"/>
+        <source>Compatibility</source>
+        <translation>Tương thích</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="333"/>
+        <source>Add-ons</source>
+        <translation>Tiện ích ngoài</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="335"/>
+        <source>File type</source>
+        <translation>Loại tệp tin</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="336"/>
+        <source>Size</source>
+        <translation>Kích cỡ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="508"/>
+        <source>Favorite</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="510"/>
+        <source>Start Game</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="512"/>
+        <source>Start Game without Custom Configuration</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="514"/>
+        <source>Open Save Data Location</source>
+        <translation>Mở vị trí lưu dữ liệu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="515"/>
+        <source>Open Mod Data Location</source>
+        <translation>Mở vị trí chỉnh sửa dữ liệu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="517"/>
+        <source>Open Transferable Pipeline Cache</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="519"/>
+        <source>Remove</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="520"/>
+        <source>Remove Installed Update</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="521"/>
+        <source>Remove All Installed DLC</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="522"/>
+        <source>Remove Custom Configuration</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="523"/>
+        <source>Remove OpenGL Pipeline Cache</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="524"/>
+        <source>Remove Vulkan Pipeline Cache</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="526"/>
+        <source>Remove All Pipeline Caches</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="527"/>
+        <source>Remove All Installed Contents</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="528"/>
+        <location filename="../../src/yuzu/game_list.cpp" line="529"/>
+        <source>Dump RomFS</source>
+        <translation>Kết xuất RomFS</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="530"/>
+        <source>Dump RomFS to SDMC</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="531"/>
+        <source>Copy Title ID to Clipboard</source>
+        <translation>Sao chép ID tiêu đề vào bộ nhớ tạm</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="532"/>
+        <source>Navigate to GameDB entry</source>
+        <translation>Điều hướng đến mục cơ sở dữ liệu trò chơi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="534"/>
+        <source>Properties</source>
+        <translation>Thuộc tính</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="606"/>
+        <source>Scan Subfolders</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="607"/>
+        <source>Remove Game Directory</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="626"/>
+        <source>▲ Move Up</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="627"/>
+        <source>▼ Move Down</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="628"/>
+        <source>Open Directory Location</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="673"/>
+        <source>Clear</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>GameListItemCompat</name>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+        <source>Perfect</source>
+        <translation>Tốt nhất</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+        <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+        <translation>Có thể chơi một cách mượt mà mà không có lỗi âm thanh hoặc đồ họa nào, tất cả các chức năng đều hoạt động như ý mà không cần bất kì tinh chỉnh nào.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+        <source>Great</source>
+        <translation>Tốt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+        <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+        <translation>Có thể chơi từ đầu đến cuối nhưng vẫn có một số lỗi nhỏ về đồ họa hoặc âm thanh. Có thể sẽ cần tới một vài tinh chỉnh nào đó.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="153"/>
+        <source>Okay</source>
+        <translation>Tạm ổn</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="153"/>
+        <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+        <translation>Tạm chơi được nhưng có lỗi đồ họa hoặc âm thanh một cách đáng kể, tuy vậy vẫn có thể chơi hết từ đầu đến cuối với một số tinh chỉnh.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="154"/>
+        <source>Bad</source>
+        <translation>Không tốt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="154"/>
+        <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+        <translation>Chơi được nhưng có nhiều lỗi đồ họa hoặc âm thanh. Không thể chơi tiếp ở một số nơi nào đó do lỗi dù có tinh chỉnh thế nào đi nữa.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="155"/>
+        <source>Intro/Menu</source>
+        <translation>Phần mở đầu/Menu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="155"/>
+        <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+        <translation>Hoàn toàn không thể chơi được do có nhiều lỗi đồ họa hoặc âm thanh. Không thể chơi tiếp sau khi bấm nút bắt đầu trò chơi.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="156"/>
+        <source>Won&apos;t Boot</source>
+        <translation>Không hoạt động</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="156"/>
+        <source>The game crashes when attempting to startup.</source>
+        <translation>Trò chơi sẽ thoát đột ngột khi khởi động.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="157"/>
+        <source>Not Tested</source>
+        <translation>Chưa ai thử</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="157"/>
+        <source>The game has not yet been tested.</source>
+        <translation>Trò chơi này chưa có ai thử cả.</translation>
+    </message>
+</context>
+<context>
+    <name>GameListPlaceholder</name>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="846"/>
+        <source>Double-click to add a new folder to the game list</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>GameListSearchField</name>
+    <message numerus="yes">
+        <location filename="../../src/yuzu/game_list.cpp" line="85"/>
+        <source>%1 of %n result(s)</source>
+        <translation type="unfinished"><numerusform></numerusform></translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="128"/>
+        <source>Filter:</source>
+        <translation>Bộ lọc:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list.cpp" line="131"/>
+        <source>Enter pattern to filter</source>
+        <translation>Nhập khuôn để lọc</translation>
+    </message>
+</context>
+<context>
+    <name>InstallDialog</name>
+    <message>
+        <location filename="../../src/yuzu/install_dialog.cpp" line="31"/>
+        <source>Please confirm these are the files you wish to install.</source>
+        <translation>Xin hãy xác nhận đây là những tệp tin bạn muốn cài.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/install_dialog.cpp" line="34"/>
+        <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+        <translation>Cài đặt một tệp tin cập nhật hoặc DLC mới sẽ thay thế những tệp cũ đã cài trước đó.</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/install_dialog.cpp" line="38"/>
+        <source>Install</source>
+        <translation>Cài đặt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/install_dialog.cpp" line="52"/>
+        <source>Install Files to NAND</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>LimitableInputDialog</name>
+    <message>
+        <location filename="../../src/yuzu/util/limitable_input_dialog.cpp" line="62"/>
+        <source>The text can't contain any of the following characters:
+%1</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>LoadingScreen</name>
+    <message>
+        <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+        <source>Loading Shaders 387 / 1628</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+        <source>Loading Shaders %v out of %m</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+        <source>Estimated Time 5m 4s</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/loading_screen.cpp" line="91"/>
+        <source>Loading...</source>
+        <translation>Đang tải...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/loading_screen.cpp" line="92"/>
+        <source>Loading Shaders %1 / %2</source>
+        <translation>Đang nạp shader %1 / %2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/loading_screen.cpp" line="93"/>
+        <source>Launching...</source>
+        <translation>Đang mở...</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/loading_screen.cpp" line="174"/>
+        <source>Estimated Time %1</source>
+        <translation>Ước tính thời gian %1</translation>
+    </message>
+</context>
+<context>
+    <name>MainWindow</name>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="14"/>
+        <source>yuzu</source>
+        <translation>yuzu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="44"/>
+        <source>&amp;File</source>
+        <translation>&amp;Tệp tin</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="48"/>
+        <source>&amp;Recent Files</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="67"/>
+        <source>&amp;Emulation</source>
+        <translation>&amp;Giả lập</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="78"/>
+        <source>&amp;View</source>
+        <translation>&amp;Xem</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="82"/>
+        <source>&amp;Reset Window Size</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="87"/>
+        <source>&amp;Debugging</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="92"/>
+        <source>Reset Window Size to &amp;720p</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="95"/>
+        <source>Reset Window Size to 720p</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="100"/>
+        <source>Reset Window Size to &amp;900p</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="103"/>
+        <source>Reset Window Size to 900p</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="108"/>
+        <source>Reset Window Size to &amp;1080p</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="111"/>
+        <source>Reset Window Size to 1080p</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="125"/>
+        <source>&amp;Tools</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="129"/>
+        <source>&amp;TAS</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="144"/>
+        <source>&amp;Help</source>
+        <translation>&amp;Trợ giúp</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="164"/>
+        <source>&amp;Install Files to NAND...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="169"/>
+        <source>L&amp;oad File...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="174"/>
+        <source>Load &amp;Folder...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="179"/>
+        <source>E&amp;xit</source>
+        <translation>Th&amp;oát</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="187"/>
+        <source>&amp;Pause</source>
+        <translation>&amp;Tạm dừng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="195"/>
+        <source>&amp;Stop</source>
+        <translation>&amp;Dừng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="200"/>
+        <source>&amp;Reinitialize keys...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="205"/>
+        <source>&amp;About yuzu</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="213"/>
+        <source>Single &amp;Window Mode</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="218"/>
+        <source>Con&amp;figure...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="226"/>
+        <source>Display D&amp;ock Widget Headers</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="234"/>
+        <source>Show &amp;Filter Bar</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="242"/>
+        <source>Show &amp;Status Bar</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="245"/>
+        <source>Show Status Bar</source>
+        <translation>Hiển thị thanh trạng thái</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="253"/>
+        <source>F&amp;ullscreen</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="261"/>
+        <source>&amp;Restart</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="269"/>
+        <source>Load &amp;Amiibo...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="277"/>
+        <source>&amp;Report Compatibility</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="285"/>
+        <source>Open &amp;Mods Page</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="290"/>
+        <source>Open &amp;Quickstart Guide</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="295"/>
+        <source>&amp;FAQ</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="300"/>
+        <source>Open &amp;yuzu Folder</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="308"/>
+        <source>&amp;Capture Screenshot</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="313"/>
+        <source>&amp;Configure TAS...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="321"/>
+        <source>Configure C&amp;urrent Game...</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="329"/>
+        <source>&amp;Start</source>
+        <translation>&amp;Bắt đầu</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="337"/>
+        <source>&amp;Reset</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/main.ui" line="345"/>
+        <source>R&amp;ecord</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>MicroProfileDialog</name>
+    <message>
+        <location filename="../../src/yuzu/debugger/profiler.cpp" line="51"/>
+        <source>&amp;MicroProfile</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>OverlayDialog</name>
+    <message>
+        <location filename="../../src/yuzu/util/overlay_dialog.ui" line="14"/>
+        <source>Dialog</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/util/overlay_dialog.ui" line="134"/>
+        <location filename="../../src/yuzu/util/overlay_dialog.ui" line="353"/>
+        <source>Cancel</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/util/overlay_dialog.ui" line="152"/>
+        <location filename="../../src/yuzu/util/overlay_dialog.ui" line="371"/>
+        <source>OK</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/util/overlay_dialog.ui" line="313"/>
+        <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:18pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>PlayerControlPreview</name>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player_widget.cpp" line="1572"/>
+        <source>START/PAUSE</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player_widget.cpp" line="2685"/>
+        <source>Charging</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>QObject</name>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="244"/>
+        <source>Installed SD Titles</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="252"/>
+        <source>Installed NAND Titles</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="260"/>
+        <source>System Titles</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="303"/>
+        <source>Add New Game Directory</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/game_list_p.h" line="326"/>
+        <source>Favorites</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="43"/>
+        <source>Shift</source>
+        <translation>Shift</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="45"/>
+        <source>Ctrl</source>
+        <translation>Ctrl</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="27"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="47"/>
+        <source>Alt</source>
+        <translation>Alt</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="37"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="130"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="196"/>
+        <source>[not set]</source>
+        <translation>[chưa đặt nút]</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="49"/>
+        <source>Hat %1 %2</source>
+        <translation>Mũ %1 %2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="56"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="219"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="223"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="227"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="231"/>
+        <source>Axis %1%2</source>
+        <translation>Trục %1%2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="62"/>
+        <source>Button %1</source>
+        <translation>Nút %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="68"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="190"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="204"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="234"/>
+        <source>[unknown]</source>
+        <translation>[không xác định]</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="58"/>
+        <source>Left</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="60"/>
+        <source>Right</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="62"/>
+        <source>Down</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="64"/>
+        <source>Up</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="66"/>
+        <source>Z</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="68"/>
+        <source>R</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="70"/>
+        <source>L</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="72"/>
+        <source>A</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="74"/>
+        <source>B</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="76"/>
+        <source>X</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="78"/>
+        <source>Y</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="80"/>
+        <source>Start</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="82"/>
+        <source>L1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="84"/>
+        <source>L2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="86"/>
+        <source>L3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="88"/>
+        <source>R1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+        <source>R2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+        <source>R3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+        <source>Circle</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+        <source>Cross</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+        <source>Square</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+        <source>Triangle</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+        <source>Share</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+        <source>Options</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="106"/>
+        <source>[undefined]</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="140"/>
+        <source>%1%2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="144"/>
+        <source>[invalid]</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="154"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="178"/>
+        <source>%1%2Hat %3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="158"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="181"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="184"/>
+        <source>%1%2Axis %3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="164"/>
+        <source>%1%2Axis %3,%4,%5</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="168"/>
+        <source>%1%2Motion %3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="172"/>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="187"/>
+        <source>%1%2Button %3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="214"/>
+        <source>[unused]</source>
+        <translation>[không sử dụng]</translation>
+    </message>
+</context>
+<context>
+    <name>QtControllerSelectorDialog</name>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="14"/>
+        <source>Controller Applet</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="129"/>
+        <source>Supported Controller Types:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="282"/>
+        <source>Players:</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="300"/>
+        <source>1 - 8</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="418"/>
+        <source>P4</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="514"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="711"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="912"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1222"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1459"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1656"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1857"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2054"/>
+        <location filename="../../src/yuzu/applets/qt_controller.cpp" line="411"/>
+        <source>Pro Controller</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="519"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="716"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="917"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1227"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1464"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1661"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1862"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2059"/>
+        <location filename="../../src/yuzu/applets/qt_controller.cpp" line="415"/>
+        <source>Dual Joycons</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="524"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="721"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="922"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1232"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1469"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1666"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1867"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2064"/>
+        <location filename="../../src/yuzu/applets/qt_controller.cpp" line="419"/>
+        <source>Left Joycon</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="529"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="726"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="927"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1237"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1474"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1671"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1872"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2069"/>
+        <location filename="../../src/yuzu/applets/qt_controller.cpp" line="423"/>
+        <source>Right Joycon</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="538"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="735"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="941"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1246"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1483"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1680"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1881"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2078"/>
+        <source>Use Current Config</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="615"/>
+        <source>P2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="812"/>
+        <source>P1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="932"/>
+        <location filename="../../src/yuzu/applets/qt_controller.cpp" line="428"/>
+        <source>Handheld</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1126"/>
+        <source>P3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1363"/>
+        <source>P7</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1560"/>
+        <source>P8</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1757"/>
+        <source>P5</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="1958"/>
+        <source>P6</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2272"/>
+        <source>Console Mode</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2293"/>
+        <source>Docked</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2303"/>
+        <source>Undocked</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2313"/>
+        <source>Vibration</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2349"/>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2395"/>
+        <source>Configure</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2359"/>
+        <source>Motion</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2405"/>
+        <source>Profiles</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2432"/>
+        <source>Create</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2467"/>
+        <source>Controllers</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2481"/>
+        <source>1</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2508"/>
+        <source>2</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2518"/>
+        <source>4</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2528"/>
+        <source>3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2538"/>
+        <source>Connected</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2552"/>
+        <source>5</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2569"/>
+        <source>7</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2586"/>
+        <source>6</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.ui" line="2596"/>
+        <source>8</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_controller.cpp" line="433"/>
+        <source>GameCube Controller</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>QtErrorDisplay</name>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_error.cpp" line="22"/>
+        <location filename="../../src/yuzu/applets/qt_error.cpp" line="35"/>
+        <location filename="../../src/yuzu/applets/qt_error.cpp" line="50"/>
+        <source>Error Code: %1-%2 (0x%3)</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_error.cpp" line="26"/>
+        <source>An error has occurred.
+Please try again or contact the developer of the software.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_error.cpp" line="39"/>
+        <source>An error occurred on %1 at %2.
+Please try again or contact the developer of the software.</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_error.cpp" line="54"/>
+        <source>An error has occurred.
+
+%1
+
+%2</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>QtProfileSelectionDialog</name>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="22"/>
+        <source>%1
+%2</source>
+        <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+        <translation>%1
+%2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="52"/>
+        <source>Select a user:</source>
+        <translation>Chọn một người dùng:</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="81"/>
+        <source>Users</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="112"/>
+        <source>Profile Selector</source>
+        <translation>Chọn hồ sơ</translation>
+    </message>
+</context>
+<context>
+    <name>QtSoftwareKeyboardDialog</name>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="14"/>
+        <source>Software Keyboard</source>
+        <translation>Phần mềm bàn phím</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="199"/>
+        <source>Enter Text</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="479"/>
+        <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="399"/>
+        <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="409"/>
+        <source>OK</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="409"/>
+        <source>Cancel</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>SequenceDialog</name>
+    <message>
+        <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="11"/>
+        <source>Enter a hotkey</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>WaitTreeCallstack</name>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="150"/>
+        <source>Call stack</source>
+        <translation>Chùm cuộc gọi</translation>
+    </message>
+</context>
+<context>
+    <name>WaitTreeMutexInfo</name>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="129"/>
+        <source>waiting for mutex 0x%1</source>
+        <translation>chờ đợi loại trừ lẫn nhau 0x%1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="136"/>
+        <source>has waiters: %1</source>
+        <translation>đã chờ đợi: %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="138"/>
+        <source>owner handle: 0x%1</source>
+        <translation>chủ điều khiển: 0x%1</translation>
+    </message>
+</context>
+<context>
+    <name>WaitTreeObjectList</name>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="231"/>
+        <source>waiting for all objects</source>
+        <translation>chờ đợi toàn bộ đối tượng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="232"/>
+        <source>waiting for one of the following objects</source>
+        <translation>chờ đợi một đối tượng đang theo dõi</translation>
+    </message>
+</context>
+<context>
+    <name>WaitTreeSynchronizationObject</name>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="188"/>
+        <source>[%1] %2 %3</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="215"/>
+        <source>waited by no thread</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+<context>
+    <name>WaitTreeThread</name>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="253"/>
+        <source>runnable</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="255"/>
+        <source>paused</source>
+        <translation>tạm dừng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="261"/>
+        <source>sleeping</source>
+        <translation>ngủ</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="264"/>
+        <source>waiting for IPC reply</source>
+        <translation>chờ đợi IPC phản hồi</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="267"/>
+        <source>waiting for objects</source>
+        <translation>chờ đợi đối tượng</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="270"/>
+        <source>waiting for condition variable</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="273"/>
+        <source>waiting for address arbiter</source>
+        <translation>chờ đợi địa chỉ người đứng giữa</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="276"/>
+        <source>waiting for suspend resume</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="279"/>
+        <source>waiting</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+        <source>initialized</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="287"/>
+        <source>terminated</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="290"/>
+        <source>unknown</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="295"/>
+        <source> PC = 0x%1 LR = 0x%2</source>
+        <translation> PC = 0x%1 LR = 0x%2</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="345"/>
+        <source>ideal</source>
+        <translation type="unfinished"/>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="348"/>
+        <source>core %1</source>
+        <translation>lõi %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="352"/>
+        <source>processor = %1</source>
+        <translation>bộ xử lý = %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="354"/>
+        <source>ideal core = %1</source>
+        <translation>lõi lý tưởng = %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="356"/>
+        <source>affinity mask = %1</source>
+        <translation>che đậy tánh giống nhau = %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="357"/>
+        <source>thread id = %1</source>
+        <translation>id luồng = %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="358"/>
+        <source>priority = %1(current) / %2(normal)</source>
+        <translation>quyền ưu tiên = %1(hiện tại) / %2(bình thường)</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="362"/>
+        <source>last running ticks = %1</source>
+        <translation>lần chạy cuối cùng = %1</translation>
+    </message>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="370"/>
+        <source>not waiting for mutex</source>
+        <translation>không có chờ đợi loại trừ lẫn nhau</translation>
+    </message>
+</context>
+<context>
+    <name>WaitTreeThreadList</name>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="394"/>
+        <source>waited by thread</source>
+        <translation>chờ đợi bởi vì có luồng</translation>
+    </message>
+</context>
+<context>
+    <name>WaitTreeWidget</name>
+    <message>
+        <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="468"/>
+        <source>&amp;Wait Tree</source>
+        <translation type="unfinished"/>
+    </message>
+</context>
+</TS>

Файловите разлики са ограничени, защото са твърде много
+ 382 - 241
dist/languages/zh_CN.ts


Файловите разлики са ограничени, защото са твърде много
+ 419 - 270
dist/languages/zh_TW.ts


+ 0 - 4
externals/CMakeLists.txt

@@ -44,10 +44,6 @@ target_include_directories(mbedtls PUBLIC ./mbedtls/include)
 add_library(microprofile INTERFACE)
 target_include_directories(microprofile INTERFACE ./microprofile)
 
-# Unicorn
-add_library(unicorn-headers INTERFACE)
-target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
-
 # libusb
 if (NOT LIBUSB_FOUND OR YUZU_USE_BUNDLED_LIBUSB)
     add_subdirectory(libusb)

+ 1 - 1
externals/SDL

@@ -1 +1 @@
-Subproject commit 25f9ed87ff6947d9576fc9d79dee0784e638ac58
+Subproject commit 2e9821423a237a1206e3c09020778faacfe430be

+ 1 - 1
externals/cubeb

@@ -1 +1 @@
-Subproject commit 1d66483ad2b93f0e00e175f9480c771af90003a7
+Subproject commit 75d9d125ee655ef80f3bfcd97ae5a805931042b8

+ 0 - 18
externals/find-modules/FindUnicorn.cmake

@@ -1,18 +0,0 @@
-# Exports:
-#  LIBUNICORN_FOUND
-#  LIBUNICORN_INCLUDE_DIR
-#  LIBUNICORN_LIBRARY
-
-find_path(LIBUNICORN_INCLUDE_DIR
-          unicorn/unicorn.h
-          HINTS $ENV{UNICORNDIR}
-          PATH_SUFFIXES include)
-
-find_library(LIBUNICORN_LIBRARY
-             NAMES unicorn
-             HINTS $ENV{UNICORNDIR})
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(unicorn DEFAULT_MSG
-                                  LIBUNICORN_LIBRARY LIBUNICORN_INCLUDE_DIR)
-mark_as_advanced(LIBUNICORN_INCLUDE_DIR LIBUNICORN_LIBRARY)

+ 2 - 0
src/CMakeLists.txt

@@ -24,6 +24,7 @@ if (MSVC)
     # /W3                 - Level 3 warnings
     # /MP                 - Multi-threaded compilation
     # /Zi                 - Output debugging information
+    # /Zm                 - Specifies the precompiled header memory allocation limit
     # /Zo                 - Enhanced debug info for optimized builds
     # /permissive-        - Enables stricter C++ standards conformance checks
     # /EHsc               - C++-only exception handling semantics
@@ -36,6 +37,7 @@ if (MSVC)
     add_compile_options(
         /MP
         /Zi
+        /Zm200
         /Zo
         /permissive-
         /EHsc

+ 4 - 0
src/audio_core/delay_line.cpp

@@ -1,3 +1,7 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
 #include <cstring>
 #include "audio_core/delay_line.h"
 

+ 4 - 0
src/audio_core/delay_line.h

@@ -1,3 +1,7 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
 #pragma once
 
 #include "common/common_types.h"

+ 1 - 0
src/common/CMakeLists.txt

@@ -73,6 +73,7 @@ add_library(common STATIC
     hex_util.h
     host_memory.cpp
     host_memory.h
+    input.h
     intrusive_red_black_tree.h
     literals.h
     logging/backend.cpp

+ 7 - 0
src/common/bit_util.h

@@ -7,6 +7,7 @@
 #include <bit>
 #include <climits>
 #include <cstddef>
+#include <type_traits>
 
 #include "common/common_types.h"
 
@@ -44,4 +45,10 @@ template <typename T>
     return static_cast<u32>(log2_f + static_cast<u64>((value ^ (1ULL << log2_f)) != 0ULL));
 }
 
+template <typename T>
+requires std::is_integral_v<T>
+[[nodiscard]] T NextPow2(T value) {
+    return static_cast<T>(1ULL << ((8U * sizeof(T)) - std::countl_zero(value - 1U)));
+}
+
 } // namespace Common

+ 4 - 0
src/common/host_memory.cpp

@@ -1,3 +1,7 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
 #ifdef _WIN32
 
 #include <iterator>

+ 366 - 0
src/common/input.h

@@ -0,0 +1,366 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include "common/logging/log.h"
+#include "common/param_package.h"
+#include "common/uuid.h"
+
+namespace Common::Input {
+
+// Type of data that is expected to recieve or send
+enum class InputType {
+    None,
+    Battery,
+    Button,
+    Stick,
+    Analog,
+    Trigger,
+    Motion,
+    Touch,
+    Color,
+    Vibration,
+    Nfc,
+    Ir,
+};
+
+// Internal battery charge level
+enum class BatteryLevel : u32 {
+    None,
+    Empty,
+    Critical,
+    Low,
+    Medium,
+    Full,
+    Charging,
+};
+
+enum class PollingMode {
+    // Constant polling of buttons, analogs and motion data
+    Active,
+    // Only update on button change, digital analogs
+    Pasive,
+    // Enable near field communication polling
+    NFC,
+    // Enable infrared camera polling
+    IR,
+};
+
+// Vibration reply from the controller
+enum class VibrationError {
+    None,
+    NotSupported,
+    Disabled,
+    Unknown,
+};
+
+// Polling mode reply from the controller
+enum class PollingError {
+    None,
+    NotSupported,
+    Unknown,
+};
+
+// Hint for amplification curve to be used
+enum class VibrationAmplificationType {
+    Linear,
+    Exponential,
+};
+
+// Analog properties for calibration
+struct AnalogProperties {
+    // Anything below this value will be detected as zero
+    float deadzone{};
+    // Anyting above this values will be detected as one
+    float range{1.0f};
+    // Minimum value to be detected as active
+    float threshold{0.5f};
+    // Drift correction applied to the raw data
+    float offset{};
+    // Invert direction of the sensor data
+    bool inverted{};
+};
+
+// Single analog sensor data
+struct AnalogStatus {
+    float value{};
+    float raw_value{};
+    AnalogProperties properties{};
+};
+
+// Button data
+struct ButtonStatus {
+    Common::UUID uuid{};
+    bool value{};
+    bool inverted{};
+    bool toggle{};
+    bool locked{};
+};
+
+// Internal battery data
+using BatteryStatus = BatteryLevel;
+
+// Analog and digital joystick data
+struct StickStatus {
+    Common::UUID uuid{};
+    AnalogStatus x{};
+    AnalogStatus y{};
+    bool left{};
+    bool right{};
+    bool up{};
+    bool down{};
+};
+
+// Analog and digital trigger data
+struct TriggerStatus {
+    Common::UUID uuid{};
+    AnalogStatus analog{};
+    ButtonStatus pressed{};
+};
+
+// 3D vector representing motion input
+struct MotionSensor {
+    AnalogStatus x{};
+    AnalogStatus y{};
+    AnalogStatus z{};
+};
+
+// Motion data used to calculate controller orientation
+struct MotionStatus {
+    // Gyroscope vector measurement in radians/s.
+    MotionSensor gyro{};
+    // Acceleration vector measurement in G force
+    MotionSensor accel{};
+    // Time since last measurement in microseconds
+    u64 delta_timestamp{};
+    // Request to update after reading the value
+    bool force_update{};
+};
+
+// Data of a single point on a touch screen
+struct TouchStatus {
+    ButtonStatus pressed{};
+    AnalogStatus x{};
+    AnalogStatus y{};
+    int id{};
+};
+
+// Physical controller color in RGB format
+struct BodyColorStatus {
+    u32 body{};
+    u32 buttons{};
+};
+
+// HD rumble data
+struct VibrationStatus {
+    f32 low_amplitude{};
+    f32 low_frequency{};
+    f32 high_amplitude{};
+    f32 high_frequency{};
+    VibrationAmplificationType type;
+};
+
+// Physical controller LED pattern
+struct LedStatus {
+    bool led_1{};
+    bool led_2{};
+    bool led_3{};
+    bool led_4{};
+};
+
+// List of buttons to be passed to Qt that can be translated
+enum class ButtonNames {
+    Undefined,
+    Invalid,
+    // This will display the engine name instead of the button name
+    Engine,
+    // This will display the button by value instead of the button name
+    Value,
+    ButtonLeft,
+    ButtonRight,
+    ButtonDown,
+    ButtonUp,
+    TriggerZ,
+    TriggerR,
+    TriggerL,
+    ButtonA,
+    ButtonB,
+    ButtonX,
+    ButtonY,
+    ButtonStart,
+
+    // DS4 button names
+    L1,
+    L2,
+    L3,
+    R1,
+    R2,
+    R3,
+    Circle,
+    Cross,
+    Square,
+    Triangle,
+    Share,
+    Options,
+};
+
+// Callback data consisting of an input type and the equivalent data status
+struct CallbackStatus {
+    InputType type{InputType::None};
+    ButtonStatus button_status{};
+    StickStatus stick_status{};
+    AnalogStatus analog_status{};
+    TriggerStatus trigger_status{};
+    MotionStatus motion_status{};
+    TouchStatus touch_status{};
+    BodyColorStatus color_status{};
+    BatteryStatus battery_status{};
+    VibrationStatus vibration_status{};
+};
+
+// Triggered once every input change
+struct InputCallback {
+    std::function<void(const CallbackStatus&)> on_change;
+};
+
+/// An abstract class template for an input device (a button, an analog input, etc.).
+class InputDevice {
+public:
+    virtual ~InputDevice() = default;
+
+    // Request input device to update if necessary
+    virtual void SoftUpdate() {}
+
+    // Force input device to update data regardless of the current state
+    virtual void ForceUpdate() {}
+
+    // Sets the function to be triggered when input changes
+    void SetCallback(InputCallback callback_) {
+        callback = std::move(callback_);
+    }
+
+    // Triggers the function set in the callback
+    void TriggerOnChange(const CallbackStatus& status) {
+        if (callback.on_change) {
+            callback.on_change(status);
+        }
+    }
+
+private:
+    InputCallback callback;
+};
+
+/// An abstract class template for an output device (rumble, LED pattern, polling mode).
+class OutputDevice {
+public:
+    virtual ~OutputDevice() = default;
+
+    virtual void SetLED([[maybe_unused]] const LedStatus& led_status) {}
+
+    virtual VibrationError SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
+        return VibrationError::NotSupported;
+    }
+
+    virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
+        return PollingError::NotSupported;
+    }
+};
+
+/// An abstract class template for a factory that can create input devices.
+template <typename InputDeviceType>
+class Factory {
+public:
+    virtual ~Factory() = default;
+    virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
+};
+
+namespace Impl {
+
+template <typename InputDeviceType>
+using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
+
+template <typename InputDeviceType>
+struct FactoryList {
+    static FactoryListType<InputDeviceType> list;
+};
+
+template <typename InputDeviceType>
+FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
+
+} // namespace Impl
+
+/**
+ * Registers an input device factory.
+ * @tparam InputDeviceType the type of input devices the factory can create
+ * @param name the name of the factory. Will be used to match the "engine" parameter when creating
+ *     a device
+ * @param factory the factory object to register
+ */
+template <typename InputDeviceType>
+void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
+    auto pair = std::make_pair(name, std::move(factory));
+    if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
+        LOG_ERROR(Input, "Factory '{}' already registered", name);
+    }
+}
+
+/**
+ * Unregisters an input device factory.
+ * @tparam InputDeviceType the type of input devices the factory can create
+ * @param name the name of the factory to unregister
+ */
+template <typename InputDeviceType>
+void UnregisterFactory(const std::string& name) {
+    if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
+        LOG_ERROR(Input, "Factory '{}' not registered", name);
+    }
+}
+
+/**
+ * Create an input device from given paramters.
+ * @tparam InputDeviceType the type of input devices to create
+ * @param params a serialized ParamPackage string that contains all parameters for creating the
+ * device
+ */
+template <typename InputDeviceType>
+std::unique_ptr<InputDeviceType> CreateDeviceFromString(const std::string& params) {
+    const Common::ParamPackage package(params);
+    const std::string engine = package.Get("engine", "null");
+    const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
+    const auto pair = factory_list.find(engine);
+    if (pair == factory_list.end()) {
+        if (engine != "null") {
+            LOG_ERROR(Input, "Unknown engine name: {}", engine);
+        }
+        return std::make_unique<InputDeviceType>();
+    }
+    return pair->second->Create(package);
+}
+
+/**
+ * Create an input device from given paramters.
+ * @tparam InputDeviceType the type of input devices to create
+ * @param A ParamPackage that contains all parameters for creating the device
+ */
+template <typename InputDeviceType>
+std::unique_ptr<InputDeviceType> CreateDevice(const Common::ParamPackage package) {
+    const std::string engine = package.Get("engine", "null");
+    const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
+    const auto pair = factory_list.find(engine);
+    if (pair == factory_list.end()) {
+        if (engine != "null") {
+            LOG_ERROR(Input, "Unknown engine name: {}", engine);
+        }
+        return std::make_unique<InputDeviceType>();
+    }
+    return pair->second->Create(package);
+}
+
+} // namespace Common::Input

+ 1 - 0
src/common/logging/filter.cpp

@@ -114,6 +114,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
     SUB(Service, NGCT)                                                                             \
     SUB(Service, NIFM)                                                                             \
     SUB(Service, NIM)                                                                              \
+    SUB(Service, NOTIF)                                                                            \
     SUB(Service, NPNS)                                                                             \
     SUB(Service, NS)                                                                               \
     SUB(Service, NVDRV)                                                                            \

+ 1 - 0
src/common/logging/types.h

@@ -82,6 +82,7 @@ enum class Class : u8 {
     Service_NGCT,      ///< The NGCT (No Good Content for Terra) service
     Service_NIFM,      ///< The NIFM (Network interface) service
     Service_NIM,       ///< The NIM service
+    Service_NOTIF,     ///< The NOTIF (Notification) service
     Service_NPNS,      ///< The NPNS service
     Service_NS,        ///< The NS services
     Service_NVDRV,     ///< The NVDRV (Nvidia driver) service

+ 1 - 0
src/common/settings.cpp

@@ -183,6 +183,7 @@ void RestoreGlobalState(bool is_powered_on) {
     values.max_anisotropy.SetGlobal(true);
     values.use_speed_limit.SetGlobal(true);
     values.speed_limit.SetGlobal(true);
+    values.fps_cap.SetGlobal(true);
     values.use_disk_shader_cache.SetGlobal(true);
     values.gpu_accuracy.SetGlobal(true);
     values.use_asynchronous_gpu_emulation.SetGlobal(true);

+ 3 - 12
src/common/settings.h

@@ -6,7 +6,6 @@
 
 #include <algorithm>
 #include <array>
-#include <atomic>
 #include <map>
 #include <optional>
 #include <string>
@@ -525,7 +524,7 @@ struct Values {
     Setting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
     Setting<bool> accelerate_astc{true, "accelerate_astc"};
     Setting<bool> use_vsync{true, "use_vsync"};
-    BasicRangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
+    RangedSetting<u16> fps_cap{1000, 1, 1000, "fps_cap"};
     BasicSetting<bool> disable_fps_limit{false, "disable_fps_limit"};
     RangedSetting<ShaderBackend> shader_backend{ShaderBackend::GLASM, ShaderBackend::GLSL,
                                                 ShaderBackend::SPIRV, "shader_backend"};
@@ -560,25 +559,19 @@ struct Values {
     Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
 
     Setting<bool> motion_enabled{true, "motion_enabled"};
-    BasicSetting<std::string> motion_device{"engine:motion_emu,update_period:100,sensitivity:0.01",
-                                            "motion_device"};
     BasicSetting<std::string> udp_input_servers{"127.0.0.1:26760", "udp_input_servers"};
+    BasicSetting<bool> enable_udp_controller{false, "enable_udp_controller"};
 
     BasicSetting<bool> pause_tas_on_load{true, "pause_tas_on_load"};
     BasicSetting<bool> tas_enable{false, "tas_enable"};
     BasicSetting<bool> tas_loop{false, "tas_loop"};
-    BasicSetting<bool> tas_swap_controllers{true, "tas_swap_controllers"};
 
     BasicSetting<bool> mouse_panning{false, "mouse_panning"};
     BasicRangedSetting<u8> mouse_panning_sensitivity{10, 1, 100, "mouse_panning_sensitivity"};
     BasicSetting<bool> mouse_enabled{false, "mouse_enabled"};
-    std::string mouse_device;
-    MouseButtonsRaw mouse_buttons;
 
     BasicSetting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
     BasicSetting<bool> keyboard_enabled{false, "keyboard_enabled"};
-    KeyboardKeysRaw keyboard_keys;
-    KeyboardModsRaw keyboard_mods;
 
     BasicSetting<bool> debug_pad_enabled{false, "debug_pad_enabled"};
     ButtonsRaw debug_pad_buttons;
@@ -586,14 +579,11 @@ struct Values {
 
     TouchscreenInput touchscreen;
 
-    BasicSetting<bool> use_touch_from_button{false, "use_touch_from_button"};
     BasicSetting<std::string> touch_device{"min_x:100,min_y:50,max_x:1800,max_y:850",
                                            "touch_device"};
     BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
     std::vector<TouchFromButtonMap> touch_from_button_maps;
 
-    std::atomic_bool is_device_reload_pending{true};
-
     // Data Storage
     BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
     BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
@@ -614,6 +604,7 @@ struct Values {
     BasicSetting<bool> extended_logging{false, "extended_logging"};
     BasicSetting<bool> use_debug_asserts{false, "use_debug_asserts"};
     BasicSetting<bool> use_auto_stub{false, "use_auto_stub"};
+    BasicSetting<bool> enable_all_controllers{false, "enable_all_controllers"};
 
     // Miscellaneous
     BasicSetting<std::string> log_filter{"*:Info", "log_filter"};

+ 51 - 19
src/common/settings_input.h

@@ -62,11 +62,22 @@ enum Values : int {
 
 constexpr int STICK_HID_BEGIN = LStick;
 constexpr int STICK_HID_END = NumAnalogs;
-constexpr int NUM_STICKS_HID = NumAnalogs;
 
 extern const std::array<const char*, NumAnalogs> mapping;
 } // namespace NativeAnalog
 
+namespace NativeTrigger {
+enum Values : int {
+    LTrigger,
+    RTrigger,
+
+    NumTriggers,
+};
+
+constexpr int TRIGGER_HID_BEGIN = LTrigger;
+constexpr int TRIGGER_HID_END = NumTriggers;
+} // namespace NativeTrigger
+
 namespace NativeVibration {
 enum Values : int {
     LeftVibrationDevice,
@@ -115,10 +126,20 @@ constexpr int NUM_MOUSE_HID = NumMouseButtons;
 extern const std::array<const char*, NumMouseButtons> mapping;
 } // namespace NativeMouseButton
 
+namespace NativeMouseWheel {
+enum Values {
+    X,
+    Y,
+
+    NumMouseWheels,
+};
+
+extern const std::array<const char*, NumMouseWheels> mapping;
+} // namespace NativeMouseWheel
+
 namespace NativeKeyboard {
 enum Keys {
     None,
-    Error,
 
     A = 4,
     B,
@@ -156,22 +177,22 @@ enum Keys {
     N8,
     N9,
     N0,
-    Enter,
+    Return,
     Escape,
     Backspace,
     Tab,
     Space,
     Minus,
-    Equal,
-    LeftBrace,
-    RightBrace,
-    Backslash,
+    Plus,
+    OpenBracket,
+    CloseBracket,
+    Pipe,
     Tilde,
     Semicolon,
-    Apostrophe,
-    Grave,
+    Quote,
+    Backquote,
     Comma,
-    Dot,
+    Period,
     Slash,
     CapsLockKey,
 
@@ -188,7 +209,7 @@ enum Keys {
     F11,
     F12,
 
-    SystemRequest,
+    PrintScreen,
     ScrollLockKey,
     Pause,
     Insert,
@@ -257,8 +278,18 @@ enum Keys {
     ScrollLockActive,
     KPComma,
 
-    KPLeftParenthesis,
-    KPRightParenthesis,
+    Ro = 0x87,
+    KatakanaHiragana,
+    Yen,
+    Henkan,
+    Muhenkan,
+    NumPadCommaPc98,
+
+    HangulEnglish = 0x90,
+    Hanja,
+    KatakanaKey,
+    HiraganaKey,
+    ZenkakuHankaku,
 
     LeftControlKey = 0xE0,
     LeftShiftKey,
@@ -307,6 +338,8 @@ enum Modifiers {
     CapsLock,
     ScrollLock,
     NumLock,
+    Katakana,
+    Hiragana,
 
     NumKeyboardMods,
 };
@@ -324,11 +357,6 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
 using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
 using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
 using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
-using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>;
-
-using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
-using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
-using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
 
 constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
 constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
@@ -342,6 +370,11 @@ enum class ControllerType {
     RightJoycon,
     Handheld,
     GameCube,
+    Pokeball,
+    NES,
+    SNES,
+    N64,
+    SegaGenesis,
 };
 
 struct PlayerInput {
@@ -349,7 +382,6 @@ struct PlayerInput {
     ControllerType controller_type;
     ButtonsRaw buttons;
     AnalogsRaw analogs;
-    VibrationsRaw vibrations;
     MotionsRaw motions;
 
     bool vibration_enabled;

+ 0 - 12
src/common/x64/cpu_detect.cpp

@@ -71,9 +71,6 @@ static CPUCaps Detect() {
     else
         caps.manufacturer = Manufacturer::Unknown;
 
-    u32 family = {};
-    u32 model = {};
-
     __cpuid(cpu_id, 0x80000000);
 
     u32 max_ex_fn = cpu_id[0];
@@ -84,15 +81,6 @@ static CPUCaps Detect() {
     // Detect family and other miscellaneous features
     if (max_std_fn >= 1) {
         __cpuid(cpu_id, 0x00000001);
-        family = (cpu_id[0] >> 8) & 0xf;
-        model = (cpu_id[0] >> 4) & 0xf;
-        if (family == 0xf) {
-            family += (cpu_id[0] >> 20) & 0xff;
-        }
-        if (family >= 6) {
-            model += ((cpu_id[0] >> 16) & 0xf) << 4;
-        }
-
         if ((cpu_id[3] >> 25) & 1)
             caps.sse = true;
         if ((cpu_id[3] >> 26) & 1)

+ 18 - 18
src/common/x64/native_clock.cpp

@@ -15,26 +15,26 @@
 namespace Common {
 
 u64 EstimateRDTSCFrequency() {
-    const auto milli_10 = std::chrono::milliseconds{10};
-    // get current time
+    // Discard the first result measuring the rdtsc.
     _mm_mfence();
-    const u64 tscStart = __rdtsc();
-    const auto startTime = std::chrono::high_resolution_clock::now();
-    // wait roughly 3 seconds
-    while (true) {
-        auto milli = std::chrono::duration_cast<std::chrono::milliseconds>(
-            std::chrono::high_resolution_clock::now() - startTime);
-        if (milli.count() >= 3000)
-            break;
-        std::this_thread::sleep_for(milli_10);
-    }
-    const auto endTime = std::chrono::high_resolution_clock::now();
+    __rdtsc();
+    std::this_thread::sleep_for(std::chrono::milliseconds{1});
+    _mm_mfence();
+    __rdtsc();
+
+    // Get the current time.
+    const auto start_time = std::chrono::steady_clock::now();
+    _mm_mfence();
+    const u64 tsc_start = __rdtsc();
+    // Wait for 200 milliseconds.
+    std::this_thread::sleep_for(std::chrono::milliseconds{200});
+    const auto end_time = std::chrono::steady_clock::now();
     _mm_mfence();
-    const u64 tscEnd = __rdtsc();
-    // calculate difference
-    const u64 timer_diff =
-        std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
-    const u64 tsc_diff = tscEnd - tscStart;
+    const u64 tsc_end = __rdtsc();
+    // Calculate differences.
+    const u64 timer_diff = static_cast<u64>(
+        std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
+    const u64 tsc_diff = tsc_end - tsc_start;
     const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
     return tsc_freq;
 }

+ 24 - 3
src/core/CMakeLists.txt

@@ -132,11 +132,23 @@ add_library(core STATIC
     frontend/emu_window.h
     frontend/framebuffer_layout.cpp
     frontend/framebuffer_layout.h
-    frontend/input_interpreter.cpp
-    frontend/input_interpreter.h
-    frontend/input.h
     hardware_interrupt_manager.cpp
     hardware_interrupt_manager.h
+    hid/emulated_console.cpp
+    hid/emulated_console.h
+    hid/emulated_controller.cpp
+    hid/emulated_controller.h
+    hid/emulated_devices.cpp
+    hid/emulated_devices.h
+    hid/hid_core.cpp
+    hid/hid_core.h
+    hid/hid_types.h
+    hid/input_converter.cpp
+    hid/input_converter.h
+    hid/input_interpreter.cpp
+    hid/input_interpreter.h
+    hid/motion_input.cpp
+    hid/motion_input.h
     hle/api_version.h
     hle/ipc.h
     hle/ipc_helpers.h
@@ -167,12 +179,15 @@ add_library(core STATIC
     hle/kernel/k_client_port.h
     hle/kernel/k_client_session.cpp
     hle/kernel/k_client_session.h
+    hle/kernel/k_code_memory.cpp
+    hle/kernel/k_code_memory.h
     hle/kernel/k_condition_variable.cpp
     hle/kernel/k_condition_variable.h
     hle/kernel/k_event.cpp
     hle/kernel/k_event.h
     hle/kernel/k_handle_table.cpp
     hle/kernel/k_handle_table.h
+    hle/kernel/k_light_condition_variable.cpp
     hle/kernel/k_light_condition_variable.h
     hle/kernel/k_light_lock.cpp
     hle/kernel/k_light_lock.h
@@ -225,6 +240,7 @@ add_library(core STATIC
     hle/kernel/k_system_control.h
     hle/kernel/k_thread.cpp
     hle/kernel/k_thread.h
+    hle/kernel/k_thread_queue.cpp
     hle/kernel/k_thread_queue.h
     hle/kernel/k_trace.h
     hle/kernel/k_transfer_memory.cpp
@@ -396,12 +412,15 @@ add_library(core STATIC
     hle/service/glue/glue.h
     hle/service/glue/glue_manager.cpp
     hle/service/glue/glue_manager.h
+    hle/service/glue/notif.cpp
+    hle/service/glue/notif.h
     hle/service/grc/grc.cpp
     hle/service/grc/grc.h
     hle/service/hid/hid.cpp
     hle/service/hid/hid.h
     hle/service/hid/irs.cpp
     hle/service/hid/irs.h
+    hle/service/hid/ring_lifo.h
     hle/service/hid/xcd.cpp
     hle/service/hid/xcd.h
     hle/service/hid/errors.h
@@ -466,6 +485,8 @@ add_library(core STATIC
     hle/service/ns/language.h
     hle/service/ns/ns.cpp
     hle/service/ns/ns.h
+    hle/service/ns/pdm_qry.cpp
+    hle/service/ns/pdm_qry.h
     hle/service/ns/pl_u.cpp
     hle/service/ns/pl_u.h
     hle/service/nvdrv/devices/nvdevice.h

+ 20 - 0
src/core/arm/dynarmic/arm_dynarmic_64.cpp

@@ -86,6 +86,26 @@ public:
                   num_instructions, MemoryReadCode(pc));
     }
 
+    void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op,
+                                         VAddr value) override {
+        switch (op) {
+        case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: {
+            static constexpr u64 ICACHE_LINE_SIZE = 64;
+
+            const u64 cache_line_start = value & ~(ICACHE_LINE_SIZE - 1);
+            parent.InvalidateCacheRange(cache_line_start, ICACHE_LINE_SIZE);
+            break;
+        }
+        case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoU:
+            parent.ClearInstructionCache();
+            break;
+        case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoUInnerSharable:
+        default:
+            LOG_DEBUG(Core_ARM, "Unprocesseed instruction cache operation: {}", op);
+            break;
+        }
+    }
+
     void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
         switch (exception) {
         case Dynarmic::A64::Exception::WaitForInterrupt:

+ 11 - 9
src/core/core.cpp

@@ -27,6 +27,7 @@
 #include "core/file_sys/vfs_concat.h"
 #include "core/file_sys/vfs_real.h"
 #include "core/hardware_interrupt_manager.h"
+#include "core/hid/hid_core.h"
 #include "core/hle/kernel/k_process.h"
 #include "core/hle/kernel/k_scheduler.h"
 #include "core/hle/kernel/kernel.h"
@@ -126,7 +127,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
 
 struct System::Impl {
     explicit Impl(System& system)
-        : kernel{system}, fs_controller{system}, memory{system},
+        : kernel{system}, fs_controller{system}, memory{system}, hid_core{},
           cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {}
 
     SystemResultStatus Run() {
@@ -391,6 +392,7 @@ struct System::Impl {
     std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
     std::unique_ptr<Core::DeviceMemory> device_memory;
     Core::Memory::Memory memory;
+    Core::HID::HIDCore hid_core;
     CpuManager cpu_manager;
     std::atomic_bool is_powered_on{};
     bool exit_lock = false;
@@ -519,12 +521,6 @@ const ARM_Interface& System::CurrentArmInterface() const {
     return impl->kernel.CurrentPhysicalCore().ArmInterface();
 }
 
-std::size_t System::CurrentCoreIndex() const {
-    std::size_t core = impl->kernel.GetCurrentHostThreadID();
-    ASSERT(core < Core::Hardware::NUM_CPU_CORES);
-    return core;
-}
-
 Kernel::PhysicalCore& System::CurrentPhysicalCore() {
     return impl->kernel.CurrentPhysicalCore();
 }
@@ -615,6 +611,14 @@ const Kernel::KernelCore& System::Kernel() const {
     return impl->kernel;
 }
 
+HID::HIDCore& System::HIDCore() {
+    return impl->hid_core;
+}
+
+const HID::HIDCore& System::HIDCore() const {
+    return impl->hid_core;
+}
+
 Timing::CoreTiming& System::CoreTiming() {
     return impl->core_timing;
 }
@@ -825,8 +829,6 @@ void System::ApplySettings() {
     if (IsPoweredOn()) {
         Renderer().RefreshBaseSettings();
     }
-
-    Service::HID::ReloadInputDevices();
 }
 
 } // namespace Core

+ 10 - 3
src/core/core.h

@@ -89,6 +89,10 @@ namespace Core::Hardware {
 class InterruptManager;
 }
 
+namespace Core::HID {
+class HIDCore;
+}
+
 namespace Core {
 
 class ARM_Interface;
@@ -204,9 +208,6 @@ public:
     /// Gets an ARM interface to the CPU core that is currently running
     [[nodiscard]] const ARM_Interface& CurrentArmInterface() const;
 
-    /// Gets the index of the currently running CPU core
-    [[nodiscard]] std::size_t CurrentCoreIndex() const;
-
     /// Gets the physical core for the CPU core that is currently running
     [[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore();
 
@@ -285,6 +286,12 @@ public:
     /// Provides a constant reference to the kernel instance.
     [[nodiscard]] const Kernel::KernelCore& Kernel() const;
 
+    /// Gets a mutable reference to the HID interface.
+    [[nodiscard]] HID::HIDCore& HIDCore();
+
+    /// Gets an immutable reference to the HID interface.
+    [[nodiscard]] const HID::HIDCore& HIDCore() const;
+
     /// Provides a reference to the internal PerfStats instance.
     [[nodiscard]] Core::PerfStats& GetPerfStats();
 

+ 10 - 13
src/core/cpu_manager.cpp

@@ -117,17 +117,18 @@ void CpuManager::MultiCoreRunGuestLoop() {
             physical_core = &kernel.CurrentPhysicalCore();
         }
         system.ExitDynarmicProfile();
-        physical_core->ArmInterface().ClearExclusiveState();
-        kernel.CurrentScheduler()->RescheduleCurrentCore();
+        {
+            Kernel::KScopedDisableDispatch dd(kernel);
+            physical_core->ArmInterface().ClearExclusiveState();
+        }
     }
 }
 
 void CpuManager::MultiCoreRunIdleThread() {
     auto& kernel = system.Kernel();
     while (true) {
-        auto& physical_core = kernel.CurrentPhysicalCore();
-        physical_core.Idle();
-        kernel.CurrentScheduler()->RescheduleCurrentCore();
+        Kernel::KScopedDisableDispatch dd(kernel);
+        kernel.CurrentPhysicalCore().Idle();
     }
 }
 
@@ -135,12 +136,12 @@ void CpuManager::MultiCoreRunSuspendThread() {
     auto& kernel = system.Kernel();
     kernel.CurrentScheduler()->OnThreadStart();
     while (true) {
-        auto core = kernel.GetCurrentHostThreadID();
+        auto core = kernel.CurrentPhysicalCoreIndex();
         auto& scheduler = *kernel.CurrentScheduler();
         Kernel::KThread* current_thread = scheduler.GetCurrentThread();
         Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
         ASSERT(scheduler.ContextSwitchPending());
-        ASSERT(core == kernel.GetCurrentHostThreadID());
+        ASSERT(core == kernel.CurrentPhysicalCoreIndex());
         scheduler.RescheduleCurrentCore();
     }
 }
@@ -346,13 +347,9 @@ void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) {
             sc_sync_first_use = false;
         }
 
-        // Abort if emulation was killed before the session really starts
-        if (!system.IsPoweredOn()) {
-            return;
-        }
-
+        // Emulation was stopped
         if (stop_token.stop_requested()) {
-            break;
+            return;
         }
 
         auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();

+ 20 - 25
src/core/frontend/applets/controller.cpp

@@ -5,16 +5,15 @@
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "core/frontend/applets/controller.h"
-#include "core/hle/service/hid/controllers/npad.h"
-#include "core/hle/service/hid/hid.h"
-#include "core/hle/service/sm/sm.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
 
 namespace Core::Frontend {
 
 ControllerApplet::~ControllerApplet() = default;
 
-DefaultControllerApplet::DefaultControllerApplet(Service::SM::ServiceManager& service_manager_)
-    : service_manager{service_manager_} {}
+DefaultControllerApplet::DefaultControllerApplet(HID::HIDCore& hid_core_) : hid_core{hid_core_} {}
 
 DefaultControllerApplet::~DefaultControllerApplet() = default;
 
@@ -22,24 +21,20 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
                                                      const ControllerParameters& parameters) const {
     LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
 
-    auto& npad =
-        service_manager.GetService<Service::HID::Hid>("hid")
-            ->GetAppletResource()
-            ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
-
-    auto& players = Settings::values.players.GetValue();
-
     const std::size_t min_supported_players =
         parameters.enable_single_mode ? 1 : parameters.min_players;
 
     // Disconnect Handheld first.
-    npad.DisconnectNpadAtIndex(8);
+    auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
+    handheld->Disconnect();
 
     // Deduce the best configuration based on the input parameters.
-    for (std::size_t index = 0; index < players.size() - 2; ++index) {
+    for (std::size_t index = 0; index < hid_core.available_controllers - 2; ++index) {
+        auto* controller = hid_core.GetEmulatedControllerByIndex(index);
+
         // First, disconnect all controllers regardless of the value of keep_controllers_connected.
         // This makes it easy to connect the desired controllers.
-        npad.DisconnectNpadAtIndex(index);
+        controller->Disconnect();
 
         // Only connect the minimum number of required players.
         if (index >= min_supported_players) {
@@ -49,27 +44,27 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
         // Connect controllers based on the following priority list from highest to lowest priority:
         // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
         if (parameters.allow_pro_controller) {
-            npad.AddNewControllerAt(
-                npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index);
+            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
+            controller->Connect();
         } else if (parameters.allow_dual_joycons) {
-            npad.AddNewControllerAt(
-                npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index);
+            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconDual);
+            controller->Connect();
         } else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
             // Assign left joycons to even player indices and right joycons to odd player indices.
             // We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
             // a right Joycon for Player 2 in 2 Player Assist mode.
             if (index % 2 == 0) {
-                npad.AddNewControllerAt(
-                    npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index);
+                controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconLeft);
+                controller->Connect();
             } else {
-                npad.AddNewControllerAt(
-                    npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
+                controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::JoyconRight);
+                controller->Connect();
             }
         } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
                    !Settings::values.use_docked_mode.GetValue()) {
             // We should *never* reach here under any normal circumstances.
-            npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
-                                    index);
+            controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
+            controller->Connect();
         } else {
             UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
         }

+ 4 - 4
src/core/frontend/applets/controller.h

@@ -8,8 +8,8 @@
 
 #include "common/common_types.h"
 
-namespace Service::SM {
-class ServiceManager;
+namespace Core::HID {
+class HIDCore;
 }
 
 namespace Core::Frontend {
@@ -44,14 +44,14 @@ public:
 
 class DefaultControllerApplet final : public ControllerApplet {
 public:
-    explicit DefaultControllerApplet(Service::SM::ServiceManager& service_manager_);
+    explicit DefaultControllerApplet(HID::HIDCore& hid_core_);
     ~DefaultControllerApplet() override;
 
     void ReconfigureControllers(std::function<void()> callback,
                                 const ControllerParameters& parameters) const override;
 
 private:
-    Service::SM::ServiceManager& service_manager;
+    HID::HIDCore& hid_core;
 };
 
 } // namespace Core::Frontend

+ 10 - 88
src/core/frontend/emu_window.cpp

@@ -3,87 +3,23 @@
 // Refer to the license.txt file included.
 
 #include <mutex>
-#include "common/settings.h"
 #include "core/frontend/emu_window.h"
-#include "core/frontend/input.h"
 
 namespace Core::Frontend {
 
 GraphicsContext::~GraphicsContext() = default;
 
-class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
-                              public std::enable_shared_from_this<TouchState> {
-public:
-    std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage&) override {
-        return std::make_unique<Device>(shared_from_this());
-    }
-
-    std::mutex mutex;
-
-    Input::TouchStatus status;
-
-private:
-    class Device : public Input::TouchDevice {
-    public:
-        explicit Device(std::weak_ptr<TouchState>&& touch_state_) : touch_state(touch_state_) {}
-        Input::TouchStatus GetStatus() const override {
-            if (auto state = touch_state.lock()) {
-                std::lock_guard guard{state->mutex};
-                return state->status;
-            }
-            return {};
-        }
-
-    private:
-        std::weak_ptr<TouchState> touch_state;
-    };
-};
-
 EmuWindow::EmuWindow() {
     // TODO: Find a better place to set this.
     config.min_client_area_size =
         std::make_pair(Layout::MinimumSize::Width, Layout::MinimumSize::Height);
     active_config = config;
-    touch_state = std::make_shared<TouchState>();
-    Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
-}
-
-EmuWindow::~EmuWindow() {
-    Input::UnregisterFactory<Input::TouchDevice>("emu_window");
-}
-
-/**
- * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
- * @param layout FramebufferLayout object describing the framebuffer size and screen positions
- * @param framebuffer_x Framebuffer x-coordinate to check
- * @param framebuffer_y Framebuffer y-coordinate to check
- * @return True if the coordinates are within the touchpad, otherwise false
- */
-static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, u32 framebuffer_x,
-                                u32 framebuffer_y) {
-    return (framebuffer_y >= layout.screen.top && framebuffer_y < layout.screen.bottom &&
-            framebuffer_x >= layout.screen.left && framebuffer_x < layout.screen.right);
-}
-
-std::pair<u32, u32> EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const {
-    new_x = std::max(new_x, framebuffer_layout.screen.left);
-    new_x = std::min(new_x, framebuffer_layout.screen.right - 1);
-
-    new_y = std::max(new_y, framebuffer_layout.screen.top);
-    new_y = std::min(new_y, framebuffer_layout.screen.bottom - 1);
-
-    return std::make_pair(new_x, new_y);
 }
 
-void EmuWindow::TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id) {
-    if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) {
-        return;
-    }
-    if (id >= touch_state->status.size()) {
-        return;
-    }
+EmuWindow::~EmuWindow() {}
 
-    std::lock_guard guard{touch_state->mutex};
+std::pair<f32, f32> EmuWindow::MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const {
+    std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y);
     const float x =
         static_cast<float>(framebuffer_x - framebuffer_layout.screen.left) /
         static_cast<float>(framebuffer_layout.screen.right - framebuffer_layout.screen.left);
@@ -91,31 +27,17 @@ void EmuWindow::TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id) {
         static_cast<float>(framebuffer_y - framebuffer_layout.screen.top) /
         static_cast<float>(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top);
 
-    touch_state->status[id] = std::make_tuple(x, y, true);
-}
-
-void EmuWindow::TouchReleased(size_t id) {
-    if (id >= touch_state->status.size()) {
-        return;
-    }
-    std::lock_guard guard{touch_state->mutex};
-    touch_state->status[id] = std::make_tuple(0.0f, 0.0f, false);
+    return std::make_pair(x, y);
 }
 
-void EmuWindow::TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id) {
-    if (id >= touch_state->status.size()) {
-        return;
-    }
-
-    if (!std::get<2>(touch_state->status[id])) {
-        return;
-    }
+std::pair<u32, u32> EmuWindow::ClipToTouchScreen(u32 new_x, u32 new_y) const {
+    new_x = std::max(new_x, framebuffer_layout.screen.left);
+    new_x = std::min(new_x, framebuffer_layout.screen.right - 1);
 
-    if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) {
-        std::tie(framebuffer_x, framebuffer_y) = ClipToTouchScreen(framebuffer_x, framebuffer_y);
-    }
+    new_y = std::max(new_y, framebuffer_layout.screen.top);
+    new_y = std::min(new_y, framebuffer_layout.screen.bottom - 1);
 
-    TouchPressed(framebuffer_x, framebuffer_y, id);
+    return std::make_pair(new_x, new_y);
 }
 
 void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height) {

+ 5 - 25
src/core/frontend/emu_window.h

@@ -112,28 +112,6 @@ public:
     /// Returns if window is shown (not minimized)
     virtual bool IsShown() const = 0;
 
-    /**
-     * Signal that a touch pressed event has occurred (e.g. mouse click pressed)
-     * @param framebuffer_x Framebuffer x-coordinate that was pressed
-     * @param framebuffer_y Framebuffer y-coordinate that was pressed
-     * @param id Touch event ID
-     */
-    void TouchPressed(u32 framebuffer_x, u32 framebuffer_y, size_t id);
-
-    /**
-     * Signal that a touch released event has occurred (e.g. mouse click released)
-     * @param id Touch event ID
-     */
-    void TouchReleased(size_t id);
-
-    /**
-     * Signal that a touch movement event has occurred (e.g. mouse was moved over the emu window)
-     * @param framebuffer_x Framebuffer x-coordinate
-     * @param framebuffer_y Framebuffer y-coordinate
-     * @param id Touch event ID
-     */
-    void TouchMoved(u32 framebuffer_x, u32 framebuffer_y, size_t id);
-
     /**
      * Returns currently active configuration.
      * @note Accesses to the returned object need not be consistent because it may be modified in
@@ -212,6 +190,11 @@ protected:
         client_area_height = size.second;
     }
 
+    /**
+     * Converts a screen postion into the equivalent touchscreen position.
+     */
+    std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
+
     WindowSystemInfo window_info;
 
 private:
@@ -237,9 +220,6 @@ private:
     WindowConfig config;        ///< Internal configuration (changes pending for being applied in
                                 /// ProcessConfigurationChanges)
     WindowConfig active_config; ///< Internal active configuration
-
-    class TouchState;
-    std::shared_ptr<TouchState> touch_state;
 };
 
 } // namespace Core::Frontend

+ 6 - 1
src/core/frontend/framebuffer_layout.cpp

@@ -25,7 +25,12 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
     ASSERT(height > 0);
     // The drawing code needs at least somewhat valid values for both screens
     // so just calculate them both even if the other isn't showing.
-    FramebufferLayout res{width, height, false, {}};
+    FramebufferLayout res{
+        .width = width,
+        .height = height,
+        .screen = {},
+        .is_srgb = false,
+    };
 
     const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
     const float emulation_aspect_ratio = EmulationAspectRatio(

+ 1 - 10
src/core/frontend/framebuffer_layout.h

@@ -35,17 +35,8 @@ enum class AspectRatio {
 struct FramebufferLayout {
     u32 width{ScreenUndocked::Width};
     u32 height{ScreenUndocked::Height};
-    bool is_srgb{};
-
     Common::Rectangle<u32> screen;
-
-    /**
-     * Returns the ration of pixel size of the screen, compared to the native size of the undocked
-     * Switch screen.
-     */
-    float GetScalingRatio() const {
-        return static_cast<float>(screen.GetWidth()) / ScreenUndocked::Width;
-    }
+    bool is_srgb{};
 };
 
 /**

+ 0 - 217
src/core/frontend/input.h

@@ -1,217 +0,0 @@
-// Copyright 2017 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <functional>
-#include <memory>
-#include <string>
-#include <tuple>
-#include <unordered_map>
-#include <utility>
-#include "common/logging/log.h"
-#include "common/param_package.h"
-#include "common/quaternion.h"
-#include "common/vector_math.h"
-
-namespace Input {
-
-enum class AnalogDirection : u8 {
-    RIGHT,
-    LEFT,
-    UP,
-    DOWN,
-};
-struct AnalogProperties {
-    float deadzone;
-    float range;
-    float threshold;
-};
-template <typename StatusType>
-struct InputCallback {
-    std::function<void(StatusType)> on_change;
-};
-
-/// An abstract class template for an input device (a button, an analog input, etc.).
-template <typename StatusType>
-class InputDevice {
-public:
-    virtual ~InputDevice() = default;
-    virtual StatusType GetStatus() const {
-        return {};
-    }
-    virtual StatusType GetRawStatus() const {
-        return GetStatus();
-    }
-    virtual AnalogProperties GetAnalogProperties() const {
-        return {};
-    }
-    virtual bool GetAnalogDirectionStatus([[maybe_unused]] AnalogDirection direction) const {
-        return {};
-    }
-    virtual bool SetRumblePlay([[maybe_unused]] f32 amp_low, [[maybe_unused]] f32 freq_low,
-                               [[maybe_unused]] f32 amp_high,
-                               [[maybe_unused]] f32 freq_high) const {
-        return {};
-    }
-    void SetCallback(InputCallback<StatusType> callback_) {
-        callback = std::move(callback_);
-    }
-    void TriggerOnChange() {
-        if (callback.on_change) {
-            callback.on_change(GetStatus());
-        }
-    }
-
-private:
-    InputCallback<StatusType> callback;
-};
-
-/// An abstract class template for a factory that can create input devices.
-template <typename InputDeviceType>
-class Factory {
-public:
-    virtual ~Factory() = default;
-    virtual std::unique_ptr<InputDeviceType> Create(const Common::ParamPackage&) = 0;
-};
-
-namespace Impl {
-
-template <typename InputDeviceType>
-using FactoryListType = std::unordered_map<std::string, std::shared_ptr<Factory<InputDeviceType>>>;
-
-template <typename InputDeviceType>
-struct FactoryList {
-    static FactoryListType<InputDeviceType> list;
-};
-
-template <typename InputDeviceType>
-FactoryListType<InputDeviceType> FactoryList<InputDeviceType>::list;
-
-} // namespace Impl
-
-/**
- * Registers an input device factory.
- * @tparam InputDeviceType the type of input devices the factory can create
- * @param name the name of the factory. Will be used to match the "engine" parameter when creating
- *     a device
- * @param factory the factory object to register
- */
-template <typename InputDeviceType>
-void RegisterFactory(const std::string& name, std::shared_ptr<Factory<InputDeviceType>> factory) {
-    auto pair = std::make_pair(name, std::move(factory));
-    if (!Impl::FactoryList<InputDeviceType>::list.insert(std::move(pair)).second) {
-        LOG_ERROR(Input, "Factory '{}' already registered", name);
-    }
-}
-
-/**
- * Unregisters an input device factory.
- * @tparam InputDeviceType the type of input devices the factory can create
- * @param name the name of the factory to unregister
- */
-template <typename InputDeviceType>
-void UnregisterFactory(const std::string& name) {
-    if (Impl::FactoryList<InputDeviceType>::list.erase(name) == 0) {
-        LOG_ERROR(Input, "Factory '{}' not registered", name);
-    }
-}
-
-/**
- * Create an input device from given paramters.
- * @tparam InputDeviceType the type of input devices to create
- * @param params a serialized ParamPackage string contains all parameters for creating the device
- */
-template <typename InputDeviceType>
-std::unique_ptr<InputDeviceType> CreateDevice(const std::string& params) {
-    const Common::ParamPackage package(params);
-    const std::string engine = package.Get("engine", "null");
-    const auto& factory_list = Impl::FactoryList<InputDeviceType>::list;
-    const auto pair = factory_list.find(engine);
-    if (pair == factory_list.end()) {
-        if (engine != "null") {
-            LOG_ERROR(Input, "Unknown engine name: {}", engine);
-        }
-        return std::make_unique<InputDeviceType>();
-    }
-    return pair->second->Create(package);
-}
-
-/**
- * A button device is an input device that returns bool as status.
- * true for pressed; false for released.
- */
-using ButtonDevice = InputDevice<bool>;
-
-/**
- * An analog device is an input device that returns a tuple of x and y coordinates as status. The
- * coordinates are within the unit circle. x+ is defined as right direction, and y+ is defined as up
- * direction
- */
-using AnalogDevice = InputDevice<std::tuple<float, float>>;
-
-/**
- * A vibration device is an input device that returns an unsigned byte as status.
- * It represents whether the vibration device supports vibration or not.
- * If the status returns 1, it supports vibration. Otherwise, it does not support vibration.
- */
-using VibrationDevice = InputDevice<u8>;
-
-/**
- * A motion status is an object that returns a tuple of accelerometer state vector,
- * gyroscope state vector, rotation state vector, orientation state matrix and quaterion state
- * vector.
- *
- * For both 3D vectors:
- *   x+ is the same direction as RIGHT on D-pad.
- *   y+ is normal to the touch screen, pointing outward.
- *   z+ is the same direction as UP on D-pad.
- *
- * For accelerometer state vector
- *   Units: g (gravitational acceleration)
- *
- * For gyroscope state vector:
- *   Orientation is determined by right-hand rule.
- *   Units: deg/sec
- *
- * For rotation state vector
- *   Units: rotations
- *
- * For orientation state matrix
- *   x vector
- *   y vector
- *   z vector
- *
- * For quaternion state vector
- *   xyz vector
- *   w float
- */
-using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>,
-                                std::array<Common::Vec3f, 3>, Common::Quaternion<f32>>;
-
-/**
- * A motion device is an input device that returns a motion status object
- */
-using MotionDevice = InputDevice<MotionStatus>;
-
-/**
- * A touch status is an object that returns an array of 16 tuple elements of two floats and a bool.
- * The floats are x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is
- * pressed.
- */
-using TouchStatus = std::array<std::tuple<float, float, bool>, 16>;
-
-/**
- * A touch device is an input device that returns a touch status object
- */
-using TouchDevice = InputDevice<TouchStatus>;
-
-/**
- * A mouse device is an input device that returns a tuple of two floats and four ints.
- * The first two floats are X and Y device coordinates of the mouse (from 0-1).
- * The s32s are the mouse wheel.
- */
-using MouseDevice = InputDevice<std::tuple<float, float, s32, s32>>;
-
-} // namespace Input

+ 232 - 0
src/core/hid/emulated_console.cpp

@@ -0,0 +1,232 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include "common/settings.h"
+#include "core/hid/emulated_console.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+EmulatedConsole::EmulatedConsole() = default;
+
+EmulatedConsole::~EmulatedConsole() = default;
+
+void EmulatedConsole::ReloadFromSettings() {
+    // Using first motion device from player 1. No need to assign any unique config at the moment
+    const auto& player = Settings::values.players.GetValue()[0];
+    motion_params = Common::ParamPackage(player.motions[0]);
+
+    ReloadInput();
+}
+
+void EmulatedConsole::SetTouchParams() {
+    // TODO(german77): Support any number of fingers
+    std::size_t index = 0;
+
+    // Hardcode mouse, touchscreen and cemuhook parameters
+    if (!Settings::values.mouse_enabled) {
+        // We can't use mouse as touch if native mouse is enabled
+        touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"};
+    }
+    touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0"};
+    touch_params[index++] = Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1"};
+    touch_params[index++] =
+        Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};
+    touch_params[index++] =
+        Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"};
+
+    const auto button_index =
+        static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
+    const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons;
+
+    // Map the rest of the fingers from touch from button configuration
+    for (const auto& config_entry : touch_buttons) {
+        if (index >= touch_params.size()) {
+            continue;
+        }
+        Common::ParamPackage params{config_entry};
+        Common::ParamPackage touch_button_params;
+        const int x = params.Get("x", 0);
+        const int y = params.Get("y", 0);
+        params.Erase("x");
+        params.Erase("y");
+        touch_button_params.Set("engine", "touch_from_button");
+        touch_button_params.Set("button", params.Serialize());
+        touch_button_params.Set("x", x);
+        touch_button_params.Set("y", y);
+        touch_button_params.Set("touch_id", static_cast<int>(index));
+        touch_params[index] = touch_button_params;
+        index++;
+    }
+}
+
+void EmulatedConsole::ReloadInput() {
+    // If you load any device here add the equivalent to the UnloadInput() function
+    SetTouchParams();
+
+    motion_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(motion_params);
+    if (motion_devices) {
+        motion_devices->SetCallback({
+            .on_change =
+                [this](const Common::Input::CallbackStatus& callback) { SetMotion(callback); },
+        });
+    }
+
+    // Unique index for identifying touch device source
+    std::size_t index = 0;
+    for (auto& touch_device : touch_devices) {
+        touch_device = Common::Input::CreateDevice<Common::Input::InputDevice>(touch_params[index]);
+        if (!touch_device) {
+            continue;
+        }
+        touch_device->SetCallback({
+            .on_change =
+                [this, index](const Common::Input::CallbackStatus& callback) {
+                    SetTouch(callback, index);
+                },
+        });
+        index++;
+    }
+}
+
+void EmulatedConsole::UnloadInput() {
+    motion_devices.reset();
+    for (auto& touch : touch_devices) {
+        touch.reset();
+    }
+}
+
+void EmulatedConsole::EnableConfiguration() {
+    is_configuring = true;
+    SaveCurrentConfig();
+}
+
+void EmulatedConsole::DisableConfiguration() {
+    is_configuring = false;
+}
+
+bool EmulatedConsole::IsConfiguring() const {
+    return is_configuring;
+}
+
+void EmulatedConsole::SaveCurrentConfig() {
+    if (!is_configuring) {
+        return;
+    }
+}
+
+void EmulatedConsole::RestoreConfig() {
+    if (!is_configuring) {
+        return;
+    }
+    ReloadFromSettings();
+}
+
+Common::ParamPackage EmulatedConsole::GetMotionParam() const {
+    return motion_params;
+}
+
+void EmulatedConsole::SetMotionParam(Common::ParamPackage param) {
+    motion_params = param;
+    ReloadInput();
+}
+
+void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
+    std::lock_guard lock{mutex};
+    auto& raw_status = console.motion_values.raw_status;
+    auto& emulated = console.motion_values.emulated;
+
+    raw_status = TransformToMotion(callback);
+    emulated.SetAcceleration(Common::Vec3f{
+        raw_status.accel.x.value,
+        raw_status.accel.y.value,
+        raw_status.accel.z.value,
+    });
+    emulated.SetGyroscope(Common::Vec3f{
+        raw_status.gyro.x.value,
+        raw_status.gyro.y.value,
+        raw_status.gyro.z.value,
+    });
+    emulated.UpdateRotation(raw_status.delta_timestamp);
+    emulated.UpdateOrientation(raw_status.delta_timestamp);
+
+    if (is_configuring) {
+        TriggerOnChange(ConsoleTriggerType::Motion);
+        return;
+    }
+
+    auto& motion = console.motion_state;
+    motion.accel = emulated.GetAcceleration();
+    motion.gyro = emulated.GetGyroscope();
+    motion.rotation = emulated.GetGyroscope();
+    motion.orientation = emulated.GetOrientation();
+    motion.quaternion = emulated.GetQuaternion();
+    motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
+
+    TriggerOnChange(ConsoleTriggerType::Motion);
+}
+
+void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index) {
+    if (index >= console.touch_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+
+    console.touch_values[index] = TransformToTouch(callback);
+
+    if (is_configuring) {
+        TriggerOnChange(ConsoleTriggerType::Touch);
+        return;
+    }
+
+    // TODO(german77): Remap touch id in sequential order
+    console.touch_state[index] = {
+        .position = {console.touch_values[index].x.value, console.touch_values[index].y.value},
+        .id = static_cast<u32>(console.touch_values[index].id),
+        .pressed = console.touch_values[index].pressed.value,
+    };
+
+    TriggerOnChange(ConsoleTriggerType::Touch);
+}
+
+ConsoleMotionValues EmulatedConsole::GetMotionValues() const {
+    return console.motion_values;
+}
+
+TouchValues EmulatedConsole::GetTouchValues() const {
+    return console.touch_values;
+}
+
+ConsoleMotion EmulatedConsole::GetMotion() const {
+    return console.motion_state;
+}
+
+TouchFingerState EmulatedConsole::GetTouch() const {
+    return console.touch_state;
+}
+
+void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
+    for (const auto& poller_pair : callback_list) {
+        const ConsoleUpdateCallback& poller = poller_pair.second;
+        if (poller.on_change) {
+            poller.on_change(type);
+        }
+    }
+}
+
+int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) {
+    std::lock_guard lock{mutex};
+    callback_list.insert_or_assign(last_callback_key, update_callback);
+    return last_callback_key++;
+}
+
+void EmulatedConsole::DeleteCallback(int key) {
+    std::lock_guard lock{mutex};
+    const auto& iterator = callback_list.find(key);
+    if (iterator == callback_list.end()) {
+        LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+        return;
+    }
+    callback_list.erase(iterator);
+}
+} // namespace Core::HID

+ 190 - 0
src/core/hid/emulated_console.h

@@ -0,0 +1,190 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/common_types.h"
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/point.h"
+#include "common/quaternion.h"
+#include "common/vector_math.h"
+#include "core/hid/hid_types.h"
+#include "core/hid/motion_input.h"
+
+namespace Core::HID {
+
+struct ConsoleMotionInfo {
+    Common::Input::MotionStatus raw_status{};
+    MotionInput emulated{};
+};
+
+using ConsoleMotionDevices = std::unique_ptr<Common::Input::InputDevice>;
+using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 16>;
+
+using ConsoleMotionParams = Common::ParamPackage;
+using TouchParams = std::array<Common::ParamPackage, 16>;
+
+using ConsoleMotionValues = ConsoleMotionInfo;
+using TouchValues = std::array<Common::Input::TouchStatus, 16>;
+
+struct TouchFinger {
+    u64 last_touch{};
+    Common::Point<float> position{};
+    u32 id{};
+    TouchAttribute attribute{};
+    bool pressed{};
+};
+
+// Contains all motion related data that is used on the services
+struct ConsoleMotion {
+    Common::Vec3f accel{};
+    Common::Vec3f gyro{};
+    Common::Vec3f rotation{};
+    std::array<Common::Vec3f, 3> orientation{};
+    Common::Quaternion<f32> quaternion{};
+    bool is_at_rest{};
+};
+
+using TouchFingerState = std::array<TouchFinger, 16>;
+
+struct ConsoleStatus {
+    // Data from input_common
+    ConsoleMotionValues motion_values{};
+    TouchValues touch_values{};
+
+    // Data for HID services
+    ConsoleMotion motion_state{};
+    TouchFingerState touch_state{};
+};
+
+enum class ConsoleTriggerType {
+    Motion,
+    Touch,
+    All,
+};
+
+struct ConsoleUpdateCallback {
+    std::function<void(ConsoleTriggerType)> on_change;
+};
+
+class EmulatedConsole {
+public:
+    /**
+     * Contains all input data within the emulated switch console tablet such as touch and motion
+     */
+    explicit EmulatedConsole();
+    ~EmulatedConsole();
+
+    YUZU_NON_COPYABLE(EmulatedConsole);
+    YUZU_NON_MOVEABLE(EmulatedConsole);
+
+    /// Removes all callbacks created from input devices
+    void UnloadInput();
+
+    /**
+     * Sets the emulated console into configuring mode
+     * This prevents the modification of the HID state of the emulated console by input commands
+     */
+    void EnableConfiguration();
+
+    /// Returns the emulated console into normal mode, allowing the modification of the HID state
+    void DisableConfiguration();
+
+    /// Returns true if the emulated console is in configuring mode
+    bool IsConfiguring() const;
+
+    /// Reload all input devices
+    void ReloadInput();
+
+    /// Overrides current mapped devices with the stored configuration and reloads all input devices
+    void ReloadFromSettings();
+
+    /// Saves the current mapped configuration
+    void SaveCurrentConfig();
+
+    /// Reverts any mapped changes made that weren't saved
+    void RestoreConfig();
+
+    // Returns the current mapped motion device
+    Common::ParamPackage GetMotionParam() const;
+
+    /**
+     * Updates the current mapped motion device
+     * @param param ParamPackage with controller data to be mapped
+     */
+    void SetMotionParam(Common::ParamPackage param);
+
+    /// Returns the latest status of motion input from the console with parameters
+    ConsoleMotionValues GetMotionValues() const;
+
+    /// Returns the latest status of touch input from the console with parameters
+    TouchValues GetTouchValues() const;
+
+    /// Returns the latest status of motion input from the console
+    ConsoleMotion GetMotion() const;
+
+    /// Returns the latest status of touch input from the console
+    TouchFingerState GetTouch() const;
+
+    /**
+     * Adds a callback to the list of events
+     * @param update_callback A ConsoleUpdateCallback that will be triggered
+     * @return an unique key corresponding to the callback index in the list
+     */
+    int SetCallback(ConsoleUpdateCallback update_callback);
+
+    /**
+     * Removes a callback from the list stopping any future events to this object
+     * @param key Key corresponding to the callback index in the list
+     */
+    void DeleteCallback(int key);
+
+private:
+    /// Creates and stores the touch params
+    void SetTouchParams();
+
+    /**
+     * Updates the motion status of the console
+     * @param callback A CallbackStatus containing gyro and accelerometer data
+     */
+    void SetMotion(const Common::Input::CallbackStatus& callback);
+
+    /**
+     * Updates the touch status of the console
+     * @param callback A CallbackStatus containing the touch position
+     * @param index Finger ID to be updated
+     */
+    void SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index);
+
+    /**
+     * Triggers a callback that something has changed on the console status
+     * @param type Input type of the event to trigger
+     */
+    void TriggerOnChange(ConsoleTriggerType type);
+
+    bool is_configuring{false};
+    f32 motion_sensitivity{0.01f};
+
+    ConsoleMotionParams motion_params;
+    TouchParams touch_params;
+
+    ConsoleMotionDevices motion_devices;
+    TouchDevices touch_devices;
+
+    mutable std::mutex mutex;
+    std::unordered_map<int, ConsoleUpdateCallback> callback_list;
+    int last_callback_key = 0;
+
+    // Stores the current status of all console input
+    ConsoleStatus console;
+};
+
+} // namespace Core::HID

+ 1139 - 0
src/core/hid/emulated_controller.cpp

@@ -0,0 +1,1139 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
+constexpr s32 HID_TRIGGER_MAX = 0x7fff;
+
+EmulatedController::EmulatedController(NpadIdType npad_id_type_) : npad_id_type(npad_id_type_) {}
+
+EmulatedController::~EmulatedController() = default;
+
+NpadStyleIndex EmulatedController::MapSettingsTypeToNPad(Settings::ControllerType type) {
+    switch (type) {
+    case Settings::ControllerType::ProController:
+        return NpadStyleIndex::ProController;
+    case Settings::ControllerType::DualJoyconDetached:
+        return NpadStyleIndex::JoyconDual;
+    case Settings::ControllerType::LeftJoycon:
+        return NpadStyleIndex::JoyconLeft;
+    case Settings::ControllerType::RightJoycon:
+        return NpadStyleIndex::JoyconRight;
+    case Settings::ControllerType::Handheld:
+        return NpadStyleIndex::Handheld;
+    case Settings::ControllerType::GameCube:
+        return NpadStyleIndex::GameCube;
+    case Settings::ControllerType::Pokeball:
+        return NpadStyleIndex::Pokeball;
+    case Settings::ControllerType::NES:
+        return NpadStyleIndex::NES;
+    case Settings::ControllerType::SNES:
+        return NpadStyleIndex::SNES;
+    case Settings::ControllerType::N64:
+        return NpadStyleIndex::N64;
+    case Settings::ControllerType::SegaGenesis:
+        return NpadStyleIndex::SegaGenesis;
+    default:
+        return NpadStyleIndex::ProController;
+    }
+}
+
+Settings::ControllerType EmulatedController::MapNPadToSettingsType(NpadStyleIndex type) {
+    switch (type) {
+    case NpadStyleIndex::ProController:
+        return Settings::ControllerType::ProController;
+    case NpadStyleIndex::JoyconDual:
+        return Settings::ControllerType::DualJoyconDetached;
+    case NpadStyleIndex::JoyconLeft:
+        return Settings::ControllerType::LeftJoycon;
+    case NpadStyleIndex::JoyconRight:
+        return Settings::ControllerType::RightJoycon;
+    case NpadStyleIndex::Handheld:
+        return Settings::ControllerType::Handheld;
+    case NpadStyleIndex::GameCube:
+        return Settings::ControllerType::GameCube;
+    case NpadStyleIndex::Pokeball:
+        return Settings::ControllerType::Pokeball;
+    case NpadStyleIndex::NES:
+        return Settings::ControllerType::NES;
+    case NpadStyleIndex::SNES:
+        return Settings::ControllerType::SNES;
+    case NpadStyleIndex::N64:
+        return Settings::ControllerType::N64;
+    case NpadStyleIndex::SegaGenesis:
+        return Settings::ControllerType::SegaGenesis;
+    default:
+        return Settings::ControllerType::ProController;
+    }
+}
+
+void EmulatedController::ReloadFromSettings() {
+    const auto player_index = NpadIdTypeToIndex(npad_id_type);
+    const auto& player = Settings::values.players.GetValue()[player_index];
+
+    for (std::size_t index = 0; index < player.buttons.size(); ++index) {
+        button_params[index] = Common::ParamPackage(player.buttons[index]);
+    }
+    for (std::size_t index = 0; index < player.analogs.size(); ++index) {
+        stick_params[index] = Common::ParamPackage(player.analogs[index]);
+    }
+    for (std::size_t index = 0; index < player.motions.size(); ++index) {
+        motion_params[index] = Common::ParamPackage(player.motions[index]);
+    }
+
+    controller.colors_state.left = {
+        .body = player.body_color_left,
+        .button = player.button_color_left,
+    };
+
+    controller.colors_state.right = {
+        .body = player.body_color_right,
+        .button = player.button_color_right,
+    };
+
+    controller.colors_state.fullkey = controller.colors_state.left;
+
+    // Other or debug controller should always be a pro controller
+    if (npad_id_type != NpadIdType::Other) {
+        SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
+    } else {
+        SetNpadStyleIndex(NpadStyleIndex::ProController);
+    }
+
+    if (player.connected) {
+        Connect();
+    } else {
+        Disconnect();
+    }
+
+    ReloadInput();
+}
+
+void EmulatedController::LoadDevices() {
+    // TODO(german77): Use more buttons to detect the correct device
+    const auto left_joycon = button_params[Settings::NativeButton::DRight];
+    const auto right_joycon = button_params[Settings::NativeButton::A];
+
+    // Triggers for GC controllers
+    trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL];
+    trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR];
+
+    battery_params[LeftIndex] = left_joycon;
+    battery_params[RightIndex] = right_joycon;
+    battery_params[LeftIndex].Set("battery", true);
+    battery_params[RightIndex].Set("battery", true);
+
+    output_params[LeftIndex] = left_joycon;
+    output_params[RightIndex] = right_joycon;
+    output_params[LeftIndex].Set("output", true);
+    output_params[RightIndex].Set("output", true);
+
+    LoadTASParams();
+
+    std::transform(button_params.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
+                   button_params.begin() + Settings::NativeButton::BUTTON_NS_END,
+                   button_devices.begin(), Common::Input::CreateDevice<Common::Input::InputDevice>);
+    std::transform(stick_params.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
+                   stick_params.begin() + Settings::NativeAnalog::STICK_HID_END,
+                   stick_devices.begin(), Common::Input::CreateDevice<Common::Input::InputDevice>);
+    std::transform(motion_params.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
+                   motion_params.begin() + Settings::NativeMotion::MOTION_HID_END,
+                   motion_devices.begin(), Common::Input::CreateDevice<Common::Input::InputDevice>);
+    std::transform(trigger_params.begin(), trigger_params.end(), trigger_devices.begin(),
+                   Common::Input::CreateDevice<Common::Input::InputDevice>);
+    std::transform(battery_params.begin(), battery_params.begin(), battery_devices.end(),
+                   Common::Input::CreateDevice<Common::Input::InputDevice>);
+    std::transform(output_params.begin(), output_params.end(), output_devices.begin(),
+                   Common::Input::CreateDevice<Common::Input::OutputDevice>);
+
+    // Initialize TAS devices
+    std::transform(tas_button_params.begin(), tas_button_params.end(), tas_button_devices.begin(),
+                   Common::Input::CreateDevice<Common::Input::InputDevice>);
+    std::transform(tas_stick_params.begin(), tas_stick_params.end(), tas_stick_devices.begin(),
+                   Common::Input::CreateDevice<Common::Input::InputDevice>);
+}
+
+void EmulatedController::LoadTASParams() {
+    const auto player_index = NpadIdTypeToIndex(npad_id_type);
+    Common::ParamPackage common_params{};
+    common_params.Set("engine", "tas");
+    common_params.Set("port", static_cast<int>(player_index));
+    for (auto& param : tas_button_params) {
+        param = common_params;
+    }
+    for (auto& param : tas_stick_params) {
+        param = common_params;
+    }
+
+    // TODO(german77): Replace this with an input profile or something better
+    tas_button_params[Settings::NativeButton::A].Set("button", 0);
+    tas_button_params[Settings::NativeButton::B].Set("button", 1);
+    tas_button_params[Settings::NativeButton::X].Set("button", 2);
+    tas_button_params[Settings::NativeButton::Y].Set("button", 3);
+    tas_button_params[Settings::NativeButton::LStick].Set("button", 4);
+    tas_button_params[Settings::NativeButton::RStick].Set("button", 5);
+    tas_button_params[Settings::NativeButton::L].Set("button", 6);
+    tas_button_params[Settings::NativeButton::R].Set("button", 7);
+    tas_button_params[Settings::NativeButton::ZL].Set("button", 8);
+    tas_button_params[Settings::NativeButton::ZR].Set("button", 9);
+    tas_button_params[Settings::NativeButton::Plus].Set("button", 10);
+    tas_button_params[Settings::NativeButton::Minus].Set("button", 11);
+    tas_button_params[Settings::NativeButton::DLeft].Set("button", 12);
+    tas_button_params[Settings::NativeButton::DUp].Set("button", 13);
+    tas_button_params[Settings::NativeButton::DRight].Set("button", 14);
+    tas_button_params[Settings::NativeButton::DDown].Set("button", 15);
+    tas_button_params[Settings::NativeButton::SL].Set("button", 16);
+    tas_button_params[Settings::NativeButton::SR].Set("button", 17);
+    tas_button_params[Settings::NativeButton::Home].Set("button", 18);
+    tas_button_params[Settings::NativeButton::Screenshot].Set("button", 19);
+
+    tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_x", 0);
+    tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
+    tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2);
+    tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3);
+}
+
+void EmulatedController::ReloadInput() {
+    // If you load any device here add the equivalent to the UnloadInput() function
+    LoadDevices();
+    for (std::size_t index = 0; index < button_devices.size(); ++index) {
+        if (!button_devices[index]) {
+            continue;
+        }
+        const auto uuid = Common::UUID{button_params[index].Get("guid", "")};
+        button_devices[index]->SetCallback({
+            .on_change =
+                [this, index, uuid](const Common::Input::CallbackStatus& callback) {
+                    SetButton(callback, index, uuid);
+                },
+        });
+        button_devices[index]->ForceUpdate();
+    }
+
+    for (std::size_t index = 0; index < stick_devices.size(); ++index) {
+        if (!stick_devices[index]) {
+            continue;
+        }
+        const auto uuid = Common::UUID{stick_params[index].Get("guid", "")};
+        stick_devices[index]->SetCallback({
+            .on_change =
+                [this, index, uuid](const Common::Input::CallbackStatus& callback) {
+                    SetStick(callback, index, uuid);
+                },
+        });
+        stick_devices[index]->ForceUpdate();
+    }
+
+    for (std::size_t index = 0; index < trigger_devices.size(); ++index) {
+        if (!trigger_devices[index]) {
+            continue;
+        }
+        const auto uuid = Common::UUID{trigger_params[index].Get("guid", "")};
+        trigger_devices[index]->SetCallback({
+            .on_change =
+                [this, index, uuid](const Common::Input::CallbackStatus& callback) {
+                    SetTrigger(callback, index, uuid);
+                },
+        });
+        trigger_devices[index]->ForceUpdate();
+    }
+
+    for (std::size_t index = 0; index < battery_devices.size(); ++index) {
+        if (!battery_devices[index]) {
+            continue;
+        }
+        battery_devices[index]->SetCallback({
+            .on_change =
+                [this, index](const Common::Input::CallbackStatus& callback) {
+                    SetBattery(callback, index);
+                },
+        });
+        battery_devices[index]->ForceUpdate();
+    }
+
+    for (std::size_t index = 0; index < motion_devices.size(); ++index) {
+        if (!motion_devices[index]) {
+            continue;
+        }
+        motion_devices[index]->SetCallback({
+            .on_change =
+                [this, index](const Common::Input::CallbackStatus& callback) {
+                    SetMotion(callback, index);
+                },
+        });
+        motion_devices[index]->ForceUpdate();
+    }
+
+    // Use a common UUID for TAS
+    const auto tas_uuid = Common::UUID{0x0, 0x7A5};
+
+    // Register TAS devices. No need to force update
+    for (std::size_t index = 0; index < tas_button_devices.size(); ++index) {
+        if (!tas_button_devices[index]) {
+            continue;
+        }
+        tas_button_devices[index]->SetCallback({
+            .on_change =
+                [this, index, tas_uuid](const Common::Input::CallbackStatus& callback) {
+                    SetButton(callback, index, tas_uuid);
+                },
+        });
+    }
+
+    for (std::size_t index = 0; index < tas_stick_devices.size(); ++index) {
+        if (!tas_stick_devices[index]) {
+            continue;
+        }
+        tas_stick_devices[index]->SetCallback({
+            .on_change =
+                [this, index, tas_uuid](const Common::Input::CallbackStatus& callback) {
+                    SetStick(callback, index, tas_uuid);
+                },
+        });
+    }
+}
+
+void EmulatedController::UnloadInput() {
+    for (auto& button : button_devices) {
+        button.reset();
+    }
+    for (auto& stick : stick_devices) {
+        stick.reset();
+    }
+    for (auto& motion : motion_devices) {
+        motion.reset();
+    }
+    for (auto& trigger : trigger_devices) {
+        trigger.reset();
+    }
+    for (auto& battery : battery_devices) {
+        battery.reset();
+    }
+    for (auto& output : output_devices) {
+        output.reset();
+    }
+    for (auto& button : tas_button_devices) {
+        button.reset();
+    }
+    for (auto& stick : tas_stick_devices) {
+        stick.reset();
+    }
+}
+
+void EmulatedController::EnableConfiguration() {
+    is_configuring = true;
+    tmp_is_connected = is_connected;
+    tmp_npad_type = npad_type;
+}
+
+void EmulatedController::DisableConfiguration() {
+    is_configuring = false;
+
+    // Apply temporary npad type to the real controller
+    if (tmp_npad_type != npad_type) {
+        if (is_connected) {
+            Disconnect();
+        }
+        SetNpadStyleIndex(tmp_npad_type);
+    }
+
+    // Apply temporary connected status to the real controller
+    if (tmp_is_connected != is_connected) {
+        if (tmp_is_connected) {
+            Connect();
+            return;
+        }
+        Disconnect();
+    }
+}
+
+bool EmulatedController::IsConfiguring() const {
+    return is_configuring;
+}
+
+void EmulatedController::SaveCurrentConfig() {
+    const auto player_index = NpadIdTypeToIndex(npad_id_type);
+    auto& player = Settings::values.players.GetValue()[player_index];
+    player.connected = is_connected;
+    player.controller_type = MapNPadToSettingsType(npad_type);
+    for (std::size_t index = 0; index < player.buttons.size(); ++index) {
+        player.buttons[index] = button_params[index].Serialize();
+    }
+    for (std::size_t index = 0; index < player.analogs.size(); ++index) {
+        player.analogs[index] = stick_params[index].Serialize();
+    }
+    for (std::size_t index = 0; index < player.motions.size(); ++index) {
+        player.motions[index] = motion_params[index].Serialize();
+    }
+}
+
+void EmulatedController::RestoreConfig() {
+    if (!is_configuring) {
+        return;
+    }
+    ReloadFromSettings();
+}
+
+std::vector<Common::ParamPackage> EmulatedController::GetMappedDevices(
+    EmulatedDeviceIndex device_index) const {
+    std::vector<Common::ParamPackage> devices;
+    for (const auto& param : button_params) {
+        if (!param.Has("engine")) {
+            continue;
+        }
+        const auto devices_it = std::find_if(
+            devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
+                return param.Get("engine", "") == param_.Get("engine", "") &&
+                       param.Get("guid", "") == param_.Get("guid", "") &&
+                       param.Get("port", 0) == param_.Get("port", 0);
+            });
+        if (devices_it != devices.end()) {
+            continue;
+        }
+        Common::ParamPackage device{};
+        device.Set("engine", param.Get("engine", ""));
+        device.Set("guid", param.Get("guid", ""));
+        device.Set("port", param.Get("port", 0));
+        devices.push_back(device);
+    }
+
+    for (const auto& param : stick_params) {
+        if (!param.Has("engine")) {
+            continue;
+        }
+        if (param.Get("engine", "") == "analog_from_button") {
+            continue;
+        }
+        const auto devices_it = std::find_if(
+            devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
+                return param.Get("engine", "") == param_.Get("engine", "") &&
+                       param.Get("guid", "") == param_.Get("guid", "") &&
+                       param.Get("port", 0) == param_.Get("port", 0);
+            });
+        if (devices_it != devices.end()) {
+            continue;
+        }
+        Common::ParamPackage device{};
+        device.Set("engine", param.Get("engine", ""));
+        device.Set("guid", param.Get("guid", ""));
+        device.Set("port", param.Get("port", 0));
+        devices.push_back(device);
+    }
+    return devices;
+}
+
+Common::ParamPackage EmulatedController::GetButtonParam(std::size_t index) const {
+    if (index >= button_params.size()) {
+        return {};
+    }
+    return button_params[index];
+}
+
+Common::ParamPackage EmulatedController::GetStickParam(std::size_t index) const {
+    if (index >= stick_params.size()) {
+        return {};
+    }
+    return stick_params[index];
+}
+
+Common::ParamPackage EmulatedController::GetMotionParam(std::size_t index) const {
+    if (index >= motion_params.size()) {
+        return {};
+    }
+    return motion_params[index];
+}
+
+void EmulatedController::SetButtonParam(std::size_t index, Common::ParamPackage param) {
+    if (index >= button_params.size()) {
+        return;
+    }
+    button_params[index] = std::move(param);
+    ReloadInput();
+}
+
+void EmulatedController::SetStickParam(std::size_t index, Common::ParamPackage param) {
+    if (index >= stick_params.size()) {
+        return;
+    }
+    stick_params[index] = std::move(param);
+    ReloadInput();
+}
+
+void EmulatedController::SetMotionParam(std::size_t index, Common::ParamPackage param) {
+    if (index >= motion_params.size()) {
+        return;
+    }
+    motion_params[index] = std::move(param);
+    ReloadInput();
+}
+
+void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
+                                   Common::UUID uuid) {
+    if (index >= controller.button_values.size()) {
+        return;
+    }
+    {
+        std::lock_guard lock{mutex};
+        bool value_changed = false;
+        const auto new_status = TransformToButton(callback);
+        auto& current_status = controller.button_values[index];
+
+        // Only read button values that have the same uuid or are pressed once
+        if (current_status.uuid != uuid) {
+            if (!new_status.value) {
+                return;
+            }
+        }
+
+        current_status.toggle = new_status.toggle;
+        current_status.uuid = uuid;
+
+        // Update button status with current
+        if (!current_status.toggle) {
+            current_status.locked = false;
+            if (current_status.value != new_status.value) {
+                current_status.value = new_status.value;
+                value_changed = true;
+            }
+        } else {
+            // Toggle button and lock status
+            if (new_status.value && !current_status.locked) {
+                current_status.locked = true;
+                current_status.value = !current_status.value;
+                value_changed = true;
+            }
+
+            // Unlock button ready for next press
+            if (!new_status.value && current_status.locked) {
+                current_status.locked = false;
+            }
+        }
+
+        if (!value_changed) {
+            return;
+        }
+
+        if (is_configuring) {
+            controller.npad_button_state.raw = NpadButton::None;
+            controller.debug_pad_button_state.raw = 0;
+            TriggerOnChange(ControllerTriggerType::Button, false);
+            return;
+        }
+
+        switch (index) {
+        case Settings::NativeButton::A:
+            controller.npad_button_state.a.Assign(current_status.value);
+            controller.debug_pad_button_state.a.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::B:
+            controller.npad_button_state.b.Assign(current_status.value);
+            controller.debug_pad_button_state.b.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::X:
+            controller.npad_button_state.x.Assign(current_status.value);
+            controller.debug_pad_button_state.x.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::Y:
+            controller.npad_button_state.y.Assign(current_status.value);
+            controller.debug_pad_button_state.y.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::LStick:
+            controller.npad_button_state.stick_l.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::RStick:
+            controller.npad_button_state.stick_r.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::L:
+            controller.npad_button_state.l.Assign(current_status.value);
+            controller.debug_pad_button_state.l.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::R:
+            controller.npad_button_state.r.Assign(current_status.value);
+            controller.debug_pad_button_state.r.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::ZL:
+            controller.npad_button_state.zl.Assign(current_status.value);
+            controller.debug_pad_button_state.zl.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::ZR:
+            controller.npad_button_state.zr.Assign(current_status.value);
+            controller.debug_pad_button_state.zr.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::Plus:
+            controller.npad_button_state.plus.Assign(current_status.value);
+            controller.debug_pad_button_state.plus.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::Minus:
+            controller.npad_button_state.minus.Assign(current_status.value);
+            controller.debug_pad_button_state.minus.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::DLeft:
+            controller.npad_button_state.left.Assign(current_status.value);
+            controller.debug_pad_button_state.d_left.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::DUp:
+            controller.npad_button_state.up.Assign(current_status.value);
+            controller.debug_pad_button_state.d_up.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::DRight:
+            controller.npad_button_state.right.Assign(current_status.value);
+            controller.debug_pad_button_state.d_right.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::DDown:
+            controller.npad_button_state.down.Assign(current_status.value);
+            controller.debug_pad_button_state.d_down.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::SL:
+            controller.npad_button_state.left_sl.Assign(current_status.value);
+            controller.npad_button_state.right_sl.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::SR:
+            controller.npad_button_state.left_sr.Assign(current_status.value);
+            controller.npad_button_state.right_sr.Assign(current_status.value);
+            break;
+        case Settings::NativeButton::Home:
+        case Settings::NativeButton::Screenshot:
+            break;
+        }
+    }
+    if (!is_connected) {
+        if (npad_id_type == NpadIdType::Player1 && npad_type != NpadStyleIndex::Handheld) {
+            Connect();
+        }
+        if (npad_id_type == NpadIdType::Handheld && npad_type == NpadStyleIndex::Handheld) {
+            Connect();
+        }
+    }
+    TriggerOnChange(ControllerTriggerType::Button, true);
+}
+
+void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback, std::size_t index,
+                                  Common::UUID uuid) {
+    if (index >= controller.stick_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    const auto stick_value = TransformToStick(callback);
+
+    // Only read stick values that have the same uuid or are over the threshold to avoid flapping
+    if (controller.stick_values[index].uuid != uuid) {
+        if (!stick_value.down && !stick_value.up && !stick_value.left && !stick_value.right) {
+            return;
+        }
+    }
+
+    controller.stick_values[index] = stick_value;
+    controller.stick_values[index].uuid = uuid;
+
+    if (is_configuring) {
+        controller.analog_stick_state.left = {};
+        controller.analog_stick_state.right = {};
+        TriggerOnChange(ControllerTriggerType::Stick, false);
+        return;
+    }
+
+    const AnalogStickState stick{
+        .x = static_cast<s32>(controller.stick_values[index].x.value * HID_JOYSTICK_MAX),
+        .y = static_cast<s32>(controller.stick_values[index].y.value * HID_JOYSTICK_MAX),
+    };
+
+    switch (index) {
+    case Settings::NativeAnalog::LStick:
+        controller.analog_stick_state.left = stick;
+        controller.npad_button_state.stick_l_left.Assign(controller.stick_values[index].left);
+        controller.npad_button_state.stick_l_up.Assign(controller.stick_values[index].up);
+        controller.npad_button_state.stick_l_right.Assign(controller.stick_values[index].right);
+        controller.npad_button_state.stick_l_down.Assign(controller.stick_values[index].down);
+        break;
+    case Settings::NativeAnalog::RStick:
+        controller.analog_stick_state.right = stick;
+        controller.npad_button_state.stick_r_left.Assign(controller.stick_values[index].left);
+        controller.npad_button_state.stick_r_up.Assign(controller.stick_values[index].up);
+        controller.npad_button_state.stick_r_right.Assign(controller.stick_values[index].right);
+        controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);
+        break;
+    }
+
+    TriggerOnChange(ControllerTriggerType::Stick, true);
+}
+
+void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback,
+                                    std::size_t index, Common::UUID uuid) {
+    if (index >= controller.trigger_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    const auto trigger_value = TransformToTrigger(callback);
+
+    // Only read trigger values that have the same uuid or are pressed once
+    if (controller.trigger_values[index].uuid != uuid) {
+        if (!trigger_value.pressed.value) {
+            return;
+        }
+    }
+
+    controller.trigger_values[index] = trigger_value;
+    controller.trigger_values[index].uuid = uuid;
+
+    if (is_configuring) {
+        controller.gc_trigger_state.left = 0;
+        controller.gc_trigger_state.right = 0;
+        TriggerOnChange(ControllerTriggerType::Trigger, false);
+        return;
+    }
+
+    const auto& trigger = controller.trigger_values[index];
+
+    switch (index) {
+    case Settings::NativeTrigger::LTrigger:
+        controller.gc_trigger_state.left = static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX);
+        controller.npad_button_state.zl.Assign(trigger.pressed.value);
+        break;
+    case Settings::NativeTrigger::RTrigger:
+        controller.gc_trigger_state.right =
+            static_cast<s32>(trigger.analog.value * HID_TRIGGER_MAX);
+        controller.npad_button_state.zr.Assign(trigger.pressed.value);
+        break;
+    }
+
+    TriggerOnChange(ControllerTriggerType::Trigger, true);
+}
+
+void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback,
+                                   std::size_t index) {
+    if (index >= controller.motion_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    auto& raw_status = controller.motion_values[index].raw_status;
+    auto& emulated = controller.motion_values[index].emulated;
+
+    raw_status = TransformToMotion(callback);
+    emulated.SetAcceleration(Common::Vec3f{
+        raw_status.accel.x.value,
+        raw_status.accel.y.value,
+        raw_status.accel.z.value,
+    });
+    emulated.SetGyroscope(Common::Vec3f{
+        raw_status.gyro.x.value,
+        raw_status.gyro.y.value,
+        raw_status.gyro.z.value,
+    });
+    emulated.UpdateRotation(raw_status.delta_timestamp);
+    emulated.UpdateOrientation(raw_status.delta_timestamp);
+    force_update_motion = raw_status.force_update;
+
+    if (is_configuring) {
+        TriggerOnChange(ControllerTriggerType::Motion, false);
+        return;
+    }
+
+    auto& motion = controller.motion_state[index];
+    motion.accel = emulated.GetAcceleration();
+    motion.gyro = emulated.GetGyroscope();
+    motion.rotation = emulated.GetRotations();
+    motion.orientation = emulated.GetOrientation();
+    motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
+
+    TriggerOnChange(ControllerTriggerType::Motion, true);
+}
+
+void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
+                                    std::size_t index) {
+    if (index >= controller.battery_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    controller.battery_values[index] = TransformToBattery(callback);
+
+    if (is_configuring) {
+        TriggerOnChange(ControllerTriggerType::Battery, false);
+        return;
+    }
+
+    bool is_charging = false;
+    bool is_powered = false;
+    NpadBatteryLevel battery_level = 0;
+    switch (controller.battery_values[index]) {
+    case Common::Input::BatteryLevel::Charging:
+        is_charging = true;
+        is_powered = true;
+        battery_level = 6;
+        break;
+    case Common::Input::BatteryLevel::Medium:
+        battery_level = 6;
+        break;
+    case Common::Input::BatteryLevel::Low:
+        battery_level = 4;
+        break;
+    case Common::Input::BatteryLevel::Critical:
+        battery_level = 2;
+        break;
+    case Common::Input::BatteryLevel::Empty:
+        battery_level = 0;
+        break;
+    case Common::Input::BatteryLevel::None:
+    case Common::Input::BatteryLevel::Full:
+    default:
+        is_powered = true;
+        battery_level = 8;
+        break;
+    }
+
+    switch (index) {
+    case LeftIndex:
+        controller.battery_state.left = {
+            .is_powered = is_powered,
+            .is_charging = is_charging,
+            .battery_level = battery_level,
+        };
+        break;
+    case RightIndex:
+        controller.battery_state.right = {
+            .is_powered = is_powered,
+            .is_charging = is_charging,
+            .battery_level = battery_level,
+        };
+        break;
+    case DualIndex:
+        controller.battery_state.dual = {
+            .is_powered = is_powered,
+            .is_charging = is_charging,
+            .battery_level = battery_level,
+        };
+        break;
+    }
+    TriggerOnChange(ControllerTriggerType::Battery, true);
+}
+
+bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
+    if (device_index >= output_devices.size()) {
+        return false;
+    }
+    if (!output_devices[device_index]) {
+        return false;
+    }
+    const auto player_index = NpadIdTypeToIndex(npad_id_type);
+    const auto& player = Settings::values.players.GetValue()[player_index];
+    const f32 strength = static_cast<f32>(player.vibration_strength) / 100.0f;
+
+    if (!player.vibration_enabled) {
+        return false;
+    }
+
+    // Exponential amplification is too strong at low amplitudes. Switch to a linear
+    // amplification if strength is set below 0.7f
+    const Common::Input::VibrationAmplificationType type =
+        strength > 0.7f ? Common::Input::VibrationAmplificationType::Exponential
+                        : Common::Input::VibrationAmplificationType::Linear;
+
+    const Common::Input::VibrationStatus status = {
+        .low_amplitude = std::min(vibration.low_amplitude * strength, 1.0f),
+        .low_frequency = vibration.low_frequency,
+        .high_amplitude = std::min(vibration.high_amplitude * strength, 1.0f),
+        .high_frequency = vibration.high_frequency,
+        .type = type,
+    };
+    return output_devices[device_index]->SetVibration(status) ==
+           Common::Input::VibrationError::None;
+}
+
+bool EmulatedController::TestVibration(std::size_t device_index) {
+    if (device_index >= output_devices.size()) {
+        return false;
+    }
+    if (!output_devices[device_index]) {
+        return false;
+    }
+
+    // Send a slight vibration to test for rumble support
+    constexpr Common::Input::VibrationStatus status = {
+        .low_amplitude = 0.001f,
+        .low_frequency = 160.0f,
+        .high_amplitude = 0.001f,
+        .high_frequency = 320.0f,
+        .type = Common::Input::VibrationAmplificationType::Linear,
+    };
+    return output_devices[device_index]->SetVibration(status) ==
+           Common::Input::VibrationError::None;
+}
+
+void EmulatedController::SetLedPattern() {
+    for (auto& device : output_devices) {
+        if (!device) {
+            continue;
+        }
+
+        const LedPattern pattern = GetLedPattern();
+        const Common::Input::LedStatus status = {
+            .led_1 = pattern.position1 != 0,
+            .led_2 = pattern.position2 != 0,
+            .led_3 = pattern.position3 != 0,
+            .led_4 = pattern.position4 != 0,
+        };
+        device->SetLED(status);
+    }
+}
+
+void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) {
+    supported_style_tag = supported_styles;
+    if (!is_connected) {
+        return;
+    }
+    if (!IsControllerSupported()) {
+        LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller",
+                  npad_type);
+        Disconnect();
+    }
+}
+
+bool EmulatedController::IsControllerSupported() const {
+    switch (npad_type) {
+    case NpadStyleIndex::ProController:
+        return supported_style_tag.fullkey;
+    case NpadStyleIndex::Handheld:
+        return supported_style_tag.handheld;
+    case NpadStyleIndex::JoyconDual:
+        return supported_style_tag.joycon_dual;
+    case NpadStyleIndex::JoyconLeft:
+        return supported_style_tag.joycon_left;
+    case NpadStyleIndex::JoyconRight:
+        return supported_style_tag.joycon_right;
+    case NpadStyleIndex::GameCube:
+        return supported_style_tag.gamecube;
+    case NpadStyleIndex::Pokeball:
+        return supported_style_tag.palma;
+    case NpadStyleIndex::NES:
+        return supported_style_tag.lark;
+    case NpadStyleIndex::SNES:
+        return supported_style_tag.lucia;
+    case NpadStyleIndex::N64:
+        return supported_style_tag.lagoon;
+    case NpadStyleIndex::SegaGenesis:
+        return supported_style_tag.lager;
+    default:
+        return false;
+    }
+}
+
+void EmulatedController::Connect() {
+    if (!IsControllerSupported()) {
+        LOG_ERROR(Service_HID, "Controller type {} is not supported", npad_type);
+        return;
+    }
+    {
+        std::lock_guard lock{mutex};
+        if (is_configuring) {
+            tmp_is_connected = true;
+            TriggerOnChange(ControllerTriggerType::Connected, false);
+            return;
+        }
+
+        if (is_connected) {
+            return;
+        }
+        is_connected = true;
+    }
+    TriggerOnChange(ControllerTriggerType::Connected, true);
+}
+
+void EmulatedController::Disconnect() {
+    {
+        std::lock_guard lock{mutex};
+        if (is_configuring) {
+            tmp_is_connected = false;
+            TriggerOnChange(ControllerTriggerType::Disconnected, false);
+            return;
+        }
+
+        if (!is_connected) {
+            return;
+        }
+        is_connected = false;
+    }
+    TriggerOnChange(ControllerTriggerType::Disconnected, true);
+}
+
+bool EmulatedController::IsConnected(bool get_temporary_value) const {
+    if (get_temporary_value && is_configuring) {
+        return tmp_is_connected;
+    }
+    return is_connected;
+}
+
+bool EmulatedController::IsVibrationEnabled() const {
+    const auto player_index = NpadIdTypeToIndex(npad_id_type);
+    const auto& player = Settings::values.players.GetValue()[player_index];
+    return player.vibration_enabled;
+}
+
+NpadIdType EmulatedController::GetNpadIdType() const {
+    return npad_id_type;
+}
+
+NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const {
+    if (get_temporary_value && is_configuring) {
+        return tmp_npad_type;
+    }
+    return npad_type;
+}
+
+void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
+    {
+        std::lock_guard lock{mutex};
+
+        if (is_configuring) {
+            if (tmp_npad_type == npad_type_) {
+                return;
+            }
+            tmp_npad_type = npad_type_;
+            TriggerOnChange(ControllerTriggerType::Type, false);
+            return;
+        }
+
+        if (npad_type == npad_type_) {
+            return;
+        }
+        if (is_connected) {
+            LOG_WARNING(Service_HID, "Controller {} type changed while it's connected",
+                        NpadIdTypeToIndex(npad_id_type));
+        }
+        npad_type = npad_type_;
+    }
+    TriggerOnChange(ControllerTriggerType::Type, true);
+}
+
+LedPattern EmulatedController::GetLedPattern() const {
+    switch (npad_id_type) {
+    case NpadIdType::Player1:
+        return LedPattern{1, 0, 0, 0};
+    case NpadIdType::Player2:
+        return LedPattern{1, 1, 0, 0};
+    case NpadIdType::Player3:
+        return LedPattern{1, 1, 1, 0};
+    case NpadIdType::Player4:
+        return LedPattern{1, 1, 1, 1};
+    case NpadIdType::Player5:
+        return LedPattern{1, 0, 0, 1};
+    case NpadIdType::Player6:
+        return LedPattern{1, 0, 1, 0};
+    case NpadIdType::Player7:
+        return LedPattern{1, 0, 1, 1};
+    case NpadIdType::Player8:
+        return LedPattern{0, 1, 1, 0};
+    default:
+        return LedPattern{0, 0, 0, 0};
+    }
+}
+
+ButtonValues EmulatedController::GetButtonsValues() const {
+    return controller.button_values;
+}
+
+SticksValues EmulatedController::GetSticksValues() const {
+    return controller.stick_values;
+}
+
+TriggerValues EmulatedController::GetTriggersValues() const {
+    return controller.trigger_values;
+}
+
+ControllerMotionValues EmulatedController::GetMotionValues() const {
+    return controller.motion_values;
+}
+
+ColorValues EmulatedController::GetColorsValues() const {
+    return controller.color_values;
+}
+
+BatteryValues EmulatedController::GetBatteryValues() const {
+    return controller.battery_values;
+}
+
+NpadButtonState EmulatedController::GetNpadButtons() const {
+    if (is_configuring) {
+        return {};
+    }
+    return controller.npad_button_state;
+}
+
+DebugPadButton EmulatedController::GetDebugPadButtons() const {
+    if (is_configuring) {
+        return {};
+    }
+    return controller.debug_pad_button_state;
+}
+
+AnalogSticks EmulatedController::GetSticks() const {
+    if (is_configuring) {
+        return {};
+    }
+    // Some drivers like stick from buttons need constant refreshing
+    for (auto& device : stick_devices) {
+        if (!device) {
+            continue;
+        }
+        device->SoftUpdate();
+    }
+    return controller.analog_stick_state;
+}
+
+NpadGcTriggerState EmulatedController::GetTriggers() const {
+    if (is_configuring) {
+        return {};
+    }
+    return controller.gc_trigger_state;
+}
+
+MotionState EmulatedController::GetMotions() const {
+    if (force_update_motion) {
+        for (auto& device : motion_devices) {
+            if (!device) {
+                continue;
+            }
+            device->ForceUpdate();
+        }
+    }
+    return controller.motion_state;
+}
+
+ControllerColors EmulatedController::GetColors() const {
+    return controller.colors_state;
+}
+
+BatteryLevelState EmulatedController::GetBattery() const {
+    return controller.battery_state;
+}
+
+void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) {
+    for (const auto& poller_pair : callback_list) {
+        const ControllerUpdateCallback& poller = poller_pair.second;
+        if (!is_npad_service_update && poller.is_npad_service) {
+            continue;
+        }
+        if (poller.on_change) {
+            poller.on_change(type);
+        }
+    }
+}
+
+int EmulatedController::SetCallback(ControllerUpdateCallback update_callback) {
+    std::lock_guard lock{mutex};
+    callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
+    return last_callback_key++;
+}
+
+void EmulatedController::DeleteCallback(int key) {
+    std::lock_guard lock{mutex};
+    const auto& iterator = callback_list.find(key);
+    if (iterator == callback_list.end()) {
+        LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+        return;
+    }
+    callback_list.erase(iterator);
+}
+} // namespace Core::HID

+ 411 - 0
src/core/hid/emulated_controller.h

@@ -0,0 +1,411 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/common_types.h"
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/point.h"
+#include "common/quaternion.h"
+#include "common/settings.h"
+#include "common/vector_math.h"
+#include "core/hid/hid_types.h"
+#include "core/hid/motion_input.h"
+
+namespace Core::HID {
+const std::size_t max_emulated_controllers = 2;
+struct ControllerMotionInfo {
+    Common::Input::MotionStatus raw_status{};
+    MotionInput emulated{};
+};
+
+using ButtonDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeButton::NumButtons>;
+using StickDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeAnalog::NumAnalogs>;
+using ControllerMotionDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
+using TriggerDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
+using BatteryDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
+using OutputDevices =
+    std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>;
+
+using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
+using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
+using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
+using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
+using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
+using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>;
+
+using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
+using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
+using TriggerValues =
+    std::array<Common::Input::TriggerStatus, Settings::NativeTrigger::NumTriggers>;
+using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
+using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
+using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
+using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
+
+struct AnalogSticks {
+    AnalogStickState left{};
+    AnalogStickState right{};
+};
+
+struct ControllerColors {
+    NpadControllerColor fullkey{};
+    NpadControllerColor left{};
+    NpadControllerColor right{};
+};
+
+struct BatteryLevelState {
+    NpadPowerInfo dual{};
+    NpadPowerInfo left{};
+    NpadPowerInfo right{};
+};
+
+struct ControllerMotion {
+    Common::Vec3f accel{};
+    Common::Vec3f gyro{};
+    Common::Vec3f rotation{};
+    std::array<Common::Vec3f, 3> orientation{};
+    bool is_at_rest{};
+};
+
+enum EmulatedDeviceIndex : u8 {
+    LeftIndex,
+    RightIndex,
+    DualIndex,
+    AllDevices,
+};
+
+using MotionState = std::array<ControllerMotion, 2>;
+
+struct ControllerStatus {
+    // Data from input_common
+    ButtonValues button_values{};
+    SticksValues stick_values{};
+    ControllerMotionValues motion_values{};
+    TriggerValues trigger_values{};
+    ColorValues color_values{};
+    BatteryValues battery_values{};
+    VibrationValues vibration_values{};
+
+    // Data for HID serices
+    NpadButtonState npad_button_state{};
+    DebugPadButton debug_pad_button_state{};
+    AnalogSticks analog_stick_state{};
+    MotionState motion_state{};
+    NpadGcTriggerState gc_trigger_state{};
+    ControllerColors colors_state{};
+    BatteryLevelState battery_state{};
+};
+
+enum class ControllerTriggerType {
+    Button,
+    Stick,
+    Trigger,
+    Motion,
+    Color,
+    Battery,
+    Vibration,
+    Connected,
+    Disconnected,
+    Type,
+    All,
+};
+
+struct ControllerUpdateCallback {
+    std::function<void(ControllerTriggerType)> on_change;
+    bool is_npad_service;
+};
+
+class EmulatedController {
+public:
+    /**
+     * Contains all input data (buttons, joysticks, vibration, and motion) within this controller.
+     * @param npad_id_type npad id type for this specific controller
+     */
+    explicit EmulatedController(NpadIdType npad_id_type_);
+    ~EmulatedController();
+
+    YUZU_NON_COPYABLE(EmulatedController);
+    YUZU_NON_MOVEABLE(EmulatedController);
+
+    /// Converts the controller type from settings to npad type
+    static NpadStyleIndex MapSettingsTypeToNPad(Settings::ControllerType type);
+
+    /// Converts npad type to the equivalent of controller type from settings
+    static Settings::ControllerType MapNPadToSettingsType(NpadStyleIndex type);
+
+    /// Gets the NpadIdType for this controller
+    NpadIdType GetNpadIdType() const;
+
+    /// Sets the NpadStyleIndex for this controller
+    void SetNpadStyleIndex(NpadStyleIndex npad_type_);
+
+    /**
+     * Gets the NpadStyleIndex for this controller
+     * @param get_temporary_value If true tmp_npad_type will be returned
+     * @return NpadStyleIndex set on the controller
+     */
+    NpadStyleIndex GetNpadStyleIndex(bool get_temporary_value = false) const;
+
+    /**
+     * Sets the supported controller types. Disconnects the controller if current type is not
+     * supported
+     * @param supported_styles bitflag with supported types
+     */
+    void SetSupportedNpadStyleTag(NpadStyleTag supported_styles);
+
+    /// Sets the connected status to true
+    void Connect();
+
+    /// Sets the connected status to false
+    void Disconnect();
+
+    /**
+     * Is the emulated connected
+     * @param get_temporary_value If true tmp_is_connected will be returned
+     * @return true if the controller has the connected status
+     */
+    bool IsConnected(bool get_temporary_value = false) const;
+
+    /// Returns true if vibration is enabled
+    bool IsVibrationEnabled() const;
+
+    /// Removes all callbacks created from input devices
+    void UnloadInput();
+
+    /**
+     * Sets the emulated controller into configuring mode
+     * This prevents the modification of the HID state of the emulated controller by input commands
+     */
+    void EnableConfiguration();
+
+    /// Returns the emulated controller into normal mode, allowing the modification of the HID state
+    void DisableConfiguration();
+
+    /// Returns true if the emulated controller is in configuring mode
+    bool IsConfiguring() const;
+
+    /// Reload all input devices
+    void ReloadInput();
+
+    /// Overrides current mapped devices with the stored configuration and reloads all input devices
+    void ReloadFromSettings();
+
+    /// Saves the current mapped configuration
+    void SaveCurrentConfig();
+
+    /// Reverts any mapped changes made that weren't saved
+    void RestoreConfig();
+
+    /// Returns a vector of mapped devices from the mapped button and stick parameters
+    std::vector<Common::ParamPackage> GetMappedDevices(EmulatedDeviceIndex device_index) const;
+
+    // Returns the current mapped button device
+    Common::ParamPackage GetButtonParam(std::size_t index) const;
+
+    // Returns the current mapped stick device
+    Common::ParamPackage GetStickParam(std::size_t index) const;
+
+    // Returns the current mapped motion device
+    Common::ParamPackage GetMotionParam(std::size_t index) const;
+
+    /**
+     * Updates the current mapped button device
+     * @param param ParamPackage with controller data to be mapped
+     */
+    void SetButtonParam(std::size_t index, Common::ParamPackage param);
+
+    /**
+     * Updates the current mapped stick device
+     * @param param ParamPackage with controller data to be mapped
+     */
+    void SetStickParam(std::size_t index, Common::ParamPackage param);
+
+    /**
+     * Updates the current mapped motion device
+     * @param param ParamPackage with controller data to be mapped
+     */
+    void SetMotionParam(std::size_t index, Common::ParamPackage param);
+
+    /// Returns the latest button status from the controller with parameters
+    ButtonValues GetButtonsValues() const;
+
+    /// Returns the latest analog stick status from the controller with parameters
+    SticksValues GetSticksValues() const;
+
+    /// Returns the latest trigger status from the controller with parameters
+    TriggerValues GetTriggersValues() const;
+
+    /// Returns the latest motion status from the controller with parameters
+    ControllerMotionValues GetMotionValues() const;
+
+    /// Returns the latest color status from the controller with parameters
+    ColorValues GetColorsValues() const;
+
+    /// Returns the latest battery status from the controller with parameters
+    BatteryValues GetBatteryValues() const;
+
+    /// Returns the latest status of button input for the npad service
+    NpadButtonState GetNpadButtons() const;
+
+    /// Returns the latest status of button input for the debug pad service
+    DebugPadButton GetDebugPadButtons() const;
+
+    /// Returns the latest status of stick input from the mouse
+    AnalogSticks GetSticks() const;
+
+    /// Returns the latest status of trigger input from the mouse
+    NpadGcTriggerState GetTriggers() const;
+
+    /// Returns the latest status of motion input from the mouse
+    MotionState GetMotions() const;
+
+    /// Returns the latest color value from the controller
+    ControllerColors GetColors() const;
+
+    /// Returns the latest battery status from the controller
+    BatteryLevelState GetBattery() const;
+
+    /**
+     * Sends a specific vibration to the output device
+     * @return returns true if vibration had no errors
+     */
+    bool SetVibration(std::size_t device_index, VibrationValue vibration);
+
+    /**
+     * Sends a small vibration to the output device
+     * @return returns true if SetVibration was successfull
+     */
+    bool TestVibration(std::size_t device_index);
+
+    /// Returns the led pattern corresponding to this emulated controller
+    LedPattern GetLedPattern() const;
+
+    /// Asks the output device to change the player led pattern
+    void SetLedPattern();
+
+    /**
+     * Adds a callback to the list of events
+     * @param update_callback A ConsoleUpdateCallback that will be triggered
+     * @return an unique key corresponding to the callback index in the list
+     */
+    int SetCallback(ControllerUpdateCallback update_callback);
+
+    /**
+     * Removes a callback from the list stopping any future events to this object
+     * @param key Key corresponding to the callback index in the list
+     */
+    void DeleteCallback(int key);
+
+private:
+    /// creates input devices from params
+    void LoadDevices();
+
+    /// Set the params for TAS devices
+    void LoadTASParams();
+
+    /**
+     * Checks the current controller type against the supported_style_tag
+     * @return true if the controller is supported
+     */
+    bool IsControllerSupported() const;
+
+    /**
+     * Updates the button status of the controller
+     * @param callback A CallbackStatus containing the button status
+     * @param index Button ID of the to be updated
+     */
+    void SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
+                   Common::UUID uuid);
+
+    /**
+     * Updates the analog stick status of the controller
+     * @param callback A CallbackStatus containing the analog stick status
+     * @param index stick ID of the to be updated
+     */
+    void SetStick(const Common::Input::CallbackStatus& callback, std::size_t index,
+                  Common::UUID uuid);
+
+    /**
+     * Updates the trigger status of the controller
+     * @param callback A CallbackStatus containing the trigger status
+     * @param index trigger ID of the to be updated
+     */
+    void SetTrigger(const Common::Input::CallbackStatus& callback, std::size_t index,
+                    Common::UUID uuid);
+
+    /**
+     * Updates the motion status of the controller
+     * @param callback A CallbackStatus containing gyro and accelerometer data
+     * @param index motion ID of the to be updated
+     */
+    void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);
+
+    /**
+     * Updates the battery status of the controller
+     * @param callback A CallbackStatus containing the battery status
+     * @param index Button ID of the to be updated
+     */
+    void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
+
+    /**
+     * Triggers a callback that something has changed on the controller status
+     * @param type Input type of the event to trigger
+     * @param is_service_update indicates if this event should only be sent to HID services
+     */
+    void TriggerOnChange(ControllerTriggerType type, bool is_service_update);
+
+    NpadIdType npad_id_type;
+    NpadStyleIndex npad_type{NpadStyleIndex::None};
+    NpadStyleTag supported_style_tag{NpadStyleSet::All};
+    bool is_connected{false};
+    bool is_configuring{false};
+    f32 motion_sensitivity{0.01f};
+    bool force_update_motion{false};
+
+    // Temporary values to avoid doing changes while the controller is in configuring mode
+    NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
+    bool tmp_is_connected{false};
+
+    ButtonParams button_params;
+    StickParams stick_params;
+    ControllerMotionParams motion_params;
+    TriggerParams trigger_params;
+    BatteryParams battery_params;
+    OutputParams output_params;
+
+    ButtonDevices button_devices;
+    StickDevices stick_devices;
+    ControllerMotionDevices motion_devices;
+    TriggerDevices trigger_devices;
+    BatteryDevices battery_devices;
+    OutputDevices output_devices;
+
+    // TAS related variables
+    ButtonParams tas_button_params;
+    StickParams tas_stick_params;
+    ButtonDevices tas_button_devices;
+    StickDevices tas_stick_devices;
+
+    mutable std::mutex mutex;
+    std::unordered_map<int, ControllerUpdateCallback> callback_list;
+    int last_callback_key = 0;
+
+    // Stores the current status of all controller input
+    ControllerStatus controller;
+};
+
+} // namespace Core::HID

+ 459 - 0
src/core/hid/emulated_devices.cpp

@@ -0,0 +1,459 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <algorithm>
+#include <fmt/format.h>
+
+#include "core/hid/emulated_devices.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+
+EmulatedDevices::EmulatedDevices() = default;
+
+EmulatedDevices::~EmulatedDevices() = default;
+
+void EmulatedDevices::ReloadFromSettings() {
+    ReloadInput();
+}
+
+void EmulatedDevices::ReloadInput() {
+    // If you load any device here add the equivalent to the UnloadInput() function
+    std::size_t key_index = 0;
+    for (auto& mouse_device : mouse_button_devices) {
+        Common::ParamPackage mouse_params;
+        mouse_params.Set("engine", "mouse");
+        mouse_params.Set("button", static_cast<int>(key_index));
+        mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
+        key_index++;
+    }
+
+    mouse_stick_device = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
+        "engine:mouse,axis_x:0,axis_y:1");
+
+    // First two axis are reserved for mouse position
+    key_index = 2;
+    for (auto& mouse_device : mouse_analog_devices) {
+        Common::ParamPackage mouse_params;
+        mouse_params.Set("engine", "mouse");
+        mouse_params.Set("axis", static_cast<int>(key_index));
+        mouse_device = Common::Input::CreateDevice<Common::Input::InputDevice>(mouse_params);
+        key_index++;
+    }
+
+    key_index = 0;
+    for (auto& keyboard_device : keyboard_devices) {
+        // Keyboard keys are only mapped on port 1, pad 0
+        Common::ParamPackage keyboard_params;
+        keyboard_params.Set("engine", "keyboard");
+        keyboard_params.Set("button", static_cast<int>(key_index));
+        keyboard_params.Set("port", 1);
+        keyboard_params.Set("pad", 0);
+        keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
+        key_index++;
+    }
+
+    key_index = 0;
+    for (auto& keyboard_device : keyboard_modifier_devices) {
+        // Keyboard moddifiers are only mapped on port 1, pad 1
+        Common::ParamPackage keyboard_params;
+        keyboard_params.Set("engine", "keyboard");
+        keyboard_params.Set("button", static_cast<int>(key_index));
+        keyboard_params.Set("port", 1);
+        keyboard_params.Set("pad", 1);
+        keyboard_device = Common::Input::CreateDevice<Common::Input::InputDevice>(keyboard_params);
+        key_index++;
+    }
+
+    for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
+        if (!mouse_button_devices[index]) {
+            continue;
+        }
+        mouse_button_devices[index]->SetCallback({
+            .on_change =
+                [this, index](const Common::Input::CallbackStatus& callback) {
+                    SetMouseButton(callback, index);
+                },
+        });
+    }
+
+    for (std::size_t index = 0; index < mouse_analog_devices.size(); ++index) {
+        if (!mouse_analog_devices[index]) {
+            continue;
+        }
+        mouse_analog_devices[index]->SetCallback({
+            .on_change =
+                [this, index](const Common::Input::CallbackStatus& callback) {
+                    SetMouseAnalog(callback, index);
+                },
+        });
+    }
+
+    if (mouse_stick_device) {
+        mouse_stick_device->SetCallback({
+            .on_change =
+                [this](const Common::Input::CallbackStatus& callback) { SetMouseStick(callback); },
+        });
+    }
+
+    for (std::size_t index = 0; index < keyboard_devices.size(); ++index) {
+        if (!keyboard_devices[index]) {
+            continue;
+        }
+        keyboard_devices[index]->SetCallback({
+            .on_change =
+                [this, index](const Common::Input::CallbackStatus& callback) {
+                    SetKeyboardButton(callback, index);
+                },
+        });
+    }
+
+    for (std::size_t index = 0; index < keyboard_modifier_devices.size(); ++index) {
+        if (!keyboard_modifier_devices[index]) {
+            continue;
+        }
+        keyboard_modifier_devices[index]->SetCallback({
+            .on_change =
+                [this, index](const Common::Input::CallbackStatus& callback) {
+                    SetKeyboardModifier(callback, index);
+                },
+        });
+    }
+}
+
+void EmulatedDevices::UnloadInput() {
+    for (auto& button : mouse_button_devices) {
+        button.reset();
+    }
+    for (auto& analog : mouse_analog_devices) {
+        analog.reset();
+    }
+    mouse_stick_device.reset();
+    for (auto& button : keyboard_devices) {
+        button.reset();
+    }
+    for (auto& button : keyboard_modifier_devices) {
+        button.reset();
+    }
+}
+
+void EmulatedDevices::EnableConfiguration() {
+    is_configuring = true;
+    SaveCurrentConfig();
+}
+
+void EmulatedDevices::DisableConfiguration() {
+    is_configuring = false;
+}
+
+bool EmulatedDevices::IsConfiguring() const {
+    return is_configuring;
+}
+
+void EmulatedDevices::SaveCurrentConfig() {
+    if (!is_configuring) {
+        return;
+    }
+}
+
+void EmulatedDevices::RestoreConfig() {
+    if (!is_configuring) {
+        return;
+    }
+    ReloadFromSettings();
+}
+
+void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
+                                        std::size_t index) {
+    if (index >= device_status.keyboard_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    bool value_changed = false;
+    const auto new_status = TransformToButton(callback);
+    auto& current_status = device_status.keyboard_values[index];
+    current_status.toggle = new_status.toggle;
+
+    // Update button status with current status
+    if (!current_status.toggle) {
+        current_status.locked = false;
+        if (current_status.value != new_status.value) {
+            current_status.value = new_status.value;
+            value_changed = true;
+        }
+    } else {
+        // Toggle button and lock status
+        if (new_status.value && !current_status.locked) {
+            current_status.locked = true;
+            current_status.value = !current_status.value;
+            value_changed = true;
+        }
+
+        // Unlock button, ready for next press
+        if (!new_status.value && current_status.locked) {
+            current_status.locked = false;
+        }
+    }
+
+    if (!value_changed) {
+        return;
+    }
+
+    if (is_configuring) {
+        TriggerOnChange(DeviceTriggerType::Keyboard);
+        return;
+    }
+
+    // Index should be converted from NativeKeyboard to KeyboardKeyIndex
+    UpdateKey(index, current_status.value);
+
+    TriggerOnChange(DeviceTriggerType::Keyboard);
+}
+
+void EmulatedDevices::UpdateKey(std::size_t key_index, bool status) {
+    constexpr std::size_t KEYS_PER_BYTE = 8;
+    auto& entry = device_status.keyboard_state.key[key_index / KEYS_PER_BYTE];
+    const u8 mask = static_cast<u8>(1 << (key_index % KEYS_PER_BYTE));
+    if (status) {
+        entry = entry | mask;
+    } else {
+        entry = static_cast<u8>(entry & ~mask);
+    }
+}
+
+void EmulatedDevices::SetKeyboardModifier(const Common::Input::CallbackStatus& callback,
+                                          std::size_t index) {
+    if (index >= device_status.keyboard_moddifier_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    bool value_changed = false;
+    const auto new_status = TransformToButton(callback);
+    auto& current_status = device_status.keyboard_moddifier_values[index];
+    current_status.toggle = new_status.toggle;
+
+    // Update button status with current
+    if (!current_status.toggle) {
+        current_status.locked = false;
+        if (current_status.value != new_status.value) {
+            current_status.value = new_status.value;
+            value_changed = true;
+        }
+    } else {
+        // Toggle button and lock status
+        if (new_status.value && !current_status.locked) {
+            current_status.locked = true;
+            current_status.value = !current_status.value;
+            value_changed = true;
+        }
+
+        // Unlock button ready for next press
+        if (!new_status.value && current_status.locked) {
+            current_status.locked = false;
+        }
+    }
+
+    if (!value_changed) {
+        return;
+    }
+
+    if (is_configuring) {
+        TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
+        return;
+    }
+
+    switch (index) {
+    case Settings::NativeKeyboard::LeftControl:
+    case Settings::NativeKeyboard::RightControl:
+        device_status.keyboard_moddifier_state.control.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::LeftShift:
+    case Settings::NativeKeyboard::RightShift:
+        device_status.keyboard_moddifier_state.shift.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::LeftAlt:
+        device_status.keyboard_moddifier_state.left_alt.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::RightAlt:
+        device_status.keyboard_moddifier_state.right_alt.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::CapsLock:
+        device_status.keyboard_moddifier_state.caps_lock.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::ScrollLock:
+        device_status.keyboard_moddifier_state.scroll_lock.Assign(current_status.value);
+        break;
+    case Settings::NativeKeyboard::NumLock:
+        device_status.keyboard_moddifier_state.num_lock.Assign(current_status.value);
+        break;
+    }
+
+    TriggerOnChange(DeviceTriggerType::KeyboardModdifier);
+}
+
+void EmulatedDevices::SetMouseButton(const Common::Input::CallbackStatus& callback,
+                                     std::size_t index) {
+    if (index >= device_status.mouse_button_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    bool value_changed = false;
+    const auto new_status = TransformToButton(callback);
+    auto& current_status = device_status.mouse_button_values[index];
+    current_status.toggle = new_status.toggle;
+
+    // Update button status with current
+    if (!current_status.toggle) {
+        current_status.locked = false;
+        if (current_status.value != new_status.value) {
+            current_status.value = new_status.value;
+            value_changed = true;
+        }
+    } else {
+        // Toggle button and lock status
+        if (new_status.value && !current_status.locked) {
+            current_status.locked = true;
+            current_status.value = !current_status.value;
+            value_changed = true;
+        }
+
+        // Unlock button ready for next press
+        if (!new_status.value && current_status.locked) {
+            current_status.locked = false;
+        }
+    }
+
+    if (!value_changed) {
+        return;
+    }
+
+    if (is_configuring) {
+        TriggerOnChange(DeviceTriggerType::Mouse);
+        return;
+    }
+
+    switch (index) {
+    case Settings::NativeMouseButton::Left:
+        device_status.mouse_button_state.left.Assign(current_status.value);
+        break;
+    case Settings::NativeMouseButton::Right:
+        device_status.mouse_button_state.right.Assign(current_status.value);
+        break;
+    case Settings::NativeMouseButton::Middle:
+        device_status.mouse_button_state.middle.Assign(current_status.value);
+        break;
+    case Settings::NativeMouseButton::Forward:
+        device_status.mouse_button_state.forward.Assign(current_status.value);
+        break;
+    case Settings::NativeMouseButton::Back:
+        device_status.mouse_button_state.back.Assign(current_status.value);
+        break;
+    }
+
+    TriggerOnChange(DeviceTriggerType::Mouse);
+}
+
+void EmulatedDevices::SetMouseAnalog(const Common::Input::CallbackStatus& callback,
+                                     std::size_t index) {
+    if (index >= device_status.mouse_analog_values.size()) {
+        return;
+    }
+    std::lock_guard lock{mutex};
+    const auto analog_value = TransformToAnalog(callback);
+
+    device_status.mouse_analog_values[index] = analog_value;
+
+    if (is_configuring) {
+        device_status.mouse_position_state = {};
+        TriggerOnChange(DeviceTriggerType::Mouse);
+        return;
+    }
+
+    switch (index) {
+    case Settings::NativeMouseWheel::X:
+        device_status.mouse_wheel_state.x = static_cast<s32>(analog_value.value);
+        break;
+    case Settings::NativeMouseWheel::Y:
+        device_status.mouse_wheel_state.y = static_cast<s32>(analog_value.value);
+        break;
+    }
+
+    TriggerOnChange(DeviceTriggerType::Mouse);
+}
+
+void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callback) {
+    std::lock_guard lock{mutex};
+    const auto touch_value = TransformToTouch(callback);
+
+    device_status.mouse_stick_value = touch_value;
+
+    if (is_configuring) {
+        device_status.mouse_position_state = {};
+        TriggerOnChange(DeviceTriggerType::Mouse);
+        return;
+    }
+
+    device_status.mouse_position_state.x = touch_value.x.value;
+    device_status.mouse_position_state.y = touch_value.y.value;
+
+    TriggerOnChange(DeviceTriggerType::Mouse);
+}
+
+KeyboardValues EmulatedDevices::GetKeyboardValues() const {
+    return device_status.keyboard_values;
+}
+
+KeyboardModifierValues EmulatedDevices::GetKeyboardModdifierValues() const {
+    return device_status.keyboard_moddifier_values;
+}
+
+MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
+    return device_status.mouse_button_values;
+}
+
+KeyboardKey EmulatedDevices::GetKeyboard() const {
+    return device_status.keyboard_state;
+}
+
+KeyboardModifier EmulatedDevices::GetKeyboardModifier() const {
+    return device_status.keyboard_moddifier_state;
+}
+
+MouseButton EmulatedDevices::GetMouseButtons() const {
+    return device_status.mouse_button_state;
+}
+
+MousePosition EmulatedDevices::GetMousePosition() const {
+    return device_status.mouse_position_state;
+}
+
+AnalogStickState EmulatedDevices::GetMouseWheel() const {
+    return device_status.mouse_wheel_state;
+}
+
+void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
+    for (const auto& poller_pair : callback_list) {
+        const InterfaceUpdateCallback& poller = poller_pair.second;
+        if (poller.on_change) {
+            poller.on_change(type);
+        }
+    }
+}
+
+int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) {
+    std::lock_guard lock{mutex};
+    callback_list.insert_or_assign(last_callback_key, std::move(update_callback));
+    return last_callback_key++;
+}
+
+void EmulatedDevices::DeleteCallback(int key) {
+    std::lock_guard lock{mutex};
+    const auto& iterator = callback_list.find(key);
+    if (iterator == callback_list.end()) {
+        LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+        return;
+    }
+    callback_list.erase(iterator);
+}
+} // namespace Core::HID

+ 210 - 0
src/core/hid/emulated_devices.h

@@ -0,0 +1,210 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/common_types.h"
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/settings.h"
+#include "core/hid/hid_types.h"
+
+namespace Core::HID {
+using KeyboardDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
+                                   Settings::NativeKeyboard::NumKeyboardKeys>;
+using KeyboardModifierDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
+                                           Settings::NativeKeyboard::NumKeyboardMods>;
+using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
+                                      Settings::NativeMouseButton::NumMouseButtons>;
+using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
+                                      Settings::NativeMouseWheel::NumMouseWheels>;
+using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
+
+using MouseButtonParams =
+    std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
+
+using KeyboardValues =
+    std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
+using KeyboardModifierValues =
+    std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardMods>;
+using MouseButtonValues =
+    std::array<Common::Input::ButtonStatus, Settings::NativeMouseButton::NumMouseButtons>;
+using MouseAnalogValues =
+    std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
+using MouseStickValue = Common::Input::TouchStatus;
+
+struct MousePosition {
+    f32 x;
+    f32 y;
+};
+
+struct DeviceStatus {
+    // Data from input_common
+    KeyboardValues keyboard_values{};
+    KeyboardModifierValues keyboard_moddifier_values{};
+    MouseButtonValues mouse_button_values{};
+    MouseAnalogValues mouse_analog_values{};
+    MouseStickValue mouse_stick_value{};
+
+    // Data for HID serices
+    KeyboardKey keyboard_state{};
+    KeyboardModifier keyboard_moddifier_state{};
+    MouseButton mouse_button_state{};
+    MousePosition mouse_position_state{};
+    AnalogStickState mouse_wheel_state{};
+};
+
+enum class DeviceTriggerType {
+    Keyboard,
+    KeyboardModdifier,
+    Mouse,
+};
+
+struct InterfaceUpdateCallback {
+    std::function<void(DeviceTriggerType)> on_change;
+};
+
+class EmulatedDevices {
+public:
+    /**
+     * Contains all input data related to external devices that aren't necesarily a controller
+     * This includes devices such as the keyboard or mouse
+     */
+    explicit EmulatedDevices();
+    ~EmulatedDevices();
+
+    YUZU_NON_COPYABLE(EmulatedDevices);
+    YUZU_NON_MOVEABLE(EmulatedDevices);
+
+    /// Removes all callbacks created from input devices
+    void UnloadInput();
+
+    /**
+     * Sets the emulated devices into configuring mode
+     * This prevents the modification of the HID state of the emulated devices by input commands
+     */
+    void EnableConfiguration();
+
+    /// Returns the emulated devices into normal mode, allowing the modification of the HID state
+    void DisableConfiguration();
+
+    /// Returns true if the emulated device is in configuring mode
+    bool IsConfiguring() const;
+
+    /// Reload all input devices
+    void ReloadInput();
+
+    /// Overrides current mapped devices with the stored configuration and reloads all input devices
+    void ReloadFromSettings();
+
+    /// Saves the current mapped configuration
+    void SaveCurrentConfig();
+
+    /// Reverts any mapped changes made that weren't saved
+    void RestoreConfig();
+
+    /// Returns the latest status of button input from the keyboard with parameters
+    KeyboardValues GetKeyboardValues() const;
+
+    /// Returns the latest status of button input from the keyboard modifiers with parameters
+    KeyboardModifierValues GetKeyboardModdifierValues() const;
+
+    /// Returns the latest status of button input from the mouse with parameters
+    MouseButtonValues GetMouseButtonsValues() const;
+
+    /// Returns the latest status of button input from the keyboard
+    KeyboardKey GetKeyboard() const;
+
+    /// Returns the latest status of button input from the keyboard modifiers
+    KeyboardModifier GetKeyboardModifier() const;
+
+    /// Returns the latest status of button input from the mouse
+    MouseButton GetMouseButtons() const;
+
+    /// Returns the latest mouse coordinates
+    MousePosition GetMousePosition() const;
+
+    /// Returns the latest mouse wheel change
+    AnalogStickState GetMouseWheel() const;
+
+    /**
+     * Adds a callback to the list of events
+     * @param update_callback InterfaceUpdateCallback that will be triggered
+     * @return an unique key corresponding to the callback index in the list
+     */
+    int SetCallback(InterfaceUpdateCallback update_callback);
+
+    /**
+     * Removes a callback from the list stopping any future events to this object
+     * @param key Key corresponding to the callback index in the list
+     */
+    void DeleteCallback(int key);
+
+private:
+    /// Helps assigning a value to keyboard_state
+    void UpdateKey(std::size_t key_index, bool status);
+
+    /**
+     * Updates the touch status of the keyboard device
+     * @param callback A CallbackStatus containing the key status
+     * @param index key ID to be updated
+     */
+    void SetKeyboardButton(const Common::Input::CallbackStatus& callback, std::size_t index);
+
+    /**
+     * Updates the keyboard status of the keyboard device
+     * @param callback A CallbackStatus containing the modifier key status
+     * @param index modifier key ID to be updated
+     */
+    void SetKeyboardModifier(const Common::Input::CallbackStatus& callback, std::size_t index);
+
+    /**
+     * Updates the mouse button status of the mouse device
+     * @param callback A CallbackStatus containing the button status
+     * @param index Button ID to be updated
+     */
+    void SetMouseButton(const Common::Input::CallbackStatus& callback, std::size_t index);
+
+    /**
+     * Updates the mouse wheel status of the mouse device
+     * @param callback A CallbackStatus containing the wheel status
+     * @param index wheel ID to be updated
+     */
+    void SetMouseAnalog(const Common::Input::CallbackStatus& callback, std::size_t index);
+
+    /**
+     * Updates the mouse position status of the mouse device
+     * @param callback A CallbackStatus containing the position status
+     */
+    void SetMouseStick(const Common::Input::CallbackStatus& callback);
+
+    /**
+     * Triggers a callback that something has changed on the device status
+     * @param type Input type of the event to trigger
+     */
+    void TriggerOnChange(DeviceTriggerType type);
+
+    bool is_configuring{false};
+
+    KeyboardDevices keyboard_devices;
+    KeyboardModifierDevices keyboard_modifier_devices;
+    MouseButtonDevices mouse_button_devices;
+    MouseAnalogDevices mouse_analog_devices;
+    MouseStickDevice mouse_stick_device;
+
+    mutable std::mutex mutex;
+    std::unordered_map<int, InterfaceUpdateCallback> callback_list;
+    int last_callback_key = 0;
+
+    // Stores the current status of all external device input
+    DeviceStatus device_status;
+};
+
+} // namespace Core::HID

+ 214 - 0
src/core/hid/hid_core.cpp

@@ -0,0 +1,214 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "core/hid/emulated_console.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/emulated_devices.h"
+#include "core/hid/hid_core.h"
+
+namespace Core::HID {
+
+HIDCore::HIDCore()
+    : player_1{std::make_unique<EmulatedController>(NpadIdType::Player1)},
+      player_2{std::make_unique<EmulatedController>(NpadIdType::Player2)},
+      player_3{std::make_unique<EmulatedController>(NpadIdType::Player3)},
+      player_4{std::make_unique<EmulatedController>(NpadIdType::Player4)},
+      player_5{std::make_unique<EmulatedController>(NpadIdType::Player5)},
+      player_6{std::make_unique<EmulatedController>(NpadIdType::Player6)},
+      player_7{std::make_unique<EmulatedController>(NpadIdType::Player7)},
+      player_8{std::make_unique<EmulatedController>(NpadIdType::Player8)},
+      other{std::make_unique<EmulatedController>(NpadIdType::Other)},
+      handheld{std::make_unique<EmulatedController>(NpadIdType::Handheld)},
+      console{std::make_unique<EmulatedConsole>()}, devices{std::make_unique<EmulatedDevices>()} {}
+
+HIDCore::~HIDCore() = default;
+
+EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) {
+    switch (npad_id_type) {
+    case NpadIdType::Player1:
+        return player_1.get();
+    case NpadIdType::Player2:
+        return player_2.get();
+    case NpadIdType::Player3:
+        return player_3.get();
+    case NpadIdType::Player4:
+        return player_4.get();
+    case NpadIdType::Player5:
+        return player_5.get();
+    case NpadIdType::Player6:
+        return player_6.get();
+    case NpadIdType::Player7:
+        return player_7.get();
+    case NpadIdType::Player8:
+        return player_8.get();
+    case NpadIdType::Other:
+        return other.get();
+    case NpadIdType::Handheld:
+        return handheld.get();
+    case NpadIdType::Invalid:
+    default:
+        UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
+        return nullptr;
+    }
+}
+
+const EmulatedController* HIDCore::GetEmulatedController(NpadIdType npad_id_type) const {
+    switch (npad_id_type) {
+    case NpadIdType::Player1:
+        return player_1.get();
+    case NpadIdType::Player2:
+        return player_2.get();
+    case NpadIdType::Player3:
+        return player_3.get();
+    case NpadIdType::Player4:
+        return player_4.get();
+    case NpadIdType::Player5:
+        return player_5.get();
+    case NpadIdType::Player6:
+        return player_6.get();
+    case NpadIdType::Player7:
+        return player_7.get();
+    case NpadIdType::Player8:
+        return player_8.get();
+    case NpadIdType::Other:
+        return other.get();
+    case NpadIdType::Handheld:
+        return handheld.get();
+    case NpadIdType::Invalid:
+    default:
+        UNREACHABLE_MSG("Invalid NpadIdType={}", npad_id_type);
+        return nullptr;
+    }
+}
+EmulatedConsole* HIDCore::GetEmulatedConsole() {
+    return console.get();
+}
+
+const EmulatedConsole* HIDCore::GetEmulatedConsole() const {
+    return console.get();
+}
+
+EmulatedDevices* HIDCore::GetEmulatedDevices() {
+    return devices.get();
+}
+
+const EmulatedDevices* HIDCore::GetEmulatedDevices() const {
+    return devices.get();
+}
+
+EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) {
+    return GetEmulatedController(IndexToNpadIdType(index));
+}
+
+const EmulatedController* HIDCore::GetEmulatedControllerByIndex(std::size_t index) const {
+    return GetEmulatedController(IndexToNpadIdType(index));
+}
+
+void HIDCore::SetSupportedStyleTag(NpadStyleTag style_tag) {
+    supported_style_tag.raw = style_tag.raw;
+    player_1->SetSupportedNpadStyleTag(supported_style_tag);
+    player_2->SetSupportedNpadStyleTag(supported_style_tag);
+    player_3->SetSupportedNpadStyleTag(supported_style_tag);
+    player_4->SetSupportedNpadStyleTag(supported_style_tag);
+    player_5->SetSupportedNpadStyleTag(supported_style_tag);
+    player_6->SetSupportedNpadStyleTag(supported_style_tag);
+    player_7->SetSupportedNpadStyleTag(supported_style_tag);
+    player_8->SetSupportedNpadStyleTag(supported_style_tag);
+    other->SetSupportedNpadStyleTag(supported_style_tag);
+    handheld->SetSupportedNpadStyleTag(supported_style_tag);
+}
+
+NpadStyleTag HIDCore::GetSupportedStyleTag() const {
+    return supported_style_tag;
+}
+
+s8 HIDCore::GetPlayerCount() const {
+    s8 active_players = 0;
+    for (std::size_t player_index = 0; player_index < available_controllers - 2; ++player_index) {
+        const auto* const controller = GetEmulatedControllerByIndex(player_index);
+        if (controller->IsConnected()) {
+            active_players++;
+        }
+    }
+    return active_players;
+}
+
+NpadIdType HIDCore::GetFirstNpadId() const {
+    for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
+        const auto* const controller = GetEmulatedControllerByIndex(player_index);
+        if (controller->IsConnected()) {
+            return controller->GetNpadIdType();
+        }
+    }
+    return NpadIdType::Player1;
+}
+
+NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
+    for (std::size_t player_index = 0; player_index < available_controllers; ++player_index) {
+        const auto* const controller = GetEmulatedControllerByIndex(player_index);
+        if (!controller->IsConnected()) {
+            return controller->GetNpadIdType();
+        }
+    }
+    return NpadIdType::Player1;
+}
+
+void HIDCore::EnableAllControllerConfiguration() {
+    player_1->EnableConfiguration();
+    player_2->EnableConfiguration();
+    player_3->EnableConfiguration();
+    player_4->EnableConfiguration();
+    player_5->EnableConfiguration();
+    player_6->EnableConfiguration();
+    player_7->EnableConfiguration();
+    player_8->EnableConfiguration();
+    other->EnableConfiguration();
+    handheld->EnableConfiguration();
+}
+
+void HIDCore::DisableAllControllerConfiguration() {
+    player_1->DisableConfiguration();
+    player_2->DisableConfiguration();
+    player_3->DisableConfiguration();
+    player_4->DisableConfiguration();
+    player_5->DisableConfiguration();
+    player_6->DisableConfiguration();
+    player_7->DisableConfiguration();
+    player_8->DisableConfiguration();
+    other->DisableConfiguration();
+    handheld->DisableConfiguration();
+}
+
+void HIDCore::ReloadInputDevices() {
+    player_1->ReloadFromSettings();
+    player_2->ReloadFromSettings();
+    player_3->ReloadFromSettings();
+    player_4->ReloadFromSettings();
+    player_5->ReloadFromSettings();
+    player_6->ReloadFromSettings();
+    player_7->ReloadFromSettings();
+    player_8->ReloadFromSettings();
+    other->ReloadFromSettings();
+    handheld->ReloadFromSettings();
+    console->ReloadFromSettings();
+    devices->ReloadFromSettings();
+}
+
+void HIDCore::UnloadInputDevices() {
+    player_1->UnloadInput();
+    player_2->UnloadInput();
+    player_3->UnloadInput();
+    player_4->UnloadInput();
+    player_5->UnloadInput();
+    player_6->UnloadInput();
+    player_7->UnloadInput();
+    player_8->UnloadInput();
+    other->UnloadInput();
+    handheld->UnloadInput();
+    console->UnloadInput();
+    devices->UnloadInput();
+}
+
+} // namespace Core::HID

+ 82 - 0
src/core/hid/hid_core.h

@@ -0,0 +1,82 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include "core/hid/hid_types.h"
+
+namespace Core::HID {
+class EmulatedConsole;
+class EmulatedController;
+class EmulatedDevices;
+} // namespace Core::HID
+
+namespace Core::HID {
+
+class HIDCore {
+public:
+    explicit HIDCore();
+    ~HIDCore();
+
+    YUZU_NON_COPYABLE(HIDCore);
+    YUZU_NON_MOVEABLE(HIDCore);
+
+    EmulatedController* GetEmulatedController(NpadIdType npad_id_type);
+    const EmulatedController* GetEmulatedController(NpadIdType npad_id_type) const;
+
+    EmulatedController* GetEmulatedControllerByIndex(std::size_t index);
+    const EmulatedController* GetEmulatedControllerByIndex(std::size_t index) const;
+
+    EmulatedConsole* GetEmulatedConsole();
+    const EmulatedConsole* GetEmulatedConsole() const;
+
+    EmulatedDevices* GetEmulatedDevices();
+    const EmulatedDevices* GetEmulatedDevices() const;
+
+    void SetSupportedStyleTag(NpadStyleTag style_tag);
+    NpadStyleTag GetSupportedStyleTag() const;
+
+    /// Counts the connected players from P1-P8
+    s8 GetPlayerCount() const;
+
+    /// Returns the first connected npad id
+    NpadIdType GetFirstNpadId() const;
+
+    /// Returns the first disconnected npad id
+    NpadIdType GetFirstDisconnectedNpadId() const;
+
+    /// Sets all emulated controllers into configuring mode.
+    void EnableAllControllerConfiguration();
+
+    /// Sets all emulated controllers into normal mode.
+    void DisableAllControllerConfiguration();
+
+    /// Reloads all input devices from settings
+    void ReloadInputDevices();
+
+    /// Removes all callbacks from input common
+    void UnloadInputDevices();
+
+    /// Number of emulated controllers
+    static constexpr std::size_t available_controllers{10};
+
+private:
+    std::unique_ptr<EmulatedController> player_1;
+    std::unique_ptr<EmulatedController> player_2;
+    std::unique_ptr<EmulatedController> player_3;
+    std::unique_ptr<EmulatedController> player_4;
+    std::unique_ptr<EmulatedController> player_5;
+    std::unique_ptr<EmulatedController> player_6;
+    std::unique_ptr<EmulatedController> player_7;
+    std::unique_ptr<EmulatedController> player_8;
+    std::unique_ptr<EmulatedController> other;
+    std::unique_ptr<EmulatedController> handheld;
+    std::unique_ptr<EmulatedConsole> console;
+    std::unique_ptr<EmulatedDevices> devices;
+    NpadStyleTag supported_style_tag{NpadStyleSet::All};
+};
+
+} // namespace Core::HID

+ 635 - 0
src/core/hid/hid_types.h

@@ -0,0 +1,635 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/point.h"
+#include "common/uuid.h"
+
+namespace Core::HID {
+
+enum class DeviceIndex : u8 {
+    Left = 0,
+    Right = 1,
+    None = 2,
+    MaxDeviceIndex = 3,
+};
+
+// This is nn::hid::NpadButton
+enum class NpadButton : u64 {
+    None = 0,
+    A = 1U << 0,
+    B = 1U << 1,
+    X = 1U << 2,
+    Y = 1U << 3,
+    StickL = 1U << 4,
+    StickR = 1U << 5,
+    L = 1U << 6,
+    R = 1U << 7,
+    ZL = 1U << 8,
+    ZR = 1U << 9,
+    Plus = 1U << 10,
+    Minus = 1U << 11,
+
+    Left = 1U << 12,
+    Up = 1U << 13,
+    Right = 1U << 14,
+    Down = 1U << 15,
+
+    StickLLeft = 1U << 16,
+    StickLUp = 1U << 17,
+    StickLRight = 1U << 18,
+    StickLDown = 1U << 19,
+
+    StickRLeft = 1U << 20,
+    StickRUp = 1U << 21,
+    StickRRight = 1U << 22,
+    StickRDown = 1U << 23,
+
+    LeftSL = 1U << 24,
+    LeftSR = 1U << 25,
+
+    RightSL = 1U << 26,
+    RightSR = 1U << 27,
+
+    Palma = 1U << 28,
+    Verification = 1U << 29,
+    HandheldLeftB = 1U << 30,
+    LagonCLeft = 1U << 31,
+    LagonCUp = 1ULL << 32,
+    LagonCRight = 1ULL << 33,
+    LagonCDown = 1ULL << 34,
+
+    All = 0xFFFFFFFFFFFFFFFFULL,
+};
+DECLARE_ENUM_FLAG_OPERATORS(NpadButton);
+
+enum class KeyboardKeyIndex : u32 {
+    A = 4,
+    B = 5,
+    C = 6,
+    D = 7,
+    E = 8,
+    F = 9,
+    G = 10,
+    H = 11,
+    I = 12,
+    J = 13,
+    K = 14,
+    L = 15,
+    M = 16,
+    N = 17,
+    O = 18,
+    P = 19,
+    Q = 20,
+    R = 21,
+    S = 22,
+    T = 23,
+    U = 24,
+    V = 25,
+    W = 26,
+    X = 27,
+    Y = 28,
+    Z = 29,
+    D1 = 30,
+    D2 = 31,
+    D3 = 32,
+    D4 = 33,
+    D5 = 34,
+    D6 = 35,
+    D7 = 36,
+    D8 = 37,
+    D9 = 38,
+    D0 = 39,
+    Return = 40,
+    Escape = 41,
+    Backspace = 42,
+    Tab = 43,
+    Space = 44,
+    Minus = 45,
+    Plus = 46,
+    OpenBracket = 47,
+    CloseBracket = 48,
+    Pipe = 49,
+    Tilde = 50,
+    Semicolon = 51,
+    Quote = 52,
+    Backquote = 53,
+    Comma = 54,
+    Period = 55,
+    Slash = 56,
+    CapsLock = 57,
+    F1 = 58,
+    F2 = 59,
+    F3 = 60,
+    F4 = 61,
+    F5 = 62,
+    F6 = 63,
+    F7 = 64,
+    F8 = 65,
+    F9 = 66,
+    F10 = 67,
+    F11 = 68,
+    F12 = 69,
+    PrintScreen = 70,
+    ScrollLock = 71,
+    Pause = 72,
+    Insert = 73,
+    Home = 74,
+    PageUp = 75,
+    Delete = 76,
+    End = 77,
+    PageDown = 78,
+    RightArrow = 79,
+    LeftArrow = 80,
+    DownArrow = 81,
+    UpArrow = 82,
+    NumLock = 83,
+    NumPadDivide = 84,
+    NumPadMultiply = 85,
+    NumPadSubtract = 86,
+    NumPadAdd = 87,
+    NumPadEnter = 88,
+    NumPad1 = 89,
+    NumPad2 = 90,
+    NumPad3 = 91,
+    NumPad4 = 92,
+    NumPad5 = 93,
+    NumPad6 = 94,
+    NumPad7 = 95,
+    NumPad8 = 96,
+    NumPad9 = 97,
+    NumPad0 = 98,
+    NumPadDot = 99,
+    Backslash = 100,
+    Application = 101,
+    Power = 102,
+    NumPadEquals = 103,
+    F13 = 104,
+    F14 = 105,
+    F15 = 106,
+    F16 = 107,
+    F17 = 108,
+    F18 = 109,
+    F19 = 110,
+    F20 = 111,
+    F21 = 112,
+    F22 = 113,
+    F23 = 114,
+    F24 = 115,
+    NumPadComma = 133,
+    Ro = 135,
+    KatakanaHiragana = 136,
+    Yen = 137,
+    Henkan = 138,
+    Muhenkan = 139,
+    NumPadCommaPc98 = 140,
+    HangulEnglish = 144,
+    Hanja = 145,
+    Katakana = 146,
+    Hiragana = 147,
+    ZenkakuHankaku = 148,
+    LeftControl = 224,
+    LeftShift = 225,
+    LeftAlt = 226,
+    LeftGui = 227,
+    RightControl = 228,
+    RightShift = 229,
+    RightAlt = 230,
+    RightGui = 231,
+};
+
+// This is nn::hid::NpadIdType
+enum class NpadIdType : u32 {
+    Player1 = 0x0,
+    Player2 = 0x1,
+    Player3 = 0x2,
+    Player4 = 0x3,
+    Player5 = 0x4,
+    Player6 = 0x5,
+    Player7 = 0x6,
+    Player8 = 0x7,
+    Other = 0x10,
+    Handheld = 0x20,
+
+    Invalid = 0xFFFFFFFF,
+};
+
+// This is nn::hid::NpadStyleIndex
+enum class NpadStyleIndex : u8 {
+    None = 0,
+    ProController = 3,
+    Handheld = 4,
+    HandheldNES = 4,
+    JoyconDual = 5,
+    JoyconLeft = 6,
+    JoyconRight = 7,
+    GameCube = 8,
+    Pokeball = 9,
+    NES = 10,
+    SNES = 12,
+    N64 = 13,
+    SegaGenesis = 14,
+    SystemExt = 32,
+    System = 33,
+    MaxNpadType = 34,
+};
+
+// This is nn::hid::NpadStyleSet
+enum class NpadStyleSet : u32 {
+    None = 0,
+    Fullkey = 1U << 0,
+    Handheld = 1U << 1,
+    JoyDual = 1U << 2,
+    JoyLeft = 1U << 3,
+    JoyRight = 1U << 4,
+    Gc = 1U << 5,
+    Palma = 1U << 6,
+    Lark = 1U << 7,
+    HandheldLark = 1U << 8,
+    Lucia = 1U << 9,
+    Lagoon = 1U << 10,
+    Lager = 1U << 11,
+    SystemExt = 1U << 29,
+    System = 1U << 30,
+
+    All = 0xFFFFFFFFU,
+};
+static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
+
+// This is nn::hid::VibrationDevicePosition
+enum class VibrationDevicePosition : u32 {
+    None = 0,
+    Left = 1,
+    Right = 2,
+};
+
+// This is nn::hid::VibrationDeviceType
+enum class VibrationDeviceType : u32 {
+    Unknown = 0,
+    LinearResonantActuator = 1,
+    GcErm = 2,
+};
+
+// This is nn::hid::VibrationGcErmCommand
+enum class VibrationGcErmCommand : u64 {
+    Stop = 0,
+    Start = 1,
+    StopHard = 2,
+};
+
+// This is nn::hid::NpadStyleTag
+struct NpadStyleTag {
+    union {
+        NpadStyleSet raw{};
+
+        BitField<0, 1, u32> fullkey;
+        BitField<1, 1, u32> handheld;
+        BitField<2, 1, u32> joycon_dual;
+        BitField<3, 1, u32> joycon_left;
+        BitField<4, 1, u32> joycon_right;
+        BitField<5, 1, u32> gamecube;
+        BitField<6, 1, u32> palma;
+        BitField<7, 1, u32> lark;
+        BitField<8, 1, u32> handheld_lark;
+        BitField<9, 1, u32> lucia;
+        BitField<10, 1, u32> lagoon;
+        BitField<11, 1, u32> lager;
+        BitField<29, 1, u32> system_ext;
+        BitField<30, 1, u32> system;
+    };
+};
+static_assert(sizeof(NpadStyleTag) == 4, "NpadStyleTag is an invalid size");
+
+// This is nn::hid::TouchAttribute
+struct TouchAttribute {
+    union {
+        u32 raw{};
+        BitField<0, 1, u32> start_touch;
+        BitField<1, 1, u32> end_touch;
+    };
+};
+static_assert(sizeof(TouchAttribute) == 0x4, "TouchAttribute is an invalid size");
+
+// This is nn::hid::TouchState
+struct TouchState {
+    u64 delta_time;
+    TouchAttribute attribute;
+    u32 finger;
+    Common::Point<u32> position;
+    u32 diameter_x;
+    u32 diameter_y;
+    u32 rotation_angle;
+};
+static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
+
+// This is nn::hid::NpadControllerColor
+struct NpadControllerColor {
+    u32 body;
+    u32 button;
+};
+static_assert(sizeof(NpadControllerColor) == 8, "NpadControllerColor is an invalid size");
+
+// This is nn::hid::AnalogStickState
+struct AnalogStickState {
+    s32 x;
+    s32 y;
+};
+static_assert(sizeof(AnalogStickState) == 8, "AnalogStickState is an invalid size");
+
+// This is nn::hid::server::NpadGcTriggerState
+struct NpadGcTriggerState {
+    s64 sampling_number{};
+    s32 left{};
+    s32 right{};
+};
+static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size");
+
+// This is nn::hid::system::NpadBatteryLevel
+using NpadBatteryLevel = u32;
+static_assert(sizeof(NpadBatteryLevel) == 0x4, "NpadBatteryLevel is an invalid size");
+
+// This is nn::hid::system::NpadPowerInfo
+struct NpadPowerInfo {
+    bool is_powered;
+    bool is_charging;
+    INSERT_PADDING_BYTES(0x6);
+    NpadBatteryLevel battery_level;
+};
+static_assert(sizeof(NpadPowerInfo) == 0xC, "NpadPowerInfo is an invalid size");
+
+struct LedPattern {
+    explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
+        position1.Assign(light1);
+        position2.Assign(light2);
+        position3.Assign(light3);
+        position4.Assign(light4);
+    }
+    union {
+        u64 raw{};
+        BitField<0, 1, u64> position1;
+        BitField<1, 1, u64> position2;
+        BitField<2, 1, u64> position3;
+        BitField<3, 1, u64> position4;
+    };
+};
+
+struct NpadButtonState {
+    union {
+        NpadButton raw{};
+
+        // Buttons
+        BitField<0, 1, u64> a;
+        BitField<1, 1, u64> b;
+        BitField<2, 1, u64> x;
+        BitField<3, 1, u64> y;
+        BitField<4, 1, u64> stick_l;
+        BitField<5, 1, u64> stick_r;
+        BitField<6, 1, u64> l;
+        BitField<7, 1, u64> r;
+        BitField<8, 1, u64> zl;
+        BitField<9, 1, u64> zr;
+        BitField<10, 1, u64> plus;
+        BitField<11, 1, u64> minus;
+
+        // D-Pad
+        BitField<12, 1, u64> left;
+        BitField<13, 1, u64> up;
+        BitField<14, 1, u64> right;
+        BitField<15, 1, u64> down;
+
+        // Left JoyStick
+        BitField<16, 1, u64> stick_l_left;
+        BitField<17, 1, u64> stick_l_up;
+        BitField<18, 1, u64> stick_l_right;
+        BitField<19, 1, u64> stick_l_down;
+
+        // Right JoyStick
+        BitField<20, 1, u64> stick_r_left;
+        BitField<21, 1, u64> stick_r_up;
+        BitField<22, 1, u64> stick_r_right;
+        BitField<23, 1, u64> stick_r_down;
+
+        BitField<24, 1, u64> left_sl;
+        BitField<25, 1, u64> left_sr;
+
+        BitField<26, 1, u64> right_sl;
+        BitField<27, 1, u64> right_sr;
+
+        BitField<28, 1, u64> palma;
+        BitField<29, 1, u64> verification;
+        BitField<30, 1, u64> handheld_left_b;
+        BitField<31, 1, u64> lagon_c_left;
+        BitField<32, 1, u64> lagon_c_up;
+        BitField<33, 1, u64> lagon_c_right;
+        BitField<34, 1, u64> lagon_c_down;
+    };
+};
+static_assert(sizeof(NpadButtonState) == 0x8, "NpadButtonState has incorrect size.");
+
+// This is nn::hid::DebugPadButton
+struct DebugPadButton {
+    union {
+        u32 raw{};
+        BitField<0, 1, u32> a;
+        BitField<1, 1, u32> b;
+        BitField<2, 1, u32> x;
+        BitField<3, 1, u32> y;
+        BitField<4, 1, u32> l;
+        BitField<5, 1, u32> r;
+        BitField<6, 1, u32> zl;
+        BitField<7, 1, u32> zr;
+        BitField<8, 1, u32> plus;
+        BitField<9, 1, u32> minus;
+        BitField<10, 1, u32> d_left;
+        BitField<11, 1, u32> d_up;
+        BitField<12, 1, u32> d_right;
+        BitField<13, 1, u32> d_down;
+    };
+};
+static_assert(sizeof(DebugPadButton) == 0x4, "DebugPadButton is an invalid size");
+
+// This is nn::hid::ConsoleSixAxisSensorHandle
+struct ConsoleSixAxisSensorHandle {
+    u8 unknown_1;
+    u8 unknown_2;
+    INSERT_PADDING_BYTES_NOINIT(2);
+};
+static_assert(sizeof(ConsoleSixAxisSensorHandle) == 4,
+              "ConsoleSixAxisSensorHandle is an invalid size");
+
+// This is nn::hid::SixAxisSensorHandle
+struct SixAxisSensorHandle {
+    NpadStyleIndex npad_type;
+    u8 npad_id;
+    DeviceIndex device_index;
+    INSERT_PADDING_BYTES_NOINIT(1);
+};
+static_assert(sizeof(SixAxisSensorHandle) == 4, "SixAxisSensorHandle is an invalid size");
+
+struct SixAxisSensorFusionParameters {
+    f32 parameter1;
+    f32 parameter2;
+};
+static_assert(sizeof(SixAxisSensorFusionParameters) == 8,
+              "SixAxisSensorFusionParameters is an invalid size");
+
+// This is nn::hid::VibrationDeviceHandle
+struct VibrationDeviceHandle {
+    NpadStyleIndex npad_type;
+    u8 npad_id;
+    DeviceIndex device_index;
+    INSERT_PADDING_BYTES_NOINIT(1);
+};
+static_assert(sizeof(VibrationDeviceHandle) == 4, "SixAxisSensorHandle is an invalid size");
+
+// This is nn::hid::VibrationValue
+struct VibrationValue {
+    f32 low_amplitude;
+    f32 low_frequency;
+    f32 high_amplitude;
+    f32 high_frequency;
+};
+static_assert(sizeof(VibrationValue) == 0x10, "VibrationValue has incorrect size.");
+
+// This is nn::hid::VibrationDeviceInfo
+struct VibrationDeviceInfo {
+    VibrationDeviceType type{};
+    VibrationDevicePosition position{};
+};
+static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
+
+// This is nn::hid::KeyboardModifier
+struct KeyboardModifier {
+    union {
+        u32 raw{};
+        BitField<0, 1, u32> control;
+        BitField<1, 1, u32> shift;
+        BitField<2, 1, u32> left_alt;
+        BitField<3, 1, u32> right_alt;
+        BitField<4, 1, u32> gui;
+        BitField<8, 1, u32> caps_lock;
+        BitField<9, 1, u32> scroll_lock;
+        BitField<10, 1, u32> num_lock;
+        BitField<11, 1, u32> katakana;
+        BitField<12, 1, u32> hiragana;
+    };
+};
+
+static_assert(sizeof(KeyboardModifier) == 0x4, "KeyboardModifier is an invalid size");
+
+// This is nn::hid::KeyboardAttribute
+struct KeyboardAttribute {
+    union {
+        u32 raw{};
+        BitField<0, 1, u32> is_connected;
+    };
+};
+static_assert(sizeof(KeyboardAttribute) == 0x4, "KeyboardAttribute is an invalid size");
+
+// This is nn::hid::KeyboardKey
+struct KeyboardKey {
+    // This should be a 256 bit flag
+    std::array<u8, 32> key;
+};
+static_assert(sizeof(KeyboardKey) == 0x20, "KeyboardKey is an invalid size");
+
+// This is nn::hid::MouseButton
+struct MouseButton {
+    union {
+        u32_le raw{};
+        BitField<0, 1, u32> left;
+        BitField<1, 1, u32> right;
+        BitField<2, 1, u32> middle;
+        BitField<3, 1, u32> forward;
+        BitField<4, 1, u32> back;
+    };
+};
+static_assert(sizeof(MouseButton) == 0x4, "MouseButton is an invalid size");
+
+// This is nn::hid::MouseAttribute
+struct MouseAttribute {
+    union {
+        u32 raw{};
+        BitField<0, 1, u32> transferable;
+        BitField<1, 1, u32> is_connected;
+    };
+};
+static_assert(sizeof(MouseAttribute) == 0x4, "MouseAttribute is an invalid size");
+
+// This is nn::hid::detail::MouseState
+struct MouseState {
+    s64 sampling_number;
+    s32 x;
+    s32 y;
+    s32 delta_x;
+    s32 delta_y;
+    // Axis Order in HW is switched for the wheel
+    s32 delta_wheel_y;
+    s32 delta_wheel_x;
+    MouseButton button;
+    MouseAttribute attribute;
+};
+static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size");
+
+/// Converts a NpadIdType to an array index.
+constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) {
+    switch (npad_id_type) {
+    case NpadIdType::Player1:
+        return 0;
+    case NpadIdType::Player2:
+        return 1;
+    case NpadIdType::Player3:
+        return 2;
+    case NpadIdType::Player4:
+        return 3;
+    case NpadIdType::Player5:
+        return 4;
+    case NpadIdType::Player6:
+        return 5;
+    case NpadIdType::Player7:
+        return 6;
+    case NpadIdType::Player8:
+        return 7;
+    case NpadIdType::Handheld:
+        return 8;
+    case NpadIdType::Other:
+        return 9;
+    default:
+        return 0;
+    }
+}
+
+/// Converts an array index to a NpadIdType
+constexpr NpadIdType IndexToNpadIdType(size_t index) {
+    switch (index) {
+    case 0:
+        return NpadIdType::Player1;
+    case 1:
+        return NpadIdType::Player2;
+    case 2:
+        return NpadIdType::Player3;
+    case 3:
+        return NpadIdType::Player4;
+    case 4:
+        return NpadIdType::Player5;
+    case 5:
+        return NpadIdType::Player6;
+    case 6:
+        return NpadIdType::Player7;
+    case 7:
+        return NpadIdType::Player8;
+    case 8:
+        return NpadIdType::Handheld;
+    case 9:
+        return NpadIdType::Other;
+    default:
+        return NpadIdType::Invalid;
+    }
+}
+
+} // namespace Core::HID

+ 383 - 0
src/core/hid/input_converter.cpp

@@ -0,0 +1,383 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include <random>
+
+#include "common/input.h"
+#include "core/hid/input_converter.h"
+
+namespace Core::HID {
+
+Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback) {
+    Common::Input::BatteryStatus battery{Common::Input::BatteryStatus::None};
+    switch (callback.type) {
+    case Common::Input::InputType::Analog:
+    case Common::Input::InputType::Trigger: {
+        const auto value = TransformToTrigger(callback).analog.value;
+        battery = Common::Input::BatteryLevel::Empty;
+        if (value > 0.2f) {
+            battery = Common::Input::BatteryLevel::Critical;
+        }
+        if (value > 0.4f) {
+            battery = Common::Input::BatteryLevel::Low;
+        }
+        if (value > 0.6f) {
+            battery = Common::Input::BatteryLevel::Medium;
+        }
+        if (value > 0.8f) {
+            battery = Common::Input::BatteryLevel::Full;
+        }
+        if (value >= 1.0f) {
+            battery = Common::Input::BatteryLevel::Charging;
+        }
+        break;
+    }
+    case Common::Input::InputType::Button:
+        battery = callback.button_status.value ? Common::Input::BatteryLevel::Charging
+                                               : Common::Input::BatteryLevel::Critical;
+        break;
+    case Common::Input::InputType::Battery:
+        battery = callback.battery_status;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to battery not implemented", callback.type);
+        break;
+    }
+
+    return battery;
+}
+
+Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback) {
+    Common::Input::ButtonStatus status{};
+    switch (callback.type) {
+    case Common::Input::InputType::Analog:
+    case Common::Input::InputType::Trigger:
+        status.value = TransformToTrigger(callback).pressed.value;
+        break;
+    case Common::Input::InputType::Button:
+        status = callback.button_status;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type);
+        break;
+    }
+
+    if (status.inverted) {
+        status.value = !status.value;
+    }
+
+    return status;
+}
+
+Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback) {
+    Common::Input::MotionStatus status{};
+    switch (callback.type) {
+    case Common::Input::InputType::Button: {
+        Common::Input::AnalogProperties properties{
+            .deadzone = 0.0f,
+            .range = 1.0f,
+            .offset = 0.0f,
+        };
+        status.delta_timestamp = 5000;
+        status.force_update = true;
+        status.accel.x = {
+            .value = 0.0f,
+            .raw_value = 0.0f,
+            .properties = properties,
+        };
+        status.accel.y = {
+            .value = 0.0f,
+            .raw_value = 0.0f,
+            .properties = properties,
+        };
+        status.accel.z = {
+            .value = 0.0f,
+            .raw_value = -1.0f,
+            .properties = properties,
+        };
+        status.gyro.x = {
+            .value = 0.0f,
+            .raw_value = 0.0f,
+            .properties = properties,
+        };
+        status.gyro.y = {
+            .value = 0.0f,
+            .raw_value = 0.0f,
+            .properties = properties,
+        };
+        status.gyro.z = {
+            .value = 0.0f,
+            .raw_value = 0.0f,
+            .properties = properties,
+        };
+        if (TransformToButton(callback).value) {
+            std::random_device device;
+            std::mt19937 gen(device());
+            std::uniform_int_distribution<s16> distribution(-1000, 1000);
+            status.accel.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
+            status.accel.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
+            status.accel.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
+            status.gyro.x.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
+            status.gyro.y.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
+            status.gyro.z.raw_value = static_cast<f32>(distribution(gen)) * 0.001f;
+        }
+        break;
+    }
+    case Common::Input::InputType::Motion:
+        status = callback.motion_status;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to motion not implemented", callback.type);
+        break;
+    }
+    SanitizeAnalog(status.accel.x, false);
+    SanitizeAnalog(status.accel.y, false);
+    SanitizeAnalog(status.accel.z, false);
+    SanitizeAnalog(status.gyro.x, false);
+    SanitizeAnalog(status.gyro.y, false);
+    SanitizeAnalog(status.gyro.z, false);
+
+    return status;
+}
+
+Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback) {
+    Common::Input::StickStatus status{};
+
+    switch (callback.type) {
+    case Common::Input::InputType::Stick:
+        status = callback.stick_status;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to stick not implemented", callback.type);
+        break;
+    }
+
+    SanitizeStick(status.x, status.y, true);
+    const auto& properties_x = status.x.properties;
+    const auto& properties_y = status.y.properties;
+    const float x = status.x.value;
+    const float y = status.y.value;
+
+    // Set directional buttons
+    status.right = x > properties_x.threshold;
+    status.left = x < -properties_x.threshold;
+    status.up = y > properties_y.threshold;
+    status.down = y < -properties_y.threshold;
+
+    return status;
+}
+
+Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback) {
+    Common::Input::TouchStatus status{};
+
+    switch (callback.type) {
+    case Common::Input::InputType::Touch:
+        status = callback.touch_status;
+        break;
+    case Common::Input::InputType::Stick:
+        status.x = callback.stick_status.x;
+        status.y = callback.stick_status.y;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to touch not implemented", callback.type);
+        break;
+    }
+
+    SanitizeAnalog(status.x, true);
+    SanitizeAnalog(status.y, true);
+    float& x = status.x.value;
+    float& y = status.y.value;
+
+    // Adjust if value is inverted
+    x = status.x.properties.inverted ? 1.0f + x : x;
+    y = status.y.properties.inverted ? 1.0f + y : y;
+
+    // clamp value
+    x = std::clamp(x, 0.0f, 1.0f);
+    y = std::clamp(y, 0.0f, 1.0f);
+
+    if (status.pressed.inverted) {
+        status.pressed.value = !status.pressed.value;
+    }
+
+    return status;
+}
+
+Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback) {
+    Common::Input::TriggerStatus status{};
+    float& raw_value = status.analog.raw_value;
+    bool calculate_button_value = true;
+
+    switch (callback.type) {
+    case Common::Input::InputType::Analog:
+        status.analog.properties = callback.analog_status.properties;
+        raw_value = callback.analog_status.raw_value;
+        break;
+    case Common::Input::InputType::Button:
+        status.analog.properties.range = 1.0f;
+        status.analog.properties.inverted = callback.button_status.inverted;
+        raw_value = callback.button_status.value ? 1.0f : 0.0f;
+        break;
+    case Common::Input::InputType::Trigger:
+        status = callback.trigger_status;
+        calculate_button_value = false;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type);
+        break;
+    }
+
+    SanitizeAnalog(status.analog, true);
+    const auto& properties = status.analog.properties;
+    float& value = status.analog.value;
+
+    // Set button status
+    if (calculate_button_value) {
+        status.pressed.value = value > properties.threshold;
+    }
+
+    // Adjust if value is inverted
+    value = properties.inverted ? 1.0f + value : value;
+
+    // clamp value
+    value = std::clamp(value, 0.0f, 1.0f);
+
+    return status;
+}
+
+Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback) {
+    Common::Input::AnalogStatus status{};
+
+    switch (callback.type) {
+    case Common::Input::InputType::Analog:
+        status.properties = callback.analog_status.properties;
+        status.raw_value = callback.analog_status.raw_value;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to analog not implemented", callback.type);
+        break;
+    }
+
+    SanitizeAnalog(status, false);
+
+    // Adjust if value is inverted
+    status.value = status.properties.inverted ? -status.value : status.value;
+
+    return status;
+}
+
+void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
+    const auto& properties = analog.properties;
+    float& raw_value = analog.raw_value;
+    float& value = analog.value;
+
+    if (!std::isnormal(raw_value)) {
+        raw_value = 0;
+    }
+
+    // Apply center offset
+    raw_value -= properties.offset;
+
+    // Set initial values to be formated
+    value = raw_value;
+
+    // Calculate vector size
+    const float r = std::abs(value);
+
+    // Return zero if value is smaller than the deadzone
+    if (r <= properties.deadzone || properties.deadzone == 1.0f) {
+        analog.value = 0;
+        return;
+    }
+
+    // Adjust range of value
+    const float deadzone_factor =
+        1.0f / r * (r - properties.deadzone) / (1.0f - properties.deadzone);
+    value = value * deadzone_factor / properties.range;
+
+    // Invert direction if needed
+    if (properties.inverted) {
+        value = -value;
+    }
+
+    // Clamp value
+    if (clamp_value) {
+        value = std::clamp(value, -1.0f, 1.0f);
+    }
+}
+
+void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
+                   bool clamp_value) {
+    const auto& properties_x = analog_x.properties;
+    const auto& properties_y = analog_y.properties;
+    float& raw_x = analog_x.raw_value;
+    float& raw_y = analog_y.raw_value;
+    float& x = analog_x.value;
+    float& y = analog_y.value;
+
+    if (!std::isnormal(raw_x)) {
+        raw_x = 0;
+    }
+    if (!std::isnormal(raw_y)) {
+        raw_y = 0;
+    }
+
+    // Apply center offset
+    raw_x += properties_x.offset;
+    raw_y += properties_y.offset;
+
+    // Apply X scale correction from offset
+    if (std::abs(properties_x.offset) < 0.5f) {
+        if (raw_x > 0) {
+            raw_x /= 1 + properties_x.offset;
+        } else {
+            raw_x /= 1 - properties_x.offset;
+        }
+    }
+
+    // Apply Y scale correction from offset
+    if (std::abs(properties_y.offset) < 0.5f) {
+        if (raw_y > 0) {
+            raw_y /= 1 + properties_y.offset;
+        } else {
+            raw_y /= 1 - properties_y.offset;
+        }
+    }
+
+    // Invert direction if needed
+    raw_x = properties_x.inverted ? -raw_x : raw_x;
+    raw_y = properties_y.inverted ? -raw_y : raw_y;
+
+    // Set initial values to be formated
+    x = raw_x;
+    y = raw_y;
+
+    // Calculate vector size
+    float r = x * x + y * y;
+    r = std::sqrt(r);
+
+    // TODO(German77): Use deadzone and range of both axis
+
+    // Return zero if values are smaller than the deadzone
+    if (r <= properties_x.deadzone || properties_x.deadzone >= 1.0f) {
+        x = 0;
+        y = 0;
+        return;
+    }
+
+    // Adjust range of joystick
+    const float deadzone_factor =
+        1.0f / r * (r - properties_x.deadzone) / (1.0f - properties_x.deadzone);
+    x = x * deadzone_factor / properties_x.range;
+    y = y * deadzone_factor / properties_x.range;
+    r = r * deadzone_factor / properties_x.range;
+
+    // Normalize joystick
+    if (clamp_value && r > 1.0f) {
+        x /= r;
+        y /= r;
+    }
+}
+
+} // namespace Core::HID

+ 96 - 0
src/core/hid/input_converter.h

@@ -0,0 +1,96 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#pragma once
+
+namespace Common::Input {
+struct CallbackStatus;
+enum class BatteryLevel : u32;
+using BatteryStatus = BatteryLevel;
+struct AnalogStatus;
+struct ButtonStatus;
+struct MotionStatus;
+struct StickStatus;
+struct TouchStatus;
+struct TriggerStatus;
+}; // namespace Common::Input
+
+namespace Core::HID {
+
+/**
+ * Converts raw input data into a valid battery status.
+ *
+ * @param callback Supported callbacks: Analog, Battery, Trigger.
+ * @return A valid BatteryStatus object.
+ */
+Common::Input::BatteryStatus TransformToBattery(const Common::Input::CallbackStatus& callback);
+
+/**
+ * Converts raw input data into a valid button status. Applies invert properties to the output.
+ *
+ * @param callback Supported callbacks: Analog, Button, Trigger.
+ * @return A valid TouchStatus object.
+ */
+Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatus& callback);
+
+/**
+ * Converts raw input data into a valid motion status.
+ *
+ * @param callback Supported callbacks: Motion.
+ * @return A valid TouchStatus object.
+ */
+Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatus& callback);
+
+/**
+ * Converts raw input data into a valid stick status. Applies offset, deadzone, range and invert
+ * properties to the output.
+ *
+ * @param callback Supported callbacks: Stick.
+ * @return A valid StickStatus object.
+ */
+Common::Input::StickStatus TransformToStick(const Common::Input::CallbackStatus& callback);
+
+/**
+ * Converts raw input data into a valid touch status.
+ *
+ * @param callback Supported callbacks: Touch.
+ * @return A valid TouchStatus object.
+ */
+Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus& callback);
+
+/**
+ * Converts raw input data into a valid trigger status. Applies offset, deadzone, range and
+ * invert properties to the output. Button status uses the threshold property if necessary.
+ *
+ * @param callback Supported callbacks: Analog, Button, Trigger.
+ * @return A valid TriggerStatus object.
+ */
+Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackStatus& callback);
+
+/**
+ * Converts raw input data into a valid analog status. Applies offset, deadzone, range and
+ * invert properties to the output.
+ *
+ * @param callback Supported callbacks: Analog.
+ * @return A valid AnalogStatus object.
+ */
+Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback);
+
+/**
+ * Converts raw analog data into a valid analog value
+ * @param analog An analog object containing raw data and properties
+ * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f.
+ */
+void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value);
+
+/**
+ * Converts raw stick data into a valid stick value
+ * @param analog_x raw analog data and properties for the x-axis
+ * @param analog_y raw analog data and properties for the y-axis
+ * @param clamp_value bool that determines if the value needs to be clamped into the unit circle.
+ */
+void SanitizeStick(Common::Input::AnalogStatus& analog_x, Common::Input::AnalogStatus& analog_y,
+                   bool clamp_value);
+
+} // namespace Core::HID

+ 13 - 14
src/core/frontend/input_interpreter.cpp → src/core/hid/input_interpreter.cpp

@@ -3,7 +3,8 @@
 // Refer to the license.txt file included.
 
 #include "core/core.h"
-#include "core/frontend/input_interpreter.h"
+#include "core/hid/hid_types.h"
+#include "core/hid/input_interpreter.h"
 #include "core/hle/service/hid/controllers/npad.h"
 #include "core/hle/service/hid/hid.h"
 #include "core/hle/service/sm/sm.h"
@@ -19,7 +20,7 @@ InputInterpreter::InputInterpreter(Core::System& system)
 InputInterpreter::~InputInterpreter() = default;
 
 void InputInterpreter::PollInput() {
-    const u32 button_state = npad.GetAndResetPressState();
+    const auto button_state = npad.GetAndResetPressState();
 
     previous_index = current_index;
     current_index = (current_index + 1) % button_states.size();
@@ -31,32 +32,30 @@ void InputInterpreter::ResetButtonStates() {
     previous_index = 0;
     current_index = 0;
 
-    button_states[0] = 0xFFFFFFFF;
+    button_states[0] = Core::HID::NpadButton::All;
 
     for (std::size_t i = 1; i < button_states.size(); ++i) {
-        button_states[i] = 0;
+        button_states[i] = Core::HID::NpadButton::None;
     }
 }
 
-bool InputInterpreter::IsButtonPressed(HIDButton button) const {
-    return (button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
+bool InputInterpreter::IsButtonPressed(Core::HID::NpadButton button) const {
+    return True(button_states[current_index] & button);
 }
 
-bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const {
-    const bool current_press =
-        (button_states[current_index] & (1U << static_cast<u8>(button))) != 0;
-    const bool previous_press =
-        (button_states[previous_index] & (1U << static_cast<u8>(button))) != 0;
+bool InputInterpreter::IsButtonPressedOnce(Core::HID::NpadButton button) const {
+    const bool current_press = True(button_states[current_index] & button);
+    const bool previous_press = True(button_states[previous_index] & button);
 
     return current_press && !previous_press;
 }
 
-bool InputInterpreter::IsButtonHeld(HIDButton button) const {
-    u32 held_buttons{button_states[0]};
+bool InputInterpreter::IsButtonHeld(Core::HID::NpadButton button) const {
+    Core::HID::NpadButton held_buttons{button_states[0]};
 
     for (std::size_t i = 1; i < button_states.size(); ++i) {
         held_buttons &= button_states[i];
     }
 
-    return (held_buttons & (1U << static_cast<u8>(button))) != 0;
+    return True(held_buttons & button);
 }

+ 11 - 43
src/core/frontend/input_interpreter.h → src/core/hid/input_interpreter.h

@@ -12,46 +12,14 @@ namespace Core {
 class System;
 }
 
+namespace Core::HID {
+enum class NpadButton : u64;
+}
+
 namespace Service::HID {
 class Controller_NPad;
 }
 
-enum class HIDButton : u8 {
-    A,
-    B,
-    X,
-    Y,
-    LStick,
-    RStick,
-    L,
-    R,
-    ZL,
-    ZR,
-    Plus,
-    Minus,
-
-    DLeft,
-    DUp,
-    DRight,
-    DDown,
-
-    LStickLeft,
-    LStickUp,
-    LStickRight,
-    LStickDown,
-
-    RStickLeft,
-    RStickUp,
-    RStickRight,
-    RStickDown,
-
-    LeftSL,
-    LeftSR,
-
-    RightSL,
-    RightSR,
-};
-
 /**
  * The InputInterpreter class interfaces with HID to retrieve button press states.
  * Input is intended to be polled every 50ms so that a button is considered to be
@@ -76,7 +44,7 @@ public:
      *
      * @returns True when the button is pressed.
      */
-    [[nodiscard]] bool IsButtonPressed(HIDButton button) const;
+    [[nodiscard]] bool IsButtonPressed(Core::HID::NpadButton button) const;
 
     /**
      * Checks whether any of the buttons in the parameter list is pressed.
@@ -85,7 +53,7 @@ public:
      *
      * @returns True when at least one of the buttons is pressed.
      */
-    template <HIDButton... T>
+    template <Core::HID::NpadButton... T>
     [[nodiscard]] bool IsAnyButtonPressed() {
         return (IsButtonPressed(T) || ...);
     }
@@ -98,7 +66,7 @@ public:
      *
      * @returns True when the button is pressed once.
      */
-    [[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const;
+    [[nodiscard]] bool IsButtonPressedOnce(Core::HID::NpadButton button) const;
 
     /**
      * Checks whether any of the buttons in the parameter list is pressed once.
@@ -107,7 +75,7 @@ public:
      *
      * @returns True when at least one of the buttons is pressed once.
      */
-    template <HIDButton... T>
+    template <Core::HID::NpadButton... T>
     [[nodiscard]] bool IsAnyButtonPressedOnce() const {
         return (IsButtonPressedOnce(T) || ...);
     }
@@ -119,7 +87,7 @@ public:
      *
      * @returns True when the button is held down.
      */
-    [[nodiscard]] bool IsButtonHeld(HIDButton button) const;
+    [[nodiscard]] bool IsButtonHeld(Core::HID::NpadButton button) const;
 
     /**
      * Checks whether any of the buttons in the parameter list is held down.
@@ -128,7 +96,7 @@ public:
      *
      * @returns True when at least one of the buttons is held down.
      */
-    template <HIDButton... T>
+    template <Core::HID::NpadButton... T>
     [[nodiscard]] bool IsAnyButtonHeld() const {
         return (IsButtonHeld(T) || ...);
     }
@@ -137,7 +105,7 @@ private:
     Service::HID::Controller_NPad& npad;
 
     /// Stores 9 consecutive button states polled from HID.
-    std::array<u32, 9> button_states{};
+    std::array<Core::HID::NpadButton, 9> button_states{};
 
     std::size_t previous_index{};
     std::size_t current_index{};

+ 15 - 42
src/input_common/motion_input.cpp → src/core/hid/motion_input.cpp

@@ -2,13 +2,21 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included
 
-#include <random>
 #include "common/math_util.h"
-#include "input_common/motion_input.h"
+#include "core/hid/motion_input.h"
 
-namespace InputCommon {
+namespace Core::HID {
 
-MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {}
+MotionInput::MotionInput() {
+    // Initialize PID constants with default values
+    SetPID(0.3f, 0.005f, 0.0f);
+}
+
+void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) {
+    kp = new_kp;
+    ki = new_ki;
+    kd = new_kd;
+}
 
 void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
     accel = acceleration;
@@ -65,6 +73,8 @@ void MotionInput::UpdateRotation(u64 elapsed_time) {
     rotations += gyro * sample_period;
 }
 
+// Based on Madgwick's implementation of Mayhony's AHRS algorithm.
+// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
 void MotionInput::UpdateOrientation(u64 elapsed_time) {
     if (!IsCalibrated(0.1f)) {
         ResetOrientation();
@@ -190,43 +200,6 @@ Common::Vec3f MotionInput::GetRotations() const {
     return rotations;
 }
 
-Input::MotionStatus MotionInput::GetMotion() const {
-    const Common::Vec3f gyroscope = GetGyroscope();
-    const Common::Vec3f accelerometer = GetAcceleration();
-    const Common::Vec3f rotation = GetRotations();
-    const std::array<Common::Vec3f, 3> orientation = GetOrientation();
-    const Common::Quaternion<f32> quaternion = GetQuaternion();
-    return {accelerometer, gyroscope, rotation, orientation, quaternion};
-}
-
-Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
-    std::random_device device;
-    std::mt19937 gen(device());
-    std::uniform_int_distribution<s16> distribution(-1000, 1000);
-    const Common::Vec3f gyroscope{
-        static_cast<f32>(distribution(gen)) * 0.001f,
-        static_cast<f32>(distribution(gen)) * 0.001f,
-        static_cast<f32>(distribution(gen)) * 0.001f,
-    };
-    const Common::Vec3f accelerometer{
-        static_cast<f32>(distribution(gen)) * 0.001f,
-        static_cast<f32>(distribution(gen)) * 0.001f,
-        static_cast<f32>(distribution(gen)) * 0.001f,
-    };
-    constexpr Common::Vec3f rotation;
-    constexpr std::array orientation{
-        Common::Vec3f{1.0f, 0.0f, 0.0f},
-        Common::Vec3f{0.0f, 1.0f, 0.0f},
-        Common::Vec3f{0.0f, 0.0f, 1.0f},
-    };
-    constexpr Common::Quaternion<f32> quaternion{
-        {0.0f, 0.0f, 0.0f},
-        1.0f,
-    };
-    return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation,
-            quaternion};
-}
-
 void MotionInput::ResetOrientation() {
     if (!reset_enabled || only_accelerometer) {
         return;
@@ -304,4 +277,4 @@ void MotionInput::SetOrientationFromAccelerometer() {
         quat = quat.Normalized();
     }
 }
-} // namespace InputCommon
+} // namespace Core::HID

+ 20 - 7
src/input_common/motion_input.h → src/core/hid/motion_input.h

@@ -7,13 +7,12 @@
 #include "common/common_types.h"
 #include "common/quaternion.h"
 #include "common/vector_math.h"
-#include "core/frontend/input.h"
 
-namespace InputCommon {
+namespace Core::HID {
 
 class MotionInput {
 public:
-    explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
+    explicit MotionInput();
 
     MotionInput(const MotionInput&) = default;
     MotionInput& operator=(const MotionInput&) = default;
@@ -21,6 +20,7 @@ public:
     MotionInput(MotionInput&&) = default;
     MotionInput& operator=(MotionInput&&) = default;
 
+    void SetPID(f32 new_kp, f32 new_ki, f32 new_kd);
     void SetAcceleration(const Common::Vec3f& acceleration);
     void SetGyroscope(const Common::Vec3f& gyroscope);
     void SetQuaternion(const Common::Quaternion<f32>& quaternion);
@@ -38,9 +38,6 @@ public:
     [[nodiscard]] Common::Vec3f GetGyroscope() const;
     [[nodiscard]] Common::Vec3f GetRotations() const;
     [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
-    [[nodiscard]] Input::MotionStatus GetMotion() const;
-    [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude,
-                                                      int gyro_magnitude) const;
 
     [[nodiscard]] bool IsMoving(f32 sensitivity) const;
     [[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
@@ -59,16 +56,32 @@ private:
     Common::Vec3f integral_error;
     Common::Vec3f derivative_error;
 
+    // Quaternion containing the device orientation
     Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
+
+    // Number of full rotations in each axis
     Common::Vec3f rotations;
+
+    // Acceleration vector measurement in G force
     Common::Vec3f accel;
+
+    // Gyroscope vector measurement in radians/s.
     Common::Vec3f gyro;
+
+    // Vector to be substracted from gyro measurements
     Common::Vec3f gyro_drift;
 
+    // Minimum gyro amplitude to detect if the device is moving
     f32 gyro_threshold = 0.0f;
+
+    // Number of invalid sequential data
     u32 reset_counter = 0;
+
+    // If the provided data is invalid the device will be autocalibrated
     bool reset_enabled = true;
+
+    // Use accelerometer values to calculate position
     bool only_accelerometer = true;
 };
 
-} // namespace InputCommon
+} // namespace Core::HID

+ 2 - 0
src/core/hle/kernel/init/init_slab_setup.cpp

@@ -9,6 +9,7 @@
 #include "core/core.h"
 #include "core/hardware_properties.h"
 #include "core/hle/kernel/init/init_slab_setup.h"
+#include "core/hle/kernel/k_code_memory.h"
 #include "core/hle/kernel/k_event.h"
 #include "core/hle/kernel/k_memory_layout.h"
 #include "core/hle/kernel/k_memory_manager.h"
@@ -32,6 +33,7 @@ namespace Kernel::Init {
     HANDLER(KPort, (SLAB_COUNT(KPort)), ##__VA_ARGS__)                                             \
     HANDLER(KSharedMemory, (SLAB_COUNT(KSharedMemory)), ##__VA_ARGS__)                             \
     HANDLER(KTransferMemory, (SLAB_COUNT(KTransferMemory)), ##__VA_ARGS__)                         \
+    HANDLER(KCodeMemory, (SLAB_COUNT(KCodeMemory)), ##__VA_ARGS__)                                 \
     HANDLER(KSession, (SLAB_COUNT(KSession)), ##__VA_ARGS__)                                       \
     HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__)
 

+ 43 - 49
src/core/hle/kernel/k_address_arbiter.cpp

@@ -8,6 +8,7 @@
 #include "core/hle/kernel/k_scheduler.h"
 #include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
 #include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_thread_queue.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/svc_results.h"
 #include "core/hle/kernel/time_manager.h"
@@ -28,7 +29,7 @@ bool ReadFromUser(Core::System& system, s32* out, VAddr address) {
 
 bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 value) {
     auto& monitor = system.Monitor();
-    const auto current_core = system.CurrentCoreIndex();
+    const auto current_core = system.Kernel().CurrentPhysicalCoreIndex();
 
     // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
     // TODO(bunnei): We should call CanAccessAtomic(..) here.
@@ -58,7 +59,7 @@ bool DecrementIfLessThan(Core::System& system, s32* out, VAddr address, s32 valu
 
 bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32 new_value) {
     auto& monitor = system.Monitor();
-    const auto current_core = system.CurrentCoreIndex();
+    const auto current_core = system.Kernel().CurrentPhysicalCoreIndex();
 
     // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
     // TODO(bunnei): We should call CanAccessAtomic(..) here.
@@ -85,6 +86,27 @@ bool UpdateIfEqual(Core::System& system, s32* out, VAddr address, s32 value, s32
     return true;
 }
 
+class ThreadQueueImplForKAddressArbiter final : public KThreadQueue {
+public:
+    explicit ThreadQueueImplForKAddressArbiter(KernelCore& kernel_, KAddressArbiter::ThreadTree* t)
+        : KThreadQueue(kernel_), m_tree(t) {}
+
+    void CancelWait(KThread* waiting_thread, ResultCode wait_result,
+                    bool cancel_timer_task) override {
+        // If the thread is waiting on an address arbiter, remove it from the tree.
+        if (waiting_thread->IsWaitingForAddressArbiter()) {
+            m_tree->erase(m_tree->iterator_to(*waiting_thread));
+            waiting_thread->ClearAddressArbiter();
+        }
+
+        // Invoke the base cancel wait handler.
+        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
+    }
+
+private:
+    KAddressArbiter::ThreadTree* m_tree;
+};
+
 } // namespace
 
 ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) {
@@ -96,14 +118,14 @@ ResultCode KAddressArbiter::Signal(VAddr addr, s32 count) {
         auto it = thread_tree.nfind_light({addr, -1});
         while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
                (it->GetAddressArbiterKey() == addr)) {
+            // End the thread's wait.
             KThread* target_thread = std::addressof(*it);
-            target_thread->SetSyncedObject(nullptr, ResultSuccess);
+            target_thread->EndWait(ResultSuccess);
 
             ASSERT(target_thread->IsWaitingForAddressArbiter());
-            target_thread->Wakeup();
+            target_thread->ClearAddressArbiter();
 
             it = thread_tree.erase(it);
-            target_thread->ClearAddressArbiter();
             ++num_waiters;
         }
     }
@@ -129,14 +151,14 @@ ResultCode KAddressArbiter::SignalAndIncrementIfEqual(VAddr addr, s32 value, s32
         auto it = thread_tree.nfind_light({addr, -1});
         while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
                (it->GetAddressArbiterKey() == addr)) {
+            // End the thread's wait.
             KThread* target_thread = std::addressof(*it);
-            target_thread->SetSyncedObject(nullptr, ResultSuccess);
+            target_thread->EndWait(ResultSuccess);
 
             ASSERT(target_thread->IsWaitingForAddressArbiter());
-            target_thread->Wakeup();
+            target_thread->ClearAddressArbiter();
 
             it = thread_tree.erase(it);
-            target_thread->ClearAddressArbiter();
             ++num_waiters;
         }
     }
@@ -197,14 +219,14 @@ ResultCode KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32
 
         while ((it != thread_tree.end()) && (count <= 0 || num_waiters < count) &&
                (it->GetAddressArbiterKey() == addr)) {
+            // End the thread's wait.
             KThread* target_thread = std::addressof(*it);
-            target_thread->SetSyncedObject(nullptr, ResultSuccess);
+            target_thread->EndWait(ResultSuccess);
 
             ASSERT(target_thread->IsWaitingForAddressArbiter());
-            target_thread->Wakeup();
+            target_thread->ClearAddressArbiter();
 
             it = thread_tree.erase(it);
-            target_thread->ClearAddressArbiter();
             ++num_waiters;
         }
     }
@@ -214,6 +236,7 @@ ResultCode KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(VAddr addr, s32
 ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement, s64 timeout) {
     // Prepare to wait.
     KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+    ThreadQueueImplForKAddressArbiter wait_queue(kernel, std::addressof(thread_tree));
 
     {
         KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
@@ -224,9 +247,6 @@ ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement
             return ResultTerminationRequested;
         }
 
-        // Set the synced object.
-        cur_thread->SetSyncedObject(nullptr, ResultTimedOut);
-
         // Read the value from userspace.
         s32 user_value{};
         bool succeeded{};
@@ -256,31 +276,20 @@ ResultCode KAddressArbiter::WaitIfLessThan(VAddr addr, s32 value, bool decrement
         // Set the arbiter.
         cur_thread->SetAddressArbiter(&thread_tree, addr);
         thread_tree.insert(*cur_thread);
-        cur_thread->SetState(ThreadState::Waiting);
-        cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
-    }
-
-    // Cancel the timer wait.
-    kernel.TimeManager().UnscheduleTimeEvent(cur_thread);
 
-    // Remove from the address arbiter.
-    {
-        KScopedSchedulerLock sl(kernel);
-
-        if (cur_thread->IsWaitingForAddressArbiter()) {
-            thread_tree.erase(thread_tree.iterator_to(*cur_thread));
-            cur_thread->ClearAddressArbiter();
-        }
+        // Wait for the thread to finish.
+        cur_thread->BeginWait(std::addressof(wait_queue));
+        cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
     }
 
     // Get the result.
-    KSynchronizationObject* dummy{};
-    return cur_thread->GetWaitResult(&dummy);
+    return cur_thread->GetWaitResult();
 }
 
 ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
     // Prepare to wait.
     KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+    ThreadQueueImplForKAddressArbiter wait_queue(kernel, std::addressof(thread_tree));
 
     {
         KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
@@ -291,9 +300,6 @@ ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
             return ResultTerminationRequested;
         }
 
-        // Set the synced object.
-        cur_thread->SetSyncedObject(nullptr, ResultTimedOut);
-
         // Read the value from userspace.
         s32 user_value{};
         if (!ReadFromUser(system, &user_value, addr)) {
@@ -316,26 +322,14 @@ ResultCode KAddressArbiter::WaitIfEqual(VAddr addr, s32 value, s64 timeout) {
         // Set the arbiter.
         cur_thread->SetAddressArbiter(&thread_tree, addr);
         thread_tree.insert(*cur_thread);
-        cur_thread->SetState(ThreadState::Waiting);
-        cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
-    }
-
-    // Cancel the timer wait.
-    kernel.TimeManager().UnscheduleTimeEvent(cur_thread);
 
-    // Remove from the address arbiter.
-    {
-        KScopedSchedulerLock sl(kernel);
-
-        if (cur_thread->IsWaitingForAddressArbiter()) {
-            thread_tree.erase(thread_tree.iterator_to(*cur_thread));
-            cur_thread->ClearAddressArbiter();
-        }
+        // Wait for the thread to finish.
+        cur_thread->BeginWait(std::addressof(wait_queue));
+        cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration);
     }
 
     // Get the result.
-    KSynchronizationObject* dummy{};
-    return cur_thread->GetWaitResult(&dummy);
+    return cur_thread->GetWaitResult();
 }
 
 } // namespace Kernel

+ 4 - 0
src/core/hle/kernel/k_auto_object.h

@@ -170,6 +170,10 @@ public:
         }
     }
 
+    const std::string& GetName() const {
+        return name;
+    }
+
 private:
     void RegisterWithKernel();
     void UnregisterWithKernel();

+ 3 - 2
src/core/hle/kernel/k_class_token.cpp

@@ -6,6 +6,7 @@
 #include "core/hle/kernel/k_class_token.h"
 #include "core/hle/kernel/k_client_port.h"
 #include "core/hle/kernel/k_client_session.h"
+#include "core/hle/kernel/k_code_memory.h"
 #include "core/hle/kernel/k_event.h"
 #include "core/hle/kernel/k_port.h"
 #include "core/hle/kernel/k_process.h"
@@ -48,7 +49,7 @@ static_assert(ClassToken<KWritableEvent> == 0b10001001'00000000);
 static_assert(ClassToken<KTransferMemory> == 0b10010001'00000000);
 // static_assert(ClassToken<KDeviceAddressSpace> == 0b01100001'00000000);
 // static_assert(ClassToken<KSessionRequest> == 0b10100001'00000000);
-// static_assert(ClassToken<KCodeMemory> == 0b11000001'00000000);
+static_assert(ClassToken<KCodeMemory> == 0b11000001'00000000);
 
 // Ensure that the token hierarchy is correct.
 
@@ -79,7 +80,7 @@ static_assert(ClassToken<KWritableEvent> == ((0b10001001 << 8) | ClassToken<KAut
 static_assert(ClassToken<KTransferMemory> == ((0b10010001 << 8) | ClassToken<KAutoObject>));
 // static_assert(ClassToken<KDeviceAddressSpace> == ((0b01100001 << 8) | ClassToken<KAutoObject>));
 // static_assert(ClassToken<KSessionRequest> == ((0b10100001 << 8) | ClassToken<KAutoObject>));
-// static_assert(ClassToken<KCodeMemory> == ((0b11000001 << 8) | ClassToken<KAutoObject>));
+static_assert(ClassToken<KCodeMemory> == ((0b11000001 << 8) | ClassToken<KAutoObject>));
 
 // Ensure that the token hierarchy reflects the class hierarchy.
 

+ 146 - 0
src/core/hle/kernel/k_code_memory.cpp

@@ -0,0 +1,146 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/common_types.h"
+#include "core/device_memory.h"
+#include "core/hle/kernel/k_auto_object.h"
+#include "core/hle/kernel/k_code_memory.h"
+#include "core/hle/kernel/k_light_lock.h"
+#include "core/hle/kernel/k_memory_block.h"
+#include "core/hle/kernel/k_page_linked_list.h"
+#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/slab_helpers.h"
+#include "core/hle/kernel/svc_types.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+KCodeMemory::KCodeMemory(KernelCore& kernel_)
+    : KAutoObjectWithSlabHeapAndContainer{kernel_}, m_lock(kernel_) {}
+
+ResultCode KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, size_t size) {
+    // Set members.
+    m_owner = kernel.CurrentProcess();
+
+    // Get the owner page table.
+    auto& page_table = m_owner->PageTable();
+
+    // Construct the page group.
+    KMemoryInfo kBlockInfo = page_table.QueryInfo(addr);
+    m_page_group = KPageLinkedList(kBlockInfo.GetAddress(), kBlockInfo.GetNumPages());
+
+    // Lock the memory.
+    R_TRY(page_table.LockForCodeMemory(addr, size))
+
+    // Clear the memory.
+    for (const auto& block : m_page_group.Nodes()) {
+        std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize());
+    }
+
+    // Set remaining tracking members.
+    m_address = addr;
+    m_is_initialized = true;
+    m_is_owner_mapped = false;
+    m_is_mapped = false;
+
+    // We succeeded.
+    return ResultSuccess;
+}
+
+void KCodeMemory::Finalize() {
+    // Unlock.
+    if (!m_is_mapped && !m_is_owner_mapped) {
+        const size_t size = m_page_group.GetNumPages() * PageSize;
+        m_owner->PageTable().UnlockForCodeMemory(m_address, size);
+    }
+}
+
+ResultCode KCodeMemory::Map(VAddr address, size_t size) {
+    // Validate the size.
+    R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+
+    // Lock ourselves.
+    KScopedLightLock lk(m_lock);
+
+    // Ensure we're not already mapped.
+    R_UNLESS(!m_is_mapped, ResultInvalidState);
+
+    // Map the memory.
+    R_TRY(kernel.CurrentProcess()->PageTable().MapPages(
+        address, m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite));
+
+    // Mark ourselves as mapped.
+    m_is_mapped = true;
+
+    return ResultSuccess;
+}
+
+ResultCode KCodeMemory::Unmap(VAddr address, size_t size) {
+    // Validate the size.
+    R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+
+    // Lock ourselves.
+    KScopedLightLock lk(m_lock);
+
+    // Unmap the memory.
+    R_TRY(kernel.CurrentProcess()->PageTable().UnmapPages(address, m_page_group,
+                                                          KMemoryState::CodeOut));
+
+    // Mark ourselves as unmapped.
+    m_is_mapped = false;
+
+    return ResultSuccess;
+}
+
+ResultCode KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm) {
+    // Validate the size.
+    R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+
+    // Lock ourselves.
+    KScopedLightLock lk(m_lock);
+
+    // Ensure we're not already mapped.
+    R_UNLESS(!m_is_owner_mapped, ResultInvalidState);
+
+    // Convert the memory permission.
+    KMemoryPermission k_perm{};
+    switch (perm) {
+    case Svc::MemoryPermission::Read:
+        k_perm = KMemoryPermission::UserRead;
+        break;
+    case Svc::MemoryPermission::ReadExecute:
+        k_perm = KMemoryPermission::UserReadExecute;
+        break;
+    default:
+        break;
+    }
+
+    // Map the memory.
+    R_TRY(
+        m_owner->PageTable().MapPages(address, m_page_group, KMemoryState::GeneratedCode, k_perm));
+
+    // Mark ourselves as mapped.
+    m_is_owner_mapped = true;
+
+    return ResultSuccess;
+}
+
+ResultCode KCodeMemory::UnmapFromOwner(VAddr address, size_t size) {
+    // Validate the size.
+    R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+
+    // Lock ourselves.
+    KScopedLightLock lk(m_lock);
+
+    // Unmap the memory.
+    R_TRY(m_owner->PageTable().UnmapPages(address, m_page_group, KMemoryState::GeneratedCode));
+
+    // Mark ourselves as unmapped.
+    m_is_owner_mapped = false;
+
+    return ResultSuccess;
+}
+
+} // namespace Kernel

+ 66 - 0
src/core/hle/kernel/k_code_memory.h

@@ -0,0 +1,66 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/device_memory.h"
+#include "core/hle/kernel/k_auto_object.h"
+#include "core/hle/kernel/k_light_lock.h"
+#include "core/hle/kernel/k_page_linked_list.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/slab_helpers.h"
+#include "core/hle/kernel/svc_types.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+
+enum class CodeMemoryOperation : u32 {
+    Map = 0,
+    MapToOwner = 1,
+    Unmap = 2,
+    UnmapFromOwner = 3,
+};
+
+class KCodeMemory final
+    : public KAutoObjectWithSlabHeapAndContainer<KCodeMemory, KAutoObjectWithList> {
+    KERNEL_AUTOOBJECT_TRAITS(KCodeMemory, KAutoObject);
+
+public:
+    explicit KCodeMemory(KernelCore& kernel_);
+
+    ResultCode Initialize(Core::DeviceMemory& device_memory, VAddr address, size_t size);
+    void Finalize();
+
+    ResultCode Map(VAddr address, size_t size);
+    ResultCode Unmap(VAddr address, size_t size);
+    ResultCode MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm);
+    ResultCode UnmapFromOwner(VAddr address, size_t size);
+
+    bool IsInitialized() const {
+        return m_is_initialized;
+    }
+    static void PostDestroy([[maybe_unused]] uintptr_t arg) {}
+
+    KProcess* GetOwner() const {
+        return m_owner;
+    }
+    VAddr GetSourceAddress() const {
+        return m_address;
+    }
+    size_t GetSize() const {
+        return m_is_initialized ? m_page_group.GetNumPages() * PageSize : 0;
+    }
+
+private:
+    KPageLinkedList m_page_group{};
+    KProcess* m_owner{};
+    VAddr m_address{};
+    KLightLock m_lock;
+    bool m_is_initialized{};
+    bool m_is_owner_mapped{};
+    bool m_is_mapped{};
+};
+
+} // namespace Kernel

+ 115 - 130
src/core/hle/kernel/k_condition_variable.cpp

@@ -11,6 +11,7 @@
 #include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
 #include "core/hle/kernel/k_synchronization_object.h"
 #include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_thread_queue.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/svc_common.h"
 #include "core/hle/kernel/svc_results.h"
@@ -33,7 +34,7 @@ bool WriteToUser(Core::System& system, VAddr address, const u32* p) {
 bool UpdateLockAtomic(Core::System& system, u32* out, VAddr address, u32 if_zero,
                       u32 new_orr_mask) {
     auto& monitor = system.Monitor();
-    const auto current_core = system.CurrentCoreIndex();
+    const auto current_core = system.Kernel().CurrentPhysicalCoreIndex();
 
     // Load the value from the address.
     const auto expected = monitor.ExclusiveRead32(current_core, address);
@@ -57,6 +58,48 @@ bool UpdateLockAtomic(Core::System& system, u32* out, VAddr address, u32 if_zero
     return true;
 }
 
+class ThreadQueueImplForKConditionVariableWaitForAddress final : public KThreadQueue {
+public:
+    explicit ThreadQueueImplForKConditionVariableWaitForAddress(KernelCore& kernel_)
+        : KThreadQueue(kernel_) {}
+
+    void CancelWait(KThread* waiting_thread, ResultCode wait_result,
+                    bool cancel_timer_task) override {
+        // Remove the thread as a waiter from its owner.
+        waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread);
+
+        // Invoke the base cancel wait handler.
+        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
+    }
+};
+
+class ThreadQueueImplForKConditionVariableWaitConditionVariable final : public KThreadQueue {
+private:
+    KConditionVariable::ThreadTree* m_tree;
+
+public:
+    explicit ThreadQueueImplForKConditionVariableWaitConditionVariable(
+        KernelCore& kernel_, KConditionVariable::ThreadTree* t)
+        : KThreadQueue(kernel_), m_tree(t) {}
+
+    void CancelWait(KThread* waiting_thread, ResultCode wait_result,
+                    bool cancel_timer_task) override {
+        // Remove the thread as a waiter from its owner.
+        if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) {
+            owner->RemoveWaiter(waiting_thread);
+        }
+
+        // If the thread is waiting on a condvar, remove it from the tree.
+        if (waiting_thread->IsWaitingForConditionVariable()) {
+            m_tree->erase(m_tree->iterator_to(*waiting_thread));
+            waiting_thread->ClearConditionVariable();
+        }
+
+        // Invoke the base cancel wait handler.
+        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
+    }
+};
+
 } // namespace
 
 KConditionVariable::KConditionVariable(Core::System& system_)
@@ -78,84 +121,77 @@ ResultCode KConditionVariable::SignalToAddress(VAddr addr) {
 
         // Determine the next tag.
         u32 next_value{};
-        if (next_owner_thread) {
+        if (next_owner_thread != nullptr) {
             next_value = next_owner_thread->GetAddressKeyValue();
             if (num_waiters > 1) {
                 next_value |= Svc::HandleWaitMask;
             }
 
-            next_owner_thread->SetSyncedObject(nullptr, ResultSuccess);
-            next_owner_thread->Wakeup();
-        }
-
-        // Write the value to userspace.
-        if (!WriteToUser(system, addr, std::addressof(next_value))) {
-            if (next_owner_thread) {
-                next_owner_thread->SetSyncedObject(nullptr, ResultInvalidCurrentMemory);
+            // Write the value to userspace.
+            ResultCode result{ResultSuccess};
+            if (WriteToUser(system, addr, std::addressof(next_value))) [[likely]] {
+                result = ResultSuccess;
+            } else {
+                result = ResultInvalidCurrentMemory;
             }
 
-            return ResultInvalidCurrentMemory;
+            // Signal the next owner thread.
+            next_owner_thread->EndWait(result);
+            return result;
+        } else {
+            // Just write the value to userspace.
+            R_UNLESS(WriteToUser(system, addr, std::addressof(next_value)),
+                     ResultInvalidCurrentMemory);
+
+            return ResultSuccess;
         }
     }
-
-    return ResultSuccess;
 }
 
 ResultCode KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) {
     KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+    ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(kernel);
 
     // Wait for the address.
+    KThread* owner_thread{};
     {
-        KScopedAutoObject<KThread> owner_thread;
-        ASSERT(owner_thread.IsNull());
-        {
-            KScopedSchedulerLock sl(kernel);
-            cur_thread->SetSyncedObject(nullptr, ResultSuccess);
+        KScopedSchedulerLock sl(kernel);
 
-            // Check if the thread should terminate.
-            R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested);
+        // Check if the thread should terminate.
+        R_UNLESS(!cur_thread->IsTerminationRequested(), ResultTerminationRequested);
 
-            {
-                // Read the tag from userspace.
-                u32 test_tag{};
-                R_UNLESS(ReadFromUser(system, std::addressof(test_tag), addr),
-                         ResultInvalidCurrentMemory);
-
-                // If the tag isn't the handle (with wait mask), we're done.
-                R_UNLESS(test_tag == (handle | Svc::HandleWaitMask), ResultSuccess);
-
-                // Get the lock owner thread.
-                owner_thread =
-                    kernel.CurrentProcess()->GetHandleTable().GetObjectWithoutPseudoHandle<KThread>(
-                        handle);
-                R_UNLESS(owner_thread.IsNotNull(), ResultInvalidHandle);
-
-                // Update the lock.
-                cur_thread->SetAddressKey(addr, value);
-                owner_thread->AddWaiter(cur_thread);
-                cur_thread->SetState(ThreadState::Waiting);
-                cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
-                cur_thread->SetMutexWaitAddressForDebugging(addr);
-            }
-        }
-        ASSERT(owner_thread.IsNotNull());
-    }
+        // Read the tag from userspace.
+        u32 test_tag{};
+        R_UNLESS(ReadFromUser(system, std::addressof(test_tag), addr), ResultInvalidCurrentMemory);
 
-    // Remove the thread as a waiter from the lock owner.
-    {
-        KScopedSchedulerLock sl(kernel);
-        KThread* owner_thread = cur_thread->GetLockOwner();
-        if (owner_thread != nullptr) {
-            owner_thread->RemoveWaiter(cur_thread);
-        }
+        // If the tag isn't the handle (with wait mask), we're done.
+        R_SUCCEED_IF(test_tag != (handle | Svc::HandleWaitMask));
+
+        // Get the lock owner thread.
+        owner_thread = kernel.CurrentProcess()
+                           ->GetHandleTable()
+                           .GetObjectWithoutPseudoHandle<KThread>(handle)
+                           .ReleasePointerUnsafe();
+        R_UNLESS(owner_thread != nullptr, ResultInvalidHandle);
+
+        // Update the lock.
+        cur_thread->SetAddressKey(addr, value);
+        owner_thread->AddWaiter(cur_thread);
+
+        // Begin waiting.
+        cur_thread->BeginWait(std::addressof(wait_queue));
+        cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
+        cur_thread->SetMutexWaitAddressForDebugging(addr);
     }
 
+    // Close our reference to the owner thread, now that the wait is over.
+    owner_thread->Close();
+
     // Get the wait result.
-    KSynchronizationObject* dummy{};
-    return cur_thread->GetWaitResult(std::addressof(dummy));
+    return cur_thread->GetWaitResult();
 }
 
-KThread* KConditionVariable::SignalImpl(KThread* thread) {
+void KConditionVariable::SignalImpl(KThread* thread) {
     // Check pre-conditions.
     ASSERT(kernel.GlobalSchedulerContext().IsLocked());
 
@@ -169,18 +205,16 @@ KThread* KConditionVariable::SignalImpl(KThread* thread) {
         // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
         // TODO(bunnei): We should call CanAccessAtomic(..) here.
         can_access = true;
-        if (can_access) {
+        if (can_access) [[likely]] {
             UpdateLockAtomic(system, std::addressof(prev_tag), address, own_tag,
                              Svc::HandleWaitMask);
         }
     }
 
-    KThread* thread_to_close = nullptr;
-    if (can_access) {
+    if (can_access) [[likely]] {
         if (prev_tag == Svc::InvalidHandle) {
             // If nobody held the lock previously, we're all good.
-            thread->SetSyncedObject(nullptr, ResultSuccess);
-            thread->Wakeup();
+            thread->EndWait(ResultSuccess);
         } else {
             // Get the previous owner.
             KThread* owner_thread = kernel.CurrentProcess()
@@ -189,33 +223,22 @@ KThread* KConditionVariable::SignalImpl(KThread* thread) {
                                             static_cast<Handle>(prev_tag & ~Svc::HandleWaitMask))
                                         .ReleasePointerUnsafe();
 
-            if (owner_thread) {
+            if (owner_thread) [[likely]] {
                 // Add the thread as a waiter on the owner.
                 owner_thread->AddWaiter(thread);
-                thread_to_close = owner_thread;
+                owner_thread->Close();
             } else {
                 // The lock was tagged with a thread that doesn't exist.
-                thread->SetSyncedObject(nullptr, ResultInvalidState);
-                thread->Wakeup();
+                thread->EndWait(ResultInvalidState);
             }
         }
     } else {
         // If the address wasn't accessible, note so.
-        thread->SetSyncedObject(nullptr, ResultInvalidCurrentMemory);
-        thread->Wakeup();
+        thread->EndWait(ResultInvalidCurrentMemory);
     }
-
-    return thread_to_close;
 }
 
 void KConditionVariable::Signal(u64 cv_key, s32 count) {
-    // Prepare for signaling.
-    constexpr int MaxThreads = 16;
-
-    KLinkedList<KThread> thread_list{kernel};
-    std::array<KThread*, MaxThreads> thread_array;
-    s32 num_to_close{};
-
     // Perform signaling.
     s32 num_waiters{};
     {
@@ -226,14 +249,7 @@ void KConditionVariable::Signal(u64 cv_key, s32 count) {
                (it->GetConditionVariableKey() == cv_key)) {
             KThread* target_thread = std::addressof(*it);
 
-            if (KThread* thread = SignalImpl(target_thread); thread != nullptr) {
-                if (num_to_close < MaxThreads) {
-                    thread_array[num_to_close++] = thread;
-                } else {
-                    thread_list.push_back(*thread);
-                }
-            }
-
+            this->SignalImpl(target_thread);
             it = thread_tree.erase(it);
             target_thread->ClearConditionVariable();
             ++num_waiters;
@@ -245,27 +261,16 @@ void KConditionVariable::Signal(u64 cv_key, s32 count) {
             WriteToUser(system, cv_key, std::addressof(has_waiter_flag));
         }
     }
-
-    // Close threads in the array.
-    for (auto i = 0; i < num_to_close; ++i) {
-        thread_array[i]->Close();
-    }
-
-    // Close threads in the list.
-    for (auto it = thread_list.begin(); it != thread_list.end(); it = thread_list.erase(it)) {
-        (*it).Close();
-    }
 }
 
 ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout) {
     // Prepare to wait.
-    KThread* cur_thread = kernel.CurrentScheduler()->GetCurrentThread();
+    KThread* cur_thread = GetCurrentThreadPointer(kernel);
+    ThreadQueueImplForKConditionVariableWaitConditionVariable wait_queue(
+        kernel, std::addressof(thread_tree));
 
     {
-        KScopedSchedulerLockAndSleep slp{kernel, cur_thread, timeout};
-
-        // Set the synced object.
-        cur_thread->SetSyncedObject(nullptr, ResultTimedOut);
+        KScopedSchedulerLockAndSleep slp(kernel, cur_thread, timeout);
 
         // Check that the thread isn't terminating.
         if (cur_thread->IsTerminationRequested()) {
@@ -290,8 +295,7 @@ ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout)
                 }
 
                 // Wake up the next owner.
-                next_owner_thread->SetSyncedObject(nullptr, ResultSuccess);
-                next_owner_thread->Wakeup();
+                next_owner_thread->EndWait(ResultSuccess);
             }
 
             // Write to the cv key.
@@ -308,40 +312,21 @@ ResultCode KConditionVariable::Wait(VAddr addr, u64 key, u32 value, s64 timeout)
             }
         }
 
-        // Update condition variable tracking.
-        {
-            cur_thread->SetConditionVariable(std::addressof(thread_tree), addr, key, value);
-            thread_tree.insert(*cur_thread);
-        }
+        // If timeout is zero, time out.
+        R_UNLESS(timeout != 0, ResultTimedOut);
 
-        // If the timeout is non-zero, set the thread as waiting.
-        if (timeout != 0) {
-            cur_thread->SetState(ThreadState::Waiting);
-            cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
-            cur_thread->SetMutexWaitAddressForDebugging(addr);
-        }
-    }
-
-    // Cancel the timer wait.
-    kernel.TimeManager().UnscheduleTimeEvent(cur_thread);
-
-    // Remove from the condition variable.
-    {
-        KScopedSchedulerLock sl(kernel);
-
-        if (KThread* owner = cur_thread->GetLockOwner(); owner != nullptr) {
-            owner->RemoveWaiter(cur_thread);
-        }
+        // Update condition variable tracking.
+        cur_thread->SetConditionVariable(std::addressof(thread_tree), addr, key, value);
+        thread_tree.insert(*cur_thread);
 
-        if (cur_thread->IsWaitingForConditionVariable()) {
-            thread_tree.erase(thread_tree.iterator_to(*cur_thread));
-            cur_thread->ClearConditionVariable();
-        }
+        // Begin waiting.
+        cur_thread->BeginWait(std::addressof(wait_queue));
+        cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar);
+        cur_thread->SetMutexWaitAddressForDebugging(addr);
     }
 
-    // Get the result.
-    KSynchronizationObject* dummy{};
-    return cur_thread->GetWaitResult(std::addressof(dummy));
+    // Get the wait result.
+    return cur_thread->GetWaitResult();
 }
 
 } // namespace Kernel

+ 1 - 1
src/core/hle/kernel/k_condition_variable.h

@@ -34,7 +34,7 @@ public:
     [[nodiscard]] ResultCode Wait(VAddr addr, u64 key, u32 value, s64 timeout);
 
 private:
-    [[nodiscard]] KThread* SignalImpl(KThread* thread);
+    void SignalImpl(KThread* thread);
 
     ThreadTree thread_tree;
 

+ 6 - 0
src/core/hle/kernel/k_handle_table.cpp

@@ -13,6 +13,7 @@ ResultCode KHandleTable::Finalize() {
     // Get the table and clear our record of it.
     u16 saved_table_size = 0;
     {
+        KScopedDisableDispatch dd(kernel);
         KScopedSpinLock lk(m_lock);
 
         std::swap(m_table_size, saved_table_size);
@@ -43,6 +44,7 @@ bool KHandleTable::Remove(Handle handle) {
     // Find the object and free the entry.
     KAutoObject* obj = nullptr;
     {
+        KScopedDisableDispatch dd(kernel);
         KScopedSpinLock lk(m_lock);
 
         if (this->IsValidHandle(handle)) {
@@ -62,6 +64,7 @@ bool KHandleTable::Remove(Handle handle) {
 }
 
 ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) {
+    KScopedDisableDispatch dd(kernel);
     KScopedSpinLock lk(m_lock);
 
     // Never exceed our capacity.
@@ -84,6 +87,7 @@ ResultCode KHandleTable::Add(Handle* out_handle, KAutoObject* obj, u16 type) {
 }
 
 ResultCode KHandleTable::Reserve(Handle* out_handle) {
+    KScopedDisableDispatch dd(kernel);
     KScopedSpinLock lk(m_lock);
 
     // Never exceed our capacity.
@@ -94,6 +98,7 @@ ResultCode KHandleTable::Reserve(Handle* out_handle) {
 }
 
 void KHandleTable::Unreserve(Handle handle) {
+    KScopedDisableDispatch dd(kernel);
     KScopedSpinLock lk(m_lock);
 
     // Unpack the handle.
@@ -112,6 +117,7 @@ void KHandleTable::Unreserve(Handle handle) {
 }
 
 void KHandleTable::Register(Handle handle, KAutoObject* obj, u16 type) {
+    KScopedDisableDispatch dd(kernel);
     KScopedSpinLock lk(m_lock);
 
     // Unpack the handle.

+ 2 - 0
src/core/hle/kernel/k_handle_table.h

@@ -68,6 +68,7 @@ public:
     template <typename T = KAutoObject>
     KScopedAutoObject<T> GetObjectWithoutPseudoHandle(Handle handle) const {
         // Lock and look up in table.
+        KScopedDisableDispatch dd(kernel);
         KScopedSpinLock lk(m_lock);
 
         if constexpr (std::is_same_v<T, KAutoObject>) {
@@ -122,6 +123,7 @@ public:
         size_t num_opened;
         {
             // Lock the table.
+            KScopedDisableDispatch dd(kernel);
             KScopedSpinLock lk(m_lock);
             for (num_opened = 0; num_opened < num_handles; num_opened++) {
                 // Get the current handle.

+ 80 - 0
src/core/hle/kernel/k_light_condition_variable.cpp

@@ -0,0 +1,80 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/k_light_condition_variable.h"
+#include "core/hle/kernel/k_scheduler.h"
+#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
+#include "core/hle/kernel/k_thread_queue.h"
+#include "core/hle/kernel/svc_results.h"
+
+namespace Kernel {
+
+namespace {
+
+class ThreadQueueImplForKLightConditionVariable final : public KThreadQueue {
+public:
+    ThreadQueueImplForKLightConditionVariable(KernelCore& kernel_, KThread::WaiterList* wl,
+                                              bool term)
+        : KThreadQueue(kernel_), m_wait_list(wl), m_allow_terminating_thread(term) {}
+
+    void CancelWait(KThread* waiting_thread, ResultCode wait_result,
+                    bool cancel_timer_task) override {
+        // Only process waits if we're allowed to.
+        if (ResultTerminationRequested == wait_result && m_allow_terminating_thread) {
+            return;
+        }
+
+        // Remove the thread from the waiting thread from the light condition variable.
+        m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));
+
+        // Invoke the base cancel wait handler.
+        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
+    }
+
+private:
+    KThread::WaiterList* m_wait_list;
+    bool m_allow_terminating_thread;
+};
+
+} // namespace
+
+void KLightConditionVariable::Wait(KLightLock* lock, s64 timeout, bool allow_terminating_thread) {
+    // Create thread queue.
+    KThread* owner = GetCurrentThreadPointer(kernel);
+
+    ThreadQueueImplForKLightConditionVariable wait_queue(kernel, std::addressof(wait_list),
+                                                         allow_terminating_thread);
+
+    // Sleep the thread.
+    {
+        KScopedSchedulerLockAndSleep lk(kernel, owner, timeout);
+
+        if (!allow_terminating_thread && owner->IsTerminationRequested()) {
+            lk.CancelSleep();
+            return;
+        }
+
+        lock->Unlock();
+
+        // Add the thread to the queue.
+        wait_list.push_back(*owner);
+
+        // Begin waiting.
+        owner->BeginWait(std::addressof(wait_queue));
+    }
+
+    // Re-acquire the lock.
+    lock->Lock();
+}
+
+void KLightConditionVariable::Broadcast() {
+    KScopedSchedulerLock lk(kernel);
+
+    // Signal all threads.
+    for (auto it = wait_list.begin(); it != wait_list.end(); it = wait_list.erase(it)) {
+        it->EndWait(ResultSuccess);
+    }
+}
+
+} // namespace Kernel

+ 5 - 53
src/core/hle/kernel/k_light_condition_variable.h

@@ -2,72 +2,24 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-// This file references various implementation details from Atmosphere, an open-source firmware for
-// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX.
-
 #pragma once
 
 #include "common/common_types.h"
-#include "core/hle/kernel/k_scheduler.h"
-#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
-#include "core/hle/kernel/time_manager.h"
+#include "core/hle/kernel/k_thread.h"
 
 namespace Kernel {
+
 class KernelCore;
+class KLightLock;
 
 class KLightConditionVariable {
 public:
     explicit KLightConditionVariable(KernelCore& kernel_) : kernel{kernel_} {}
 
-    void Wait(KLightLock* lock, s64 timeout = -1, bool allow_terminating_thread = true) {
-        WaitImpl(lock, timeout, allow_terminating_thread);
-    }
-
-    void Broadcast() {
-        KScopedSchedulerLock lk{kernel};
-
-        // Signal all threads.
-        for (auto& thread : wait_list) {
-            thread.SetState(ThreadState::Runnable);
-        }
-    }
+    void Wait(KLightLock* lock, s64 timeout = -1, bool allow_terminating_thread = true);
+    void Broadcast();
 
 private:
-    void WaitImpl(KLightLock* lock, s64 timeout, bool allow_terminating_thread) {
-        KThread* owner = GetCurrentThreadPointer(kernel);
-
-        // Sleep the thread.
-        {
-            KScopedSchedulerLockAndSleep lk{kernel, owner, timeout};
-
-            if (!allow_terminating_thread && owner->IsTerminationRequested()) {
-                lk.CancelSleep();
-                return;
-            }
-
-            lock->Unlock();
-
-            // Set the thread as waiting.
-            GetCurrentThread(kernel).SetState(ThreadState::Waiting);
-
-            // Add the thread to the queue.
-            wait_list.push_back(GetCurrentThread(kernel));
-        }
-
-        // Remove the thread from the wait list.
-        {
-            KScopedSchedulerLock sl{kernel};
-
-            wait_list.erase(wait_list.iterator_to(GetCurrentThread(kernel)));
-        }
-
-        // Cancel the task that the sleep setup.
-        kernel.TimeManager().UnscheduleTimeEvent(owner);
-
-        // Re-acquire the lock.
-        lock->Lock();
-    }
-
     KernelCore& kernel;
     KThread::WaiterList wait_list{};
 };

+ 39 - 33
src/core/hle/kernel/k_light_lock.cpp

@@ -5,44 +5,59 @@
 #include "core/hle/kernel/k_light_lock.h"
 #include "core/hle/kernel/k_scheduler.h"
 #include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_thread_queue.h"
 #include "core/hle/kernel/kernel.h"
 
 namespace Kernel {
 
+namespace {
+
+class ThreadQueueImplForKLightLock final : public KThreadQueue {
+public:
+    explicit ThreadQueueImplForKLightLock(KernelCore& kernel_) : KThreadQueue(kernel_) {}
+
+    void CancelWait(KThread* waiting_thread, ResultCode wait_result,
+                    bool cancel_timer_task) override {
+        // Remove the thread as a waiter from its owner.
+        if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) {
+            owner->RemoveWaiter(waiting_thread);
+        }
+
+        // Invoke the base cancel wait handler.
+        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
+    }
+};
+
+} // namespace
+
 void KLightLock::Lock() {
     const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel));
-    const uintptr_t cur_thread_tag = (cur_thread | 1);
 
     while (true) {
         uintptr_t old_tag = tag.load(std::memory_order_relaxed);
 
-        while (!tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : old_tag | 1,
+        while (!tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : (old_tag | 1),
                                           std::memory_order_acquire)) {
-            if ((old_tag | 1) == cur_thread_tag) {
-                return;
-            }
         }
 
-        if ((old_tag == 0) || ((old_tag | 1) == cur_thread_tag)) {
+        if (old_tag == 0 || this->LockSlowPath(old_tag | 1, cur_thread)) {
             break;
         }
-
-        LockSlowPath(old_tag | 1, cur_thread);
     }
 }
 
 void KLightLock::Unlock() {
     const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer(kernel));
+
     uintptr_t expected = cur_thread;
-    do {
-        if (expected != cur_thread) {
-            return UnlockSlowPath(cur_thread);
-        }
-    } while (!tag.compare_exchange_weak(expected, 0, std::memory_order_release));
+    if (!tag.compare_exchange_strong(expected, 0, std::memory_order_release)) {
+        this->UnlockSlowPath(cur_thread);
+    }
 }
 
-void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
+bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
     KThread* cur_thread = reinterpret_cast<KThread*>(_cur_thread);
+    ThreadQueueImplForKLightLock wait_queue(kernel);
 
     // Pend the current thread waiting on the owner thread.
     {
@@ -50,7 +65,7 @@ void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
 
         // Ensure we actually have locking to do.
         if (tag.load(std::memory_order_relaxed) != _owner) {
-            return;
+            return false;
         }
 
         // Add the current thread as a waiter on the owner.
@@ -58,22 +73,15 @@ void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
         cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag)));
         owner_thread->AddWaiter(cur_thread);
 
-        // Set thread states.
-        cur_thread->SetState(ThreadState::Waiting);
+        // Begin waiting to hold the lock.
+        cur_thread->BeginWait(std::addressof(wait_queue));
 
         if (owner_thread->IsSuspended()) {
             owner_thread->ContinueIfHasKernelWaiters();
         }
     }
 
-    // We're no longer waiting on the lock owner.
-    {
-        KScopedSchedulerLock sl{kernel};
-
-        if (KThread* owner_thread = cur_thread->GetLockOwner(); owner_thread != nullptr) {
-            owner_thread->RemoveWaiter(cur_thread);
-        }
-    }
+    return true;
 }
 
 void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
@@ -81,22 +89,20 @@ void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
 
     // Unlock.
     {
-        KScopedSchedulerLock sl{kernel};
+        KScopedSchedulerLock sl(kernel);
 
         // Get the next owner.
-        s32 num_waiters = 0;
+        s32 num_waiters;
         KThread* next_owner = owner_thread->RemoveWaiterByKey(
             std::addressof(num_waiters), reinterpret_cast<uintptr_t>(std::addressof(tag)));
 
         // Pass the lock to the next owner.
         uintptr_t next_tag = 0;
         if (next_owner != nullptr) {
-            next_tag = reinterpret_cast<uintptr_t>(next_owner);
-            if (num_waiters > 1) {
-                next_tag |= 0x1;
-            }
+            next_tag =
+                reinterpret_cast<uintptr_t>(next_owner) | static_cast<uintptr_t>(num_waiters > 1);
 
-            next_owner->SetState(ThreadState::Runnable);
+            next_owner->EndWait(ResultSuccess);
 
             if (next_owner->IsSuspended()) {
                 next_owner->ContinueIfHasKernelWaiters();
@@ -110,7 +116,7 @@ void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
         }
 
         // Write the new tag value.
-        tag.store(next_tag);
+        tag.store(next_tag, std::memory_order_release);
     }
 }
 

+ 1 - 1
src/core/hle/kernel/k_light_lock.h

@@ -20,7 +20,7 @@ public:
 
     void Unlock();
 
-    void LockSlowPath(uintptr_t owner, uintptr_t cur_thread);
+    bool LockSlowPath(uintptr_t owner, uintptr_t cur_thread);
 
     void UnlockSlowPath(uintptr_t cur_thread);
 

+ 20 - 0
src/core/hle/kernel/k_memory_block.h

@@ -131,6 +131,26 @@ enum class KMemoryPermission : u8 {
 
     UserMask = static_cast<u8>(Svc::MemoryPermission::Read | Svc::MemoryPermission::Write |
                                Svc::MemoryPermission::Execute),
+
+    KernelShift = 3,
+
+    KernelRead = Read << KernelShift,
+    KernelWrite = Write << KernelShift,
+    KernelExecute = Execute << KernelShift,
+
+    NotMapped = (1 << (2 * KernelShift)),
+
+    KernelReadWrite = KernelRead | KernelWrite,
+    KernelReadExecute = KernelRead | KernelExecute,
+
+    UserRead = Read | KernelRead,
+    UserWrite = Write | KernelWrite,
+    UserExecute = Execute,
+
+    UserReadWrite = UserRead | UserWrite,
+    UserReadExecute = UserRead | UserExecute,
+
+    IpcLockChangeMask = NotMapped | UserReadWrite
 };
 DECLARE_ENUM_FLAG_OPERATORS(KMemoryPermission);
 

+ 4 - 0
src/core/hle/kernel/k_page_linked_list.h

@@ -27,6 +27,10 @@ public:
             return num_pages;
         }
 
+        constexpr std::size_t GetSize() const {
+            return GetNumPages() * PageSize;
+        }
+
     private:
         u64 addr{};
         std::size_t num_pages{};

+ 121 - 2
src/core/hle/kernel/k_page_table.cpp

@@ -368,6 +368,33 @@ ResultCode KPageTable::UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, st
     return ResultSuccess;
 }
 
+ResultCode KPageTable::UnmapProcessMemory(VAddr dst_addr, std::size_t size,
+                                          KPageTable& src_page_table, VAddr src_addr) {
+    std::lock_guard lock{page_table_lock};
+
+    const std::size_t num_pages{size / PageSize};
+
+    // Check that the memory is mapped in the destination process.
+    size_t num_allocator_blocks;
+    R_TRY(CheckMemoryState(&num_allocator_blocks, dst_addr, size, KMemoryState::All,
+                           KMemoryState::SharedCode, KMemoryPermission::UserReadWrite,
+                           KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
+                           KMemoryAttribute::None));
+
+    // Check that the memory is mapped in the source process.
+    R_TRY(src_page_table.CheckMemoryState(src_addr, size, KMemoryState::FlagCanMapProcess,
+                                          KMemoryState::FlagCanMapProcess, KMemoryPermission::None,
+                                          KMemoryPermission::None, KMemoryAttribute::All,
+                                          KMemoryAttribute::None));
+
+    CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap));
+
+    // Apply the memory block update.
+    block_manager->Update(dst_addr, num_pages, KMemoryState::Free, KMemoryPermission::None,
+                          KMemoryAttribute::None);
+
+    return ResultSuccess;
+}
 void KPageTable::MapPhysicalMemory(KPageLinkedList& page_linked_list, VAddr start, VAddr end) {
     auto node{page_linked_list.Nodes().begin()};
     PAddr map_addr{node->GetAddress()};
@@ -685,8 +712,8 @@ ResultCode KPageTable::UnmapPages(VAddr addr, KPageLinkedList& page_linked_list,
     return ResultSuccess;
 }
 
-ResultCode KPageTable::SetCodeMemoryPermission(VAddr addr, std::size_t size,
-                                               KMemoryPermission perm) {
+ResultCode KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size,
+                                                  KMemoryPermission perm) {
 
     std::lock_guard lock{page_table_lock};
 
@@ -942,6 +969,60 @@ ResultCode KPageTable::UnlockForDeviceAddressSpace(VAddr addr, std::size_t size)
     return ResultSuccess;
 }
 
+ResultCode KPageTable::LockForCodeMemory(VAddr addr, std::size_t size) {
+    std::lock_guard lock{page_table_lock};
+
+    KMemoryPermission new_perm = KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite;
+
+    KMemoryPermission old_perm{};
+
+    if (const ResultCode result{CheckMemoryState(
+            nullptr, &old_perm, nullptr, addr, size, KMemoryState::FlagCanCodeMemory,
+            KMemoryState::FlagCanCodeMemory, KMemoryPermission::Mask,
+            KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None)};
+        result.IsError()) {
+        return result;
+    }
+
+    new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
+
+    block_manager->UpdateLock(
+        addr, size / PageSize,
+        [](KMemoryBlockManager::iterator block, KMemoryPermission permission) {
+            block->ShareToDevice(permission);
+        },
+        new_perm);
+
+    return ResultSuccess;
+}
+
+ResultCode KPageTable::UnlockForCodeMemory(VAddr addr, std::size_t size) {
+    std::lock_guard lock{page_table_lock};
+
+    KMemoryPermission new_perm = KMemoryPermission::UserReadWrite;
+
+    KMemoryPermission old_perm{};
+
+    if (const ResultCode result{CheckMemoryState(
+            nullptr, &old_perm, nullptr, addr, size, KMemoryState::FlagCanCodeMemory,
+            KMemoryState::FlagCanCodeMemory, KMemoryPermission::None, KMemoryPermission::None,
+            KMemoryAttribute::All, KMemoryAttribute::Locked)};
+        result.IsError()) {
+        return result;
+    }
+
+    new_perm = (new_perm != KMemoryPermission::None) ? new_perm : old_perm;
+
+    block_manager->UpdateLock(
+        addr, size / PageSize,
+        [](KMemoryBlockManager::iterator block, KMemoryPermission permission) {
+            block->UnshareToDevice(permission);
+        },
+        new_perm);
+
+    return ResultSuccess;
+}
+
 ResultCode KPageTable::InitializeMemoryLayout(VAddr start, VAddr end) {
     block_manager = std::make_unique<KMemoryBlockManager>(start, end);
 
@@ -1231,4 +1312,42 @@ ResultCode KPageTable::CheckMemoryState(KMemoryState* out_state, KMemoryPermissi
     return ResultSuccess;
 }
 
+ResultCode KPageTable::CheckMemoryState(size_t* out_blocks_needed, VAddr addr, size_t size,
+                                        KMemoryState state_mask, KMemoryState state,
+                                        KMemoryPermission perm_mask, KMemoryPermission perm,
+                                        KMemoryAttribute attr_mask, KMemoryAttribute attr) const {
+    // Get information about the first block.
+    const VAddr last_addr = addr + size - 1;
+    KMemoryBlockManager::const_iterator it{block_manager->FindIterator(addr)};
+    KMemoryInfo info = it->GetMemoryInfo();
+
+    // If the start address isn't aligned, we need a block.
+    const size_t blocks_for_start_align =
+        (Common::AlignDown(addr, PageSize) != info.GetAddress()) ? 1 : 0;
+
+    while (true) {
+        // Validate against the provided masks.
+        R_TRY(CheckMemoryState(info, state_mask, state, perm_mask, perm, attr_mask, attr));
+
+        // Break once we're done.
+        if (last_addr <= info.GetLastAddress()) {
+            break;
+        }
+
+        // Advance our iterator.
+        it++;
+        info = it->GetMemoryInfo();
+    }
+
+    // If the end address isn't aligned, we need a block.
+    const size_t blocks_for_end_align =
+        (Common::AlignUp(addr + size, PageSize) != info.GetEndAddress()) ? 1 : 0;
+
+    if (out_blocks_needed != nullptr) {
+        *out_blocks_needed = blocks_for_start_align + blocks_for_end_align;
+    }
+
+    return ResultSuccess;
+}
+
 } // namespace Kernel

+ 9 - 1
src/core/hle/kernel/k_page_table.h

@@ -33,6 +33,8 @@ public:
                               KMemoryPermission perm);
     ResultCode MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
     ResultCode UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
+    ResultCode UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table,
+                                  VAddr src_addr);
     ResultCode MapPhysicalMemory(VAddr addr, std::size_t size);
     ResultCode UnmapPhysicalMemory(VAddr addr, std::size_t size);
     ResultCode UnmapMemory(VAddr addr, std::size_t size);
@@ -41,7 +43,7 @@ public:
     ResultCode MapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state,
                         KMemoryPermission perm);
     ResultCode UnmapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state);
-    ResultCode SetCodeMemoryPermission(VAddr addr, std::size_t size, KMemoryPermission perm);
+    ResultCode SetProcessMemoryPermission(VAddr addr, std::size_t size, KMemoryPermission perm);
     KMemoryInfo QueryInfo(VAddr addr);
     ResultCode ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm);
     ResultCode ResetTransferMemory(VAddr addr, std::size_t size);
@@ -55,6 +57,8 @@ public:
                                           KMemoryPermission perm, PAddr map_addr = 0);
     ResultCode LockForDeviceAddressSpace(VAddr addr, std::size_t size);
     ResultCode UnlockForDeviceAddressSpace(VAddr addr, std::size_t size);
+    ResultCode LockForCodeMemory(VAddr addr, std::size_t size);
+    ResultCode UnlockForCodeMemory(VAddr addr, std::size_t size);
 
     Common::PageTable& PageTableImpl() {
         return page_table_impl;
@@ -115,6 +119,10 @@ private:
         return CheckMemoryState(nullptr, nullptr, nullptr, addr, size, state_mask, state, perm_mask,
                                 perm, attr_mask, attr, ignore_attr);
     }
+    ResultCode CheckMemoryState(size_t* out_blocks_needed, VAddr addr, size_t size,
+                                KMemoryState state_mask, KMemoryState state,
+                                KMemoryPermission perm_mask, KMemoryPermission perm,
+                                KMemoryAttribute attr_mask, KMemoryAttribute attr) const;
 
     std::recursive_mutex page_table_lock;
     std::unique_ptr<KMemoryBlockManager> block_manager;

+ 24 - 6
src/core/hle/kernel/k_process.cpp

@@ -60,6 +60,7 @@ void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority
     thread->GetContext64().cpu_registers[0] = 0;
     thread->GetContext32().cpu_registers[1] = thread_handle;
     thread->GetContext64().cpu_registers[1] = thread_handle;
+    thread->DisableDispatch();
 
     auto& kernel = system.Kernel();
     // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires
@@ -227,12 +228,15 @@ void KProcess::PinCurrentThread() {
     const s32 core_id = GetCurrentCoreId(kernel);
     KThread* cur_thread = GetCurrentThreadPointer(kernel);
 
-    // Pin it.
-    PinThread(core_id, cur_thread);
-    cur_thread->Pin();
+    // If the thread isn't terminated, pin it.
+    if (!cur_thread->IsTerminationRequested()) {
+        // Pin it.
+        PinThread(core_id, cur_thread);
+        cur_thread->Pin();
 
-    // An update is needed.
-    KScheduler::SetSchedulerUpdateNeeded(kernel);
+        // An update is needed.
+        KScheduler::SetSchedulerUpdateNeeded(kernel);
+    }
 }
 
 void KProcess::UnpinCurrentThread() {
@@ -250,6 +254,20 @@ void KProcess::UnpinCurrentThread() {
     KScheduler::SetSchedulerUpdateNeeded(kernel);
 }
 
+void KProcess::UnpinThread(KThread* thread) {
+    ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+
+    // Get the thread's core id.
+    const auto core_id = thread->GetActiveCore();
+
+    // Unpin it.
+    UnpinThread(core_id, thread);
+    thread->Unpin();
+
+    // An update is needed.
+    KScheduler::SetSchedulerUpdateNeeded(kernel);
+}
+
 ResultCode KProcess::AddSharedMemory(KSharedMemory* shmem, [[maybe_unused]] VAddr address,
                                      [[maybe_unused]] size_t size) {
     // Lock ourselves, to prevent concurrent access.
@@ -528,7 +546,7 @@ void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) {
     std::lock_guard lock{HLE::g_hle_lock};
     const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
                                       KMemoryPermission permission) {
-        page_table->SetCodeMemoryPermission(segment.addr + base_addr, segment.size, permission);
+        page_table->SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
     };
 
     kernel.System().Memory().WriteBlock(*this, base_addr, code_set.memory.data(),

+ 1 - 0
src/core/hle/kernel/k_process.h

@@ -347,6 +347,7 @@ public:
 
     void PinCurrentThread();
     void UnpinCurrentThread();
+    void UnpinThread(KThread* thread);
 
     KLightLock& GetStateLock() {
         return state_lock;

+ 60 - 41
src/core/hle/kernel/k_scheduler.cpp

@@ -240,8 +240,8 @@ void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, KThread* thread, s3
 
     // If the thread is runnable, we want to change its priority in the queue.
     if (thread->GetRawState() == ThreadState::Runnable) {
-        GetPriorityQueue(kernel).ChangePriority(
-            old_priority, thread == kernel.CurrentScheduler()->GetCurrentThread(), thread);
+        GetPriorityQueue(kernel).ChangePriority(old_priority,
+                                                thread == kernel.GetCurrentEmuThread(), thread);
         IncrementScheduledCount(thread);
         SetSchedulerUpdateNeeded(kernel);
     }
@@ -360,7 +360,7 @@ void KScheduler::RotateScheduledQueue(s32 cpu_core_id, s32 priority) {
 }
 
 bool KScheduler::CanSchedule(KernelCore& kernel) {
-    return kernel.CurrentScheduler()->GetCurrentThread()->GetDisableDispatchCount() <= 1;
+    return kernel.GetCurrentEmuThread()->GetDisableDispatchCount() <= 1;
 }
 
 bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) {
@@ -376,20 +376,30 @@ void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) {
 }
 
 void KScheduler::DisableScheduling(KernelCore& kernel) {
-    if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
-        ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 0);
-        scheduler->GetCurrentThread()->DisableDispatch();
+    // If we are shutting down the kernel, none of this is relevant anymore.
+    if (kernel.IsShuttingDown()) {
+        return;
     }
+
+    ASSERT(GetCurrentThreadPointer(kernel)->GetDisableDispatchCount() >= 0);
+    GetCurrentThreadPointer(kernel)->DisableDispatch();
 }
 
 void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) {
-    if (auto* scheduler = kernel.CurrentScheduler(); scheduler) {
-        ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1);
-        if (scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 1) {
-            scheduler->GetCurrentThread()->EnableDispatch();
-        }
+    // If we are shutting down the kernel, none of this is relevant anymore.
+    if (kernel.IsShuttingDown()) {
+        return;
+    }
+
+    auto* current_thread = GetCurrentThreadPointer(kernel);
+
+    ASSERT(current_thread->GetDisableDispatchCount() >= 1);
+
+    if (current_thread->GetDisableDispatchCount() > 1) {
+        current_thread->EnableDispatch();
+    } else {
+        RescheduleCores(kernel, cores_needing_scheduling);
     }
-    RescheduleCores(kernel, cores_needing_scheduling);
 }
 
 u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) {
@@ -617,13 +627,17 @@ KScheduler::KScheduler(Core::System& system_, s32 core_id_) : system{system_}, c
     state.highest_priority_thread = nullptr;
 }
 
-KScheduler::~KScheduler() {
+void KScheduler::Finalize() {
     if (idle_thread) {
         idle_thread->Close();
         idle_thread = nullptr;
     }
 }
 
+KScheduler::~KScheduler() {
+    ASSERT(!idle_thread);
+}
+
 KThread* KScheduler::GetCurrentThread() const {
     if (auto result = current_thread.load(); result) {
         return result;
@@ -642,10 +656,12 @@ void KScheduler::RescheduleCurrentCore() {
     if (phys_core.IsInterrupted()) {
         phys_core.ClearInterrupt();
     }
+
     guard.Lock();
     if (state.needs_scheduling.load()) {
         Schedule();
     } else {
+        GetCurrentThread()->EnableDispatch();
         guard.Unlock();
     }
 }
@@ -655,26 +671,33 @@ void KScheduler::OnThreadStart() {
 }
 
 void KScheduler::Unload(KThread* thread) {
+    ASSERT(thread);
+
     LOG_TRACE(Kernel, "core {}, unload thread {}", core_id, thread ? thread->GetName() : "nullptr");
 
-    if (thread) {
-        if (thread->IsCallingSvc()) {
-            thread->ClearIsCallingSvc();
-        }
-        if (!thread->IsTerminationRequested()) {
-            prev_thread = thread;
-
-            Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
-            cpu_core.SaveContext(thread->GetContext32());
-            cpu_core.SaveContext(thread->GetContext64());
-            // Save the TPIDR_EL0 system register in case it was modified.
-            thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
-            cpu_core.ClearExclusiveState();
-        } else {
-            prev_thread = nullptr;
-        }
-        thread->context_guard.Unlock();
+    if (thread->IsCallingSvc()) {
+        thread->ClearIsCallingSvc();
+    }
+
+    auto& physical_core = system.Kernel().PhysicalCore(core_id);
+    if (!physical_core.IsInitialized()) {
+        return;
+    }
+
+    Core::ARM_Interface& cpu_core = physical_core.ArmInterface();
+    cpu_core.SaveContext(thread->GetContext32());
+    cpu_core.SaveContext(thread->GetContext64());
+    // Save the TPIDR_EL0 system register in case it was modified.
+    thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0());
+    cpu_core.ClearExclusiveState();
+
+    if (!thread->IsTerminationRequested() && thread->GetActiveCore() == core_id) {
+        prev_thread = thread;
+    } else {
+        prev_thread = nullptr;
     }
+
+    thread->context_guard.Unlock();
 }
 
 void KScheduler::Reload(KThread* thread) {
@@ -683,11 +706,6 @@ void KScheduler::Reload(KThread* thread) {
     if (thread) {
         ASSERT_MSG(thread->GetState() == ThreadState::Runnable, "Thread must be runnable.");
 
-        auto* const thread_owner_process = thread->GetOwnerProcess();
-        if (thread_owner_process != nullptr) {
-            system.Kernel().MakeCurrentProcess(thread_owner_process);
-        }
-
         Core::ARM_Interface& cpu_core = system.ArmInterface(core_id);
         cpu_core.LoadContext(thread->GetContext32());
         cpu_core.LoadContext(thread->GetContext64());
@@ -705,7 +723,7 @@ void KScheduler::SwitchContextStep2() {
 }
 
 void KScheduler::ScheduleImpl() {
-    KThread* previous_thread = current_thread.load();
+    KThread* previous_thread = GetCurrentThread();
     KThread* next_thread = state.highest_priority_thread;
 
     state.needs_scheduling = false;
@@ -717,10 +735,15 @@ void KScheduler::ScheduleImpl() {
 
     // If we're not actually switching thread, there's nothing to do.
     if (next_thread == current_thread.load()) {
+        previous_thread->EnableDispatch();
         guard.Unlock();
         return;
     }
 
+    if (next_thread->GetCurrentCore() != core_id) {
+        next_thread->SetCurrentCore(core_id);
+    }
+
     current_thread.store(next_thread);
 
     KProcess* const previous_process = system.Kernel().CurrentProcess();
@@ -731,11 +754,7 @@ void KScheduler::ScheduleImpl() {
     Unload(previous_thread);
 
     std::shared_ptr<Common::Fiber>* old_context;
-    if (previous_thread != nullptr) {
-        old_context = &previous_thread->GetHostContext();
-    } else {
-        old_context = &idle_thread->GetHostContext();
-    }
+    old_context = &previous_thread->GetHostContext();
     guard.Unlock();
 
     Common::Fiber::YieldTo(*old_context, *switch_fiber);

+ 2 - 0
src/core/hle/kernel/k_scheduler.h

@@ -33,6 +33,8 @@ public:
     explicit KScheduler(Core::System& system_, s32 core_id_);
     ~KScheduler();
 
+    void Finalize();
+
     /// Reschedules to the next available thread (call after current thread is suspended)
     void RescheduleCurrentCore();
 

+ 10 - 0
src/core/hle/kernel/k_scheduler_lock.h

@@ -23,6 +23,11 @@ public:
     }
 
     void Lock() {
+        // If we are shutting down the kernel, none of this is relevant anymore.
+        if (kernel.IsShuttingDown()) {
+            return;
+        }
+
         if (IsLockedByCurrentThread()) {
             // If we already own the lock, we can just increment the count.
             ASSERT(lock_count > 0);
@@ -43,6 +48,11 @@ public:
     }
 
     void Unlock() {
+        // If we are shutting down the kernel, none of this is relevant anymore.
+        if (kernel.IsShuttingDown()) {
+            return;
+        }
+
         ASSERT(IsLockedByCurrentThread());
         ASSERT(lock_count > 0);
 

+ 1 - 0
src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h

@@ -8,6 +8,7 @@
 #pragma once
 
 #include "common/common_types.h"
+#include "core/hle/kernel/global_scheduler_context.h"
 #include "core/hle/kernel/k_thread.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/time_manager.h"

+ 1 - 2
src/core/hle/kernel/k_server_session.cpp

@@ -175,8 +175,7 @@ ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) {
     {
         KScopedSchedulerLock lock(kernel);
         if (!context.IsThreadWaiting()) {
-            context.GetThread().Wakeup();
-            context.GetThread().SetSyncedObject(nullptr, result);
+            context.GetThread().EndWait(result);
         }
     }
 

+ 78 - 73
src/core/hle/kernel/k_synchronization_object.cpp

@@ -8,11 +8,66 @@
 #include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h"
 #include "core/hle/kernel/k_synchronization_object.h"
 #include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_thread_queue.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/svc_results.h"
 
 namespace Kernel {
 
+namespace {
+
+class ThreadQueueImplForKSynchronizationObjectWait final : public KThreadQueueWithoutEndWait {
+public:
+    ThreadQueueImplForKSynchronizationObjectWait(KernelCore& kernel_, KSynchronizationObject** o,
+                                                 KSynchronizationObject::ThreadListNode* n, s32 c)
+        : KThreadQueueWithoutEndWait(kernel_), m_objects(o), m_nodes(n), m_count(c) {}
+
+    void NotifyAvailable(KThread* waiting_thread, KSynchronizationObject* signaled_object,
+                         ResultCode wait_result) override {
+        // Determine the sync index, and unlink all nodes.
+        s32 sync_index = -1;
+        for (auto i = 0; i < m_count; ++i) {
+            // Check if this is the signaled object.
+            if (m_objects[i] == signaled_object && sync_index == -1) {
+                sync_index = i;
+            }
+
+            // Unlink the current node from the current object.
+            m_objects[i]->UnlinkNode(std::addressof(m_nodes[i]));
+        }
+
+        // Set the waiting thread's sync index.
+        waiting_thread->SetSyncedIndex(sync_index);
+
+        // Set the waiting thread as not cancellable.
+        waiting_thread->ClearCancellable();
+
+        // Invoke the base end wait handler.
+        KThreadQueue::EndWait(waiting_thread, wait_result);
+    }
+
+    void CancelWait(KThread* waiting_thread, ResultCode wait_result,
+                    bool cancel_timer_task) override {
+        // Remove all nodes from our list.
+        for (auto i = 0; i < m_count; ++i) {
+            m_objects[i]->UnlinkNode(std::addressof(m_nodes[i]));
+        }
+
+        // Set the waiting thread as not cancellable.
+        waiting_thread->ClearCancellable();
+
+        // Invoke the base cancel wait handler.
+        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
+    }
+
+private:
+    KSynchronizationObject** m_objects;
+    KSynchronizationObject::ThreadListNode* m_nodes;
+    s32 m_count;
+};
+
+} // namespace
+
 void KSynchronizationObject::Finalize() {
     this->OnFinalizeSynchronizationObject();
     KAutoObject::Finalize();
@@ -25,11 +80,19 @@ ResultCode KSynchronizationObject::Wait(KernelCore& kernel_ctx, s32* out_index,
     std::vector<ThreadListNode> thread_nodes(num_objects);
 
     // Prepare for wait.
-    KThread* thread = kernel_ctx.CurrentScheduler()->GetCurrentThread();
+    KThread* thread = GetCurrentThreadPointer(kernel_ctx);
+    ThreadQueueImplForKSynchronizationObjectWait wait_queue(kernel_ctx, objects,
+                                                            thread_nodes.data(), num_objects);
 
     {
         // Setup the scheduling lock and sleep.
-        KScopedSchedulerLockAndSleep slp{kernel_ctx, thread, timeout};
+        KScopedSchedulerLockAndSleep slp(kernel_ctx, thread, timeout);
+
+        // Check if the thread should terminate.
+        if (thread->IsTerminationRequested()) {
+            slp.CancelSleep();
+            return ResultTerminationRequested;
+        }
 
         // Check if any of the objects are already signaled.
         for (auto i = 0; i < num_objects; ++i) {
@@ -48,12 +111,6 @@ ResultCode KSynchronizationObject::Wait(KernelCore& kernel_ctx, s32* out_index,
             return ResultTimedOut;
         }
 
-        // Check if the thread should terminate.
-        if (thread->IsTerminationRequested()) {
-            slp.CancelSleep();
-            return ResultTerminationRequested;
-        }
-
         // Check if waiting was canceled.
         if (thread->IsWaitCancelled()) {
             slp.CancelSleep();
@@ -66,73 +123,25 @@ ResultCode KSynchronizationObject::Wait(KernelCore& kernel_ctx, s32* out_index,
             thread_nodes[i].thread = thread;
             thread_nodes[i].next = nullptr;
 
-            if (objects[i]->thread_list_tail == nullptr) {
-                objects[i]->thread_list_head = std::addressof(thread_nodes[i]);
-            } else {
-                objects[i]->thread_list_tail->next = std::addressof(thread_nodes[i]);
-            }
-
-            objects[i]->thread_list_tail = std::addressof(thread_nodes[i]);
+            objects[i]->LinkNode(std::addressof(thread_nodes[i]));
         }
 
-        // For debugging only
-        thread->SetWaitObjectsForDebugging({objects, static_cast<std::size_t>(num_objects)});
-
-        // Mark the thread as waiting.
+        // Mark the thread as cancellable.
         thread->SetCancellable();
-        thread->SetSyncedObject(nullptr, ResultTimedOut);
-        thread->SetState(ThreadState::Waiting);
-        thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Synchronization);
-    }
 
-    // The lock/sleep is done, so we should be able to get our result.
+        // Clear the thread's synced index.
+        thread->SetSyncedIndex(-1);
 
-    // Thread is no longer cancellable.
-    thread->ClearCancellable();
-
-    // For debugging only
-    thread->SetWaitObjectsForDebugging({});
+        // Wait for an object to be signaled.
+        thread->BeginWait(std::addressof(wait_queue));
+        thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Synchronization);
+    }
 
-    // Cancel the timer as needed.
-    kernel_ctx.TimeManager().UnscheduleTimeEvent(thread);
+    // Set the output index.
+    *out_index = thread->GetSyncedIndex();
 
     // Get the wait result.
-    ResultCode wait_result{ResultSuccess};
-    s32 sync_index = -1;
-    {
-        KScopedSchedulerLock lock(kernel_ctx);
-        KSynchronizationObject* synced_obj;
-        wait_result = thread->GetWaitResult(std::addressof(synced_obj));
-
-        for (auto i = 0; i < num_objects; ++i) {
-            // Unlink the object from the list.
-            ThreadListNode* prev_ptr =
-                reinterpret_cast<ThreadListNode*>(std::addressof(objects[i]->thread_list_head));
-            ThreadListNode* prev_val = nullptr;
-            ThreadListNode *prev, *tail_prev;
-
-            do {
-                prev = prev_ptr;
-                prev_ptr = prev_ptr->next;
-                tail_prev = prev_val;
-                prev_val = prev_ptr;
-            } while (prev_ptr != std::addressof(thread_nodes[i]));
-
-            if (objects[i]->thread_list_tail == std::addressof(thread_nodes[i])) {
-                objects[i]->thread_list_tail = tail_prev;
-            }
-
-            prev->next = thread_nodes[i].next;
-
-            if (objects[i] == synced_obj) {
-                sync_index = i;
-            }
-        }
-    }
-
-    // Set output.
-    *out_index = sync_index;
-    return wait_result;
+    return thread->GetWaitResult();
 }
 
 KSynchronizationObject::KSynchronizationObject(KernelCore& kernel_)
@@ -141,7 +150,7 @@ KSynchronizationObject::KSynchronizationObject(KernelCore& kernel_)
 KSynchronizationObject::~KSynchronizationObject() = default;
 
 void KSynchronizationObject::NotifyAvailable(ResultCode result) {
-    KScopedSchedulerLock lock(kernel);
+    KScopedSchedulerLock sl(kernel);
 
     // If we're not signaled, we've nothing to notify.
     if (!this->IsSignaled()) {
@@ -150,11 +159,7 @@ void KSynchronizationObject::NotifyAvailable(ResultCode result) {
 
     // Iterate over each thread.
     for (auto* cur_node = thread_list_head; cur_node != nullptr; cur_node = cur_node->next) {
-        KThread* thread = cur_node->thread;
-        if (thread->GetState() == ThreadState::Waiting) {
-            thread->SetSyncedObject(this, result);
-            thread->SetState(ThreadState::Runnable);
-        }
+        cur_node->thread->NotifyAvailable(this, result);
     }
 }
 

+ 32 - 0
src/core/hle/kernel/k_synchronization_object.h

@@ -35,6 +35,38 @@ public:
 
     [[nodiscard]] std::vector<KThread*> GetWaitingThreadsForDebugging() const;
 
+    void LinkNode(ThreadListNode* node_) {
+        // Link the node to the list.
+        if (thread_list_tail == nullptr) {
+            thread_list_head = node_;
+        } else {
+            thread_list_tail->next = node_;
+        }
+
+        thread_list_tail = node_;
+    }
+
+    void UnlinkNode(ThreadListNode* node_) {
+        // Unlink the node from the list.
+        ThreadListNode* prev_ptr =
+            reinterpret_cast<ThreadListNode*>(std::addressof(thread_list_head));
+        ThreadListNode* prev_val = nullptr;
+        ThreadListNode *prev, *tail_prev;
+
+        do {
+            prev = prev_ptr;
+            prev_ptr = prev_ptr->next;
+            tail_prev = prev_val;
+            prev_val = prev_ptr;
+        } while (prev_ptr != node_);
+
+        if (thread_list_tail == node_) {
+            thread_list_tail = tail_prev;
+        }
+
+        prev->next = node_->next;
+    }
+
 protected:
     explicit KSynchronizationObject(KernelCore& kernel);
     ~KSynchronizationObject() override;

+ 154 - 92
src/core/hle/kernel/k_thread.cpp

@@ -13,6 +13,9 @@
 #include "common/common_types.h"
 #include "common/fiber.h"
 #include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "common/settings.h"
+#include "common/thread_queue_list.h"
 #include "core/core.h"
 #include "core/cpu_manager.h"
 #include "core/hardware_properties.h"
@@ -56,6 +59,34 @@ static void ResetThreadContext64(Core::ARM_Interface::ThreadContext64& context,
 
 namespace Kernel {
 
+namespace {
+
+class ThreadQueueImplForKThreadSleep final : public KThreadQueueWithoutEndWait {
+public:
+    explicit ThreadQueueImplForKThreadSleep(KernelCore& kernel_)
+        : KThreadQueueWithoutEndWait(kernel_) {}
+};
+
+class ThreadQueueImplForKThreadSetProperty final : public KThreadQueue {
+public:
+    explicit ThreadQueueImplForKThreadSetProperty(KernelCore& kernel_, KThread::WaiterList* wl)
+        : KThreadQueue(kernel_), m_wait_list(wl) {}
+
+    void CancelWait(KThread* waiting_thread, ResultCode wait_result,
+                    bool cancel_timer_task) override {
+        // Remove the thread from the wait list.
+        m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread));
+
+        // Invoke the base cancel wait handler.
+        KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task);
+    }
+
+private:
+    KThread::WaiterList* m_wait_list;
+};
+
+} // namespace
+
 KThread::KThread(KernelCore& kernel_)
     : KAutoObjectWithSlabHeapAndContainer{kernel_}, activity_pause_lock{kernel_} {}
 KThread::~KThread() = default;
@@ -82,6 +113,8 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
         [[fallthrough]];
     case ThreadType::HighPriority:
         [[fallthrough]];
+    case ThreadType::Dummy:
+        [[fallthrough]];
     case ThreadType::User:
         ASSERT(((owner == nullptr) ||
                 (owner->GetCoreMask() | (1ULL << virt_core)) == owner->GetCoreMask()));
@@ -127,11 +160,8 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
     priority = prio;
     base_priority = prio;
 
-    // Set sync object and waiting lock to null.
-    synced_object = nullptr;
-
     // Initialize sleeping queue.
-    sleeping_queue = nullptr;
+    wait_queue = nullptr;
 
     // Set suspend flags.
     suspend_request_flags = 0;
@@ -184,7 +214,7 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s
     // Setup the stack parameters.
     StackParameters& sp = GetStackParameters();
     sp.cur_thread = this;
-    sp.disable_count = 1;
+    sp.disable_count = 0;
     SetInExceptionHandler();
 
     // Set thread ID.
@@ -211,15 +241,16 @@ ResultCode KThread::InitializeThread(KThread* thread, KThreadFunction func, uint
     // Initialize the thread.
     R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type));
 
-    // Initialize host context.
+    // Initialize emulation parameters.
     thread->host_context =
         std::make_shared<Common::Fiber>(std::move(init_func), init_func_parameter);
+    thread->is_single_core = !Settings::values.use_multi_core.GetValue();
 
     return ResultSuccess;
 }
 
 ResultCode KThread::InitializeDummyThread(KThread* thread) {
-    return thread->Initialize({}, {}, {}, DefaultThreadPriority, 3, {}, ThreadType::Main);
+    return thread->Initialize({}, {}, {}, DefaultThreadPriority, 3, {}, ThreadType::Dummy);
 }
 
 ResultCode KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) {
@@ -273,11 +304,14 @@ void KThread::Finalize() {
 
         auto it = waiter_list.begin();
         while (it != waiter_list.end()) {
-            // The thread shouldn't be a kernel waiter.
+            // Clear the lock owner
             it->SetLockOwner(nullptr);
-            it->SetSyncedObject(nullptr, ResultInvalidState);
-            it->Wakeup();
+
+            // Erase the waiter from our list.
             it = waiter_list.erase(it);
+
+            // Cancel the thread's wait.
+            it->CancelWait(ResultInvalidState, true);
         }
     }
 
@@ -294,15 +328,12 @@ bool KThread::IsSignaled() const {
     return signaled;
 }
 
-void KThread::Wakeup() {
-    KScopedSchedulerLock sl{kernel};
+void KThread::OnTimer() {
+    ASSERT(kernel.GlobalSchedulerContext().IsLocked());
 
+    // If we're waiting, cancel the wait.
     if (GetState() == ThreadState::Waiting) {
-        if (sleeping_queue != nullptr) {
-            sleeping_queue->WakeupThread(this);
-        } else {
-            SetState(ThreadState::Runnable);
-        }
+        wait_queue->CancelWait(this, ResultTimedOut, false);
     }
 }
 
@@ -327,7 +358,7 @@ void KThread::StartTermination() {
 
     // Signal.
     signaled = true;
-    NotifyAvailable();
+    KSynchronizationObject::NotifyAvailable();
 
     // Clear previous thread in KScheduler.
     KScheduler::ClearPreviousThread(kernel, this);
@@ -475,30 +506,32 @@ ResultCode KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_m
     return ResultSuccess;
 }
 
-ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
+ResultCode KThread::SetCoreMask(s32 core_id_, u64 v_affinity_mask) {
     ASSERT(parent != nullptr);
     ASSERT(v_affinity_mask != 0);
-    KScopedLightLock lk{activity_pause_lock};
+    KScopedLightLock lk(activity_pause_lock);
 
     // Set the core mask.
     u64 p_affinity_mask = 0;
     {
-        KScopedSchedulerLock sl{kernel};
+        KScopedSchedulerLock sl(kernel);
         ASSERT(num_core_migration_disables >= 0);
 
-        // If the core id is no-update magic, preserve the ideal core id.
-        if (cpu_core_id == Svc::IdealCoreNoUpdate) {
-            cpu_core_id = virtual_ideal_core_id;
-            R_UNLESS(((1ULL << cpu_core_id) & v_affinity_mask) != 0, ResultInvalidCombination);
+        // If we're updating, set our ideal virtual core.
+        if (core_id_ != Svc::IdealCoreNoUpdate) {
+            virtual_ideal_core_id = core_id_;
+        } else {
+            // Preserve our ideal core id.
+            core_id_ = virtual_ideal_core_id;
+            R_UNLESS(((1ULL << core_id_) & v_affinity_mask) != 0, ResultInvalidCombination);
         }
 
-        // Set the virtual core/affinity mask.
-        virtual_ideal_core_id = cpu_core_id;
+        // Set our affinity mask.
         virtual_affinity_mask = v_affinity_mask;
 
         // Translate the virtual core to a physical core.
-        if (cpu_core_id >= 0) {
-            cpu_core_id = Core::Hardware::VirtualToPhysicalCoreMap[cpu_core_id];
+        if (core_id_ >= 0) {
+            core_id_ = Core::Hardware::VirtualToPhysicalCoreMap[core_id_];
         }
 
         // Translate the virtual affinity mask to a physical one.
@@ -513,7 +546,7 @@ ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
             const KAffinityMask old_mask = physical_affinity_mask;
 
             // Set our new ideals.
-            physical_ideal_core_id = cpu_core_id;
+            physical_ideal_core_id = core_id_;
             physical_affinity_mask.SetAffinityMask(p_affinity_mask);
 
             if (physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) {
@@ -531,18 +564,18 @@ ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
             }
         } else {
             // Otherwise, we edit the original affinity for restoration later.
-            original_physical_ideal_core_id = cpu_core_id;
+            original_physical_ideal_core_id = core_id_;
             original_physical_affinity_mask.SetAffinityMask(p_affinity_mask);
         }
     }
 
     // Update the pinned waiter list.
+    ThreadQueueImplForKThreadSetProperty wait_queue_(kernel, std::addressof(pinned_waiter_list));
     {
         bool retry_update{};
-        bool thread_is_pinned{};
         do {
             // Lock the scheduler.
-            KScopedSchedulerLock sl{kernel};
+            KScopedSchedulerLock sl(kernel);
 
             // Don't do any further management if our termination has been requested.
             R_SUCCEED_IF(IsTerminationRequested());
@@ -570,12 +603,9 @@ ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
                     R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(),
                              ResultTerminationRequested);
 
-                    // Note that the thread was pinned.
-                    thread_is_pinned = true;
-
                     // Wait until the thread isn't pinned any more.
                     pinned_waiter_list.push_back(GetCurrentThread(kernel));
-                    GetCurrentThread(kernel).SetState(ThreadState::Waiting);
+                    GetCurrentThread(kernel).BeginWait(std::addressof(wait_queue_));
                 } else {
                     // If the thread isn't pinned, release the scheduler lock and retry until it's
                     // not current.
@@ -583,16 +613,6 @@ ResultCode KThread::SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask) {
                 }
             }
         } while (retry_update);
-
-        // If the thread was pinned, it no longer is, and we should remove the current thread from
-        // our waiter list.
-        if (thread_is_pinned) {
-            // Lock the scheduler.
-            KScopedSchedulerLock sl{kernel};
-
-            // Remove from the list.
-            pinned_waiter_list.erase(pinned_waiter_list.iterator_to(GetCurrentThread(kernel)));
-        }
     }
 
     return ResultSuccess;
@@ -641,15 +661,9 @@ void KThread::WaitCancel() {
     KScopedSchedulerLock sl{kernel};
 
     // Check if we're waiting and cancellable.
-    if (GetState() == ThreadState::Waiting && cancellable) {
-        if (sleeping_queue != nullptr) {
-            sleeping_queue->WakeupThread(this);
-            wait_cancelled = true;
-        } else {
-            SetSyncedObject(nullptr, ResultCancelled);
-            SetState(ThreadState::Runnable);
-            wait_cancelled = false;
-        }
+    if (this->GetState() == ThreadState::Waiting && cancellable) {
+        wait_cancelled = false;
+        wait_queue->CancelWait(this, ResultCancelled, true);
     } else {
         // Otherwise, note that we cancelled a wait.
         wait_cancelled = true;
@@ -700,60 +714,59 @@ ResultCode KThread::SetActivity(Svc::ThreadActivity activity) {
     // Set the activity.
     {
         // Lock the scheduler.
-        KScopedSchedulerLock sl{kernel};
+        KScopedSchedulerLock sl(kernel);
 
         // Verify our state.
-        const auto cur_state = GetState();
+        const auto cur_state = this->GetState();
         R_UNLESS((cur_state == ThreadState::Waiting || cur_state == ThreadState::Runnable),
                  ResultInvalidState);
 
         // Either pause or resume.
         if (activity == Svc::ThreadActivity::Paused) {
             // Verify that we're not suspended.
-            R_UNLESS(!IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
+            R_UNLESS(!this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
 
             // Suspend.
-            RequestSuspend(SuspendType::Thread);
+            this->RequestSuspend(SuspendType::Thread);
         } else {
             ASSERT(activity == Svc::ThreadActivity::Runnable);
 
             // Verify that we're suspended.
-            R_UNLESS(IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
+            R_UNLESS(this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState);
 
             // Resume.
-            Resume(SuspendType::Thread);
+            this->Resume(SuspendType::Thread);
         }
     }
 
     // If the thread is now paused, update the pinned waiter list.
     if (activity == Svc::ThreadActivity::Paused) {
-        bool thread_is_pinned{};
-        bool thread_is_current{};
+        ThreadQueueImplForKThreadSetProperty wait_queue_(kernel,
+                                                         std::addressof(pinned_waiter_list));
+
+        bool thread_is_current;
         do {
             // Lock the scheduler.
-            KScopedSchedulerLock sl{kernel};
+            KScopedSchedulerLock sl(kernel);
 
             // Don't do any further management if our termination has been requested.
-            R_SUCCEED_IF(IsTerminationRequested());
+            R_SUCCEED_IF(this->IsTerminationRequested());
+
+            // By default, treat the thread as not current.
+            thread_is_current = false;
 
             // Check whether the thread is pinned.
-            if (GetStackParameters().is_pinned) {
+            if (this->GetStackParameters().is_pinned) {
                 // Verify that the current thread isn't terminating.
                 R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(),
                          ResultTerminationRequested);
 
-                // Note that the thread was pinned and not current.
-                thread_is_pinned = true;
-                thread_is_current = false;
-
                 // Wait until the thread isn't pinned any more.
                 pinned_waiter_list.push_back(GetCurrentThread(kernel));
-                GetCurrentThread(kernel).SetState(ThreadState::Waiting);
+                GetCurrentThread(kernel).BeginWait(std::addressof(wait_queue_));
             } else {
                 // Check if the thread is currently running.
                 // If it is, we'll need to retry.
-                thread_is_current = false;
-
                 for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) {
                     if (kernel.Scheduler(i).GetCurrentThread() == this) {
                         thread_is_current = true;
@@ -762,16 +775,6 @@ ResultCode KThread::SetActivity(Svc::ThreadActivity activity) {
                 }
             }
         } while (thread_is_current);
-
-        // If the thread was pinned, it no longer is, and we should remove the current thread from
-        // our waiter list.
-        if (thread_is_pinned) {
-            // Lock the scheduler.
-            KScopedSchedulerLock sl{kernel};
-
-            // Remove from the list.
-            pinned_waiter_list.erase(pinned_waiter_list.iterator_to(GetCurrentThread(kernel)));
-        }
     }
 
     return ResultSuccess;
@@ -966,6 +969,9 @@ ResultCode KThread::Run() {
 
         // Set our state and finish.
         SetState(ThreadState::Runnable);
+
+        DisableDispatch();
+
         return ResultSuccess;
     }
 }
@@ -996,27 +1002,61 @@ ResultCode KThread::Sleep(s64 timeout) {
     ASSERT(this == GetCurrentThreadPointer(kernel));
     ASSERT(timeout > 0);
 
+    ThreadQueueImplForKThreadSleep wait_queue_(kernel);
     {
         // Setup the scheduling lock and sleep.
-        KScopedSchedulerLockAndSleep slp{kernel, this, timeout};
+        KScopedSchedulerLockAndSleep slp(kernel, this, timeout);
 
         // Check if the thread should terminate.
-        if (IsTerminationRequested()) {
+        if (this->IsTerminationRequested()) {
             slp.CancelSleep();
             return ResultTerminationRequested;
         }
 
-        // Mark the thread as waiting.
-        SetState(ThreadState::Waiting);
+        // Wait for the sleep to end.
+        this->BeginWait(std::addressof(wait_queue_));
         SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Sleep);
     }
 
-    // The lock/sleep is done.
+    return ResultSuccess;
+}
 
-    // Cancel the timer.
-    kernel.TimeManager().UnscheduleTimeEvent(this);
+void KThread::BeginWait(KThreadQueue* queue) {
+    // Set our state as waiting.
+    SetState(ThreadState::Waiting);
 
-    return ResultSuccess;
+    // Set our wait queue.
+    wait_queue = queue;
+}
+
+void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_) {
+    // Lock the scheduler.
+    KScopedSchedulerLock sl(kernel);
+
+    // If we're waiting, notify our queue that we're available.
+    if (GetState() == ThreadState::Waiting) {
+        wait_queue->NotifyAvailable(this, signaled_object, wait_result_);
+    }
+}
+
+void KThread::EndWait(ResultCode wait_result_) {
+    // Lock the scheduler.
+    KScopedSchedulerLock sl(kernel);
+
+    // If we're waiting, notify our queue that we're available.
+    if (GetState() == ThreadState::Waiting) {
+        wait_queue->EndWait(this, wait_result_);
+    }
+}
+
+void KThread::CancelWait(ResultCode wait_result_, bool cancel_timer_task) {
+    // Lock the scheduler.
+    KScopedSchedulerLock sl(kernel);
+
+    // If we're waiting, notify our queue that we're available.
+    if (GetState() == ThreadState::Waiting) {
+        wait_queue->CancelWait(this, wait_result_, cancel_timer_task);
+    }
 }
 
 void KThread::SetState(ThreadState state) {
@@ -1050,4 +1090,26 @@ s32 GetCurrentCoreId(KernelCore& kernel) {
     return GetCurrentThread(kernel).GetCurrentCore();
 }
 
+KScopedDisableDispatch::~KScopedDisableDispatch() {
+    // If we are shutting down the kernel, none of this is relevant anymore.
+    if (kernel.IsShuttingDown()) {
+        return;
+    }
+
+    // Skip the reschedule if single-core, as dispatch tracking is disabled here.
+    if (!Settings::values.use_multi_core.GetValue()) {
+        return;
+    }
+
+    if (GetCurrentThread(kernel).GetDisableDispatchCount() <= 1) {
+        auto scheduler = kernel.CurrentScheduler();
+
+        if (scheduler) {
+            scheduler->RescheduleCurrentCore();
+        }
+    } else {
+        GetCurrentThread(kernel).EnableDispatch();
+    }
+}
+
 } // namespace Kernel

+ 62 - 10
src/core/hle/kernel/k_thread.h

@@ -48,6 +48,7 @@ enum class ThreadType : u32 {
     Kernel = 1,
     HighPriority = 2,
     User = 3,
+    Dummy = 100, // Special thread type for emulation purposes only
 };
 DECLARE_ENUM_FLAG_OPERATORS(ThreadType);
 
@@ -161,8 +162,6 @@ public:
         }
     }
 
-    void Wakeup();
-
     void SetBasePriority(s32 value);
 
     [[nodiscard]] ResultCode Run();
@@ -197,13 +196,19 @@ public:
 
     void Suspend();
 
-    void SetSyncedObject(KSynchronizationObject* obj, ResultCode wait_res) {
-        synced_object = obj;
+    constexpr void SetSyncedIndex(s32 index) {
+        synced_index = index;
+    }
+
+    [[nodiscard]] constexpr s32 GetSyncedIndex() const {
+        return synced_index;
+    }
+
+    constexpr void SetWaitResult(ResultCode wait_res) {
         wait_result = wait_res;
     }
 
-    [[nodiscard]] ResultCode GetWaitResult(KSynchronizationObject** out) const {
-        *out = synced_object;
+    [[nodiscard]] constexpr ResultCode GetWaitResult() const {
         return wait_result;
     }
 
@@ -374,6 +379,8 @@ public:
 
     [[nodiscard]] bool IsSignaled() const override;
 
+    void OnTimer();
+
     static void PostDestroy(uintptr_t arg);
 
     [[nodiscard]] static ResultCode InitializeDummyThread(KThread* thread);
@@ -446,20 +453,39 @@ public:
         return per_core_priority_queue_entry[core];
     }
 
-    void SetSleepingQueue(KThreadQueue* q) {
-        sleeping_queue = q;
+    [[nodiscard]] bool IsKernelThread() const {
+        return GetActiveCore() == 3;
+    }
+
+    [[nodiscard]] bool IsDispatchTrackingDisabled() const {
+        return is_single_core || IsKernelThread();
     }
 
     [[nodiscard]] s32 GetDisableDispatchCount() const {
+        if (IsDispatchTrackingDisabled()) {
+            // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
+            return 1;
+        }
+
         return this->GetStackParameters().disable_count;
     }
 
     void DisableDispatch() {
+        if (IsDispatchTrackingDisabled()) {
+            // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
+            return;
+        }
+
         ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0);
         this->GetStackParameters().disable_count++;
     }
 
     void EnableDispatch() {
+        if (IsDispatchTrackingDisabled()) {
+            // TODO(bunnei): Until kernel threads are emulated, we cannot enable/disable dispatch.
+            return;
+        }
+
         ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0);
         this->GetStackParameters().disable_count--;
     }
@@ -573,6 +599,15 @@ public:
         address_key_value = val;
     }
 
+    void ClearWaitQueue() {
+        wait_queue = nullptr;
+    }
+
+    void BeginWait(KThreadQueue* queue);
+    void NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_);
+    void EndWait(ResultCode wait_result_);
+    void CancelWait(ResultCode wait_result_, bool cancel_timer_task);
+
     [[nodiscard]] bool HasWaiters() const {
         return !waiter_list.empty();
     }
@@ -667,7 +702,6 @@ private:
     KAffinityMask physical_affinity_mask{};
     u64 thread_id{};
     std::atomic<s64> cpu_time{};
-    KSynchronizationObject* synced_object{};
     VAddr address_key{};
     KProcess* parent{};
     VAddr kernel_stack_top{};
@@ -677,13 +711,14 @@ private:
     s64 schedule_count{};
     s64 last_scheduled_tick{};
     std::array<QueueEntry, Core::Hardware::NUM_CPU_CORES> per_core_priority_queue_entry{};
-    KThreadQueue* sleeping_queue{};
+    KThreadQueue* wait_queue{};
     WaiterList waiter_list{};
     WaiterList pinned_waiter_list{};
     KThread* lock_owner{};
     u32 address_key_value{};
     u32 suspend_request_flags{};
     u32 suspend_allowed_flags{};
+    s32 synced_index{};
     ResultCode wait_result{ResultSuccess};
     s32 base_priority{};
     s32 physical_ideal_core_id{};
@@ -708,6 +743,7 @@ private:
 
     // For emulation
     std::shared_ptr<Common::Fiber> host_context{};
+    bool is_single_core{};
 
     // For debugging
     std::vector<KSynchronizationObject*> wait_objects_for_debugging;
@@ -752,4 +788,20 @@ public:
     }
 };
 
+class KScopedDisableDispatch {
+public:
+    [[nodiscard]] explicit KScopedDisableDispatch(KernelCore& kernel_) : kernel{kernel_} {
+        // If we are shutting down the kernel, none of this is relevant anymore.
+        if (kernel.IsShuttingDown()) {
+            return;
+        }
+        GetCurrentThread(kernel).DisableDispatch();
+    }
+
+    ~KScopedDisableDispatch();
+
+private:
+    KernelCore& kernel;
+};
+
 } // namespace Kernel

+ 49 - 0
src/core/hle/kernel/k_thread_queue.cpp

@@ -0,0 +1,49 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/k_thread_queue.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/time_manager.h"
+
+namespace Kernel {
+
+void KThreadQueue::NotifyAvailable([[maybe_unused]] KThread* waiting_thread,
+                                   [[maybe_unused]] KSynchronizationObject* signaled_object,
+                                   [[maybe_unused]] ResultCode wait_result) {}
+
+void KThreadQueue::EndWait(KThread* waiting_thread, ResultCode wait_result) {
+    // Set the thread's wait result.
+    waiting_thread->SetWaitResult(wait_result);
+
+    // Set the thread as runnable.
+    waiting_thread->SetState(ThreadState::Runnable);
+
+    // Clear the thread's wait queue.
+    waiting_thread->ClearWaitQueue();
+
+    // Cancel the thread task.
+    kernel.TimeManager().UnscheduleTimeEvent(waiting_thread);
+}
+
+void KThreadQueue::CancelWait(KThread* waiting_thread, ResultCode wait_result,
+                              bool cancel_timer_task) {
+    // Set the thread's wait result.
+    waiting_thread->SetWaitResult(wait_result);
+
+    // Set the thread as runnable.
+    waiting_thread->SetState(ThreadState::Runnable);
+
+    // Clear the thread's wait queue.
+    waiting_thread->ClearWaitQueue();
+
+    // Cancel the thread task.
+    if (cancel_timer_task) {
+        kernel.TimeManager().UnscheduleTimeEvent(waiting_thread);
+    }
+}
+
+void KThreadQueueWithoutEndWait::EndWait([[maybe_unused]] KThread* waiting_thread,
+                                         [[maybe_unused]] ResultCode wait_result) {}
+
+} // namespace Kernel

+ 14 - 60
src/core/hle/kernel/k_thread_queue.h

@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include "core/hle/kernel/k_scheduler.h"
 #include "core/hle/kernel/k_thread.h"
 
 namespace Kernel {
@@ -11,71 +12,24 @@ namespace Kernel {
 class KThreadQueue {
 public:
     explicit KThreadQueue(KernelCore& kernel_) : kernel{kernel_} {}
+    virtual ~KThreadQueue() = default;
 
-    bool IsEmpty() const {
-        return wait_list.empty();
-    }
-
-    KThread::WaiterList::iterator begin() {
-        return wait_list.begin();
-    }
-    KThread::WaiterList::iterator end() {
-        return wait_list.end();
-    }
-
-    bool SleepThread(KThread* t) {
-        KScopedSchedulerLock sl{kernel};
-
-        // If the thread needs terminating, don't enqueue it.
-        if (t->IsTerminationRequested()) {
-            return false;
-        }
-
-        // Set the thread's queue and mark it as waiting.
-        t->SetSleepingQueue(this);
-        t->SetState(ThreadState::Waiting);
-
-        // Add the thread to the queue.
-        wait_list.push_back(*t);
-
-        return true;
-    }
-
-    void WakeupThread(KThread* t) {
-        KScopedSchedulerLock sl{kernel};
-
-        // Remove the thread from the queue.
-        wait_list.erase(wait_list.iterator_to(*t));
-
-        // Mark the thread as no longer sleeping.
-        t->SetState(ThreadState::Runnable);
-        t->SetSleepingQueue(nullptr);
-    }
-
-    KThread* WakeupFrontThread() {
-        KScopedSchedulerLock sl{kernel};
-
-        if (wait_list.empty()) {
-            return nullptr;
-        } else {
-            // Remove the thread from the queue.
-            auto it = wait_list.begin();
-            KThread* thread = std::addressof(*it);
-            wait_list.erase(it);
-
-            ASSERT(thread->GetState() == ThreadState::Waiting);
-
-            // Mark the thread as no longer sleeping.
-            thread->SetState(ThreadState::Runnable);
-            thread->SetSleepingQueue(nullptr);
-
-            return thread;
-        }
-    }
+    virtual void NotifyAvailable(KThread* waiting_thread, KSynchronizationObject* signaled_object,
+                                 ResultCode wait_result);
+    virtual void EndWait(KThread* waiting_thread, ResultCode wait_result);
+    virtual void CancelWait(KThread* waiting_thread, ResultCode wait_result,
+                            bool cancel_timer_task);
 
 private:
     KernelCore& kernel;
     KThread::WaiterList wait_list{};
 };
 
+class KThreadQueueWithoutEndWait : public KThreadQueue {
+public:
+    explicit KThreadQueueWithoutEndWait(KernelCore& kernel_) : KThreadQueue(kernel_) {}
+
+    void EndWait(KThread* waiting_thread, ResultCode wait_result) override final;
+};
+
 } // namespace Kernel

+ 62 - 41
src/core/hle/kernel/kernel.cpp

@@ -14,6 +14,7 @@
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "common/microprofile.h"
+#include "common/scope_exit.h"
 #include "common/thread.h"
 #include "common/thread_worker.h"
 #include "core/arm/arm_interface.h"
@@ -83,12 +84,16 @@ struct KernelCore::Impl {
     }
 
     void InitializeCores() {
-        for (auto& core : cores) {
-            core.Initialize(current_process->Is64BitProcess());
+        for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
+            cores[core_id].Initialize(current_process->Is64BitProcess());
+            system.Memory().SetCurrentPageTable(*current_process, core_id);
         }
     }
 
     void Shutdown() {
+        is_shutting_down.store(true, std::memory_order_relaxed);
+        SCOPE_EXIT({ is_shutting_down.store(false, std::memory_order_relaxed); });
+
         process_list.clear();
 
         // Close all open server ports.
@@ -123,15 +128,6 @@ struct KernelCore::Impl {
         next_user_process_id = KProcess::ProcessIDMin;
         next_thread_id = 1;
 
-        for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
-            if (suspend_threads[core_id]) {
-                suspend_threads[core_id]->Close();
-                suspend_threads[core_id] = nullptr;
-            }
-
-            schedulers[core_id].reset();
-        }
-
         cores.clear();
 
         global_handle_table->Finalize();
@@ -159,6 +155,16 @@ struct KernelCore::Impl {
         CleanupObject(time_shared_mem);
         CleanupObject(system_resource_limit);
 
+        for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
+            if (suspend_threads[core_id]) {
+                suspend_threads[core_id]->Close();
+                suspend_threads[core_id] = nullptr;
+            }
+
+            schedulers[core_id]->Finalize();
+            schedulers[core_id].reset();
+        }
+
         // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
         next_host_thread_id = Core::Hardware::NUM_CPU_CORES;
 
@@ -245,13 +251,11 @@ struct KernelCore::Impl {
                     KScopedSchedulerLock lock(kernel);
                     global_scheduler_context->PreemptThreads();
                 }
-                const auto time_interval = std::chrono::nanoseconds{
-                    Core::Timing::msToCycles(std::chrono::milliseconds(10))};
+                const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
                 system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
             });
 
-        const auto time_interval =
-            std::chrono::nanoseconds{Core::Timing::msToCycles(std::chrono::milliseconds(10))};
+        const auto time_interval = std::chrono::nanoseconds{std::chrono::milliseconds(10)};
         system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
     }
 
@@ -267,14 +271,6 @@ struct KernelCore::Impl {
 
     void MakeCurrentProcess(KProcess* process) {
         current_process = process;
-        if (process == nullptr) {
-            return;
-        }
-
-        const u32 core_id = GetCurrentHostThreadID();
-        if (core_id < Core::Hardware::NUM_CPU_CORES) {
-            system.Memory().SetCurrentPageTable(*process, core_id);
-        }
     }
 
     static inline thread_local u32 host_thread_id = UINT32_MAX;
@@ -300,15 +296,16 @@ struct KernelCore::Impl {
     // Gets the dummy KThread for the caller, allocating a new one if this is the first time
     KThread* GetHostDummyThread() {
         auto make_thread = [this]() {
-            std::unique_ptr<KThread> thread = std::make_unique<KThread>(system.Kernel());
+            std::lock_guard lk(dummy_thread_lock);
+            auto& thread = dummy_threads.emplace_back(std::make_unique<KThread>(system.Kernel()));
             KAutoObject::Create(thread.get());
             ASSERT(KThread::InitializeDummyThread(thread.get()).IsSuccess());
             thread->SetName(fmt::format("DummyThread:{}", GetHostThreadId()));
-            return thread;
+            return thread.get();
         };
 
-        thread_local auto thread = make_thread();
-        return thread.get();
+        thread_local KThread* saved_thread = make_thread();
+        return saved_thread;
     }
 
     /// Registers a CPU core thread by allocating a host thread ID for it
@@ -343,7 +340,16 @@ struct KernelCore::Impl {
         is_phantom_mode_for_singlecore = value;
     }
 
+    bool IsShuttingDown() const {
+        return is_shutting_down.load(std::memory_order_relaxed);
+    }
+
     KThread* GetCurrentEmuThread() {
+        // If we are shutting down the kernel, none of this is relevant anymore.
+        if (IsShuttingDown()) {
+            return {};
+        }
+
         const auto thread_id = GetCurrentHostThreadID();
         if (thread_id >= Core::Hardware::NUM_CPU_CORES) {
             return GetHostDummyThread();
@@ -695,6 +701,12 @@ struct KernelCore::Impl {
         return port;
     }
 
+    std::mutex server_ports_lock;
+    std::mutex server_sessions_lock;
+    std::mutex registered_objects_lock;
+    std::mutex registered_in_use_objects_lock;
+    std::mutex dummy_thread_lock;
+
     std::atomic<u32> next_object_id{0};
     std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin};
     std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin};
@@ -725,10 +737,6 @@ struct KernelCore::Impl {
     std::unordered_set<KServerSession*> server_sessions;
     std::unordered_set<KAutoObject*> registered_objects;
     std::unordered_set<KAutoObject*> registered_in_use_objects;
-    std::mutex server_ports_lock;
-    std::mutex server_sessions_lock;
-    std::mutex registered_objects_lock;
-    std::mutex registered_in_use_objects_lock;
 
     std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
     std::vector<Kernel::PhysicalCore> cores;
@@ -753,7 +761,11 @@ struct KernelCore::Impl {
     std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
     std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
 
+    // Specifically tracked to be automatically destroyed with kernel
+    std::vector<std::unique_ptr<KThread>> dummy_threads;
+
     bool is_multicore{};
+    std::atomic_bool is_shutting_down{};
     bool is_phantom_mode_for_singlecore{};
     u32 single_core_thread_id{};
 
@@ -839,16 +851,20 @@ const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
     return impl->cores[id];
 }
 
+size_t KernelCore::CurrentPhysicalCoreIndex() const {
+    const u32 core_id = impl->GetCurrentHostThreadID();
+    if (core_id >= Core::Hardware::NUM_CPU_CORES) {
+        return Core::Hardware::NUM_CPU_CORES - 1;
+    }
+    return core_id;
+}
+
 Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() {
-    u32 core_id = impl->GetCurrentHostThreadID();
-    ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
-    return impl->cores[core_id];
+    return impl->cores[CurrentPhysicalCoreIndex()];
 }
 
 const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
-    u32 core_id = impl->GetCurrentHostThreadID();
-    ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
-    return impl->cores[core_id];
+    return impl->cores[CurrentPhysicalCoreIndex()];
 }
 
 Kernel::KScheduler* KernelCore::CurrentScheduler() {
@@ -1051,6 +1067,9 @@ void KernelCore::Suspend(bool in_suspention) {
             impl->suspend_threads[core_id]->SetState(state);
             impl->suspend_threads[core_id]->SetWaitReasonForDebugging(
                 ThreadWaitReasonForDebugging::Suspended);
+            if (!should_suspend) {
+                impl->suspend_threads[core_id]->DisableDispatch();
+            }
         }
     }
 }
@@ -1059,19 +1078,21 @@ bool KernelCore::IsMulticore() const {
     return impl->is_multicore;
 }
 
+bool KernelCore::IsShuttingDown() const {
+    return impl->IsShuttingDown();
+}
+
 void KernelCore::ExceptionalExit() {
     exception_exited = true;
     Suspend(true);
 }
 
 void KernelCore::EnterSVCProfile() {
-    std::size_t core = impl->GetCurrentHostThreadID();
-    impl->svc_ticks[core] = MicroProfileEnter(MICROPROFILE_TOKEN(Kernel_SVC));
+    impl->svc_ticks[CurrentPhysicalCoreIndex()] = MicroProfileEnter(MICROPROFILE_TOKEN(Kernel_SVC));
 }
 
 void KernelCore::ExitSVCProfile() {
-    std::size_t core = impl->GetCurrentHostThreadID();
-    MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]);
+    MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[CurrentPhysicalCoreIndex()]);
 }
 
 std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) {

+ 9 - 0
src/core/hle/kernel/kernel.h

@@ -53,6 +53,7 @@ class KSharedMemoryInfo;
 class KThread;
 class KTransferMemory;
 class KWritableEvent;
+class KCodeMemory;
 class PhysicalCore;
 class ServiceThread;
 class Synchronization;
@@ -148,6 +149,9 @@ public:
     /// Gets the an instance of the respective physical CPU core.
     const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
 
+    /// Gets the current physical core index for the running host thread.
+    std::size_t CurrentPhysicalCoreIndex() const;
+
     /// Gets the sole instance of the Scheduler at the current running core.
     Kernel::KScheduler* CurrentScheduler();
 
@@ -271,6 +275,8 @@ public:
 
     bool IsMulticore() const;
 
+    bool IsShuttingDown() const;
+
     void EnterSVCProfile();
 
     void ExitSVCProfile();
@@ -326,6 +332,8 @@ public:
             return slab_heap_container->transfer_memory;
         } else if constexpr (std::is_same_v<T, KWritableEvent>) {
             return slab_heap_container->writeable_event;
+        } else if constexpr (std::is_same_v<T, KCodeMemory>) {
+            return slab_heap_container->code_memory;
         }
     }
 
@@ -377,6 +385,7 @@ private:
         KSlabHeap<KThread> thread;
         KSlabHeap<KTransferMemory> transfer_memory;
         KSlabHeap<KWritableEvent> writeable_event;
+        KSlabHeap<KCodeMemory> code_memory;
     };
 
     std::unique_ptr<SlabHeapContainer> slab_heap_container;

Някои файлове не бяха показани, защото твърде много файлове са промени