summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.lgtm.yml3
-rw-r--r--CMakeLists.txt2
-rw-r--r--dist/icons/controller/applet_dual_joycon.pngbin0 -> 3554 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_dark.pngbin0 -> 3554 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_dark_disabled.pngbin0 -> 3527 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_disabled.pngbin0 -> 3314 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_midnight.pngbin0 -> 3549 bytes
-rw-r--r--dist/icons/controller/applet_dual_joycon_midnight_disabled.pngbin0 -> 3584 bytes
-rw-r--r--dist/icons/controller/applet_handheld.pngbin0 -> 1671 bytes
-rw-r--r--dist/icons/controller/applet_handheld_dark.pngbin0 -> 1637 bytes
-rw-r--r--dist/icons/controller/applet_handheld_dark_disabled.pngbin0 -> 2642 bytes
-rw-r--r--dist/icons/controller/applet_handheld_disabled.pngbin0 -> 2221 bytes
-rw-r--r--dist/icons/controller/applet_handheld_midnight.pngbin0 -> 1644 bytes
-rw-r--r--dist/icons/controller/applet_handheld_midnight_disabled.pngbin0 -> 2634 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller.pngbin0 -> 4382 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_dark.pngbin0 -> 4236 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_dark_disabled.pngbin0 -> 4477 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_disabled.pngbin0 -> 4173 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_midnight.pngbin0 -> 4376 bytes
-rw-r--r--dist/icons/controller/applet_pro_controller_midnight_disabled.pngbin0 -> 4459 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left.pngbin0 -> 2083 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_dark.pngbin0 -> 2067 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_dark_disabled.pngbin0 -> 2520 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_disabled.pngbin0 -> 2179 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_midnight.pngbin0 -> 2065 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_left_midnight_disabled.pngbin0 -> 2529 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right.pngbin0 -> 2150 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_dark.pngbin0 -> 2146 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_dark_disabled.pngbin0 -> 2556 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_disabled.pngbin0 -> 2212 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_midnight.pngbin0 -> 2150 bytes
-rw-r--r--dist/icons/controller/applet_single_joycon_right_midnight_disabled.pngbin0 -> 2611 bytes
-rw-r--r--dist/icons/controller/controller.qrc30
-rw-r--r--dist/qt_themes/default/style.qss188
-rw-r--r--dist/qt_themes/qdarkstyle/style.qrc2
-rw-r--r--dist/qt_themes/qdarkstyle/style.qss264
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/style.qrc2
-rw-r--r--dist/qt_themes/qdarkstyle_midnight_blue/style.qss290
-rw-r--r--src/audio_core/CMakeLists.txt16
-rw-r--r--src/audio_core/algorithm/interpolate.cpp32
-rw-r--r--src/audio_core/algorithm/interpolate.h3
-rw-r--r--src/audio_core/audio_renderer.cpp543
-rw-r--r--src/audio_core/audio_renderer.h224
-rw-r--r--src/audio_core/behavior_info.cpp79
-rw-r--r--src/audio_core/behavior_info.h52
-rw-r--r--src/audio_core/command_generator.cpp977
-rw-r--r--src/audio_core/command_generator.h103
-rw-r--r--src/audio_core/common.h65
-rw-r--r--src/audio_core/cubeb_sink.cpp18
-rw-r--r--src/audio_core/effect_context.cpp299
-rw-r--r--src/audio_core/effect_context.h322
-rw-r--r--src/audio_core/info_updater.cpp517
-rw-r--r--src/audio_core/info_updater.h58
-rw-r--r--src/audio_core/memory_pool.cpp62
-rw-r--r--src/audio_core/memory_pool.h53
-rw-r--r--src/audio_core/mix_context.cpp296
-rw-r--r--src/audio_core/mix_context.h114
-rw-r--r--src/audio_core/sink_context.cpp31
-rw-r--r--src/audio_core/sink_context.h89
-rw-r--r--src/audio_core/splitter_context.cpp617
-rw-r--r--src/audio_core/splitter_context.h221
-rw-r--r--src/audio_core/stream.cpp6
-rw-r--r--src/audio_core/voice_context.cpp526
-rw-r--r--src/audio_core/voice_context.h296
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.h4
-rw-r--r--src/core/core.cpp8
-rw-r--r--src/core/core.h4
-rw-r--r--src/core/crypto/key_manager.cpp7
-rw-r--r--src/core/crypto/key_manager.h6
-rw-r--r--src/core/file_sys/bis_factory.cpp10
-rw-r--r--src/core/file_sys/bis_factory.h2
-rw-r--r--src/core/file_sys/control_metadata.h2
-rw-r--r--src/core/file_sys/nca_patch.cpp83
-rw-r--r--src/core/file_sys/nca_patch.h4
-rw-r--r--src/core/file_sys/patch_manager.cpp138
-rw-r--r--src/core/file_sys/patch_manager.h48
-rw-r--r--src/core/file_sys/romfs_factory.cpp50
-rw-r--r--src/core/file_sys/romfs_factory.h21
-rw-r--r--src/core/frontend/applets/controller.cpp81
-rw-r--r--src/core/frontend/applets/controller.h48
-rw-r--r--src/core/frontend/input.h22
-rw-r--r--src/core/hle/kernel/client_session.cpp5
-rw-r--r--src/core/hle/kernel/client_session.h7
-rw-r--r--src/core/hle/kernel/scheduler.h2
-rw-r--r--src/core/hle/kernel/server_session.cpp6
-rw-r--r--src/core/hle/kernel/server_session.h11
-rw-r--r--src/core/hle/kernel/svc.cpp2
-rw-r--r--src/core/hle/service/am/am.cpp10
-rw-r--r--src/core/hle/service/am/am.h2
-rw-r--r--src/core/hle/service/am/applets/applets.cpp71
-rw-r--r--src/core/hle/service/am/applets/applets.h19
-rw-r--r--src/core/hle/service/am/applets/controller.cpp210
-rw-r--r--src/core/hle/service/am/applets/controller.h123
-rw-r--r--src/core/hle/service/audio/audren_u.cpp149
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp222
-rw-r--r--src/core/hle/service/hid/controllers/npad.h61
-rw-r--r--src/core/hle/service/hid/hid.cpp37
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/nfp/nfp.cpp23
-rw-r--r--src/core/hle/service/service.cpp28
-rw-r--r--src/core/hle/service/service.h4
-rw-r--r--src/core/hle/service/sm/sm.cpp13
-rw-r--r--src/core/hle/service/sm/sm.h7
-rw-r--r--src/core/hle/service/sockets/blocking_worker.h7
-rw-r--r--src/core/hle/service/sockets/bsd.cpp7
-rw-r--r--src/core/hle/service/sockets/sockets_translate.cpp24
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp18
-rw-r--r--src/core/loader/deconstructed_rom_directory.h6
-rw-r--r--src/core/loader/elf.cpp3
-rw-r--r--src/core/loader/elf.h6
-rw-r--r--src/core/loader/kip.cpp3
-rw-r--r--src/core/loader/kip.h6
-rw-r--r--src/core/loader/loader.h7
-rw-r--r--src/core/loader/nax.cpp5
-rw-r--r--src/core/loader/nax.h8
-rw-r--r--src/core/loader/nca.cpp8
-rw-r--r--src/core/loader/nca.h6
-rw-r--r--src/core/loader/nro.cpp6
-rw-r--r--src/core/loader/nro.h6
-rw-r--r--src/core/loader/nso.cpp7
-rw-r--r--src/core/loader/nso.h12
-rw-r--r--src/core/loader/nsp.cpp7
-rw-r--r--src/core/loader/nsp.h6
-rw-r--r--src/core/loader/xci.cpp7
-rw-r--r--src/core/loader/xci.h6
-rw-r--r--src/core/memory/cheat_engine.cpp41
-rw-r--r--src/core/memory/cheat_engine.h5
-rw-r--r--src/core/settings.h1
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp9
-rw-r--r--src/input_common/main.cpp46
-rw-r--r--src/input_common/main.h24
-rw-r--r--src/input_common/motion_emu.cpp17
-rw-r--r--src/input_common/settings.cpp7
-rw-r--r--src/input_common/settings.h17
-rw-r--r--src/input_common/udp/client.cpp176
-rw-r--r--src/input_common/udp/client.h77
-rw-r--r--src/input_common/udp/udp.cpp181
-rw-r--r--src/input_common/udp/udp.h61
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/engines/fermi_2d.cpp12
-rw-r--r--src/video_core/engines/fermi_2d.h4
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp4
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h6
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp24
-rw-r--r--src/video_core/renderer_vulkan/wrapper.cpp16
-rw-r--r--src/video_core/shader/decode/arithmetic_half.cpp3
-rw-r--r--src/video_core/shader/decode/image.cpp15
-rw-r--r--src/video_core/shader/decode/texture.cpp2
-rw-r--r--src/yuzu/CMakeLists.txt12
-rw-r--r--src/yuzu/applets/controller.cpp601
-rw-r--r--src/yuzu/applets/controller.h133
-rw-r--r--src/yuzu/applets/controller.ui2672
-rw-r--r--src/yuzu/configuration/config.cpp31
-rw-r--r--src/yuzu/configuration/config.h1
-rw-r--r--src/yuzu/configuration/configure_input.cpp29
-rw-r--r--src/yuzu/configuration/configure_input.h2
-rw-r--r--src/yuzu/configuration/configure_input_dialog.cpp37
-rw-r--r--src/yuzu/configuration/configure_input_dialog.h38
-rw-r--r--src/yuzu/configuration/configure_input_dialog.ui57
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp199
-rw-r--r--src/yuzu/configuration/configure_input_player.h10
-rw-r--r--src/yuzu/configuration/configure_input_player.ui109
-rw-r--r--src/yuzu/main.cpp45
-rw-r--r--src/yuzu/main.h6
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_tester/config.cpp1
168 files changed, 11380 insertions, 1484 deletions
diff --git a/.lgtm.yml b/.lgtm.yml
index 932568593..5751d0e4c 100644
--- a/.lgtm.yml
+++ b/.lgtm.yml
@@ -6,8 +6,5 @@ extraction:
6 packages: 6 packages:
7 - "libsdl2-dev" 7 - "libsdl2-dev"
8 - "qtmultimedia5-dev" 8 - "qtmultimedia5-dev"
9 - "clang-format-10"
10 - "libtbb-dev" 9 - "libtbb-dev"
11 - "libjack-jackd2-dev" 10 - "libjack-jackd2-dev"
12 - "doxygen"
13 - "graphviz"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2f3c59c5d..45bd03a65 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -210,7 +210,7 @@ if(ENABLE_QT)
210 set(QT_PREFIX_HINT) 210 set(QT_PREFIX_HINT)
211 if(YUZU_USE_BUNDLED_QT) 211 if(YUZU_USE_BUNDLED_QT)
212 if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64) 212 if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
213 set(QT_VER qt-5.12.0-msvc2017_64) 213 set(QT_VER qt-5.12.8-msvc2017_64)
214 else() 214 else()
215 message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.") 215 message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
216 endif() 216 endif()
diff --git a/dist/icons/controller/applet_dual_joycon.png b/dist/icons/controller/applet_dual_joycon.png
new file mode 100644
index 000000000..32e0a04ae
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_dark.png b/dist/icons/controller/applet_dual_joycon_dark.png
new file mode 100644
index 000000000..6adc66356
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_dark_disabled.png b/dist/icons/controller/applet_dual_joycon_dark_disabled.png
new file mode 100644
index 000000000..208603ee7
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_disabled.png b/dist/icons/controller/applet_dual_joycon_disabled.png
new file mode 100644
index 000000000..43950618d
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_midnight.png b/dist/icons/controller/applet_dual_joycon_midnight.png
new file mode 100644
index 000000000..c7edea8a3
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_dual_joycon_midnight_disabled.png b/dist/icons/controller/applet_dual_joycon_midnight_disabled.png
new file mode 100644
index 000000000..ee1aafc85
--- /dev/null
+++ b/dist/icons/controller/applet_dual_joycon_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld.png b/dist/icons/controller/applet_handheld.png
new file mode 100644
index 000000000..7f8dd2227
--- /dev/null
+++ b/dist/icons/controller/applet_handheld.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_dark.png b/dist/icons/controller/applet_handheld_dark.png
new file mode 100644
index 000000000..41f6d7aea
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_dark_disabled.png b/dist/icons/controller/applet_handheld_dark_disabled.png
new file mode 100644
index 000000000..5a136ddd1
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_disabled.png b/dist/icons/controller/applet_handheld_disabled.png
new file mode 100644
index 000000000..53f8ce7ad
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_midnight.png b/dist/icons/controller/applet_handheld_midnight.png
new file mode 100644
index 000000000..7188b9154
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_handheld_midnight_disabled.png b/dist/icons/controller/applet_handheld_midnight_disabled.png
new file mode 100644
index 000000000..6ccfc3253
--- /dev/null
+++ b/dist/icons/controller/applet_handheld_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller.png b/dist/icons/controller/applet_pro_controller.png
new file mode 100644
index 000000000..e32258855
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_dark.png b/dist/icons/controller/applet_pro_controller_dark.png
new file mode 100644
index 000000000..c122b7b11
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_dark_disabled.png b/dist/icons/controller/applet_pro_controller_dark_disabled.png
new file mode 100644
index 000000000..416e1e2fb
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_disabled.png b/dist/icons/controller/applet_pro_controller_disabled.png
new file mode 100644
index 000000000..72a549ea9
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_midnight.png b/dist/icons/controller/applet_pro_controller_midnight.png
new file mode 100644
index 000000000..622e57fa1
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_pro_controller_midnight_disabled.png b/dist/icons/controller/applet_pro_controller_midnight_disabled.png
new file mode 100644
index 000000000..2907f3be4
--- /dev/null
+++ b/dist/icons/controller/applet_pro_controller_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left.png b/dist/icons/controller/applet_single_joycon_left.png
new file mode 100644
index 000000000..47c44d43a
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_dark.png b/dist/icons/controller/applet_single_joycon_left_dark.png
new file mode 100644
index 000000000..69530b69c
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_dark_disabled.png b/dist/icons/controller/applet_single_joycon_left_dark_disabled.png
new file mode 100644
index 000000000..cfe4b1475
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_disabled.png b/dist/icons/controller/applet_single_joycon_left_disabled.png
new file mode 100644
index 000000000..c0102dc20
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_midnight.png b/dist/icons/controller/applet_single_joycon_left_midnight.png
new file mode 100644
index 000000000..56522bccb
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png b/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png
new file mode 100644
index 000000000..62434c188
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_left_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right.png b/dist/icons/controller/applet_single_joycon_right.png
new file mode 100644
index 000000000..b0d4fba90
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_dark.png b/dist/icons/controller/applet_single_joycon_right_dark.png
new file mode 100644
index 000000000..af26cce4b
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_dark.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_dark_disabled.png b/dist/icons/controller/applet_single_joycon_right_dark_disabled.png
new file mode 100644
index 000000000..b33efab42
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_dark_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_disabled.png b/dist/icons/controller/applet_single_joycon_right_disabled.png
new file mode 100644
index 000000000..01ca38322
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_disabled.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_midnight.png b/dist/icons/controller/applet_single_joycon_right_midnight.png
new file mode 100644
index 000000000..5bf70e21e
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_midnight.png
Binary files differ
diff --git a/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png b/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png
new file mode 100644
index 000000000..e6693b027
--- /dev/null
+++ b/dist/icons/controller/applet_single_joycon_right_midnight_disabled.png
Binary files differ
diff --git a/dist/icons/controller/controller.qrc b/dist/icons/controller/controller.qrc
index f44725d8f..1c4e960c0 100644
--- a/dist/icons/controller/controller.qrc
+++ b/dist/icons/controller/controller.qrc
@@ -21,5 +21,35 @@
21 <file alias="single_joycon_right_vertical">single_joycon_right_vertical.png</file> 21 <file alias="single_joycon_right_vertical">single_joycon_right_vertical.png</file>
22 <file alias="single_joycon_right_vertical_dark">single_joycon_right_vertical_dark.png</file> 22 <file alias="single_joycon_right_vertical_dark">single_joycon_right_vertical_dark.png</file>
23 <file alias="single_joycon_right_vertical_midnight">single_joycon_right_vertical_midnight.png</file> 23 <file alias="single_joycon_right_vertical_midnight">single_joycon_right_vertical_midnight.png</file>
24 <file alias="applet_dual_joycon">applet_dual_joycon.png</file>
25 <file alias="applet_dual_joycon_dark">applet_dual_joycon_dark.png</file>
26 <file alias="applet_dual_joycon_midnight">applet_dual_joycon_midnight.png</file>
27 <file alias="applet_handheld">applet_handheld.png</file>
28 <file alias="applet_handheld_dark">applet_handheld_dark.png</file>
29 <file alias="applet_handheld_midnight">applet_handheld_midnight.png</file>
30 <file alias="applet_pro_controller">applet_pro_controller.png</file>
31 <file alias="applet_pro_controller_dark">applet_pro_controller_dark.png</file>
32 <file alias="applet_pro_controller_midnight">applet_pro_controller_midnight.png</file>
33 <file alias="applet_joycon_left">applet_single_joycon_left.png</file>
34 <file alias="applet_joycon_left_dark">applet_single_joycon_left_dark.png</file>
35 <file alias="applet_joycon_left_midnight">applet_single_joycon_left_midnight.png</file>
36 <file alias="applet_joycon_right">applet_single_joycon_right.png</file>
37 <file alias="applet_joycon_right_dark">applet_single_joycon_right_dark.png</file>
38 <file alias="applet_joycon_right_midnight">applet_single_joycon_right_midnight.png</file>
39 <file alias="applet_dual_joycon_disabled">applet_dual_joycon_disabled.png</file>
40 <file alias="applet_dual_joycon_dark_disabled">applet_dual_joycon_dark_disabled.png</file>
41 <file alias="applet_dual_joycon_midnight_disabled">applet_dual_joycon_midnight_disabled.png</file>
42 <file alias="applet_handheld_disabled">applet_handheld_disabled.png</file>
43 <file alias="applet_handheld_dark_disabled">applet_handheld_dark_disabled.png</file>
44 <file alias="applet_handheld_midnight_disabled">applet_handheld_midnight_disabled.png</file>
45 <file alias="applet_pro_controller_disabled">applet_pro_controller_disabled.png</file>
46 <file alias="applet_pro_controller_dark_disabled">applet_pro_controller_dark_disabled.png</file>
47 <file alias="applet_pro_controller_midnight_disabled">applet_pro_controller_midnight_disabled.png</file>
48 <file alias="applet_joycon_left_disabled">applet_single_joycon_left_disabled.png</file>
49 <file alias="applet_joycon_left_dark_disabled">applet_single_joycon_left_dark_disabled.png</file>
50 <file alias="applet_joycon_left_midnight_disabled">applet_single_joycon_left_midnight_disabled.png</file>
51 <file alias="applet_joycon_right_disabled">applet_single_joycon_right_disabled.png</file>
52 <file alias="applet_joycon_right_dark_disabled">applet_single_joycon_right_dark_disabled.png</file>
53 <file alias="applet_joycon_right_midnight_disabled">applet_single_joycon_right_midnight_disabled.png</file>
24 </qresource> 54 </qresource>
25</RCC> 55</RCC>
diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss
index 5da56520b..b6dd2063d 100644
--- a/dist/qt_themes/default/style.qss
+++ b/dist/qt_themes/default/style.qss
@@ -41,6 +41,99 @@ QPushButton#buttonRefreshDevices {
41 max-height: 20px; 41 max-height: 20px;
42} 42}
43 43
44QWidget#bottomPerGameInput,
45QWidget#topControllerApplet,
46QWidget#bottomControllerApplet,
47QGroupBox#groupPlayer1Connected:checked,
48QGroupBox#groupPlayer2Connected:checked,
49QGroupBox#groupPlayer3Connected:checked,
50QGroupBox#groupPlayer4Connected:checked,
51QGroupBox#groupPlayer5Connected:checked,
52QGroupBox#groupPlayer6Connected:checked,
53QGroupBox#groupPlayer7Connected:checked,
54QGroupBox#groupPlayer8Connected:checked {
55 background-color: #f5f5f5;
56}
57
58QWidget#topControllerApplet {
59 border-bottom: 1px solid #828790
60}
61
62QWidget#bottomPerGameInput,
63QWidget#bottomControllerApplet {
64 border-top: 1px solid #828790
65}
66
67QWidget#topPerGameInput,
68QWidget#middleControllerApplet {
69 background-color: #fff;
70}
71
72QWidget#topPerGameInput QComboBox,
73QWidget#middleControllerApplet QComboBox {
74 width: 123px;
75}
76
77QWidget#connectedControllers {
78 background: transparent;
79}
80
81QWidget#playersSupported,
82QWidget#controllersSupported,
83QWidget#controllerSupported1,
84QWidget#controllerSupported2,
85QWidget#controllerSupported3,
86QWidget#controllerSupported4,
87QWidget#controllerSupported5,
88QWidget#controllerSupported6 {
89 border: none;
90 background: transparent;
91}
92
93QGroupBox#groupPlayer1Connected,
94QGroupBox#groupPlayer2Connected,
95QGroupBox#groupPlayer3Connected,
96QGroupBox#groupPlayer4Connected,
97QGroupBox#groupPlayer5Connected,
98QGroupBox#groupPlayer6Connected,
99QGroupBox#groupPlayer7Connected,
100QGroupBox#groupPlayer8Connected {
101 border: 1px solid #828790;
102 border-radius: 3px;
103 padding: 0px;
104 min-height: 98px;
105 max-height: 98px;
106}
107
108QGroupBox#groupPlayer1Connected:unchecked,
109QGroupBox#groupPlayer2Connected:unchecked,
110QGroupBox#groupPlayer3Connected:unchecked,
111QGroupBox#groupPlayer4Connected:unchecked,
112QGroupBox#groupPlayer5Connected:unchecked,
113QGroupBox#groupPlayer6Connected:unchecked,
114QGroupBox#groupPlayer7Connected:unchecked,
115QGroupBox#groupPlayer8Connected:unchecked {
116 border: 1px solid #d9d9d9;
117}
118
119QGroupBox#groupPlayer1Connected::title,
120QGroupBox#groupPlayer2Connected::title,
121QGroupBox#groupPlayer3Connected::title,
122QGroupBox#groupPlayer4Connected::title,
123QGroupBox#groupPlayer5Connected::title,
124QGroupBox#groupPlayer6Connected::title,
125QGroupBox#groupPlayer7Connected::title,
126QGroupBox#groupPlayer8Connected::title {
127 subcontrol-origin: margin;
128 subcontrol-position: top left;
129 padding-left: 0px;
130 padding-right: 0px;
131 padding-top: 1px;
132 margin-left: 0px;
133 margin-right: -4px;
134 margin-bottom: 4px;
135}
136
44QCheckBox#checkboxPlayer1Connected, 137QCheckBox#checkboxPlayer1Connected,
45QCheckBox#checkboxPlayer2Connected, 138QCheckBox#checkboxPlayer2Connected,
46QCheckBox#checkboxPlayer3Connected, 139QCheckBox#checkboxPlayer3Connected,
@@ -52,6 +145,42 @@ QCheckBox#checkboxPlayer8Connected {
52 spacing: 0px; 145 spacing: 0px;
53} 146}
54 147
148QWidget#Player1LEDs QCheckBox,
149QWidget#Player2LEDs QCheckBox,
150QWidget#Player3LEDs QCheckBox,
151QWidget#Player4LEDs QCheckBox,
152QWidget#Player5LEDs QCheckBox,
153QWidget#Player6LEDs QCheckBox,
154QWidget#Player7LEDs QCheckBox,
155QWidget#Player8LEDs QCheckBox {
156 spacing: 0px;
157}
158
159QWidget#Player1LEDs QCheckBox::indicator,
160QWidget#Player2LEDs QCheckBox::indicator,
161QWidget#Player3LEDs QCheckBox::indicator,
162QWidget#Player4LEDs QCheckBox::indicator,
163QWidget#Player5LEDs QCheckBox::indicator,
164QWidget#Player6LEDs QCheckBox::indicator,
165QWidget#Player7LEDs QCheckBox::indicator,
166QWidget#Player8LEDs QCheckBox::indicator {
167 width: 6px;
168 height: 6px;
169 margin-left: 0px;
170}
171
172QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
173QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
174QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
175QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
176QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
177QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
178QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
179QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
180 width: 12px;
181 height: 12px;
182}
183
55QCheckBox#checkboxPlayer1Connected::indicator, 184QCheckBox#checkboxPlayer1Connected::indicator,
56QCheckBox#checkboxPlayer2Connected::indicator, 185QCheckBox#checkboxPlayer2Connected::indicator,
57QCheckBox#checkboxPlayer3Connected::indicator, 186QCheckBox#checkboxPlayer3Connected::indicator,
@@ -64,6 +193,34 @@ QCheckBox#checkboxPlayer8Connected::indicator {
64 height: 14px; 193 height: 14px;
65} 194}
66 195
196QGroupBox#groupPlayer1Connected::indicator,
197QGroupBox#groupPlayer2Connected::indicator,
198QGroupBox#groupPlayer3Connected::indicator,
199QGroupBox#groupPlayer4Connected::indicator,
200QGroupBox#groupPlayer5Connected::indicator,
201QGroupBox#groupPlayer6Connected::indicator,
202QGroupBox#groupPlayer7Connected::indicator,
203QGroupBox#groupPlayer8Connected::indicator {
204 width: 16px;
205 height: 16px;
206}
207
208QWidget#Player1LEDs QCheckBox::indicator:checked,
209QWidget#Player2LEDs QCheckBox::indicator:checked,
210QWidget#Player3LEDs QCheckBox::indicator:checked,
211QWidget#Player4LEDs QCheckBox::indicator:checked,
212QWidget#Player5LEDs QCheckBox::indicator:checked,
213QWidget#Player6LEDs QCheckBox::indicator:checked,
214QWidget#Player7LEDs QCheckBox::indicator:checked,
215QWidget#Player8LEDs QCheckBox::indicator:checked,
216QGroupBox#groupPlayer1Connected::indicator:checked,
217QGroupBox#groupPlayer2Connected::indicator:checked,
218QGroupBox#groupPlayer3Connected::indicator:checked,
219QGroupBox#groupPlayer4Connected::indicator:checked,
220QGroupBox#groupPlayer5Connected::indicator:checked,
221QGroupBox#groupPlayer6Connected::indicator:checked,
222QGroupBox#groupPlayer7Connected::indicator:checked,
223QGroupBox#groupPlayer8Connected::indicator:checked,
67QCheckBox#checkboxPlayer1Connected::indicator:checked, 224QCheckBox#checkboxPlayer1Connected::indicator:checked,
68QCheckBox#checkboxPlayer2Connected::indicator:checked, 225QCheckBox#checkboxPlayer2Connected::indicator:checked,
69QCheckBox#checkboxPlayer3Connected::indicator:checked, 226QCheckBox#checkboxPlayer3Connected::indicator:checked,
@@ -74,11 +231,27 @@ QCheckBox#checkboxPlayer7Connected::indicator:checked,
74QCheckBox#checkboxPlayer8Connected::indicator:checked, 231QCheckBox#checkboxPlayer8Connected::indicator:checked,
75QGroupBox#groupConnectedController::indicator:checked { 232QGroupBox#groupConnectedController::indicator:checked {
76 border-radius: 2px; 233 border-radius: 2px;
77 border: 1px solid black; 234 border: 1px solid #929192;
78 background: #39ff14; 235 background: #39ff14;
79 image: none; 236 image: none;
80} 237}
81 238
239QWidget#Player1LEDs QCheckBox::indicator:unchecked,
240QWidget#Player2LEDs QCheckBox::indicator:unchecked,
241QWidget#Player3LEDs QCheckBox::indicator:unchecked,
242QWidget#Player4LEDs QCheckBox::indicator:unchecked,
243QWidget#Player5LEDs QCheckBox::indicator:unchecked,
244QWidget#Player6LEDs QCheckBox::indicator:unchecked,
245QWidget#Player7LEDs QCheckBox::indicator:unchecked,
246QWidget#Player8LEDs QCheckBox::indicator:unchecked,
247QGroupBox#groupPlayer1Connected::indicator:unchecked,
248QGroupBox#groupPlayer2Connected::indicator:unchecked,
249QGroupBox#groupPlayer3Connected::indicator:unchecked,
250QGroupBox#groupPlayer4Connected::indicator:unchecked,
251QGroupBox#groupPlayer5Connected::indicator:unchecked,
252QGroupBox#groupPlayer6Connected::indicator:unchecked,
253QGroupBox#groupPlayer7Connected::indicator:unchecked,
254QGroupBox#groupPlayer8Connected::indicator:unchecked,
82QCheckBox#checkboxPlayer1Connected::indicator:unchecked, 255QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
83QCheckBox#checkboxPlayer2Connected::indicator:unchecked, 256QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
84QCheckBox#checkboxPlayer3Connected::indicator:unchecked, 257QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
@@ -89,7 +262,18 @@ QCheckBox#checkboxPlayer7Connected::indicator:unchecked,
89QCheckBox#checkboxPlayer8Connected::indicator:unchecked, 262QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
90QGroupBox#groupConnectedController::indicator:unchecked { 263QGroupBox#groupConnectedController::indicator:unchecked {
91 border-radius: 2px; 264 border-radius: 2px;
92 border: 1px solid black; 265 border: 1px solid #929192;
93 background: transparent; 266 background: transparent;
94 image: none; 267 image: none;
95} 268}
269
270QWidget#controllerPlayer1,
271QWidget#controllerPlayer2,
272QWidget#controllerPlayer3,
273QWidget#controllerPlayer4,
274QWidget#controllerPlayer5,
275QWidget#controllerPlayer6,
276QWidget#controllerPlayer7,
277QWidget#controllerPlayer8 {
278 background: transparent;
279}
diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc
index ec07ba160..2b91204f3 100644
--- a/dist/qt_themes/qdarkstyle/style.qrc
+++ b/dist/qt_themes/qdarkstyle/style.qrc
@@ -52,6 +52,6 @@
52 <file>rc/radio_unchecked.png</file> 52 <file>rc/radio_unchecked.png</file>
53 </qresource> 53 </qresource>
54 <qresource prefix="qdarkstyle"> 54 <qresource prefix="qdarkstyle">
55 <file>style.qss</file> 55 <file>style.qss</file>
56 </qresource> 56 </qresource>
57</RCC> 57</RCC>
diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss
index 16218f0c2..66026e8be 100644
--- a/dist/qt_themes/qdarkstyle/style.qss
+++ b/dist/qt_themes/qdarkstyle/style.qss
@@ -1284,6 +1284,135 @@ QPushButton#buttonRefreshDevices {
1284 padding: 0px 0px; 1284 padding: 0px 0px;
1285} 1285}
1286 1286
1287QSpinBox#spinboxLStickRange,
1288QSpinBox#spinboxRStickRange {
1289 padding: 4px 0px 5px 0px;
1290 min-width: 63px;
1291}
1292
1293QSpinBox#vibrationSpin {
1294 padding: 4px 0px 5px 0px;
1295 min-width: 63px;
1296}
1297
1298QSpinBox#spinboxLStickRange:up-button,
1299QSpinBox#spinboxRStickRange:up-button,
1300QSpinBox#vibrationSpin:up-button {
1301 left: -2px;
1302}
1303
1304QSpinBox#spinboxLStickRange:down-button,
1305QSpinBox#spinboxRStickRange:down-button,
1306QSpinBox#vibrationSpin:down-button {
1307 right: -1px;
1308}
1309
1310QGroupBox#motionGroup::indicator,
1311QGroupBox#vibrationGroup::indicator {
1312 margin-left: 0px;
1313}
1314
1315QGroupBox#motionGroup::title,
1316QGroupBox#vibrationGroup::title {
1317 spacing: 2px;
1318 padding-left: 1px;
1319 padding-right: 1px;
1320}
1321
1322QWidget#bottomPerGameInput,
1323QWidget#topControllerApplet,
1324QWidget#bottomControllerApplet,
1325QGroupBox#groupPlayer1Connected:checked,
1326QGroupBox#groupPlayer2Connected:checked,
1327QGroupBox#groupPlayer3Connected:checked,
1328QGroupBox#groupPlayer4Connected:checked,
1329QGroupBox#groupPlayer5Connected:checked,
1330QGroupBox#groupPlayer6Connected:checked,
1331QGroupBox#groupPlayer7Connected:checked,
1332QGroupBox#groupPlayer8Connected:checked {
1333 background-color: #232629;
1334}
1335
1336QWidget#topPerGameInput,
1337QWidget#middleControllerApplet {
1338 background-color: #31363b;
1339}
1340
1341QWidget#topPerGameInput QComboBox,
1342QWidget#middleControllerApplet QComboBox {
1343 width: 119px;
1344}
1345
1346QRadioButton#radioDocked {
1347 margin-left: -3px;
1348}
1349
1350
1351QRadioButton#radioUndocked {
1352 margin-right: 5px;
1353}
1354
1355QWidget#connectedControllers {
1356 background: transparent;
1357}
1358
1359QWidget#playersSupported,
1360QWidget#controllersSupported,
1361QWidget#controllerSupported1,
1362QWidget#controllerSupported2,
1363QWidget#controllerSupported3,
1364QWidget#controllerSupported4,
1365QWidget#controllerSupported5,
1366QWidget#controllerSupported6 {
1367 border: none;
1368 background: transparent;
1369}
1370
1371QGroupBox#groupPlayer1Connected,
1372QGroupBox#groupPlayer2Connected,
1373QGroupBox#groupPlayer3Connected,
1374QGroupBox#groupPlayer4Connected,
1375QGroupBox#groupPlayer5Connected,
1376QGroupBox#groupPlayer6Connected,
1377QGroupBox#groupPlayer7Connected,
1378QGroupBox#groupPlayer8Connected {
1379 border: 1px solid #76797c;
1380 border-radius: 3px;
1381 padding: 0px;
1382 min-height: 98px;
1383 max-height: 98px;
1384 margin-top: 0px;
1385}
1386
1387QGroupBox#groupPlayer1Connected:unchecked,
1388QGroupBox#groupPlayer2Connected:unchecked,
1389QGroupBox#groupPlayer3Connected:unchecked,
1390QGroupBox#groupPlayer4Connected:unchecked,
1391QGroupBox#groupPlayer5Connected:unchecked,
1392QGroupBox#groupPlayer6Connected:unchecked,
1393QGroupBox#groupPlayer7Connected:unchecked,
1394QGroupBox#groupPlayer8Connected:unchecked {
1395 border: 1px solid #54575b;
1396}
1397
1398QGroupBox#groupPlayer1Connected::title,
1399QGroupBox#groupPlayer2Connected::title,
1400QGroupBox#groupPlayer3Connected::title,
1401QGroupBox#groupPlayer4Connected::title,
1402QGroupBox#groupPlayer5Connected::title,
1403QGroupBox#groupPlayer6Connected::title,
1404QGroupBox#groupPlayer7Connected::title,
1405QGroupBox#groupPlayer8Connected::title {
1406 subcontrol-origin: margin;
1407 subcontrol-position: top left;
1408 padding-left: 0px;
1409 padding-right: 0px;
1410 padding-top: 1px;
1411 margin-left: -2px;
1412 margin-right: -4px;
1413 margin-bottom: 6px;
1414}
1415
1287QCheckBox#checkboxPlayer1Connected, 1416QCheckBox#checkboxPlayer1Connected,
1288QCheckBox#checkboxPlayer2Connected, 1417QCheckBox#checkboxPlayer2Connected,
1289QCheckBox#checkboxPlayer3Connected, 1418QCheckBox#checkboxPlayer3Connected,
@@ -1295,6 +1424,55 @@ QCheckBox#checkboxPlayer8Connected {
1295 spacing: 0px; 1424 spacing: 0px;
1296} 1425}
1297 1426
1427QWidget#Player1LEDs,
1428QWidget#Player2LEDs,
1429QWidget#Player3LEDs,
1430QWidget#Player4LEDs,
1431QWidget#Player5LEDs,
1432QWidget#Player6LEDs,
1433QWidget#Player7LEDs,
1434QWidget#Player8LEDs {
1435 background: transparent;
1436}
1437
1438QWidget#Player1LEDs QCheckBox,
1439QWidget#Player2LEDs QCheckBox,
1440QWidget#Player3LEDs QCheckBox,
1441QWidget#Player4LEDs QCheckBox,
1442QWidget#Player5LEDs QCheckBox,
1443QWidget#Player6LEDs QCheckBox,
1444QWidget#Player7LEDs QCheckBox,
1445QWidget#Player8LEDs QCheckBox {
1446 spacing: 0px;
1447 margin-bottom: 0px;
1448 margin-right: 0px;
1449}
1450
1451QWidget#Player1LEDs QCheckBox::indicator,
1452QWidget#Player2LEDs QCheckBox::indicator,
1453QWidget#Player3LEDs QCheckBox::indicator,
1454QWidget#Player4LEDs QCheckBox::indicator,
1455QWidget#Player5LEDs QCheckBox::indicator,
1456QWidget#Player6LEDs QCheckBox::indicator,
1457QWidget#Player7LEDs QCheckBox::indicator,
1458QWidget#Player8LEDs QCheckBox::indicator {
1459 width: 6px;
1460 height: 6px;
1461 margin-left: 0px;
1462}
1463
1464QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
1465QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
1466QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
1467QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
1468QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
1469QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
1470QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
1471QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
1472 width: 12px;
1473 height: 12px;
1474}
1475
1298QCheckBox#checkboxPlayer1Connected::indicator, 1476QCheckBox#checkboxPlayer1Connected::indicator,
1299QCheckBox#checkboxPlayer2Connected::indicator, 1477QCheckBox#checkboxPlayer2Connected::indicator,
1300QCheckBox#checkboxPlayer3Connected::indicator, 1478QCheckBox#checkboxPlayer3Connected::indicator,
@@ -1307,6 +1485,34 @@ QCheckBox#checkboxPlayer8Connected::indicator {
1307 height: 14px; 1485 height: 14px;
1308} 1486}
1309 1487
1488QGroupBox#groupPlayer1Connected::indicator,
1489QGroupBox#groupPlayer2Connected::indicator,
1490QGroupBox#groupPlayer3Connected::indicator,
1491QGroupBox#groupPlayer4Connected::indicator,
1492QGroupBox#groupPlayer5Connected::indicator,
1493QGroupBox#groupPlayer6Connected::indicator,
1494QGroupBox#groupPlayer7Connected::indicator,
1495QGroupBox#groupPlayer8Connected::indicator {
1496 width: 16px;
1497 height: 16px;
1498}
1499
1500QWidget#Player1LEDs QCheckBox::indicator:checked,
1501QWidget#Player2LEDs QCheckBox::indicator:checked,
1502QWidget#Player3LEDs QCheckBox::indicator:checked,
1503QWidget#Player4LEDs QCheckBox::indicator:checked,
1504QWidget#Player5LEDs QCheckBox::indicator:checked,
1505QWidget#Player6LEDs QCheckBox::indicator:checked,
1506QWidget#Player7LEDs QCheckBox::indicator:checked,
1507QWidget#Player8LEDs QCheckBox::indicator:checked,
1508QGroupBox#groupPlayer1Connected::indicator:checked,
1509QGroupBox#groupPlayer2Connected::indicator:checked,
1510QGroupBox#groupPlayer3Connected::indicator:checked,
1511QGroupBox#groupPlayer4Connected::indicator:checked,
1512QGroupBox#groupPlayer5Connected::indicator:checked,
1513QGroupBox#groupPlayer6Connected::indicator:checked,
1514QGroupBox#groupPlayer7Connected::indicator:checked,
1515QGroupBox#groupPlayer8Connected::indicator:checked,
1310QCheckBox#checkboxPlayer1Connected::indicator:checked, 1516QCheckBox#checkboxPlayer1Connected::indicator:checked,
1311QCheckBox#checkboxPlayer2Connected::indicator:checked, 1517QCheckBox#checkboxPlayer2Connected::indicator:checked,
1312QCheckBox#checkboxPlayer3Connected::indicator:checked, 1518QCheckBox#checkboxPlayer3Connected::indicator:checked,
@@ -1322,6 +1528,22 @@ QGroupBox#groupConnectedController::indicator:checked {
1322 image: none; 1528 image: none;
1323} 1529}
1324 1530
1531QWidget#Player1LEDs QCheckBox::indicator:unchecked,
1532QWidget#Player2LEDs QCheckBox::indicator:unchecked,
1533QWidget#Player3LEDs QCheckBox::indicator:unchecked,
1534QWidget#Player4LEDs QCheckBox::indicator:unchecked,
1535QWidget#Player5LEDs QCheckBox::indicator:unchecked,
1536QWidget#Player6LEDs QCheckBox::indicator:unchecked,
1537QWidget#Player7LEDs QCheckBox::indicator:unchecked,
1538QWidget#Player8LEDs QCheckBox::indicator:unchecked,
1539QGroupBox#groupPlayer1Connected::indicator:unchecked,
1540QGroupBox#groupPlayer2Connected::indicator:unchecked,
1541QGroupBox#groupPlayer3Connected::indicator:unchecked,
1542QGroupBox#groupPlayer4Connected::indicator:unchecked,
1543QGroupBox#groupPlayer5Connected::indicator:unchecked,
1544QGroupBox#groupPlayer6Connected::indicator:unchecked,
1545QGroupBox#groupPlayer7Connected::indicator:unchecked,
1546QGroupBox#groupPlayer8Connected::indicator:unchecked,
1325QCheckBox#checkboxPlayer1Connected::indicator:unchecked, 1547QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
1326QCheckBox#checkboxPlayer2Connected::indicator:unchecked, 1548QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
1327QCheckBox#checkboxPlayer3Connected::indicator:unchecked, 1549QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
@@ -1337,39 +1559,15 @@ QGroupBox#groupConnectedController::indicator:unchecked {
1337 image: none; 1559 image: none;
1338} 1560}
1339 1561
1340QSpinBox#spinboxLStickRange, 1562QWidget#controllerPlayer1,
1341QSpinBox#spinboxRStickRange { 1563QWidget#controllerPlayer2,
1342 padding: 4px 0px 5px 0px; 1564QWidget#controllerPlayer3,
1343 min-width: 63px; 1565QWidget#controllerPlayer4,
1344} 1566QWidget#controllerPlayer5,
1345 1567QWidget#controllerPlayer6,
1346QSpinBox#vibrationSpin { 1568QWidget#controllerPlayer7,
1347 padding: 4px 0px 5px 0px; 1569QWidget#controllerPlayer8 {
1348 min-width: 63px; 1570 background: transparent;
1349}
1350
1351QSpinBox#spinboxLStickRange:up-button,
1352QSpinBox#spinboxRStickRange:up-button,
1353QSpinBox#vibrationSpin:up-button {
1354 left: -2px;
1355}
1356
1357QSpinBox#spinboxLStickRange:down-button,
1358QSpinBox#spinboxRStickRange:down-button,
1359QSpinBox#vibrationSpin:down-button {
1360 right: -1px;
1361}
1362
1363QGroupBox#motionGroup::indicator,
1364QGroupBox#vibrationGroup::indicator {
1365 margin-left: 0px;
1366}
1367
1368QGroupBox#motionGroup::title,
1369QGroupBox#vibrationGroup::title {
1370 spacing: 2px;
1371 padding-left: 1px;
1372 padding-right: 1px;
1373} 1571}
1374 1572
1375/* touchscreen mapping widget */ 1573/* touchscreen mapping widget */
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
index 616aace73..579e73ece 100644
--- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc
@@ -221,6 +221,6 @@
221 <file>rc/window_undock_pressed@2x.png</file> 221 <file>rc/window_undock_pressed@2x.png</file>
222 </qresource> 222 </qresource>
223 <qresource prefix="qdarkstyle_midnight_blue"> 223 <qresource prefix="qdarkstyle_midnight_blue">
224 <file>style.qss</file> 224 <file>style.qss</file>
225 </qresource> 225 </qresource>
226</RCC> 226</RCC>
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
index a714e1475..c6318ba4e 100644
--- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
+++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss
@@ -235,19 +235,19 @@ https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qgroupbox
235 235
236--------------------------------------------------------------------------- */ 236--------------------------------------------------------------------------- */
237QGroupBox { 237QGroupBox {
238 font-weight: bold; 238 font-weight: bold;
239 border: 1px solid #32414B; 239 border: 1px solid #32414B;
240 border-radius: 4px; 240 border-radius: 4px;
241 margin-top: 12px; 241 margin-top: 12px;
242 padding: 4px; 242 padding: 4px;
243} 243}
244 244
245QGroupBox::title { 245QGroupBox::title {
246 subcontrol-origin: margin; 246 subcontrol-origin: margin;
247 subcontrol-position: top left; 247 subcontrol-position: top left;
248 padding-left: 3px; 248 padding-left: 3px;
249 padding-right: 5px; 249 padding-right: 5px;
250 padding-top: 4px; 250 padding-top: 4px;
251} 251}
252 252
253QGroupBox::indicator { 253QGroupBox::indicator {
@@ -2205,6 +2205,144 @@ QPushButton#buttonRefreshDevices {
2205 padding: 0px 0px; 2205 padding: 0px 0px;
2206} 2206}
2207 2207
2208QSpinBox#spinboxLStickRange,
2209QSpinBox#spinboxRStickRange {
2210 min-width: 38px;
2211}
2212
2213QGroupBox#motionGroup::indicator,
2214QGroupBox#vibrationGroup::indicator {
2215 margin-left: 0px;
2216}
2217
2218QWidget#bottomPerGameInput QGroupBox#motionGroup,
2219QWidget#bottomPerGameInput QGroupBox#vibrationGroup,
2220QWidget#bottomPerGameInput QGroupBox#inputConfigGroup {
2221 padding: 0px;
2222}
2223
2224QGroupBox#motionGroup::title,
2225QGroupBox#vibrationGroup::title {
2226 spacing: 2px;
2227 padding-left: 1px;
2228 padding-right: 1px;
2229}
2230
2231QListWidget#selectorList {
2232 background-color: #0f1922;
2233}
2234
2235QSpinBox,
2236QLineEdit,
2237QTreeView#hotkey_list,
2238QScrollArea#scrollArea QTreeView {
2239 background-color: #0f1922;
2240}
2241
2242QWidget#bottomPerGameInput,
2243QWidget#topControllerApplet,
2244QWidget#bottomControllerApplet,
2245QGroupBox#groupPlayer1Connected:checked,
2246QGroupBox#groupPlayer2Connected:checked,
2247QGroupBox#groupPlayer3Connected:checked,
2248QGroupBox#groupPlayer4Connected:checked,
2249QGroupBox#groupPlayer5Connected:checked,
2250QGroupBox#groupPlayer6Connected:checked,
2251QGroupBox#groupPlayer7Connected:checked,
2252QGroupBox#groupPlayer8Connected:checked {
2253 background-color: #0f1922;
2254}
2255
2256QWidget#topPerGameInput,
2257QWidget#middleControllerApplet {
2258 background-color: #19232d;
2259}
2260
2261QWidget#topPerGameInput QComboBox,
2262QWidget#middleControllerApplet QComboBox {
2263 padding-right: 2px;
2264 width: 127px;
2265}
2266
2267QGroupBox#handheldGroup {
2268 padding-left: 0px;
2269}
2270
2271QRadioButton#radioDocked {
2272 margin-left: -1px;
2273 padding-left: 0px;
2274}
2275
2276QRadioButton#radioDocked::indicator {
2277 margin-left: 0px;
2278}
2279
2280
2281QRadioButton#radioUndocked {
2282 margin-right: 2px;
2283}
2284
2285QWidget#connectedControllers {
2286 background: transparent;
2287}
2288
2289QWidget#playersSupported,
2290QWidget#controllersSupported,
2291QWidget#controllerSupported1,
2292QWidget#controllerSupported2,
2293QWidget#controllerSupported3,
2294QWidget#controllerSupported4,
2295QWidget#controllerSupported5,
2296QWidget#controllerSupported6 {
2297 border: none;
2298 background: transparent;
2299}
2300
2301QGroupBox#groupPlayer1Connected,
2302QGroupBox#groupPlayer2Connected,
2303QGroupBox#groupPlayer3Connected,
2304QGroupBox#groupPlayer4Connected,
2305QGroupBox#groupPlayer5Connected,
2306QGroupBox#groupPlayer6Connected,
2307QGroupBox#groupPlayer7Connected,
2308QGroupBox#groupPlayer8Connected {
2309 border: 1px solid #76797c;
2310 border-radius: 3px;
2311 padding: 0px;
2312 min-height: 98px;
2313 max-height: 98px;
2314 margin-top: 0px;
2315}
2316
2317
2318QGroupBox#groupPlayer1Connected:unchecked,
2319QGroupBox#groupPlayer2Connected:unchecked,
2320QGroupBox#groupPlayer3Connected:unchecked,
2321QGroupBox#groupPlayer4Connected:unchecked,
2322QGroupBox#groupPlayer5Connected:unchecked,
2323QGroupBox#groupPlayer6Connected:unchecked,
2324QGroupBox#groupPlayer7Connected:unchecked,
2325QGroupBox#groupPlayer8Connected:unchecked {
2326 border: 1px solid #32414b;
2327}
2328
2329QGroupBox#groupPlayer1Connected::title,
2330QGroupBox#groupPlayer2Connected::title,
2331QGroupBox#groupPlayer3Connected::title,
2332QGroupBox#groupPlayer4Connected::title,
2333QGroupBox#groupPlayer5Connected::title,
2334QGroupBox#groupPlayer6Connected::title,
2335QGroupBox#groupPlayer7Connected::title,
2336QGroupBox#groupPlayer8Connected::title {
2337 subcontrol-origin: margin;
2338 subcontrol-position: top left;
2339 padding-left: 0px;
2340 padding-right: 0px;
2341 padding-top: 1px;
2342 margin-left: -2px;
2343 margin-right: -4px;
2344 margin-bottom: 6px;
2345}
2208 2346
2209QCheckBox#checkboxPlayer1Connected, 2347QCheckBox#checkboxPlayer1Connected,
2210QCheckBox#checkboxPlayer2Connected, 2348QCheckBox#checkboxPlayer2Connected,
@@ -2214,7 +2352,69 @@ QCheckBox#checkboxPlayer5Connected,
2214QCheckBox#checkboxPlayer6Connected, 2352QCheckBox#checkboxPlayer6Connected,
2215QCheckBox#checkboxPlayer7Connected, 2353QCheckBox#checkboxPlayer7Connected,
2216QCheckBox#checkboxPlayer8Connected { 2354QCheckBox#checkboxPlayer8Connected {
2355 spacing: 0px;
2356}
2357
2358QWidget#connectedControllers QLabel {
2359 padding: 0px;
2360}
2361
2362QWidget#Player1LEDs,
2363QWidget#Player2LEDs,
2364QWidget#Player3LEDs,
2365QWidget#Player4LEDs,
2366QWidget#Player5LEDs,
2367QWidget#Player6LEDs,
2368QWidget#Player7LEDs,
2369QWidget#Player8LEDs {
2370 background: transparent;
2371}
2372
2373QWidget#Player1LEDs QCheckBox,
2374QWidget#Player2LEDs QCheckBox,
2375QWidget#Player3LEDs QCheckBox,
2376QWidget#Player4LEDs QCheckBox,
2377QWidget#Player5LEDs QCheckBox,
2378QWidget#Player6LEDs QCheckBox,
2379QWidget#Player7LEDs QCheckBox,
2380QWidget#Player8LEDs QCheckBox,
2381QCheckBox#checkboxPlayer1Connected,
2382QCheckBox#checkboxPlayer2Connected,
2383QCheckBox#checkboxPlayer3Connected,
2384QCheckBox#checkboxPlayer4Connected,
2385QCheckBox#checkboxPlayer5Connected,
2386QCheckBox#checkboxPlayer6Connected,
2387QCheckBox#checkboxPlayer7Connected,
2388QCheckBox#checkboxPlayer8Connected {
2217 spacing: 0px; 2389 spacing: 0px;
2390 padding-top: 0px;
2391 padding-bottom: 0px;
2392 background: transparent;
2393}
2394
2395QWidget#Player1LEDs QCheckBox::indicator,
2396QWidget#Player2LEDs QCheckBox::indicator,
2397QWidget#Player3LEDs QCheckBox::indicator,
2398QWidget#Player4LEDs QCheckBox::indicator,
2399QWidget#Player5LEDs QCheckBox::indicator,
2400QWidget#Player6LEDs QCheckBox::indicator,
2401QWidget#Player7LEDs QCheckBox::indicator,
2402QWidget#Player8LEDs QCheckBox::indicator {
2403 width: 6px;
2404 height: 6px;
2405 margin-left: 0px;
2406}
2407
2408QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator,
2409QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator,
2410QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator,
2411QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator,
2412QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator,
2413QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator,
2414QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator,
2415QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator {
2416 width: 12px;
2417 height: 12px;
2218} 2418}
2219 2419
2220QCheckBox#checkboxPlayer1Connected::indicator, 2420QCheckBox#checkboxPlayer1Connected::indicator,
@@ -2227,8 +2427,25 @@ QCheckBox#checkboxPlayer7Connected::indicator,
2227QCheckBox#checkboxPlayer8Connected::indicator { 2427QCheckBox#checkboxPlayer8Connected::indicator {
2228 width: 14px; 2428 width: 14px;
2229 height: 14px; 2429 height: 14px;
2430 margin-left: 2px;
2230} 2431}
2231 2432
2433QWidget#Player1LEDs QCheckBox::indicator:checked,
2434QWidget#Player2LEDs QCheckBox::indicator:checked,
2435QWidget#Player3LEDs QCheckBox::indicator:checked,
2436QWidget#Player4LEDs QCheckBox::indicator:checked,
2437QWidget#Player5LEDs QCheckBox::indicator:checked,
2438QWidget#Player6LEDs QCheckBox::indicator:checked,
2439QWidget#Player7LEDs QCheckBox::indicator:checked,
2440QWidget#Player8LEDs QCheckBox::indicator:checked,
2441QGroupBox#groupPlayer1Connected::indicator:checked,
2442QGroupBox#groupPlayer2Connected::indicator:checked,
2443QGroupBox#groupPlayer3Connected::indicator:checked,
2444QGroupBox#groupPlayer4Connected::indicator:checked,
2445QGroupBox#groupPlayer5Connected::indicator:checked,
2446QGroupBox#groupPlayer6Connected::indicator:checked,
2447QGroupBox#groupPlayer7Connected::indicator:checked,
2448QGroupBox#groupPlayer8Connected::indicator:checked,
2232QCheckBox#checkboxPlayer1Connected::indicator:checked, 2449QCheckBox#checkboxPlayer1Connected::indicator:checked,
2233QCheckBox#checkboxPlayer2Connected::indicator:checked, 2450QCheckBox#checkboxPlayer2Connected::indicator:checked,
2234QCheckBox#checkboxPlayer3Connected::indicator:checked, 2451QCheckBox#checkboxPlayer3Connected::indicator:checked,
@@ -2244,6 +2461,22 @@ QGroupBox#groupConnectedController::indicator:checked {
2244 image: none; 2461 image: none;
2245} 2462}
2246 2463
2464QWidget#Player1LEDs QCheckBox::indicator:unchecked,
2465QWidget#Player2LEDs QCheckBox::indicator:unchecked,
2466QWidget#Player3LEDs QCheckBox::indicator:unchecked,
2467QWidget#Player4LEDs QCheckBox::indicator:unchecked,
2468QWidget#Player5LEDs QCheckBox::indicator:unchecked,
2469QWidget#Player6LEDs QCheckBox::indicator:unchecked,
2470QWidget#Player7LEDs QCheckBox::indicator:unchecked,
2471QWidget#Player8LEDs QCheckBox::indicator:unchecked,
2472QGroupBox#groupPlayer1Connected::indicator:unchecked,
2473QGroupBox#groupPlayer2Connected::indicator:unchecked,
2474QGroupBox#groupPlayer3Connected::indicator:unchecked,
2475QGroupBox#groupPlayer4Connected::indicator:unchecked,
2476QGroupBox#groupPlayer5Connected::indicator:unchecked,
2477QGroupBox#groupPlayer6Connected::indicator:unchecked,
2478QGroupBox#groupPlayer7Connected::indicator:unchecked,
2479QGroupBox#groupPlayer8Connected::indicator:unchecked,
2247QCheckBox#checkboxPlayer1Connected::indicator:unchecked, 2480QCheckBox#checkboxPlayer1Connected::indicator:unchecked,
2248QCheckBox#checkboxPlayer2Connected::indicator:unchecked, 2481QCheckBox#checkboxPlayer2Connected::indicator:unchecked,
2249QCheckBox#checkboxPlayer3Connected::indicator:unchecked, 2482QCheckBox#checkboxPlayer3Connected::indicator:unchecked,
@@ -2255,34 +2488,17 @@ QCheckBox#checkboxPlayer8Connected::indicator:unchecked,
2255QGroupBox#groupConnectedController::indicator:unchecked { 2488QGroupBox#groupConnectedController::indicator:unchecked {
2256 border-radius: 2px; 2489 border-radius: 2px;
2257 border: 1px solid #929192; 2490 border: 1px solid #929192;
2258 background: transparent; 2491 background: #19232d;
2259 image: none; 2492 image: none;
2260} 2493}
2261 2494
2262QSpinBox#spinboxLStickRange, 2495QWidget#controllerPlayer1,
2263QSpinBox#spinboxRStickRange { 2496QWidget#controllerPlayer2,
2264 min-width: 38px; 2497QWidget#controllerPlayer3,
2265} 2498QWidget#controllerPlayer4,
2266 2499QWidget#controllerPlayer5,
2267QGroupBox#motionGroup::indicator, 2500QWidget#controllerPlayer6,
2268QGroupBox#vibrationGroup::indicator { 2501QWidget#controllerPlayer7,
2269 margin-left: 0px; 2502QWidget#controllerPlayer8 {
2270} 2503 background: transparent;
2271
2272QGroupBox#motionGroup::title,
2273QGroupBox#vibrationGroup::title {
2274spacing: 2px;
2275 padding-left: 1px;
2276 padding-right: 1px;
2277}
2278
2279QListWidget#selectorList {
2280 background-color: #0f1922;
2281} 2504}
2282
2283QSpinBox,
2284QLineEdit,
2285QTreeView#hotkey_list,
2286QScrollArea#scrollArea QTreeView {
2287 background-color: #0f1922;
2288} \ No newline at end of file
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 5ef38a337..cb00ef60e 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -12,16 +12,32 @@ add_library(audio_core STATIC
12 buffer.h 12 buffer.h
13 codec.cpp 13 codec.cpp
14 codec.h 14 codec.h
15 command_generator.cpp
16 command_generator.h
15 common.h 17 common.h
18 effect_context.cpp
19 effect_context.h
20 info_updater.cpp
21 info_updater.h
22 memory_pool.cpp
23 memory_pool.h
24 mix_context.cpp
25 mix_context.h
16 null_sink.h 26 null_sink.h
17 sink.h 27 sink.h
28 sink_context.cpp
29 sink_context.h
18 sink_details.cpp 30 sink_details.cpp
19 sink_details.h 31 sink_details.h
20 sink_stream.h 32 sink_stream.h
33 splitter_context.cpp
34 splitter_context.h
21 stream.cpp 35 stream.cpp
22 stream.h 36 stream.h
23 time_stretch.cpp 37 time_stretch.cpp
24 time_stretch.h 38 time_stretch.h
39 voice_context.cpp
40 voice_context.h
25 41
26 $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> 42 $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
27) 43)
diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp
index 49ab9d3e1..689a54508 100644
--- a/src/audio_core/algorithm/interpolate.cpp
+++ b/src/audio_core/algorithm/interpolate.cpp
@@ -197,4 +197,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
197 return output; 197 return output;
198} 198}
199 199
200void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
201 const std::array<s16, 512>& lut = [pitch] {
202 if (pitch > 0xaaaa) {
203 return curve_lut0;
204 }
205 if (pitch <= 0x8000) {
206 return curve_lut1;
207 }
208 return curve_lut2;
209 }();
210
211 std::size_t index{};
212
213 for (std::size_t i = 0; i < sample_count; i++) {
214 const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
215 const auto l0 = lut[lut_index + 0];
216 const auto l1 = lut[lut_index + 1];
217 const auto l2 = lut[lut_index + 2];
218 const auto l3 = lut[lut_index + 3];
219
220 const auto s0 = static_cast<s32>(input[index]);
221 const auto s1 = static_cast<s32>(input[index + 1]);
222 const auto s2 = static_cast<s32>(input[index + 2]);
223 const auto s3 = static_cast<s32>(input[index + 3]);
224
225 output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
226 fraction += pitch;
227 index += (fraction >> 15);
228 fraction &= 0x7fff;
229 }
230}
231
200} // namespace AudioCore 232} // namespace AudioCore
diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h
index ab1a31754..d534077af 100644
--- a/src/audio_core/algorithm/interpolate.h
+++ b/src/audio_core/algorithm/interpolate.h
@@ -38,4 +38,7 @@ inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16>
38 return Interpolate(state, std::move(input), ratio); 38 return Interpolate(state, std::move(input), ratio);
39} 39}
40 40
41/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
42void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
43
41} // namespace AudioCore 44} // namespace AudioCore
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index d64452617..56dc892b1 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -2,95 +2,49 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <vector>
5#include "audio_core/algorithm/interpolate.h" 6#include "audio_core/algorithm/interpolate.h"
6#include "audio_core/audio_out.h" 7#include "audio_core/audio_out.h"
7#include "audio_core/audio_renderer.h" 8#include "audio_core/audio_renderer.h"
8#include "audio_core/codec.h" 9#include "audio_core/codec.h"
9#include "audio_core/common.h" 10#include "audio_core/common.h"
11#include "audio_core/info_updater.h"
12#include "audio_core/voice_context.h"
10#include "common/assert.h" 13#include "common/assert.h"
11#include "common/logging/log.h" 14#include "common/logging/log.h"
12#include "core/core.h" 15#include "core/core.h"
13#include "core/hle/kernel/writable_event.h" 16#include "core/hle/kernel/writable_event.h"
14#include "core/memory.h" 17#include "core/memory.h"
18#include "core/settings.h"
15 19
16namespace AudioCore { 20namespace AudioCore {
17
18constexpr u32 STREAM_SAMPLE_RATE{48000};
19constexpr u32 STREAM_NUM_CHANNELS{2};
20using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>;
21class AudioRenderer::VoiceState {
22public:
23 bool IsPlaying() const {
24 return is_in_use && info.play_state == PlayState::Started;
25 }
26
27 const VoiceOutStatus& GetOutStatus() const {
28 return out_status;
29 }
30
31 const VoiceInfo& GetInfo() const {
32 return info;
33 }
34
35 VoiceInfo& GetInfo() {
36 return info;
37 }
38
39 void SetWaveIndex(std::size_t index);
40 std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory,
41 const VoiceChannelHolder& voice_resources);
42 void UpdateState();
43 void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources);
44
45private:
46 bool is_in_use{};
47 bool is_refresh_pending{};
48 std::size_t wave_index{};
49 std::size_t offset{};
50 Codec::ADPCMState adpcm_state{};
51 InterpolationState interp_state{};
52 std::vector<s16> samples;
53 VoiceOutStatus out_status{};
54 VoiceInfo info{};
55};
56
57class AudioRenderer::EffectState {
58public:
59 const EffectOutStatus& GetOutStatus() const {
60 return out_status;
61 }
62
63 const EffectInStatus& GetInfo() const {
64 return info;
65 }
66
67 EffectInStatus& GetInfo() {
68 return info;
69 }
70
71 void UpdateState(Core::Memory::Memory& memory);
72
73private:
74 EffectOutStatus out_status{};
75 EffectInStatus info{};
76};
77
78AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, 21AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
79 AudioRendererParameter params, 22 AudioCommon::AudioRendererParameter params,
80 std::shared_ptr<Kernel::WritableEvent> buffer_event, 23 std::shared_ptr<Kernel::WritableEvent> buffer_event,
81 std::size_t instance_number) 24 std::size_t instance_number)
82 : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count), 25 : worker_params{params}, buffer_event{buffer_event},
83 voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} { 26 memory_pool_info(params.effect_count + params.voice_count * 4),
27 voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
28 sink_context(params.sink_count), splitter_context(),
29 voices(params.voice_count), memory{memory_},
30 command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
31 memory),
32 temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) {
84 behavior_info.SetUserRevision(params.revision); 33 behavior_info.SetUserRevision(params.revision);
34 splitter_context.Initialize(behavior_info, params.splitter_count,
35 params.num_splitter_send_channels);
36 mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
85 audio_out = std::make_unique<AudioCore::AudioOut>(); 37 audio_out = std::make_unique<AudioCore::AudioOut>();
86 stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, 38 stream =
87 fmt::format("AudioRenderer-Instance{}", instance_number), 39 audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
88 [=]() { buffer_event->Signal(); }); 40 fmt::format("AudioRenderer-Instance{}", instance_number),
41 [=]() { buffer_event->Signal(); });
89 audio_out->StartStream(stream); 42 audio_out->StartStream(stream);
90 43
91 QueueMixedBuffer(0); 44 QueueMixedBuffer(0);
92 QueueMixedBuffer(1); 45 QueueMixedBuffer(1);
93 QueueMixedBuffer(2); 46 QueueMixedBuffer(2);
47 QueueMixedBuffer(3);
94} 48}
95 49
96AudioRenderer::~AudioRenderer() = default; 50AudioRenderer::~AudioRenderer() = default;
@@ -111,355 +65,200 @@ Stream::State AudioRenderer::GetStreamState() const {
111 return stream->GetState(); 65 return stream->GetState();
112} 66}
113 67
114ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { 68static constexpr s16 ClampToS16(s32 value) {
115 // Copy UpdateDataHeader struct 69 return static_cast<s16>(std::clamp(value, -32768, 32767));
116 UpdateDataHeader config{}; 70}
117 std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
118 u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
119
120 if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) {
121 LOG_ERROR(Audio, "Failed to update behavior info input parameters");
122 return Audren::ERR_INVALID_PARAMETERS;
123 }
124
125 // Copy MemoryPoolInfo structs
126 std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
127 std::memcpy(mem_pool_info.data(),
128 input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
129 memory_pool_count * sizeof(MemoryPoolInfo));
130
131 // Copy voice resources
132 const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size +
133 config.memory_pools_size};
134 std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset,
135 sizeof(VoiceResourceInformation) * voice_resources.size());
136
137 // Copy VoiceInfo structs
138 std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
139 config.memory_pools_size + config.voice_resource_size};
140 for (auto& voice : voices) {
141 std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
142 voice_offset += sizeof(VoiceInfo);
143 }
144
145 std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
146 config.memory_pools_size + config.voice_resource_size +
147 config.voices_size};
148 for (auto& effect : effects) {
149 std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
150 effect_offset += sizeof(EffectInStatus);
151 }
152
153 // Update memory pool state
154 std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
155 for (std::size_t index = 0; index < memory_pool.size(); ++index) {
156 if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
157 memory_pool[index].state = MemoryPoolStates::Attached;
158 } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
159 memory_pool[index].state = MemoryPoolStates::Detached;
160 }
161 }
162
163 // Update voices
164 for (auto& voice : voices) {
165 voice.UpdateState();
166 if (!voice.GetInfo().is_in_use) {
167 continue;
168 }
169 if (voice.GetInfo().is_new) {
170 voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
171 }
172 }
173
174 for (auto& effect : effects) {
175 effect.UpdateState(memory);
176 }
177
178 // Release previous buffers and queue next ones for playback
179 ReleaseAndQueueBuffers();
180
181 // Copy output header
182 UpdateDataHeader response_data{worker_params};
183 if (behavior_info.IsElapsedFrameCountSupported()) {
184 response_data.render_info = sizeof(RendererInfo);
185 response_data.total_size += sizeof(RendererInfo);
186 }
187
188 std::vector<u8> output_params(response_data.total_size);
189 std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
190
191 // Copy output memory pool entries
192 std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
193 response_data.memory_pools_size);
194
195 // Copy output voice status
196 std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
197 for (const auto& voice : voices) {
198 std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
199 sizeof(VoiceOutStatus));
200 voice_out_status_offset += sizeof(VoiceOutStatus);
201 }
202 71
203 std::size_t effect_out_status_offset{ 72ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
204 sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + 73 std::vector<u8>& output_params) {
205 response_data.voice_resource_size};
206 for (const auto& effect : effects) {
207 std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
208 sizeof(EffectOutStatus));
209 effect_out_status_offset += sizeof(EffectOutStatus);
210 }
211 74
212 // Update behavior info output 75 InfoUpdater info_updater{input_params, output_params, behavior_info};
213 const std::size_t behavior_out_status_offset{
214 sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
215 response_data.effects_size + response_data.sinks_size +
216 response_data.performance_manager_size};
217 76
218 if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) { 77 if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
219 LOG_ERROR(Audio, "Failed to update behavior info output parameters"); 78 LOG_ERROR(Audio, "Failed to update behavior info input parameters");
220 return Audren::ERR_INVALID_PARAMETERS; 79 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
221 } 80 }
222 81
223 if (behavior_info.IsElapsedFrameCountSupported()) { 82 if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
224 const std::size_t renderer_info_offset{ 83 LOG_ERROR(Audio, "Failed to update memory pool parameters");
225 sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + 84 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
226 response_data.effects_size + response_data.sinks_size +
227 response_data.performance_manager_size + response_data.behavior_size};
228 RendererInfo renderer_info{};
229 renderer_info.elasped_frame_count = elapsed_frame_count;
230 std::memcpy(output_params.data() + renderer_info_offset, &renderer_info,
231 sizeof(RendererInfo));
232 } 85 }
233 86
234 return MakeResult(output_params); 87 if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
235} 88 LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
236 89 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
237void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
238 wave_index = index & 3;
239 is_refresh_pending = true;
240}
241
242std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(
243 std::size_t sample_count, Core::Memory::Memory& memory,
244 const VoiceChannelHolder& voice_resources) {
245 if (!IsPlaying()) {
246 return {};
247 } 90 }
248 91
249 if (is_refresh_pending) { 92 if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
250 RefreshBuffer(memory, voice_resources); 93 LOG_ERROR(Audio, "Failed to update voice parameters");
94 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
251 } 95 }
252 96
253 const std::size_t max_size{samples.size() - offset}; 97 // TODO(ogniK): Deal with stopped audio renderer but updates still taking place
254 const std::size_t dequeue_offset{offset}; 98 if (!info_updater.UpdateEffects(effect_context, true)) {
255 std::size_t size{sample_count * STREAM_NUM_CHANNELS}; 99 LOG_ERROR(Audio, "Failed to update effect parameters");
256 if (size > max_size) { 100 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
257 size = max_size;
258 } 101 }
259 102
260 out_status.played_sample_count += size / STREAM_NUM_CHANNELS; 103 if (behavior_info.IsSplitterSupported()) {
261 offset += size; 104 if (!info_updater.UpdateSplitterInfo(splitter_context)) {
262 105 LOG_ERROR(Audio, "Failed to update splitter parameters");
263 const auto& wave_buffer{info.wave_buffer[wave_index]}; 106 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
264 if (offset == samples.size()) {
265 offset = 0;
266
267 if (!wave_buffer.is_looping && wave_buffer.buffer_sz) {
268 SetWaveIndex(wave_index + 1);
269 }
270
271 if (wave_buffer.buffer_sz) {
272 out_status.wave_buffer_consumed++;
273 }
274
275 if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) {
276 info.play_state = PlayState::Paused;
277 } 107 }
278 } 108 }
279 109
280 return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size}; 110 auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
281} 111 splitter_context, effect_context);
282 112
283void AudioRenderer::VoiceState::UpdateState() { 113 if (mix_result.IsError()) {
284 if (is_in_use && !info.is_in_use) { 114 LOG_ERROR(Audio, "Failed to update mix parameters");
285 // No longer in use, reset state 115 return mix_result;
286 is_refresh_pending = true;
287 wave_index = 0;
288 offset = 0;
289 out_status = {};
290 } 116 }
291 is_in_use = info.is_in_use;
292}
293 117
294void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory, 118 // TODO(ogniK): Sinks
295 const VoiceChannelHolder& voice_resources) { 119 if (!info_updater.UpdateSinks(sink_context)) {
296 const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr; 120 LOG_ERROR(Audio, "Failed to update sink parameters");
297 const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz; 121 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
298 std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
299 memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size);
300
301 switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
302 case Codec::PcmFormat::Int16: {
303 // PCM16 is played as-is
304 break;
305 }
306 case Codec::PcmFormat::Adpcm: {
307 // Decode ADPCM to PCM16
308 Codec::ADPCM_Coeff coeffs;
309 memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
310 new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
311 new_samples.size() * sizeof(s16), coeffs, adpcm_state);
312 break;
313 } 122 }
314 default:
315 UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format);
316 break;
317 }
318
319 switch (info.channel_count) {
320 case 1: {
321 // 1 channel is upsampled to 2 channel
322 samples.resize(new_samples.size() * 2);
323 123
324 for (std::size_t index = 0; index < new_samples.size(); ++index) { 124 // TODO(ogniK): Performance buffer
325 auto sample = static_cast<float>(new_samples[index]); 125 if (!info_updater.UpdatePerformanceBuffer()) {
326 if (voice_resources[0]->in_use) { 126 LOG_ERROR(Audio, "Failed to update performance buffer parameters");
327 sample *= voice_resources[0]->mix_volumes[0]; 127 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
328 }
329
330 samples[index * 2] = static_cast<s16>(sample * info.volume);
331 samples[index * 2 + 1] = static_cast<s16>(sample * info.volume);
332 }
333 break;
334 } 128 }
335 case 2: {
336 // 2 channel is played as is
337 samples = std::move(new_samples);
338 const std::size_t sample_count = (samples.size() / 2);
339 for (std::size_t index = 0; index < sample_count; ++index) {
340 const std::size_t index_l = index * 2;
341 const std::size_t index_r = index * 2 + 1;
342
343 auto sample_l = static_cast<float>(samples[index_l]);
344 auto sample_r = static_cast<float>(samples[index_r]);
345
346 if (voice_resources[0]->in_use) {
347 sample_l *= voice_resources[0]->mix_volumes[0];
348 }
349
350 if (voice_resources[1]->in_use) {
351 sample_r *= voice_resources[1]->mix_volumes[1];
352 }
353 129
354 samples[index_l] = static_cast<s16>(sample_l * info.volume); 130 if (!info_updater.UpdateErrorInfo(behavior_info)) {
355 samples[index_r] = static_cast<s16>(sample_r * info.volume); 131 LOG_ERROR(Audio, "Failed to update error info");
356 } 132 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
357 break;
358 } 133 }
359 case 6: {
360 samples.resize((new_samples.size() / 6) * 2);
361 const std::size_t sample_count = samples.size() / 2;
362
363 for (std::size_t index = 0; index < sample_count; ++index) {
364 auto FL = static_cast<float>(new_samples[index * 6]);
365 auto FR = static_cast<float>(new_samples[index * 6 + 1]);
366 auto FC = static_cast<float>(new_samples[index * 6 + 2]);
367 auto BL = static_cast<float>(new_samples[index * 6 + 4]);
368 auto BR = static_cast<float>(new_samples[index * 6 + 5]);
369
370 if (voice_resources[0]->in_use) {
371 FL *= voice_resources[0]->mix_volumes[0];
372 }
373 if (voice_resources[1]->in_use) {
374 FR *= voice_resources[1]->mix_volumes[1];
375 }
376 if (voice_resources[2]->in_use) {
377 FC *= voice_resources[2]->mix_volumes[2];
378 }
379 if (voice_resources[4]->in_use) {
380 BL *= voice_resources[4]->mix_volumes[4];
381 }
382 if (voice_resources[5]->in_use) {
383 BR *= voice_resources[5]->mix_volumes[5];
384 }
385 134
386 samples[index * 2] = 135 if (behavior_info.IsElapsedFrameCountSupported()) {
387 static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume); 136 if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
388 samples[index * 2 + 1] = 137 LOG_ERROR(Audio, "Failed to update renderer info");
389 static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume); 138 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
390 } 139 }
391 break;
392 }
393 default:
394 UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count);
395 break;
396 } 140 }
141 // TODO(ogniK): Statistics
397 142
398 // Only interpolate when necessary, expensive. 143 if (!info_updater.WriteOutputHeader()) {
399 if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) { 144 LOG_ERROR(Audio, "Failed to write output header");
400 samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, 145 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
401 STREAM_SAMPLE_RATE);
402 } 146 }
403 147
404 is_refresh_pending = false; 148 // TODO(ogniK): Check when all sections are implemented
405}
406 149
407void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) { 150 if (!info_updater.CheckConsumedSize()) {
408 if (info.is_new) { 151 LOG_ERROR(Audio, "Audio buffers were not consumed!");
409 out_status.state = EffectStatus::New; 152 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
410 } else {
411 if (info.type == Effect::Aux) {
412 ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0,
413 "Aux buffers tried to update");
414 ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0,
415 "Aux buffers tried to update");
416 ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0,
417 "Aux buffers tried to update");
418 ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0,
419 "Aux buffers tried to update");
420 }
421 } 153 }
422}
423 154
424static constexpr s16 ClampToS16(s32 value) { 155 ReleaseAndQueueBuffers();
425 return static_cast<s16>(std::clamp(value, -32768, 32767)); 156
157 return RESULT_SUCCESS;
426} 158}
427 159
428void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { 160void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
429 constexpr std::size_t BUFFER_SIZE{512}; 161 command_generator.PreCommand();
162 // Clear mix buffers before our next operation
163 command_generator.ClearMixBuffers();
164
165 // If the splitter is not in use, sort our mixes
166 if (!splitter_context.UsingSplitter()) {
167 mix_context.SortInfo();
168 }
169 // Sort our voices
170 voice_context.SortInfo();
171
172 // Handle samples
173 command_generator.GenerateVoiceCommands();
174 command_generator.GenerateSubMixCommands();
175 command_generator.GenerateFinalMixCommands();
176
177 command_generator.PostCommand();
178 // Base sample size
179 std::size_t BUFFER_SIZE{worker_params.sample_count};
180 // Samples
430 std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels()); 181 std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
431 182 // Make sure to clear our samples
432 for (auto& voice : voices) { 183 std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
433 if (!voice.IsPlaying()) { 184
434 continue; 185 if (sink_context.InUse()) {
435 } 186 const auto stream_channel_count = stream->GetNumChannels();
436 VoiceChannelHolder resources{}; 187 const auto buffer_offsets = sink_context.OutputBuffers();
437 for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) { 188 const auto channel_count = buffer_offsets.size();
438 const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel]; 189 const auto& final_mix = mix_context.GetFinalMixInfo();
439 resources[channel] = &voice_resources[channel_resource_id]; 190 const auto& in_params = final_mix.GetInParams();
191 std::vector<s32*> mix_buffers(channel_count);
192 for (std::size_t i = 0; i < channel_count; i++) {
193 mix_buffers[i] =
194 command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
440 } 195 }
441 196
442 std::size_t offset{}; 197 for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
443 s64 samples_remaining{BUFFER_SIZE}; 198 if (channel_count == 1) {
444 while (samples_remaining > 0) { 199 const auto sample = ClampToS16(mix_buffers[0][i]);
445 const std::vector<s16> samples{ 200 buffer[i * stream_channel_count + 0] = sample;
446 voice.DequeueSamples(samples_remaining, memory, resources)}; 201 if (stream_channel_count > 1) {
447 202 buffer[i * stream_channel_count + 1] = sample;
448 if (samples.empty()) { 203 }
449 break; 204 if (stream_channel_count == 6) {
450 } 205 buffer[i * stream_channel_count + 2] = sample;
451 206 buffer[i * stream_channel_count + 4] = sample;
452 samples_remaining -= samples.size() / stream->GetNumChannels(); 207 buffer[i * stream_channel_count + 5] = sample;
453 208 }
454 for (const auto& sample : samples) { 209 } else if (channel_count == 2) {
455 const s32 buffer_sample{buffer[offset]}; 210 const auto l_sample = ClampToS16(mix_buffers[0][i]);
456 buffer[offset++] = 211 const auto r_sample = ClampToS16(mix_buffers[1][i]);
457 ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume)); 212 if (stream_channel_count == 1) {
213 buffer[i * stream_channel_count + 0] = l_sample;
214 } else if (stream_channel_count == 2) {
215 buffer[i * stream_channel_count + 0] = l_sample;
216 buffer[i * stream_channel_count + 1] = r_sample;
217 } else if (stream_channel_count == 6) {
218 buffer[i * stream_channel_count + 0] = l_sample;
219 buffer[i * stream_channel_count + 1] = r_sample;
220
221 buffer[i * stream_channel_count + 2] =
222 ClampToS16((static_cast<s32>(l_sample) + static_cast<s32>(r_sample)) / 2);
223
224 buffer[i * stream_channel_count + 4] = l_sample;
225 buffer[i * stream_channel_count + 5] = r_sample;
226 }
227
228 } else if (channel_count == 6) {
229 const auto fl_sample = ClampToS16(mix_buffers[0][i]);
230 const auto fr_sample = ClampToS16(mix_buffers[1][i]);
231 const auto fc_sample = ClampToS16(mix_buffers[2][i]);
232 const auto lf_sample = ClampToS16(mix_buffers[3][i]);
233 const auto bl_sample = ClampToS16(mix_buffers[4][i]);
234 const auto br_sample = ClampToS16(mix_buffers[5][i]);
235
236 if (stream_channel_count == 1) {
237 buffer[i * stream_channel_count + 0] = fc_sample;
238 } else if (stream_channel_count == 2) {
239 buffer[i * stream_channel_count + 0] =
240 static_cast<s16>(0.3694f * static_cast<float>(fl_sample) +
241 0.2612f * static_cast<float>(fc_sample) +
242 0.3694f * static_cast<float>(bl_sample));
243 buffer[i * stream_channel_count + 1] =
244 static_cast<s16>(0.3694f * static_cast<float>(fr_sample) +
245 0.2612f * static_cast<float>(fc_sample) +
246 0.3694f * static_cast<float>(br_sample));
247 } else if (stream_channel_count == 6) {
248 buffer[i * stream_channel_count + 0] = fl_sample;
249 buffer[i * stream_channel_count + 1] = fr_sample;
250 buffer[i * stream_channel_count + 2] = fc_sample;
251 buffer[i * stream_channel_count + 3] = lf_sample;
252 buffer[i * stream_channel_count + 4] = bl_sample;
253 buffer[i * stream_channel_count + 5] = br_sample;
254 }
458 } 255 }
459 } 256 }
460 } 257 }
258
461 audio_out->QueueBuffer(stream, tag, std::move(buffer)); 259 audio_out->QueueBuffer(stream, tag, std::move(buffer));
462 elapsed_frame_count++; 260 elapsed_frame_count++;
261 voice_context.UpdateStateByDspShared();
463} 262}
464 263
465void AudioRenderer::ReleaseAndQueueBuffers() { 264void AudioRenderer::ReleaseAndQueueBuffers() {
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index f0b691a86..2bca795ba 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -9,8 +9,15 @@
9#include <vector> 9#include <vector>
10 10
11#include "audio_core/behavior_info.h" 11#include "audio_core/behavior_info.h"
12#include "audio_core/command_generator.h"
12#include "audio_core/common.h" 13#include "audio_core/common.h"
14#include "audio_core/effect_context.h"
15#include "audio_core/memory_pool.h"
16#include "audio_core/mix_context.h"
17#include "audio_core/sink_context.h"
18#include "audio_core/splitter_context.h"
13#include "audio_core/stream.h" 19#include "audio_core/stream.h"
20#include "audio_core/voice_context.h"
14#include "common/common_funcs.h" 21#include "common/common_funcs.h"
15#include "common/common_types.h" 22#include "common/common_types.h"
16#include "common/swap.h" 23#include "common/swap.h"
@@ -30,220 +37,25 @@ class Memory;
30} 37}
31 38
32namespace AudioCore { 39namespace AudioCore {
40using DSPStateHolder = std::array<VoiceState*, 6>;
33 41
34class AudioOut; 42class AudioOut;
35 43
36enum class PlayState : u8 {
37 Started = 0,
38 Stopped = 1,
39 Paused = 2,
40};
41
42enum class Effect : u8 {
43 None = 0,
44 Aux = 2,
45};
46
47enum class EffectStatus : u8 {
48 None = 0,
49 New = 1,
50};
51
52struct AudioRendererParameter {
53 u32_le sample_rate;
54 u32_le sample_count;
55 u32_le mix_buffer_count;
56 u32_le submix_count;
57 u32_le voice_count;
58 u32_le sink_count;
59 u32_le effect_count;
60 u32_le performance_frame_count;
61 u8 is_voice_drop_enabled;
62 u8 unknown_21;
63 u8 unknown_22;
64 u8 execution_mode;
65 u32_le splitter_count;
66 u32_le num_splitter_send_channels;
67 u32_le unknown_30;
68 u32_le revision;
69};
70static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
71
72enum class MemoryPoolStates : u32 { // Should be LE
73 Invalid = 0x0,
74 Unknown = 0x1,
75 RequestDetach = 0x2,
76 Detached = 0x3,
77 RequestAttach = 0x4,
78 Attached = 0x5,
79 Released = 0x6,
80};
81
82struct MemoryPoolEntry {
83 MemoryPoolStates state;
84 u32_le unknown_4;
85 u32_le unknown_8;
86 u32_le unknown_c;
87};
88static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
89
90struct MemoryPoolInfo {
91 u64_le pool_address;
92 u64_le pool_size;
93 MemoryPoolStates pool_state;
94 INSERT_PADDING_WORDS(3); // Unknown
95};
96static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
97struct BiquadFilter {
98 u8 enable;
99 INSERT_PADDING_BYTES(1);
100 std::array<s16_le, 3> numerator;
101 std::array<s16_le, 2> denominator;
102};
103static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
104
105struct WaveBuffer {
106 u64_le buffer_addr;
107 u64_le buffer_sz;
108 s32_le start_sample_offset;
109 s32_le end_sample_offset;
110 u8 is_looping;
111 u8 end_of_stream;
112 u8 sent_to_server;
113 INSERT_PADDING_BYTES(5);
114 u64 context_addr;
115 u64 context_sz;
116 INSERT_PADDING_BYTES(8);
117};
118static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
119
120struct VoiceResourceInformation {
121 s32_le id{};
122 std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{};
123 bool in_use{};
124 INSERT_PADDING_BYTES(11);
125};
126static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size");
127
128struct VoiceInfo {
129 u32_le id;
130 u32_le node_id;
131 u8 is_new;
132 u8 is_in_use;
133 PlayState play_state;
134 u8 sample_format;
135 u32_le sample_rate;
136 u32_le priority;
137 u32_le sorting_order;
138 u32_le channel_count;
139 float_le pitch;
140 float_le volume;
141 std::array<BiquadFilter, 2> biquad_filter;
142 u32_le wave_buffer_count;
143 u32_le wave_buffer_head;
144 INSERT_PADDING_WORDS(1);
145 u64_le additional_params_addr;
146 u64_le additional_params_sz;
147 u32_le mix_id;
148 u32_le splitter_info_id;
149 std::array<WaveBuffer, 4> wave_buffer;
150 std::array<u32_le, 6> voice_channel_resource_ids;
151 INSERT_PADDING_BYTES(24);
152};
153static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
154
155struct VoiceOutStatus {
156 u64_le played_sample_count;
157 u32_le wave_buffer_consumed;
158 u32_le voice_drops_count;
159};
160static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
161
162struct AuxInfo {
163 std::array<u8, 24> input_mix_buffers;
164 std::array<u8, 24> output_mix_buffers;
165 u32_le mix_buffer_count;
166 u32_le sample_rate; // Stored in the aux buffer currently
167 u32_le sample_count;
168 u64_le send_buffer_info;
169 u64_le send_buffer_base;
170
171 u64_le return_buffer_info;
172 u64_le return_buffer_base;
173};
174static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
175
176struct EffectInStatus {
177 Effect type;
178 u8 is_new;
179 u8 is_enabled;
180 INSERT_PADDING_BYTES(1);
181 u32_le mix_id;
182 u64_le buffer_base;
183 u64_le buffer_sz;
184 s32_le priority;
185 INSERT_PADDING_BYTES(4);
186 union {
187 std::array<u8, 0xa0> raw;
188 AuxInfo aux_info;
189 };
190};
191static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
192
193struct EffectOutStatus {
194 EffectStatus state;
195 INSERT_PADDING_BYTES(0xf);
196};
197static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
198
199struct RendererInfo { 44struct RendererInfo {
200 u64_le elasped_frame_count{}; 45 u64_le elasped_frame_count{};
201 INSERT_PADDING_WORDS(2); 46 INSERT_PADDING_WORDS(2);
202}; 47};
203static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); 48static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
204 49
205struct UpdateDataHeader {
206 UpdateDataHeader() {}
207
208 explicit UpdateDataHeader(const AudioRendererParameter& config) {
209 revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision
210 behavior_size = 0xb0;
211 memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
212 voices_size = config.voice_count * 0x10;
213 voice_resource_size = 0x0;
214 effects_size = config.effect_count * 0x10;
215 mixes_size = 0x0;
216 sinks_size = config.sink_count * 0x20;
217 performance_manager_size = 0x10;
218 render_info = 0;
219 total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
220 effects_size + sinks_size + performance_manager_size;
221 }
222
223 u32_le revision{};
224 u32_le behavior_size{};
225 u32_le memory_pools_size{};
226 u32_le voices_size{};
227 u32_le voice_resource_size{};
228 u32_le effects_size{};
229 u32_le mixes_size{};
230 u32_le sinks_size{};
231 u32_le performance_manager_size{};
232 u32_le splitter_size{};
233 u32_le render_info{};
234 INSERT_PADDING_WORDS(4);
235 u32_le total_size{};
236};
237static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
238
239class AudioRenderer { 50class AudioRenderer {
240public: 51public:
241 AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, 52 AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
242 AudioRendererParameter params, 53 AudioCommon::AudioRendererParameter params,
243 std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number); 54 std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number);
244 ~AudioRenderer(); 55 ~AudioRenderer();
245 56
246 ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params); 57 ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
58 std::vector<u8>& output_params);
247 void QueueMixedBuffer(Buffer::Tag tag); 59 void QueueMixedBuffer(Buffer::Tag tag);
248 void ReleaseAndQueueBuffers(); 60 void ReleaseAndQueueBuffers();
249 u32 GetSampleRate() const; 61 u32 GetSampleRate() const;
@@ -252,19 +64,23 @@ public:
252 Stream::State GetStreamState() const; 64 Stream::State GetStreamState() const;
253 65
254private: 66private:
255 class EffectState;
256 class VoiceState;
257 BehaviorInfo behavior_info{}; 67 BehaviorInfo behavior_info{};
258 68
259 AudioRendererParameter worker_params; 69 AudioCommon::AudioRendererParameter worker_params;
260 std::shared_ptr<Kernel::WritableEvent> buffer_event; 70 std::shared_ptr<Kernel::WritableEvent> buffer_event;
71 std::vector<ServerMemoryPoolInfo> memory_pool_info;
72 VoiceContext voice_context;
73 EffectContext effect_context;
74 MixContext mix_context;
75 SinkContext sink_context;
76 SplitterContext splitter_context;
261 std::vector<VoiceState> voices; 77 std::vector<VoiceState> voices;
262 std::vector<VoiceResourceInformation> voice_resources;
263 std::vector<EffectState> effects;
264 std::unique_ptr<AudioOut> audio_out; 78 std::unique_ptr<AudioOut> audio_out;
265 StreamPtr stream; 79 StreamPtr stream;
266 Core::Memory::Memory& memory; 80 Core::Memory::Memory& memory;
81 CommandGenerator command_generator;
267 std::size_t elapsed_frame_count{}; 82 std::size_t elapsed_frame_count{};
83 std::vector<s32> temp_mix_buffer{};
268}; 84};
269 85
270} // namespace AudioCore 86} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp
index 94b7a3bf1..5d62adb0b 100644
--- a/src/audio_core/behavior_info.cpp
+++ b/src/audio_core/behavior_info.cpp
@@ -9,39 +9,11 @@
9 9
10namespace AudioCore { 10namespace AudioCore {
11 11
12BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {} 12BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
13BehaviorInfo::~BehaviorInfo() = default; 13BehaviorInfo::~BehaviorInfo() = default;
14 14
15bool BehaviorInfo::UpdateInput(const std::vector<u8>& buffer, std::size_t offset) {
16 if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) {
17 LOG_ERROR(Audio, "Buffer is an invalid size!");
18 return false;
19 }
20 InParams params{};
21 std::memcpy(&params, buffer.data() + offset, sizeof(InParams));
22
23 if (!IsValidRevision(params.revision)) {
24 LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision);
25 return false;
26 }
27
28 if (user_revision != params.revision) {
29 LOG_ERROR(Audio,
30 "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
31 user_revision, params.revision);
32 return false;
33 }
34
35 ClearError();
36 UpdateFlags(params.flags);
37
38 // TODO(ogniK): Check input params size when InfoUpdater is used
39
40 return true;
41}
42
43bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) { 15bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
44 if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) { 16 if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
45 LOG_ERROR(Audio, "Buffer is an invalid size!"); 17 LOG_ERROR(Audio, "Buffer is an invalid size!");
46 return false; 18 return false;
47 } 19 }
@@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) {
65 user_revision = revision; 37 user_revision = revision;
66} 38}
67 39
40u32_le BehaviorInfo::GetUserRevision() const {
41 return user_revision;
42}
43
44u32_le BehaviorInfo::GetProcessRevision() const {
45 return process_revision;
46}
47
68bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { 48bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
69 return IsRevisionSupported(2, user_revision); 49 return AudioCommon::IsRevisionSupported(2, user_revision);
70} 50}
71 51
72bool BehaviorInfo::IsSplitterSupported() const { 52bool BehaviorInfo::IsSplitterSupported() const {
73 return IsRevisionSupported(2, user_revision); 53 return AudioCommon::IsRevisionSupported(2, user_revision);
74} 54}
75 55
76bool BehaviorInfo::IsLongSizePreDelaySupported() const { 56bool BehaviorInfo::IsLongSizePreDelaySupported() const {
77 return IsRevisionSupported(3, user_revision); 57 return AudioCommon::IsRevisionSupported(3, user_revision);
78} 58}
79 59
80bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const { 60bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const {
81 return IsRevisionSupported(5, user_revision); 61 return AudioCommon::IsRevisionSupported(5, user_revision);
82} 62}
83 63
84bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const { 64bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const {
85 return IsRevisionSupported(4, user_revision); 65 return AudioCommon::IsRevisionSupported(4, user_revision);
86} 66}
87 67
88bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const { 68bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const {
89 return IsRevisionSupported(1, user_revision); 69 return AudioCommon::IsRevisionSupported(1, user_revision);
90} 70}
91 71
92bool BehaviorInfo::IsElapsedFrameCountSupported() const { 72bool BehaviorInfo::IsElapsedFrameCountSupported() const {
93 return IsRevisionSupported(5, user_revision); 73 return AudioCommon::IsRevisionSupported(5, user_revision);
94} 74}
95 75
96bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { 76bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
97 return (flags & 1) != 0; 77 return (flags & 1) != 0;
98} 78}
99 79
80bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
81 return AudioCommon::IsRevisionSupported(5, user_revision);
82}
83
84bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
85 return AudioCommon::IsRevisionSupported(5, user_revision);
86}
87
88bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
89 return AudioCommon::IsRevisionSupported(5, user_revision);
90}
91
92bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
93 return AudioCommon::IsRevisionSupported(7, user_revision);
94}
95
96bool BehaviorInfo::IsSplitterBugFixed() const {
97 return AudioCommon::IsRevisionSupported(5, user_revision);
98}
99
100void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
101 dst.error_count = static_cast<u32>(error_count);
102 std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin());
103}
104
100} // namespace AudioCore 105} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h
index c5e91ab39..50948e8df 100644
--- a/src/audio_core/behavior_info.h
+++ b/src/audio_core/behavior_info.h
@@ -14,15 +14,37 @@
14namespace AudioCore { 14namespace AudioCore {
15class BehaviorInfo { 15class BehaviorInfo {
16public: 16public:
17 struct ErrorInfo {
18 u32_le result{};
19 INSERT_PADDING_WORDS(1);
20 u64_le result_info{};
21 };
22 static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
23
24 struct InParams {
25 u32_le revision{};
26 u32_le padding{};
27 u64_le flags{};
28 };
29 static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size");
30
31 struct OutParams {
32 std::array<ErrorInfo, 10> errors{};
33 u32_le error_count{};
34 INSERT_PADDING_BYTES(12);
35 };
36 static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
37
17 explicit BehaviorInfo(); 38 explicit BehaviorInfo();
18 ~BehaviorInfo(); 39 ~BehaviorInfo();
19 40
20 bool UpdateInput(const std::vector<u8>& buffer, std::size_t offset);
21 bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset); 41 bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
22 42
23 void ClearError(); 43 void ClearError();
24 void UpdateFlags(u64_le dest_flags); 44 void UpdateFlags(u64_le dest_flags);
25 void SetUserRevision(u32_le revision); 45 void SetUserRevision(u32_le revision);
46 u32_le GetUserRevision() const;
47 u32_le GetProcessRevision() const;
26 48
27 bool IsAdpcmLoopContextBugFixed() const; 49 bool IsAdpcmLoopContextBugFixed() const;
28 bool IsSplitterSupported() const; 50 bool IsSplitterSupported() const;
@@ -32,35 +54,19 @@ public:
32 bool IsAudioRenererProcessingTimeLimit70PercentSupported() const; 54 bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
33 bool IsElapsedFrameCountSupported() const; 55 bool IsElapsedFrameCountSupported() const;
34 bool IsMemoryPoolForceMappingEnabled() const; 56 bool IsMemoryPoolForceMappingEnabled() const;
57 bool IsFlushVoiceWaveBuffersSupported() const;
58 bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
59 bool IsVoicePitchAndSrcSkippedSupported() const;
60 bool IsMixInParameterDirtyOnlyUpdateSupported() const;
61 bool IsSplitterBugFixed() const;
62 void CopyErrorInfo(OutParams& dst);
35 63
36private: 64private:
37 u32_le process_revision{}; 65 u32_le process_revision{};
38 u32_le user_revision{}; 66 u32_le user_revision{};
39 u64_le flags{}; 67 u64_le flags{};
40
41 struct ErrorInfo {
42 u32_le result{};
43 INSERT_PADDING_WORDS(1);
44 u64_le result_info{};
45 };
46 static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
47
48 std::array<ErrorInfo, 10> errors{}; 68 std::array<ErrorInfo, 10> errors{};
49 std::size_t error_count{}; 69 std::size_t error_count{};
50
51 struct InParams {
52 u32_le revision{};
53 u32_le padding{};
54 u64_le flags{};
55 };
56 static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size");
57
58 struct OutParams {
59 std::array<ErrorInfo, 10> errors{};
60 u32_le error_count{};
61 INSERT_PADDING_BYTES(12);
62 };
63 static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
64}; 70};
65 71
66} // namespace AudioCore 72} // namespace AudioCore
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
new file mode 100644
index 000000000..8f7da49e6
--- /dev/null
+++ b/src/audio_core/command_generator.cpp
@@ -0,0 +1,977 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/algorithm/interpolate.h"
6#include "audio_core/command_generator.h"
7#include "audio_core/effect_context.h"
8#include "audio_core/mix_context.h"
9#include "audio_core/voice_context.h"
10#include "core/memory.h"
11
12namespace AudioCore {
13namespace {
14constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
15constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
16
17template <std::size_t N>
18void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
19 for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) {
20 for (std::size_t j = 0; j < N; j++) {
21 output[i + j] +=
22 static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
23 }
24 }
25}
26
27s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) {
28 s32 x = 0;
29 for (s32 i = 0; i < sample_count; i++) {
30 x = static_cast<s32>(static_cast<float>(input[i]) * gain);
31 output[i] += x;
32 gain += delta;
33 }
34 return x;
35}
36
37void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) {
38 for (s32 i = 0; i < sample_count; i++) {
39 output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
40 gain += delta;
41 }
42}
43
44void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) {
45 for (s32 i = 0; i < sample_count; i++) {
46 output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
47 }
48}
49
50s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
51 const bool positive = first_sample > 0;
52 auto final_sample = std::abs(first_sample);
53 for (s32 i = 0; i < sample_count; i++) {
54 final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15);
55 if (positive) {
56 output[i] += final_sample;
57 } else {
58 output[i] -= final_sample;
59 }
60 }
61 if (positive) {
62 return final_sample;
63 } else {
64 return -final_sample;
65 }
66}
67
68} // namespace
69
70CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
71 VoiceContext& voice_context, MixContext& mix_context,
72 SplitterContext& splitter_context, EffectContext& effect_context,
73 Core::Memory::Memory& memory)
74 : worker_params(worker_params), voice_context(voice_context), mix_context(mix_context),
75 splitter_context(splitter_context), effect_context(effect_context), memory(memory),
76 mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
77 worker_params.sample_count),
78 sample_buffer(MIX_BUFFER_SIZE),
79 depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
80 worker_params.sample_count) {}
81CommandGenerator::~CommandGenerator() = default;
82
83void CommandGenerator::ClearMixBuffers() {
84 std::fill(mix_buffer.begin(), mix_buffer.end(), 0);
85 std::fill(sample_buffer.begin(), sample_buffer.end(), 0);
86 // std::fill(depop_buffer.begin(), depop_buffer.end(), 0);
87}
88
89void CommandGenerator::GenerateVoiceCommands() {
90 if (dumping_frame) {
91 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands");
92 }
93 // Grab all our voices
94 const auto voice_count = voice_context.GetVoiceCount();
95 for (std::size_t i = 0; i < voice_count; i++) {
96 auto& voice_info = voice_context.GetSortedInfo(i);
97 // Update voices and check if we should queue them
98 if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
99 continue;
100 }
101
102 // Queue our voice
103 GenerateVoiceCommand(voice_info);
104 }
105 // Update our splitters
106 splitter_context.UpdateInternalState();
107}
108
109void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
110 auto& in_params = voice_info.GetInParams();
111 const auto channel_count = in_params.channel_count;
112
113 for (s32 channel = 0; channel < channel_count; channel++) {
114 const auto resource_id = in_params.voice_channel_resource_id[channel];
115 auto& dsp_state = voice_context.GetDspSharedState(resource_id);
116 auto& channel_resource = voice_context.GetChannelResource(resource_id);
117
118 // Decode our samples for our channel
119 GenerateDataSourceCommand(voice_info, dsp_state, channel);
120
121 if (in_params.should_depop) {
122 in_params.last_volume = 0.0f;
123 } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
124 in_params.mix_id != AudioCommon::NO_MIX) {
125 // Apply a biquad filter if needed
126 GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
127 worker_params.mix_buffer_count, channel);
128 // Base voice volume ramping
129 GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
130 in_params.node_id);
131 in_params.last_volume = in_params.volume;
132
133 if (in_params.mix_id != AudioCommon::NO_MIX) {
134 // If we're using a mix id
135 auto& mix_info = mix_context.GetInfo(in_params.mix_id);
136 const auto& dest_mix_params = mix_info.GetInParams();
137
138 // Voice Mixing
139 GenerateVoiceMixCommand(
140 channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
141 dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
142 worker_params.mix_buffer_count + channel, in_params.node_id);
143
144 // Update last mix volumes
145 channel_resource.UpdateLastMixVolumes();
146 } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
147 s32 base = channel;
148 while (auto* destination_data =
149 GetDestinationData(in_params.splitter_info_id, base)) {
150 base += channel_count;
151
152 if (!destination_data->IsConfigured()) {
153 continue;
154 }
155 if (destination_data->GetMixId() >= mix_context.GetCount()) {
156 continue;
157 }
158
159 const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
160 const auto& dest_mix_params = mix_info.GetInParams();
161 GenerateVoiceMixCommand(
162 destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
163 dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
164 worker_params.mix_buffer_count + channel, in_params.node_id);
165 destination_data->MarkDirty();
166 }
167 }
168 // Update biquad filter enabled states
169 for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
170 in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
171 }
172 }
173 }
174}
175
176void CommandGenerator::GenerateSubMixCommands() {
177 const auto mix_count = mix_context.GetCount();
178 for (std::size_t i = 0; i < mix_count; i++) {
179 auto& mix_info = mix_context.GetSortedInfo(i);
180 const auto& in_params = mix_info.GetInParams();
181 if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
182 continue;
183 }
184 GenerateSubMixCommand(mix_info);
185 }
186}
187
188void CommandGenerator::GenerateFinalMixCommands() {
189 GenerateFinalMixCommand();
190}
191
192void CommandGenerator::PreCommand() {
193 if (!dumping_frame) {
194 return;
195 }
196 for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
197 const auto& base = splitter_context.GetInfo(i);
198 std::string graph = fmt::format("b[{}]", i);
199 const auto* head = base.GetHead();
200 while (head != nullptr) {
201 graph += fmt::format("->{}", head->GetMixId());
202 head = head->GetNextDestination();
203 }
204 LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
205 }
206}
207
208void CommandGenerator::PostCommand() {
209 if (!dumping_frame) {
210 return;
211 }
212 dumping_frame = false;
213}
214
215void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
216 s32 channel) {
217 const auto& in_params = voice_info.GetInParams();
218 const auto depop = in_params.should_depop;
219
220 if (depop) {
221 if (in_params.mix_id != AudioCommon::NO_MIX) {
222 auto& mix_info = mix_context.GetInfo(in_params.mix_id);
223 const auto& mix_in = mix_info.GetInParams();
224 GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
225 } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
226 s32 index{};
227 while (const auto* destination =
228 GetDestinationData(in_params.splitter_info_id, index++)) {
229 if (!destination->IsConfigured()) {
230 continue;
231 }
232 auto& mix_info = mix_context.GetInfo(destination->GetMixId());
233 const auto& mix_in = mix_info.GetInParams();
234 GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
235 }
236 }
237 } else {
238 switch (in_params.sample_format) {
239 case SampleFormat::Pcm16:
240 DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
241 worker_params.sample_rate, worker_params.sample_count,
242 in_params.node_id);
243 break;
244 case SampleFormat::Adpcm:
245 ASSERT(channel == 0 && in_params.channel_count == 1);
246 DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
247 worker_params.sample_rate, worker_params.sample_count,
248 in_params.node_id);
249 break;
250 default:
251 UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
252 }
253 }
254}
255
256void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
257 VoiceState& dsp_state,
258 s32 mix_buffer_count, s32 channel) {
259 for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
260 const auto& in_params = voice_info.GetInParams();
261 auto& biquad_filter = in_params.biquad_filter[i];
262 // Check if biquad filter is actually used
263 if (!biquad_filter.enabled) {
264 continue;
265 }
266
267 // Reinitialize our biquad filter state if it was enabled previously
268 if (!in_params.was_biquad_filter_enabled[i]) {
269 dsp_state.biquad_filter_state.fill(0);
270 }
271
272 // Generate biquad filter
273 // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
274 // dsp_state.biquad_filter_state,
275 // mix_buffer_count + channel, mix_buffer_count +
276 // channel, worker_params.sample_count,
277 // voice_info.GetInParams().node_id);
278 }
279}
280
281void AudioCore::CommandGenerator::GenerateBiquadFilterCommand(
282 s32 mix_buffer, const BiquadFilterParameter& params, std::array<s64, 2>& state,
283 std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) {
284 if (dumping_frame) {
285 LOG_DEBUG(Audio,
286 "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
287 "input_mix_buffer={}, output_mix_buffer={}",
288 node_id, input_offset, output_offset);
289 }
290 const auto* input = GetMixBuffer(input_offset);
291 auto* output = GetMixBuffer(output_offset);
292
293 // Biquad filter parameters
294 const auto [n0, n1, n2] = params.numerator;
295 const auto [d0, d1] = params.denominator;
296
297 // Biquad filter states
298 auto [s0, s1] = state;
299
300 constexpr s64 int32_min = std::numeric_limits<s32>::min();
301 constexpr s64 int32_max = std::numeric_limits<s32>::max();
302
303 for (int i = 0; i < sample_count; ++i) {
304 const auto sample = static_cast<s64>(input[i]);
305 const auto f = (sample * n0 + s0 + 0x4000) >> 15;
306 const auto y = std::clamp(f, int32_min, int32_max);
307 s0 = sample * n1 + y * d0 + s1;
308 s1 = sample * n2 + y * d1;
309 output[i] = static_cast<s32>(y);
310 }
311
312 state = {s0, s1};
313}
314
315void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state,
316 std::size_t mix_buffer_count,
317 std::size_t mix_buffer_offset) {
318 for (std::size_t i = 0; i < mix_buffer_count; i++) {
319 auto& sample = dsp_state.previous_samples[i];
320 if (sample != 0) {
321 depop_buffer[mix_buffer_offset + i] += sample;
322 sample = 0;
323 }
324 }
325}
326
327void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
328 std::size_t mix_buffer_offset,
329 s32 sample_rate) {
330 const std::size_t end_offset =
331 std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount());
332 const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB;
333 for (std::size_t i = mix_buffer_offset; i < end_offset; i++) {
334 if (depop_buffer[i] == 0) {
335 continue;
336 }
337
338 depop_buffer[i] =
339 ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count);
340 }
341}
342
343void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
344 const std::size_t effect_count = effect_context.GetCount();
345 const auto buffer_offset = mix_info.GetInParams().buffer_offset;
346 for (std::size_t i = 0; i < effect_count; i++) {
347 const auto index = mix_info.GetEffectOrder(i);
348 if (index == AudioCommon::NO_EFFECT_ORDER) {
349 break;
350 }
351 auto* info = effect_context.GetInfo(index);
352 const auto type = info->GetType();
353
354 // TODO(ogniK): Finish remaining effects
355 switch (type) {
356 case EffectType::Aux:
357 GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
358 break;
359 case EffectType::I3dl2Reverb:
360 GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
361 break;
362 case EffectType::BiquadFilter:
363 GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
364 break;
365 default:
366 break;
367 }
368
369 info->UpdateForCommandGeneration();
370 }
371}
372
373void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
374 bool enabled) {
375 if (!enabled) {
376 return;
377 }
378 const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams();
379 const auto channel_count = params.channel_count;
380 for (s32 i = 0; i < channel_count; i++) {
381 // TODO(ogniK): Actually implement reverb
382 if (params.input[i] != params.output[i]) {
383 const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
384 auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
385 ApplyMix<1>(output, input, 32768, worker_params.sample_count);
386 }
387 }
388}
389
390void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
391 bool enabled) {
392 if (!enabled) {
393 return;
394 }
395 const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
396 const auto channel_count = params.channel_count;
397 for (s32 i = 0; i < channel_count; i++) {
398 // TODO(ogniK): Actually implement biquad filter
399 if (params.input[i] != params.output[i]) {
400 const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
401 auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
402 ApplyMix<1>(output, input, 32768, worker_params.sample_count);
403 }
404 }
405}
406
407void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
408 auto* aux = dynamic_cast<EffectAuxInfo*>(info);
409 const auto& params = aux->GetParams();
410 if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
411 const auto max_channels = params.count;
412 u32 offset{};
413 for (u32 channel = 0; channel < max_channels; channel++) {
414 u32 write_count = 0;
415 if (channel == (max_channels - 1)) {
416 write_count = offset + worker_params.sample_count;
417 }
418
419 const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
420 const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
421
422 if (enabled) {
423 AuxInfoDSP send_info{};
424 AuxInfoDSP recv_info{};
425 memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
426 memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
427
428 WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
429 GetMixBuffer(input_index), worker_params.sample_count, offset,
430 write_count);
431 memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
432
433 const auto samples_read = ReadAuxBuffer(
434 recv_info, aux->GetRecvBuffer(), params.sample_count,
435 GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
436 memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
437
438 if (samples_read != worker_params.sample_count &&
439 samples_read <= params.sample_count) {
440 std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read);
441 }
442 } else {
443 AuxInfoDSP empty{};
444 memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
445 memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
446 if (output_index != input_index) {
447 std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index),
448 worker_params.sample_count * sizeof(s32));
449 }
450 }
451
452 offset += worker_params.sample_count;
453 }
454 }
455}
456
457ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
458 if (splitter_id == AudioCommon::NO_SPLITTER) {
459 return nullptr;
460 }
461 return splitter_context.GetDestinationData(splitter_id, index);
462}
463
464s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
465 const s32* data, u32 sample_count, u32 write_offset,
466 u32 write_count) {
467 if (max_samples == 0) {
468 return 0;
469 }
470 u32 offset = dsp_info.write_offset + write_offset;
471 if (send_buffer == 0 || offset > max_samples) {
472 return 0;
473 }
474
475 std::size_t data_offset{};
476 u32 remaining = sample_count;
477 while (remaining > 0) {
478 // Get position in buffer
479 const auto base = send_buffer + (offset * sizeof(u32));
480 const auto samples_to_grab = std::min(max_samples - offset, remaining);
481 // Write to output
482 memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32));
483 offset = (offset + samples_to_grab) % max_samples;
484 remaining -= samples_to_grab;
485 data_offset += samples_to_grab;
486 }
487
488 if (write_count != 0) {
489 dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
490 }
491 return sample_count;
492}
493
494s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
495 s32* out_data, u32 sample_count, u32 read_offset,
496 u32 read_count) {
497 if (max_samples == 0) {
498 return 0;
499 }
500
501 u32 offset = recv_info.read_offset + read_offset;
502 if (recv_buffer == 0 || offset > max_samples) {
503 return 0;
504 }
505
506 u32 remaining = sample_count;
507 while (remaining > 0) {
508 const auto base = recv_buffer + (offset * sizeof(u32));
509 const auto samples_to_grab = std::min(max_samples - offset, remaining);
510 std::vector<s32> buffer(samples_to_grab);
511 memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
512 std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32));
513 out_data += samples_to_grab;
514 offset = (offset + samples_to_grab) % max_samples;
515 remaining -= samples_to_grab;
516 }
517
518 if (read_count != 0) {
519 recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
520 }
521 return sample_count;
522}
523
524void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
525 s32 channel, s32 node_id) {
526 const auto last = static_cast<s32>(last_volume * 32768.0f);
527 const auto current = static_cast<s32>(current_volume * 32768.0f);
528 const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
529 static_cast<float>(worker_params.sample_count));
530
531 if (dumping_frame) {
532 LOG_DEBUG(Audio,
533 "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
534 "last_volume={}, current_volume={}",
535 node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel),
536 last_volume, current_volume);
537 }
538 // Apply generic gain on samples
539 ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
540 worker_params.sample_count);
541}
542
543void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
544 const MixVolumeBuffer& last_mix_volumes,
545 VoiceState& dsp_state, s32 mix_buffer_offset,
546 s32 mix_buffer_count, s32 voice_index, s32 node_id) {
547 // Loop all our mix buffers
548 for (s32 i = 0; i < mix_buffer_count; i++) {
549 if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
550 const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
551 static_cast<float>(worker_params.sample_count);
552
553 if (dumping_frame) {
554 LOG_DEBUG(Audio,
555 "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
556 "output={}, last_volume={}, current_volume={}",
557 node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
558 mix_volumes[i]);
559 }
560
561 dsp_state.previous_samples[i] =
562 ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
563 last_mix_volumes[i], delta, worker_params.sample_count);
564 } else {
565 dsp_state.previous_samples[i] = 0;
566 }
567 }
568}
569
570void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
571 if (dumping_frame) {
572 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
573 }
574 const auto& in_params = mix_info.GetInParams();
575 GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
576 in_params.sample_rate);
577
578 GenerateEffectCommand(mix_info);
579
580 GenerateMixCommands(mix_info);
581}
582
583void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
584 if (!mix_info.HasAnyConnection()) {
585 return;
586 }
587 const auto& in_params = mix_info.GetInParams();
588 if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
589 const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
590 const auto& dest_in_params = dest_mix.GetInParams();
591
592 const auto buffer_count = in_params.buffer_count;
593
594 for (s32 i = 0; i < buffer_count; i++) {
595 for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
596 const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
597 if (mixed_volume != 0.0f) {
598 GenerateMixCommand(dest_in_params.buffer_offset + j,
599 in_params.buffer_offset + i, mixed_volume,
600 in_params.node_id);
601 }
602 }
603 }
604 } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
605 s32 base{};
606 while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
607 if (!destination_data->IsConfigured()) {
608 continue;
609 }
610
611 const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
612 const auto& dest_in_params = dest_mix.GetInParams();
613 const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
614 for (std::size_t i = 0; i < dest_in_params.buffer_count; i++) {
615 const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
616 if (mixed_volume != 0.0f) {
617 GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
618 in_params.node_id);
619 }
620 }
621 }
622 }
623}
624
625void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
626 float volume, s32 node_id) {
627
628 if (dumping_frame) {
629 LOG_DEBUG(Audio,
630 "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
631 node_id, input_offset, output_offset, volume);
632 }
633
634 auto* output = GetMixBuffer(output_offset);
635 const auto* input = GetMixBuffer(input_offset);
636
637 const s32 gain = static_cast<s32>(volume * 32768.0f);
638 // Mix with loop unrolling
639 if (worker_params.sample_count % 4 == 0) {
640 ApplyMix<4>(output, input, gain, worker_params.sample_count);
641 } else if (worker_params.sample_count % 2 == 0) {
642 ApplyMix<2>(output, input, gain, worker_params.sample_count);
643 } else {
644 ApplyMix<1>(output, input, gain, worker_params.sample_count);
645 }
646}
647
648void CommandGenerator::GenerateFinalMixCommand() {
649 if (dumping_frame) {
650 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
651 }
652 auto& mix_info = mix_context.GetFinalMixInfo();
653 const auto& in_params = mix_info.GetInParams();
654
655 GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
656 in_params.sample_rate);
657
658 GenerateEffectCommand(mix_info);
659
660 for (s32 i = 0; i < in_params.buffer_count; i++) {
661 const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
662 if (dumping_frame) {
663 LOG_DEBUG(
664 Audio,
665 "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
666 in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
667 in_params.volume);
668 }
669 ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
670 GetMixBuffer(in_params.buffer_offset + i), gain,
671 worker_params.sample_count);
672 }
673}
674
675s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
676 s32 sample_count, s32 channel, std::size_t mix_offset) {
677 const auto& in_params = voice_info.GetInParams();
678 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
679 if (wave_buffer.buffer_address == 0) {
680 return 0;
681 }
682 if (wave_buffer.buffer_size == 0) {
683 return 0;
684 }
685 if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
686 return 0;
687 }
688 const auto samples_remaining =
689 (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
690 const auto start_offset =
691 ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
692 sizeof(s16);
693 const auto buffer_pos = wave_buffer.buffer_address + start_offset;
694 const auto samples_processed = std::min(sample_count, samples_remaining);
695
696 if (in_params.channel_count == 1) {
697 std::vector<s16> buffer(samples_processed);
698 memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
699 for (std::size_t i = 0; i < buffer.size(); i++) {
700 sample_buffer[mix_offset + i] = buffer[i];
701 }
702 } else {
703 const auto channel_count = in_params.channel_count;
704 std::vector<s16> buffer(samples_processed * channel_count);
705 memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
706
707 for (std::size_t i = 0; i < samples_processed; i++) {
708 sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
709 }
710 }
711
712 return samples_processed;
713}
714
715s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
716 s32 sample_count, s32 channel, std::size_t mix_offset) {
717 const auto& in_params = voice_info.GetInParams();
718 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
719 if (wave_buffer.buffer_address == 0) {
720 return 0;
721 }
722 if (wave_buffer.buffer_size == 0) {
723 return 0;
724 }
725 if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
726 return 0;
727 }
728
729 constexpr std::array<int, 16> SIGNED_NIBBLES = {
730 {0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}};
731
732 constexpr std::size_t FRAME_LEN = 8;
733 constexpr std::size_t NIBBLES_PER_SAMPLE = 16;
734 constexpr std::size_t SAMPLES_PER_FRAME = 14;
735
736 auto frame_header = dsp_state.context.header;
737 s32 idx = (frame_header >> 4) & 0xf;
738 s32 scale = frame_header & 0xf;
739 s16 yn1 = dsp_state.context.yn1;
740 s16 yn2 = dsp_state.context.yn2;
741
742 Codec::ADPCM_Coeff coeffs;
743 memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
744 sizeof(Codec::ADPCM_Coeff));
745
746 s32 coef1 = coeffs[idx * 2];
747 s32 coef2 = coeffs[idx * 2 + 1];
748
749 const auto samples_remaining =
750 (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
751 const auto samples_processed = std::min(sample_count, samples_remaining);
752 const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset;
753
754 const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
755 auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
756 samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0);
757
758 const auto decode_sample = [&](const int nibble) -> s16 {
759 const int xn = nibble * (1 << scale);
760 // We first transform everything into 11 bit fixed point, perform the second order
761 // digital filter, then transform back.
762 // 0x400 == 0.5 in 11 bit fixed point.
763 // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
764 int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
765 // Clamp to output range.
766 val = std::clamp<s32>(val, -32768, 32767);
767 // Advance output feedback.
768 yn2 = yn1;
769 yn1 = static_cast<s16>(val);
770 return yn1;
771 };
772
773 std::size_t buffer_offset{};
774 std::vector<u8> buffer(
775 std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN));
776 memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(),
777 buffer.size());
778 std::size_t cur_mix_offset = mix_offset;
779
780 auto remaining_samples = samples_processed;
781 while (remaining_samples > 0) {
782 if (position_in_frame % NIBBLES_PER_SAMPLE == 0) {
783 // Read header
784 frame_header = buffer[buffer_offset++];
785 idx = (frame_header >> 4) & 0xf;
786 scale = frame_header & 0xf;
787 coef1 = coeffs[idx * 2];
788 coef2 = coeffs[idx * 2 + 1];
789 position_in_frame += 2;
790
791 // Decode entire frame
792 if (remaining_samples >= SAMPLES_PER_FRAME) {
793 for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) {
794
795 // Sample 1
796 const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4];
797 const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf];
798 const s16 sample_1 = decode_sample(s0);
799 const s16 sample_2 = decode_sample(s1);
800 sample_buffer[cur_mix_offset++] = sample_1;
801 sample_buffer[cur_mix_offset++] = sample_2;
802 }
803 remaining_samples -= SAMPLES_PER_FRAME;
804 position_in_frame += SAMPLES_PER_FRAME;
805 continue;
806 }
807 }
808 // Decode mid frame
809 s32 current_nibble = buffer[buffer_offset];
810 if (position_in_frame++ & 0x1) {
811 current_nibble &= 0xf;
812 buffer_offset++;
813 } else {
814 current_nibble >>= 4;
815 }
816 const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]);
817 sample_buffer[cur_mix_offset++] = sample;
818 remaining_samples--;
819 }
820
821 dsp_state.context.header = frame_header;
822 dsp_state.context.yn1 = yn1;
823 dsp_state.context.yn2 = yn2;
824
825 return samples_processed;
826}
827
828s32* CommandGenerator::GetMixBuffer(std::size_t index) {
829 return mix_buffer.data() + (index * worker_params.sample_count);
830}
831
832const s32* CommandGenerator::GetMixBuffer(std::size_t index) const {
833 return mix_buffer.data() + (index * worker_params.sample_count);
834}
835
836std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
837 return worker_params.mix_buffer_count + channel;
838}
839
840std::size_t CommandGenerator::GetTotalMixBufferCount() const {
841 return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
842}
843
844s32* CommandGenerator::GetChannelMixBuffer(s32 channel) {
845 return GetMixBuffer(worker_params.mix_buffer_count + channel);
846}
847
848const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const {
849 return GetMixBuffer(worker_params.mix_buffer_count + channel);
850}
851
852void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output,
853 VoiceState& dsp_state, s32 channel,
854 s32 target_sample_rate, s32 sample_count,
855 s32 node_id) {
856 const auto& in_params = voice_info.GetInParams();
857 if (dumping_frame) {
858 LOG_DEBUG(Audio,
859 "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
860 "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
861 node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
862 in_params.mix_id, in_params.splitter_info_id);
863 }
864 ASSERT_OR_EXECUTE(output != nullptr, { return; });
865
866 const auto resample_rate = static_cast<s32>(
867 static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
868 static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
869 auto* output_base = output;
870 if (dsp_state.fraction + sample_count * resample_rate >
871 static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) {
872 return;
873 }
874
875 auto min_required_samples =
876 std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
877 if (min_required_samples >= sample_count) {
878 min_required_samples = sample_count;
879 }
880
881 std::size_t temp_mix_offset{};
882 bool is_buffer_completed{false};
883 auto samples_remaining = sample_count;
884 while (samples_remaining > 0 && !is_buffer_completed) {
885 const auto samples_to_output = std::min(samples_remaining, min_required_samples);
886 const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
887
888 if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
889 // Append sample histtory for resampler
890 for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
891 sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
892 }
893 temp_mix_offset += 4;
894 }
895
896 s32 samples_read{};
897 while (samples_read < samples_to_read) {
898 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
899 // No more data can be read
900 if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
901 is_buffer_completed = true;
902 break;
903 }
904
905 if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
906 wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
907 // TODO(ogniK): ADPCM loop context
908 }
909
910 s32 samples_decoded{0};
911 switch (in_params.sample_format) {
912 case SampleFormat::Pcm16:
913 samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read,
914 channel, temp_mix_offset);
915 break;
916 case SampleFormat::Adpcm:
917 samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read,
918 channel, temp_mix_offset);
919 break;
920 default:
921 UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
922 }
923
924 temp_mix_offset += samples_decoded;
925 samples_read += samples_decoded;
926 dsp_state.offset += samples_decoded;
927 dsp_state.played_sample_count += samples_decoded;
928
929 if (dsp_state.offset >=
930 (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
931 samples_decoded == 0) {
932 // Reset our sample offset
933 dsp_state.offset = 0;
934 if (wave_buffer.is_looping) {
935 if (samples_decoded == 0) {
936 // End of our buffer
937 is_buffer_completed = true;
938 break;
939 }
940
941 if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
942 dsp_state.played_sample_count = 0;
943 }
944 } else {
945
946 // Update our wave buffer states
947 dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
948 dsp_state.wave_buffer_consumed++;
949 dsp_state.wave_buffer_index =
950 (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
951 if (wave_buffer.end_of_stream) {
952 dsp_state.played_sample_count = 0;
953 }
954 }
955 }
956 }
957
958 if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
959 // No need to resample
960 std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32));
961 } else {
962 std::fill(sample_buffer.begin() + temp_mix_offset,
963 sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read),
964 0);
965 AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction,
966 samples_to_output);
967 // Resample
968 for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
969 dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
970 }
971 }
972 output += samples_to_output;
973 samples_remaining -= samples_to_output;
974 }
975}
976
977} // namespace AudioCore
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
new file mode 100644
index 000000000..967d24078
--- /dev/null
+++ b/src/audio_core/command_generator.h
@@ -0,0 +1,103 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include "audio_core/common.h"
9#include "audio_core/voice_context.h"
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12
13namespace Core::Memory {
14class Memory;
15}
16
17namespace AudioCore {
18class MixContext;
19class SplitterContext;
20class ServerSplitterDestinationData;
21class ServerMixInfo;
22class EffectContext;
23class EffectBase;
24struct AuxInfoDSP;
25using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
26
27class CommandGenerator {
28public:
29 explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
30 VoiceContext& voice_context, MixContext& mix_context,
31 SplitterContext& splitter_context, EffectContext& effect_context,
32 Core::Memory::Memory& memory);
33 ~CommandGenerator();
34
35 void ClearMixBuffers();
36 void GenerateVoiceCommands();
37 void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
38 void GenerateSubMixCommands();
39 void GenerateFinalMixCommands();
40 void PreCommand();
41 void PostCommand();
42
43 s32* GetChannelMixBuffer(s32 channel);
44 const s32* GetChannelMixBuffer(s32 channel) const;
45 s32* GetMixBuffer(std::size_t index);
46 const s32* GetMixBuffer(std::size_t index) const;
47 std::size_t GetMixChannelBufferOffset(s32 channel) const;
48
49 std::size_t GetTotalMixBufferCount() const;
50
51private:
52 void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
53 void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
54 s32 mix_buffer_count, s32 channel);
55 void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
56 s32 node_id);
57 void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
58 const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
59 s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
60 s32 node_id);
61 void GenerateSubMixCommand(ServerMixInfo& mix_info);
62 void GenerateMixCommands(ServerMixInfo& mix_info);
63 void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
64 s32 node_id);
65 void GenerateFinalMixCommand();
66 void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
67 std::array<s64, 2>& state, std::size_t input_offset,
68 std::size_t output_offset, s32 sample_count, s32 node_id);
69 void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count,
70 std::size_t mix_buffer_offset);
71 void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
72 std::size_t mix_buffer_offset, s32 sample_rate);
73 void GenerateEffectCommand(ServerMixInfo& mix_info);
74 void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
75 void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
76 void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
77 ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
78
79 s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data,
80 u32 sample_count, u32 write_offset, u32 write_count);
81 s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
82 u32 sample_count, u32 read_offset, u32 read_count);
83
84 // DSP Code
85 s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
86 s32 channel, std::size_t mix_offset);
87 s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
88 s32 channel, std::size_t mix_offset);
89 void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
90 s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
91
92 AudioCommon::AudioRendererParameter& worker_params;
93 VoiceContext& voice_context;
94 MixContext& mix_context;
95 SplitterContext& splitter_context;
96 EffectContext& effect_context;
97 Core::Memory::Memory& memory;
98 std::vector<s32> mix_buffer{};
99 std::vector<s32> sample_buffer{};
100 std::vector<s32> depop_buffer{};
101 bool dumping_frame{false};
102};
103} // namespace AudioCore
diff --git a/src/audio_core/common.h b/src/audio_core/common.h
index 7bb145c53..72ebce221 100644
--- a/src/audio_core/common.h
+++ b/src/audio_core/common.h
@@ -8,13 +8,30 @@
8#include "common/swap.h" 8#include "common/swap.h"
9#include "core/hle/result.h" 9#include "core/hle/result.h"
10 10
11namespace AudioCore { 11namespace AudioCommon {
12namespace Audren { 12namespace Audren {
13constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; 13constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
14} 14constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
15} // namespace Audren
15 16
16constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8'); 17constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
17constexpr std::size_t MAX_MIX_BUFFERS = 24; 18constexpr std::size_t MAX_MIX_BUFFERS = 24;
19constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
20constexpr std::size_t MAX_CHANNEL_COUNT = 6;
21constexpr std::size_t MAX_WAVE_BUFFERS = 4;
22constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
23constexpr u32 STREAM_SAMPLE_RATE = 48000;
24constexpr u32 STREAM_NUM_CHANNELS = 6;
25constexpr s32 NO_SPLITTER = -1;
26constexpr s32 NO_MIX = 0x7fffffff;
27constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
28constexpr s32 FINAL_MIX = 0;
29constexpr s32 NO_EFFECT_ORDER = -1;
30constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
31// Any size checks seem to take the sample history into account
32// and our const ends up being 0x3f04, the 4 bytes are most
33// likely the sample history
34constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
18 35
19static constexpr u32 VersionFromRevision(u32_le rev) { 36static constexpr u32 VersionFromRevision(u32_le rev) {
20 // "REV7" -> 7 37 // "REV7" -> 7
@@ -45,4 +62,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std
45 return true; 62 return true;
46} 63}
47 64
48} // namespace AudioCore 65struct UpdateDataSizes {
66 u32_le behavior{};
67 u32_le memory_pool{};
68 u32_le voice{};
69 u32_le voice_channel_resource{};
70 u32_le effect{};
71 u32_le mixer{};
72 u32_le sink{};
73 u32_le performance{};
74 u32_le splitter{};
75 u32_le render_info{};
76 INSERT_PADDING_WORDS(4);
77};
78static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
79
80struct UpdateDataHeader {
81 u32_le revision{};
82 UpdateDataSizes size{};
83 u32_le total_size{};
84};
85static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
86
87struct AudioRendererParameter {
88 u32_le sample_rate;
89 u32_le sample_count;
90 u32_le mix_buffer_count;
91 u32_le submix_count;
92 u32_le voice_count;
93 u32_le sink_count;
94 u32_le effect_count;
95 u32_le performance_frame_count;
96 u8 is_voice_drop_enabled;
97 u8 unknown_21;
98 u8 unknown_22;
99 u8 execution_mode;
100 u32_le splitter_count;
101 u32_le num_splitter_send_channels;
102 u32_le unknown_30;
103 u32_le revision;
104};
105static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
106
107} // namespace AudioCommon
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index c27df946c..83c06c0ed 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -23,14 +23,24 @@ class CubebSinkStream final : public SinkStream {
23public: 23public:
24 CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, 24 CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
25 const std::string& name) 25 const std::string& name)
26 : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate, 26 : ctx{ctx}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
27 num_channels} { 27 num_channels} {
28 28
29 cubeb_stream_params params{}; 29 cubeb_stream_params params{};
30 params.rate = sample_rate; 30 params.rate = sample_rate;
31 params.channels = num_channels; 31 params.channels = num_channels;
32 params.format = CUBEB_SAMPLE_S16NE; 32 params.format = CUBEB_SAMPLE_S16NE;
33 params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO; 33 switch (num_channels) {
34 case 1:
35 params.layout = CUBEB_LAYOUT_MONO;
36 break;
37 case 2:
38 params.layout = CUBEB_LAYOUT_STEREO;
39 break;
40 case 6:
41 params.layout = CUBEB_LAYOUT_3F2_LFE;
42 break;
43 }
34 44
35 u32 minimum_latency{}; 45 u32 minimum_latency{};
36 if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) { 46 if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
@@ -193,6 +203,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
193 const std::size_t samples_to_write = num_channels * num_frames; 203 const std::size_t samples_to_write = num_channels * num_frames;
194 std::size_t samples_written; 204 std::size_t samples_written;
195 205
206 /*
196 if (Settings::values.enable_audio_stretching.GetValue()) { 207 if (Settings::values.enable_audio_stretching.GetValue()) {
197 const std::vector<s16> in{impl->queue.Pop()}; 208 const std::vector<s16> in{impl->queue.Pop()};
198 const std::size_t num_in{in.size() / num_channels}; 209 const std::size_t num_in{in.size() / num_channels};
@@ -207,7 +218,8 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
207 } 218 }
208 } else { 219 } else {
209 samples_written = impl->queue.Pop(buffer, samples_to_write); 220 samples_written = impl->queue.Pop(buffer, samples_to_write);
210 } 221 }*/
222 samples_written = impl->queue.Pop(buffer, samples_to_write);
211 223
212 if (samples_written >= num_channels) { 224 if (samples_written >= num_channels) {
213 std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16), 225 std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp
new file mode 100644
index 000000000..adfec3df5
--- /dev/null
+++ b/src/audio_core/effect_context.cpp
@@ -0,0 +1,299 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include "audio_core/effect_context.h"
7
8namespace AudioCore {
9namespace {
10bool ValidChannelCountForEffect(s32 channel_count) {
11 return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
12}
13} // namespace
14
15EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) {
16 effects.reserve(effect_count);
17 std::generate_n(std::back_inserter(effects), effect_count,
18 [] { return std::make_unique<EffectStubbed>(); });
19}
20EffectContext::~EffectContext() = default;
21
22std::size_t EffectContext::GetCount() const {
23 return effect_count;
24}
25
26EffectBase* EffectContext::GetInfo(std::size_t i) {
27 return effects.at(i).get();
28}
29
30EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
31 switch (effect) {
32 case EffectType::Invalid:
33 effects[i] = std::make_unique<EffectStubbed>();
34 break;
35 case EffectType::BufferMixer:
36 effects[i] = std::make_unique<EffectBufferMixer>();
37 break;
38 case EffectType::Aux:
39 effects[i] = std::make_unique<EffectAuxInfo>();
40 break;
41 case EffectType::Delay:
42 effects[i] = std::make_unique<EffectDelay>();
43 break;
44 case EffectType::Reverb:
45 effects[i] = std::make_unique<EffectReverb>();
46 break;
47 case EffectType::I3dl2Reverb:
48 effects[i] = std::make_unique<EffectI3dl2Reverb>();
49 break;
50 case EffectType::BiquadFilter:
51 effects[i] = std::make_unique<EffectBiquadFilter>();
52 break;
53 default:
54 UNREACHABLE_MSG("Unimplemented effect {}", effect);
55 effects[i] = std::make_unique<EffectStubbed>();
56 }
57 return GetInfo(i);
58}
59
60const EffectBase* EffectContext::GetInfo(std::size_t i) const {
61 return effects.at(i).get();
62}
63
64EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {}
65EffectStubbed::~EffectStubbed() = default;
66
67void EffectStubbed::Update(EffectInfo::InParams& in_params) {}
68void EffectStubbed::UpdateForCommandGeneration() {}
69
70EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {}
71EffectBase::~EffectBase() = default;
72
73UsageState EffectBase::GetUsage() const {
74 return usage;
75}
76
77EffectType EffectBase::GetType() const {
78 return effect_type;
79}
80
81bool EffectBase::IsEnabled() const {
82 return enabled;
83}
84
85s32 EffectBase::GetMixID() const {
86 return mix_id;
87}
88
89s32 EffectBase::GetProcessingOrder() const {
90 return processing_order;
91}
92
93EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {}
94EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
95
96void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
97 auto& internal_params = GetParams();
98 const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
99 if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
100 UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels);
101 return;
102 }
103
104 const auto last_status = internal_params.status;
105 mix_id = in_params.mix_id;
106 processing_order = in_params.processing_order;
107 internal_params = *reverb_params;
108 if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
109 internal_params.channel_count = internal_params.max_channels;
110 }
111 enabled = in_params.is_enabled;
112 if (last_status != ParameterStatus::Updated) {
113 internal_params.status = last_status;
114 }
115
116 if (in_params.is_new || skipped) {
117 usage = UsageState::Initialized;
118 internal_params.status = ParameterStatus::Initialized;
119 skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
120 }
121}
122
123void EffectI3dl2Reverb::UpdateForCommandGeneration() {
124 if (enabled) {
125 usage = UsageState::Running;
126 } else {
127 usage = UsageState::Stopped;
128 }
129 GetParams().status = ParameterStatus::Updated;
130}
131
132EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {}
133EffectBiquadFilter::~EffectBiquadFilter() = default;
134
135void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
136 auto& internal_params = GetParams();
137 const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
138 mix_id = in_params.mix_id;
139 processing_order = in_params.processing_order;
140 internal_params = *biquad_params;
141 enabled = in_params.is_enabled;
142}
143
144void EffectBiquadFilter::UpdateForCommandGeneration() {
145 if (enabled) {
146 usage = UsageState::Running;
147 } else {
148 usage = UsageState::Stopped;
149 }
150 GetParams().status = ParameterStatus::Updated;
151}
152
153EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {}
154EffectAuxInfo::~EffectAuxInfo() = default;
155
156void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
157 const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
158 mix_id = in_params.mix_id;
159 processing_order = in_params.processing_order;
160 GetParams() = *aux_params;
161 enabled = in_params.is_enabled;
162
163 if (in_params.is_new || skipped) {
164 skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
165 if (skipped) {
166 return;
167 }
168
169 // There's two AuxInfos which are an identical size, the first one is managed by the cpu,
170 // the second is managed by the dsp. All we care about is managing the DSP one
171 send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
172 send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
173
174 recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
175 recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
176 }
177}
178
179void EffectAuxInfo::UpdateForCommandGeneration() {
180 if (enabled) {
181 usage = UsageState::Running;
182 } else {
183 usage = UsageState::Stopped;
184 }
185}
186
187const VAddr EffectAuxInfo::GetSendInfo() const {
188 return send_info;
189}
190
191const VAddr EffectAuxInfo::GetSendBuffer() const {
192 return send_buffer;
193}
194
195const VAddr EffectAuxInfo::GetRecvInfo() const {
196 return recv_info;
197}
198
199const VAddr EffectAuxInfo::GetRecvBuffer() const {
200 return recv_buffer;
201}
202
203EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {}
204EffectDelay::~EffectDelay() = default;
205
206void EffectDelay::Update(EffectInfo::InParams& in_params) {
207 const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
208 auto& internal_params = GetParams();
209 if (!ValidChannelCountForEffect(delay_params->max_channels)) {
210 return;
211 }
212
213 const auto last_status = internal_params.status;
214 mix_id = in_params.mix_id;
215 processing_order = in_params.processing_order;
216 internal_params = *delay_params;
217 if (!ValidChannelCountForEffect(delay_params->channels)) {
218 internal_params.channels = internal_params.max_channels;
219 }
220 enabled = in_params.is_enabled;
221
222 if (last_status != ParameterStatus::Updated) {
223 internal_params.status = last_status;
224 }
225
226 if (in_params.is_new || skipped) {
227 usage = UsageState::Initialized;
228 internal_params.status = ParameterStatus::Initialized;
229 skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
230 }
231}
232
233void EffectDelay::UpdateForCommandGeneration() {
234 if (enabled) {
235 usage = UsageState::Running;
236 } else {
237 usage = UsageState::Stopped;
238 }
239 GetParams().status = ParameterStatus::Updated;
240}
241
242EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {}
243EffectBufferMixer::~EffectBufferMixer() = default;
244
245void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
246 mix_id = in_params.mix_id;
247 processing_order = in_params.processing_order;
248 GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
249 enabled = in_params.is_enabled;
250}
251
252void EffectBufferMixer::UpdateForCommandGeneration() {
253 if (enabled) {
254 usage = UsageState::Running;
255 } else {
256 usage = UsageState::Stopped;
257 }
258}
259
260EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {}
261EffectReverb::~EffectReverb() = default;
262
263void EffectReverb::Update(EffectInfo::InParams& in_params) {
264 const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
265 auto& internal_params = GetParams();
266 if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
267 return;
268 }
269
270 const auto last_status = internal_params.status;
271 mix_id = in_params.mix_id;
272 processing_order = in_params.processing_order;
273 internal_params = *reverb_params;
274 if (!ValidChannelCountForEffect(reverb_params->channels)) {
275 internal_params.channels = internal_params.max_channels;
276 }
277 enabled = in_params.is_enabled;
278
279 if (last_status != ParameterStatus::Updated) {
280 internal_params.status = last_status;
281 }
282
283 if (in_params.is_new || skipped) {
284 usage = UsageState::Initialized;
285 internal_params.status = ParameterStatus::Initialized;
286 skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
287 }
288}
289
290void EffectReverb::UpdateForCommandGeneration() {
291 if (enabled) {
292 usage = UsageState::Running;
293 } else {
294 usage = UsageState::Stopped;
295 }
296 GetParams().status = ParameterStatus::Updated;
297}
298
299} // namespace AudioCore
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h
new file mode 100644
index 000000000..2f2da72dd
--- /dev/null
+++ b/src/audio_core/effect_context.h
@@ -0,0 +1,322 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <vector>
10#include "audio_core/common.h"
11#include "common/common_funcs.h"
12#include "common/common_types.h"
13#include "common/swap.h"
14
15namespace AudioCore {
16enum class EffectType : u8 {
17 Invalid = 0,
18 BufferMixer = 1,
19 Aux = 2,
20 Delay = 3,
21 Reverb = 4,
22 I3dl2Reverb = 5,
23 BiquadFilter = 6,
24};
25
26enum class UsageStatus : u8 {
27 Invalid = 0,
28 New = 1,
29 Initialized = 2,
30 Used = 3,
31 Removed = 4,
32};
33
34enum class UsageState {
35 Invalid = 0,
36 Initialized = 1,
37 Running = 2,
38 Stopped = 3,
39};
40
41enum class ParameterStatus : u8 {
42 Initialized = 0,
43 Updating = 1,
44 Updated = 2,
45};
46
47struct BufferMixerParams {
48 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
49 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
50 std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
51 s32_le count{};
52};
53static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
54
55struct AuxInfoDSP {
56 u32_le read_offset{};
57 u32_le write_offset{};
58 u32_le remaining{};
59 INSERT_PADDING_WORDS(13);
60};
61static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
62
63struct AuxInfo {
64 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
65 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
66 u32_le count{};
67 s32_le sample_rate{};
68 s32_le sample_count{};
69 s32_le mix_buffer_count{};
70 u64_le send_buffer_info{};
71 u64_le send_buffer_base{};
72
73 u64_le return_buffer_info{};
74 u64_le return_buffer_base{};
75};
76static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
77
78struct I3dl2ReverbParams {
79 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
80 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
81 u16_le max_channels{};
82 u16_le channel_count{};
83 INSERT_PADDING_BYTES(1);
84 u32_le sample_rate{};
85 f32 room_hf{};
86 f32 hf_reference{};
87 f32 decay_time{};
88 f32 hf_decay_ratio{};
89 f32 room{};
90 f32 reflection{};
91 f32 reverb{};
92 f32 diffusion{};
93 f32 reflection_delay{};
94 f32 reverb_delay{};
95 f32 density{};
96 f32 dry_gain{};
97 ParameterStatus status{};
98 INSERT_PADDING_BYTES(3);
99};
100static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
101
102struct BiquadFilterParams {
103 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
104 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
105 std::array<s16_le, 3> numerator;
106 std::array<s16_le, 2> denominator;
107 s8 channel_count{};
108 ParameterStatus status{};
109};
110static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
111
112struct DelayParams {
113 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
114 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
115 u16_le max_channels{};
116 u16_le channels{};
117 s32_le max_delay{};
118 s32_le delay{};
119 s32_le sample_rate{};
120 s32_le gain{};
121 s32_le feedback_gain{};
122 s32_le out_gain{};
123 s32_le dry_gain{};
124 s32_le channel_spread{};
125 s32_le low_pass{};
126 ParameterStatus status{};
127 INSERT_PADDING_BYTES(3);
128};
129static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
130
131struct ReverbParams {
132 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
133 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
134 u16_le max_channels{};
135 u16_le channels{};
136 s32_le sample_rate{};
137 s32_le mode0{};
138 s32_le mode0_gain{};
139 s32_le pre_delay{};
140 s32_le mode1{};
141 s32_le mode1_gain{};
142 s32_le decay{};
143 s32_le hf_decay_ratio{};
144 s32_le coloration{};
145 s32_le reverb_gain{};
146 s32_le out_gain{};
147 s32_le dry_gain{};
148 ParameterStatus status{};
149 INSERT_PADDING_BYTES(3);
150};
151static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
152
153class EffectInfo {
154public:
155 struct InParams {
156 EffectType type{};
157 u8 is_new{};
158 u8 is_enabled{};
159 INSERT_PADDING_BYTES(1);
160 s32_le mix_id{};
161 u64_le buffer_address{};
162 u64_le buffer_size{};
163 s32_le processing_order{};
164 INSERT_PADDING_BYTES(4);
165 union {
166 std::array<u8, 0xa0> raw;
167 };
168 };
169 static_assert(sizeof(EffectInfo::InParams) == 0xc0, "InParams is an invalid size");
170
171 struct OutParams {
172 UsageStatus status{};
173 INSERT_PADDING_BYTES(15);
174 };
175 static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size");
176};
177
178struct AuxAddress {
179 VAddr send_dsp_info{};
180 VAddr send_buffer_base{};
181 VAddr return_dsp_info{};
182 VAddr return_buffer_base{};
183};
184
185class EffectBase {
186public:
187 EffectBase(EffectType effect_type);
188 ~EffectBase();
189
190 virtual void Update(EffectInfo::InParams& in_params) = 0;
191 virtual void UpdateForCommandGeneration() = 0;
192 UsageState GetUsage() const;
193 EffectType GetType() const;
194 bool IsEnabled() const;
195 s32 GetMixID() const;
196 s32 GetProcessingOrder() const;
197
198protected:
199 UsageState usage{UsageState::Invalid};
200 EffectType effect_type{};
201 s32 mix_id{};
202 s32 processing_order{};
203 bool enabled = false;
204};
205
206template <typename T>
207class EffectGeneric : public EffectBase {
208public:
209 EffectGeneric(EffectType effect_type) : EffectBase::EffectBase(effect_type) {}
210 ~EffectGeneric() = default;
211
212 T& GetParams() {
213 return internal_params;
214 }
215
216 const I3dl2ReverbParams& GetParams() const {
217 return internal_params;
218 }
219
220private:
221 T internal_params{};
222};
223
224class EffectStubbed : public EffectBase {
225public:
226 explicit EffectStubbed();
227 ~EffectStubbed();
228
229 void Update(EffectInfo::InParams& in_params) override;
230 void UpdateForCommandGeneration() override;
231};
232
233class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
234public:
235 explicit EffectI3dl2Reverb();
236 ~EffectI3dl2Reverb();
237
238 void Update(EffectInfo::InParams& in_params) override;
239 void UpdateForCommandGeneration() override;
240
241private:
242 bool skipped = false;
243};
244
245class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
246public:
247 explicit EffectBiquadFilter();
248 ~EffectBiquadFilter();
249
250 void Update(EffectInfo::InParams& in_params) override;
251 void UpdateForCommandGeneration() override;
252};
253
254class EffectAuxInfo : public EffectGeneric<AuxInfo> {
255public:
256 explicit EffectAuxInfo();
257 ~EffectAuxInfo();
258
259 void Update(EffectInfo::InParams& in_params) override;
260 void UpdateForCommandGeneration() override;
261 const VAddr GetSendInfo() const;
262 const VAddr GetSendBuffer() const;
263 const VAddr GetRecvInfo() const;
264 const VAddr GetRecvBuffer() const;
265
266private:
267 VAddr send_info{};
268 VAddr send_buffer{};
269 VAddr recv_info{};
270 VAddr recv_buffer{};
271 bool skipped = false;
272 AuxAddress addresses{};
273};
274
275class EffectDelay : public EffectGeneric<DelayParams> {
276public:
277 explicit EffectDelay();
278 ~EffectDelay();
279
280 void Update(EffectInfo::InParams& in_params) override;
281 void UpdateForCommandGeneration() override;
282
283private:
284 bool skipped = false;
285};
286
287class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
288public:
289 explicit EffectBufferMixer();
290 ~EffectBufferMixer();
291
292 void Update(EffectInfo::InParams& in_params) override;
293 void UpdateForCommandGeneration() override;
294};
295
296class EffectReverb : public EffectGeneric<ReverbParams> {
297public:
298 explicit EffectReverb();
299 ~EffectReverb();
300
301 void Update(EffectInfo::InParams& in_params) override;
302 void UpdateForCommandGeneration() override;
303
304private:
305 bool skipped = false;
306};
307
308class EffectContext {
309public:
310 explicit EffectContext(std::size_t effect_count);
311 ~EffectContext();
312
313 std::size_t GetCount() const;
314 EffectBase* GetInfo(std::size_t i);
315 EffectBase* RetargetEffect(std::size_t i, EffectType effect);
316 const EffectBase* GetInfo(std::size_t i) const;
317
318private:
319 std::size_t effect_count{};
320 std::vector<std::unique_ptr<EffectBase>> effects;
321};
322} // namespace AudioCore
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp
new file mode 100644
index 000000000..f53ce21a5
--- /dev/null
+++ b/src/audio_core/info_updater.cpp
@@ -0,0 +1,517 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/behavior_info.h"
6#include "audio_core/effect_context.h"
7#include "audio_core/info_updater.h"
8#include "audio_core/memory_pool.h"
9#include "audio_core/mix_context.h"
10#include "audio_core/sink_context.h"
11#include "audio_core/splitter_context.h"
12#include "audio_core/voice_context.h"
13#include "common/logging/log.h"
14
15namespace AudioCore {
16
17InfoUpdater::InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
18 BehaviorInfo& behavior_info)
19 : in_params(in_params), out_params(out_params), behavior_info(behavior_info) {
20 ASSERT(
21 AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
22 std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
23 output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
24}
25
26InfoUpdater::~InfoUpdater() = default;
27
28bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
29 if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
30 LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
31 sizeof(BehaviorInfo::InParams), input_header.size.behavior);
32 return false;
33 }
34
35 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
36 sizeof(BehaviorInfo::InParams))) {
37 LOG_ERROR(Audio, "Buffer is an invalid size!");
38 return false;
39 }
40
41 BehaviorInfo::InParams behavior_in{};
42 std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
43 input_offset += sizeof(BehaviorInfo::InParams);
44
45 // Make sure it's an audio revision we can actually support
46 if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
47 LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
48 return false;
49 }
50
51 // Make sure that our behavior info revision matches the input
52 if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
53 LOG_ERROR(Audio,
54 "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
55 in_behavior_info.GetUserRevision(), behavior_in.revision);
56 return false;
57 }
58
59 // Update behavior info flags
60 in_behavior_info.ClearError();
61 in_behavior_info.UpdateFlags(behavior_in.flags);
62
63 return true;
64}
65
66bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
67 const auto force_mapping = behavior_info.IsMemoryPoolForceMappingEnabled();
68 const auto memory_pool_count = memory_pool_info.size();
69 const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
70 const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
71
72 if (input_header.size.memory_pool != total_memory_pool_in) {
73 LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
74 total_memory_pool_in, input_header.size.memory_pool);
75 return false;
76 }
77
78 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
79 LOG_ERROR(Audio, "Buffer is an invalid size!");
80 return false;
81 }
82
83 std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
84 std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
85
86 std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
87 input_offset += total_memory_pool_in;
88
89 // Update our memory pools
90 for (std::size_t i = 0; i < memory_pool_count; i++) {
91 if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
92 LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
93 return false;
94 }
95 }
96
97 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
98 sizeof(BehaviorInfo::InParams))) {
99 LOG_ERROR(Audio, "Buffer is an invalid size!");
100 return false;
101 }
102
103 std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
104 output_offset += total_memory_pool_out;
105 output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
106 return true;
107}
108
109bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
110 const auto voice_count = voice_context.GetVoiceCount();
111 const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
112 std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
113
114 if (input_header.size.voice_channel_resource != voice_size) {
115 LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
116 voice_size, input_header.size.voice_channel_resource);
117 return false;
118 }
119
120 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
121 LOG_ERROR(Audio, "Buffer is an invalid size!");
122 return false;
123 }
124
125 std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
126 input_offset += voice_size;
127
128 // Update our channel resources
129 for (std::size_t i = 0; i < voice_count; i++) {
130 // Grab our channel resource
131 auto& resource = voice_context.GetChannelResource(i);
132 resource.Update(resources_in[i]);
133 }
134
135 return true;
136}
137
138bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
139 std::vector<ServerMemoryPoolInfo>& memory_pool_info,
140 VAddr audio_codec_dsp_addr) {
141 const auto voice_count = voice_context.GetVoiceCount();
142 std::vector<VoiceInfo::InParams> voice_in(voice_count);
143 std::vector<VoiceInfo::OutParams> voice_out(voice_count);
144
145 const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
146 const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
147
148 if (input_header.size.voice != voice_in_size) {
149 LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
150 voice_in_size, input_header.size.voice);
151 return false;
152 }
153
154 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
155 LOG_ERROR(Audio, "Buffer is an invalid size!");
156 return false;
157 }
158
159 std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
160 input_offset += voice_in_size;
161
162 // Set all voices to not be in use
163 for (std::size_t i = 0; i < voice_count; i++) {
164 voice_context.GetInfo(i).GetInParams().in_use = false;
165 }
166
167 // Update our voices
168 for (std::size_t i = 0; i < voice_count; i++) {
169 auto& in_params = voice_in[i];
170 const auto channel_count = static_cast<std::size_t>(in_params.channel_count);
171 // Skip if it's not currently in use
172 if (!in_params.is_in_use) {
173 continue;
174 }
175 // Voice states for each channel
176 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
177 ASSERT(in_params.id < voice_count);
178
179 // Grab our current voice info
180 auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(in_params.id));
181
182 ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
183
184 // Get all our channel voice states
185 for (std::size_t channel = 0; channel < channel_count; channel++) {
186 voice_states[channel] =
187 &voice_context.GetState(in_params.voice_channel_resource_ids[channel]);
188 }
189
190 if (in_params.is_new) {
191 // Default our values for our voice
192 voice_info.Initialize();
193 if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
194 continue;
195 }
196
197 // Zero out our voice states
198 for (std::size_t channel = 0; channel < channel_count; channel++) {
199 std::memset(voice_states[channel], 0, sizeof(VoiceState));
200 }
201 }
202
203 // Update our voice
204 voice_info.UpdateParameters(in_params, behavior_info);
205 // TODO(ogniK): Handle mapping errors with behavior info based on in params response
206
207 // Update our wave buffers
208 voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info);
209 voice_info.WriteOutStatus(voice_out[i], in_params, voice_states);
210 }
211
212 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
213 LOG_ERROR(Audio, "Buffer is an invalid size!");
214 return false;
215 }
216 std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
217 output_offset += voice_out_size;
218 output_header.size.voice = static_cast<u32>(voice_out_size);
219 return true;
220}
221
222bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
223 const auto effect_count = effect_context.GetCount();
224 std::vector<EffectInfo::InParams> effect_in(effect_count);
225 std::vector<EffectInfo::OutParams> effect_out(effect_count);
226
227 const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
228 const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
229
230 if (input_header.size.effect != total_effect_in) {
231 LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
232 total_effect_in, input_header.size.effect);
233 return false;
234 }
235
236 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
237 LOG_ERROR(Audio, "Buffer is an invalid size!");
238 return false;
239 }
240
241 std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
242 input_offset += total_effect_in;
243
244 // Update effects
245 for (std::size_t i = 0; i < effect_count; i++) {
246 auto* info = effect_context.GetInfo(i);
247 if (effect_in[i].type != info->GetType()) {
248 info = effect_context.RetargetEffect(i, effect_in[i].type);
249 }
250
251 info->Update(effect_in[i]);
252
253 if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
254 info->GetUsage() == UsageState::Stopped) {
255 effect_out[i].status = UsageStatus::Removed;
256 } else {
257 effect_out[i].status = UsageStatus::Used;
258 }
259 }
260
261 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
262 LOG_ERROR(Audio, "Buffer is an invalid size!");
263 return false;
264 }
265
266 std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
267 output_offset += total_effect_out;
268 output_header.size.effect = static_cast<u32>(total_effect_out);
269
270 return true;
271}
272
273bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
274 std::size_t start_offset = input_offset;
275 std::size_t bytes_read{};
276 // Update splitter context
277 if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
278 LOG_ERROR(Audio, "Failed to update splitter context!");
279 return false;
280 }
281
282 const auto consumed = input_offset - start_offset;
283
284 if (input_header.size.splitter != consumed) {
285 LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
286 bytes_read, input_header.size.splitter);
287 return false;
288 }
289
290 return true;
291}
292
293ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
294 SplitterContext& splitter_context,
295 EffectContext& effect_context) {
296 std::vector<MixInfo::InParams> mix_in_params;
297
298 if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
299 // If we're not dirty, get ALL mix in parameters
300 const auto context_mix_count = mix_context.GetCount();
301 const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
302 if (input_header.size.mixer != total_mix_in) {
303 LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
304 total_mix_in, input_header.size.mixer);
305 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
306 }
307
308 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
309 LOG_ERROR(Audio, "Buffer is an invalid size!");
310 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
311 }
312
313 mix_in_params.resize(context_mix_count);
314 std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
315
316 input_offset += total_mix_in;
317 } else {
318 // Only update the "dirty" mixes
319 MixInfo::DirtyHeader dirty_header{};
320 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
321 sizeof(MixInfo::DirtyHeader))) {
322 LOG_ERROR(Audio, "Buffer is an invalid size!");
323 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
324 }
325
326 std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
327 input_offset += sizeof(MixInfo::DirtyHeader);
328
329 const auto total_mix_in =
330 dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
331
332 if (input_header.size.mixer != total_mix_in) {
333 LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
334 total_mix_in, input_header.size.mixer);
335 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
336 }
337
338 if (dirty_header.mixer_count != 0) {
339 mix_in_params.resize(dirty_header.mixer_count);
340 std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
341 mix_in_params.size() * sizeof(MixInfo::InParams));
342 input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
343 }
344 }
345
346 // Get our total input count
347 const auto mix_count = mix_in_params.size();
348
349 if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
350 // Only verify our buffer count if we're not dirty
351 std::size_t total_buffer_count{};
352 for (std::size_t i = 0; i < mix_count; i++) {
353 const auto& in = mix_in_params[i];
354 total_buffer_count += in.buffer_count;
355 if (in.dest_mix_id > mix_count && in.dest_mix_id != AudioCommon::NO_MIX &&
356 in.mix_id != AudioCommon::FINAL_MIX) {
357 LOG_ERROR(
358 Audio,
359 "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
360 in.mix_id, in.dest_mix_id, mix_buffer_count);
361 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
362 }
363 }
364
365 if (total_buffer_count > mix_buffer_count) {
366 LOG_ERROR(Audio,
367 "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
368 mix_buffer_count, total_buffer_count);
369 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
370 }
371 }
372
373 if (mix_buffer_count == 0) {
374 LOG_ERROR(Audio, "No mix buffers!");
375 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
376 }
377
378 bool should_sort = false;
379 for (std::size_t i = 0; i < mix_count; i++) {
380 const auto& mix_in = mix_in_params[i];
381 std::size_t target_mix{};
382 if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
383 target_mix = mix_in.mix_id;
384 } else {
385 // Non dirty supported games just use i instead of the actual mix_id
386 target_mix = i;
387 }
388 auto& mix_info = mix_context.GetInfo(target_mix);
389 auto& mix_info_params = mix_info.GetInParams();
390 if (mix_info_params.in_use != mix_in.in_use) {
391 mix_info_params.in_use = mix_in.in_use;
392 mix_info.ResetEffectProcessingOrder();
393 should_sort = true;
394 }
395
396 if (mix_in.in_use) {
397 should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
398 splitter_context, effect_context);
399 }
400 }
401
402 if (should_sort && behavior_info.IsSplitterSupported()) {
403 // Sort our splitter data
404 if (!mix_context.TsortInfo(splitter_context)) {
405 return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
406 }
407 }
408
409 // TODO(ogniK): Sort when splitter is suppoorted
410
411 return RESULT_SUCCESS;
412}
413
414bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
415 const auto sink_count = sink_context.GetCount();
416 std::vector<SinkInfo::InParams> sink_in_params(sink_count);
417 const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
418
419 if (input_header.size.sink != total_sink_in) {
420 LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
421 total_sink_in, input_header.size.effect);
422 return false;
423 }
424
425 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
426 LOG_ERROR(Audio, "Buffer is an invalid size!");
427 return false;
428 }
429
430 std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
431 input_offset += total_sink_in;
432
433 // TODO(ogniK): Properly update sinks
434 if (!sink_in_params.empty()) {
435 sink_context.UpdateMainSink(sink_in_params[0]);
436 }
437
438 output_header.size.sink = static_cast<u32>(0x20 * sink_count);
439 output_offset += 0x20 * sink_count;
440 return true;
441}
442
443bool InfoUpdater::UpdatePerformanceBuffer() {
444 output_header.size.performance = 0x10;
445 output_offset += 0x10;
446 return true;
447}
448
449bool InfoUpdater::UpdateErrorInfo(BehaviorInfo& in_behavior_info) {
450 const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
451
452 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
453 LOG_ERROR(Audio, "Buffer is an invalid size!");
454 return false;
455 }
456
457 BehaviorInfo::OutParams behavior_info_out{};
458 behavior_info.CopyErrorInfo(behavior_info_out);
459
460 std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
461 output_offset += total_beahvior_info_out;
462 output_header.size.behavior = total_beahvior_info_out;
463
464 return true;
465}
466
467struct RendererInfo {
468 u64_le elasped_frame_count{};
469 INSERT_PADDING_WORDS(2);
470};
471static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
472
473bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
474 const auto total_renderer_info_out = sizeof(RendererInfo);
475 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
476 LOG_ERROR(Audio, "Buffer is an invalid size!");
477 return false;
478 }
479 RendererInfo out{};
480 out.elasped_frame_count = elapsed_frame_count;
481 std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
482 output_offset += total_renderer_info_out;
483 output_header.size.render_info = total_renderer_info_out;
484
485 return true;
486}
487
488bool InfoUpdater::CheckConsumedSize() const {
489 if (output_offset != out_params.size()) {
490 LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
491 output_offset, out_params.size(), out_params.size() - output_offset);
492 return false;
493 }
494 /*if (input_offset != in_params.size()) {
495 LOG_ERROR(Audio, "Input is not consumed!");
496 return false;
497 }*/
498 return true;
499}
500
501bool InfoUpdater::WriteOutputHeader() {
502 if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
503 sizeof(AudioCommon::UpdateDataHeader))) {
504 LOG_ERROR(Audio, "Buffer is an invalid size!");
505 return false;
506 }
507 output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
508 const auto& sz = output_header.size;
509 output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
510 sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
511 sz.performance + sz.splitter + sz.render_info;
512
513 std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
514 return true;
515}
516
517} // namespace AudioCore
diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h
new file mode 100644
index 000000000..06f9d770f
--- /dev/null
+++ b/src/audio_core/info_updater.h
@@ -0,0 +1,58 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <vector>
8#include "audio_core/common.h"
9#include "common/common_types.h"
10
11namespace AudioCore {
12
13class BehaviorInfo;
14class ServerMemoryPoolInfo;
15class VoiceContext;
16class EffectContext;
17class MixContext;
18class SinkContext;
19class SplitterContext;
20
21class InfoUpdater {
22public:
23 // TODO(ogniK): Pass process handle when we support it
24 InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
25 BehaviorInfo& behavior_info);
26 ~InfoUpdater();
27
28 bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
29 bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
30 bool UpdateVoiceChannelResources(VoiceContext& voice_context);
31 bool UpdateVoices(VoiceContext& voice_context,
32 std::vector<ServerMemoryPoolInfo>& memory_pool_info,
33 VAddr audio_codec_dsp_addr);
34 bool UpdateEffects(EffectContext& effect_context, bool is_active);
35 bool UpdateSplitterInfo(SplitterContext& splitter_context);
36 ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
37 SplitterContext& splitter_context, EffectContext& effect_context);
38 bool UpdateSinks(SinkContext& sink_context);
39 bool UpdatePerformanceBuffer();
40 bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
41 bool UpdateRendererInfo(std::size_t elapsed_frame_count);
42 bool CheckConsumedSize() const;
43
44 bool WriteOutputHeader();
45
46private:
47 const std::vector<u8>& in_params;
48 std::vector<u8>& out_params;
49 BehaviorInfo& behavior_info;
50
51 AudioCommon::UpdateDataHeader input_header{};
52 AudioCommon::UpdateDataHeader output_header{};
53
54 std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
55 std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
56};
57
58} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp
new file mode 100644
index 000000000..5a3453063
--- /dev/null
+++ b/src/audio_core/memory_pool.cpp
@@ -0,0 +1,62 @@
1
2// Copyright 2020 yuzu Emulator Project
3// Licensed under GPLv2 or any later version
4// Refer to the license.txt file included.
5
6#include "audio_core/memory_pool.h"
7#include "common/logging/log.h"
8
9namespace AudioCore {
10
11ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
12ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
13bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_params,
14 ServerMemoryPoolInfo::OutParams& out_params) {
15 // Our state does not need to be changed
16 if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach &&
17 in_params.state != ServerMemoryPoolInfo::State::RequestDetach) {
18 return true;
19 }
20
21 // Address or size is null
22 if (in_params.address == 0 || in_params.size == 0) {
23 LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
24 in_params.address, in_params.size);
25 return false;
26 }
27
28 // Address or size is not aligned
29 if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
30 LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
31 in_params.address, in_params.size);
32 return false;
33 }
34
35 if (in_params.state == ServerMemoryPoolInfo::State::RequestAttach) {
36 cpu_address = in_params.address;
37 size = in_params.size;
38 used = true;
39 out_params.state = ServerMemoryPoolInfo::State::Attached;
40 } else {
41 // Unexpected address
42 if (cpu_address != in_params.address) {
43 LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
44 cpu_address, in_params.address);
45 return false;
46 }
47
48 if (size != in_params.size) {
49 LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
50 in_params.size);
51 return false;
52 }
53
54 cpu_address = 0;
55 size = 0;
56 used = false;
57 out_params.state = ServerMemoryPoolInfo::State::Detached;
58 }
59 return true;
60}
61
62} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h
new file mode 100644
index 000000000..8ac503f1c
--- /dev/null
+++ b/src/audio_core/memory_pool.h
@@ -0,0 +1,53 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "common/common_funcs.h"
8#include "common/common_types.h"
9#include "common/swap.h"
10
11namespace AudioCore {
12
13class ServerMemoryPoolInfo {
14public:
15 ServerMemoryPoolInfo();
16 ~ServerMemoryPoolInfo();
17
18 enum class State : u32_le {
19 Invalid = 0x0,
20 Aquired = 0x1,
21 RequestDetach = 0x2,
22 Detached = 0x3,
23 RequestAttach = 0x4,
24 Attached = 0x5,
25 Released = 0x6,
26 };
27
28 struct InParams {
29 u64_le address{};
30 u64_le size{};
31 ServerMemoryPoolInfo::State state{};
32 INSERT_PADDING_WORDS(3);
33 };
34 static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size");
35
36 struct OutParams {
37 ServerMemoryPoolInfo::State state{};
38 INSERT_PADDING_WORDS(3);
39 };
40 static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size");
41
42 bool Update(const ServerMemoryPoolInfo::InParams& in_params,
43 ServerMemoryPoolInfo::OutParams& out_params);
44
45private:
46 // There's another entry here which is the DSP address, however since we're not talking to the
47 // DSP we can just use the same address provided by the guest without needing to remap
48 u64_le cpu_address{};
49 u64_le size{};
50 bool used{};
51};
52
53} // namespace AudioCore
diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp
new file mode 100644
index 000000000..042891490
--- /dev/null
+++ b/src/audio_core/mix_context.cpp
@@ -0,0 +1,296 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/behavior_info.h"
6#include "audio_core/common.h"
7#include "audio_core/effect_context.h"
8#include "audio_core/mix_context.h"
9#include "audio_core/splitter_context.h"
10
11namespace AudioCore {
12MixContext::MixContext() = default;
13MixContext::~MixContext() = default;
14
15void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
16 std::size_t effect_count) {
17 info_count = mix_count;
18 infos.resize(info_count);
19 auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
20 final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
21 sorted_info.reserve(infos.size());
22 for (auto& info : infos) {
23 sorted_info.push_back(&info);
24 }
25
26 for (auto& info : infos) {
27 info.SetEffectCount(effect_count);
28 }
29
30 // Only initialize our edge matrix and node states if splitters are supported
31 if (behavior_info.IsSplitterSupported()) {
32 node_states.Initialize(mix_count);
33 edge_matrix.Initialize(mix_count);
34 }
35}
36
37void MixContext::UpdateDistancesFromFinalMix() {
38 // Set all distances to be invalid
39 for (std::size_t i = 0; i < info_count; i++) {
40 GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
41 }
42
43 for (std::size_t i = 0; i < info_count; i++) {
44 auto& info = GetInfo(i);
45 auto& in_params = info.GetInParams();
46 // Populate our sorted info
47 sorted_info[i] = &info;
48
49 if (!in_params.in_use) {
50 continue;
51 }
52
53 auto mix_id = in_params.mix_id;
54 // Needs to be referenced out of scope
55 s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
56 for (; distance_to_final_mix < info_count; distance_to_final_mix++) {
57 if (mix_id == AudioCommon::FINAL_MIX) {
58 // If we're at the final mix, we're done
59 break;
60 } else if (mix_id == AudioCommon::NO_MIX) {
61 // If we have no more mix ids, we're done
62 distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
63 break;
64 } else {
65 const auto& dest_mix = GetInfo(mix_id);
66 const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
67
68 if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
69 // If our current mix isn't pointing to a final mix, follow through
70 mix_id = dest_mix.GetInParams().dest_mix_id;
71 } else {
72 // Our current mix + 1 = final distance
73 distance_to_final_mix = dest_mix_distance + 1;
74 break;
75 }
76 }
77 }
78
79 // If we're out of range for our distance, mark it as no final mix
80 if (distance_to_final_mix >= info_count) {
81 distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
82 }
83
84 in_params.final_mix_distance = distance_to_final_mix;
85 }
86}
87
88void MixContext::CalcMixBufferOffset() {
89 s32 offset{};
90 for (std::size_t i = 0; i < info_count; i++) {
91 auto& info = GetSortedInfo(i);
92 auto& in_params = info.GetInParams();
93 if (in_params.in_use) {
94 // Only update if in use
95 in_params.buffer_offset = offset;
96 offset += in_params.buffer_count;
97 }
98 }
99}
100
101void MixContext::SortInfo() {
102 // Get the distance to the final mix
103 UpdateDistancesFromFinalMix();
104
105 // Sort based on the distance to the final mix
106 std::sort(sorted_info.begin(), sorted_info.end(),
107 [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
108 return lhs->GetInParams().final_mix_distance >
109 rhs->GetInParams().final_mix_distance;
110 });
111
112 // Calculate the mix buffer offset
113 CalcMixBufferOffset();
114}
115
116bool MixContext::TsortInfo(SplitterContext& splitter_context) {
117 // If we're not using mixes, just calculate the mix buffer offset
118 if (!splitter_context.UsingSplitter()) {
119 CalcMixBufferOffset();
120 return true;
121 }
122 // Sort our node states
123 if (!node_states.Tsort(edge_matrix)) {
124 return false;
125 }
126
127 // Get our sorted list
128 const auto sorted_list = node_states.GetIndexList();
129 std::size_t info_id{};
130 for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
131 // Set our sorted info
132 sorted_info[info_id++] = &GetInfo(*itr);
133 }
134
135 // Calculate the mix buffer offset
136 CalcMixBufferOffset();
137 return true;
138}
139
140std::size_t MixContext::GetCount() const {
141 return info_count;
142}
143
144ServerMixInfo& MixContext::GetInfo(std::size_t i) {
145 ASSERT(i < info_count);
146 return infos.at(i);
147}
148
149const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
150 ASSERT(i < info_count);
151 return infos.at(i);
152}
153
154ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
155 ASSERT(i < info_count);
156 return *sorted_info.at(i);
157}
158
159const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
160 ASSERT(i < info_count);
161 return *sorted_info.at(i);
162}
163
164ServerMixInfo& MixContext::GetFinalMixInfo() {
165 return infos.at(AudioCommon::FINAL_MIX);
166}
167
168const ServerMixInfo& MixContext::GetFinalMixInfo() const {
169 return infos.at(AudioCommon::FINAL_MIX);
170}
171
172EdgeMatrix& MixContext::GetEdgeMatrix() {
173 return edge_matrix;
174}
175
176const EdgeMatrix& MixContext::GetEdgeMatrix() const {
177 return edge_matrix;
178}
179
180ServerMixInfo::ServerMixInfo() {
181 Cleanup();
182}
183ServerMixInfo::~ServerMixInfo() = default;
184
185const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
186 return in_params;
187}
188
189ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
190 return in_params;
191}
192
193bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
194 BehaviorInfo& behavior_info, SplitterContext& splitter_context,
195 EffectContext& effect_context) {
196 in_params.volume = mix_in.volume;
197 in_params.sample_rate = mix_in.sample_rate;
198 in_params.buffer_count = mix_in.buffer_count;
199 in_params.in_use = mix_in.in_use;
200 in_params.mix_id = mix_in.mix_id;
201 in_params.node_id = mix_in.node_id;
202 for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
203 std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
204 in_params.mix_volume[i].begin());
205 }
206
207 bool require_sort = false;
208
209 if (behavior_info.IsSplitterSupported()) {
210 require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
211 } else {
212 in_params.dest_mix_id = mix_in.dest_mix_id;
213 in_params.splitter_id = AudioCommon::NO_SPLITTER;
214 }
215
216 ResetEffectProcessingOrder();
217 const auto effect_count = effect_context.GetCount();
218 for (std::size_t i = 0; i < effect_count; i++) {
219 auto* effect_info = effect_context.GetInfo(i);
220 if (effect_info->GetMixID() == in_params.mix_id) {
221 effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
222 }
223 }
224
225 // TODO(ogniK): Update effect processing order
226 return require_sort;
227}
228
229bool ServerMixInfo::HasAnyConnection() const {
230 return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
231 in_params.mix_id != AudioCommon::NO_MIX;
232}
233
234void ServerMixInfo::Cleanup() {
235 in_params.volume = 0.0f;
236 in_params.sample_rate = 0;
237 in_params.buffer_count = 0;
238 in_params.in_use = false;
239 in_params.mix_id = AudioCommon::NO_MIX;
240 in_params.node_id = 0;
241 in_params.buffer_offset = 0;
242 in_params.dest_mix_id = AudioCommon::NO_MIX;
243 in_params.splitter_id = AudioCommon::NO_SPLITTER;
244 std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
245}
246
247void ServerMixInfo::SetEffectCount(std::size_t count) {
248 effect_processing_order.resize(count);
249 ResetEffectProcessingOrder();
250}
251
252void ServerMixInfo::ResetEffectProcessingOrder() {
253 for (auto& order : effect_processing_order) {
254 order = AudioCommon::NO_EFFECT_ORDER;
255 }
256}
257
258s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
259 return effect_processing_order.at(i);
260}
261
262bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
263 SplitterContext& splitter_context) {
264 // Mixes are identical
265 if (in_params.dest_mix_id == mix_in.dest_mix_id &&
266 in_params.splitter_id == mix_in.splitter_id &&
267 ((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
268 !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
269 return false;
270 }
271 // Remove current edges for mix id
272 edge_matrix.RemoveEdges(in_params.mix_id);
273 if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
274 // If we have a valid destination mix id, set our edge matrix
275 edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
276 } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
277 // Recurse our splitter linked and set our edges
278 auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
279 const auto length = splitter_info.GetLength();
280 for (s32 i = 0; i < length; i++) {
281 const auto* splitter_destination =
282 splitter_context.GetDestinationData(mix_in.splitter_id, i);
283 if (splitter_destination == nullptr) {
284 continue;
285 }
286 if (splitter_destination->ValidMixId()) {
287 edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
288 }
289 }
290 }
291 in_params.dest_mix_id = mix_in.dest_mix_id;
292 in_params.splitter_id = mix_in.splitter_id;
293 return true;
294}
295
296} // namespace AudioCore
diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h
new file mode 100644
index 000000000..6a588eeb4
--- /dev/null
+++ b/src/audio_core/mix_context.h
@@ -0,0 +1,114 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <vector>
9#include "audio_core/common.h"
10#include "audio_core/splitter_context.h"
11#include "common/common_funcs.h"
12#include "common/common_types.h"
13
14namespace AudioCore {
15class BehaviorInfo;
16class EffectContext;
17
18class MixInfo {
19public:
20 struct DirtyHeader {
21 u32_le magic{};
22 u32_le mixer_count{};
23 INSERT_PADDING_BYTES(0x18);
24 };
25 static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
26
27 struct InParams {
28 float_le volume{};
29 s32_le sample_rate{};
30 s32_le buffer_count{};
31 bool in_use{};
32 INSERT_PADDING_BYTES(3);
33 s32_le mix_id{};
34 s32_le effect_count{};
35 u32_le node_id{};
36 INSERT_PADDING_WORDS(2);
37 std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
38 mix_volume{};
39 s32_le dest_mix_id{};
40 s32_le splitter_id{};
41 INSERT_PADDING_WORDS(1);
42 };
43 static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
44};
45
46class ServerMixInfo {
47public:
48 struct InParams {
49 float volume{};
50 s32 sample_rate{};
51 s32 buffer_count{};
52 bool in_use{};
53 s32 mix_id{};
54 u32 node_id{};
55 std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
56 mix_volume{};
57 s32 dest_mix_id{};
58 s32 splitter_id{};
59 s32 buffer_offset{};
60 s32 final_mix_distance{};
61 };
62 ServerMixInfo();
63 ~ServerMixInfo();
64
65 const ServerMixInfo::InParams& GetInParams() const;
66 ServerMixInfo::InParams& GetInParams();
67
68 bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
69 BehaviorInfo& behavior_info, SplitterContext& splitter_context,
70 EffectContext& effect_context);
71 bool HasAnyConnection() const;
72 void Cleanup();
73 void SetEffectCount(std::size_t count);
74 void ResetEffectProcessingOrder();
75 s32 GetEffectOrder(std::size_t i) const;
76
77private:
78 std::vector<s32> effect_processing_order;
79 InParams in_params{};
80 bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
81 SplitterContext& splitter_context);
82};
83
84class MixContext {
85public:
86 MixContext();
87 ~MixContext();
88
89 void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
90 std::size_t effect_count);
91 void SortInfo();
92 bool TsortInfo(SplitterContext& splitter_context);
93
94 std::size_t GetCount() const;
95 ServerMixInfo& GetInfo(std::size_t i);
96 const ServerMixInfo& GetInfo(std::size_t i) const;
97 ServerMixInfo& GetSortedInfo(std::size_t i);
98 const ServerMixInfo& GetSortedInfo(std::size_t i) const;
99 ServerMixInfo& GetFinalMixInfo();
100 const ServerMixInfo& GetFinalMixInfo() const;
101 EdgeMatrix& GetEdgeMatrix();
102 const EdgeMatrix& GetEdgeMatrix() const;
103
104private:
105 void CalcMixBufferOffset();
106 void UpdateDistancesFromFinalMix();
107
108 NodeStates node_states{};
109 EdgeMatrix edge_matrix{};
110 std::size_t info_count{};
111 std::vector<ServerMixInfo> infos{};
112 std::vector<ServerMixInfo*> sorted_info{};
113};
114} // namespace AudioCore
diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp
new file mode 100644
index 000000000..0882b411a
--- /dev/null
+++ b/src/audio_core/sink_context.cpp
@@ -0,0 +1,31 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/sink_context.h"
6
7namespace AudioCore {
8SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {}
9SinkContext::~SinkContext() = default;
10
11std::size_t SinkContext::GetCount() const {
12 return sink_count;
13}
14
15void SinkContext::UpdateMainSink(SinkInfo::InParams& in) {
16 in_use = in.in_use;
17 use_count = in.device.input_count;
18 std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT);
19}
20
21bool SinkContext::InUse() const {
22 return in_use;
23}
24
25std::vector<u8> SinkContext::OutputBuffers() const {
26 std::vector<u8> buffer_ret(use_count);
27 std::memcpy(buffer_ret.data(), buffers.data(), use_count);
28 return buffer_ret;
29}
30
31} // namespace AudioCore
diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h
new file mode 100644
index 000000000..d7aa72ba7
--- /dev/null
+++ b/src/audio_core/sink_context.h
@@ -0,0 +1,89 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "audio_core/common.h"
8#include "common/common_funcs.h"
9#include "common/common_types.h"
10#include "common/swap.h"
11
12namespace AudioCore {
13
14enum class SinkTypes : u8 {
15 Invalid = 0,
16 Device = 1,
17 Circular = 2,
18};
19
20enum class SinkSampleFormat : u32_le {
21 None = 0,
22 Pcm8 = 1,
23 Pcm16 = 2,
24 Pcm24 = 3,
25 Pcm32 = 4,
26 PcmFloat = 5,
27 Adpcm = 6,
28};
29
30class SinkInfo {
31public:
32 struct CircularBufferIn {
33 u64_le address;
34 u32_le size;
35 u32_le input_count;
36 u32_le sample_count;
37 u32_le previous_position;
38 SinkSampleFormat sample_format;
39 std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
40 bool in_use;
41 INSERT_UNION_PADDING_BYTES(5);
42 };
43 static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28,
44 "SinkInfo::CircularBufferIn is in invalid size");
45
46 struct DeviceIn {
47 std::array<u8, 255> device_name;
48 INSERT_UNION_PADDING_BYTES(1);
49 s32_le input_count;
50 std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
51 INSERT_UNION_PADDING_BYTES(1);
52 bool down_matrix_enabled;
53 std::array<float_le, 4> down_matrix_coef;
54 };
55 static_assert(sizeof(SinkInfo::DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
56
57 struct InParams {
58 SinkTypes type{};
59 bool in_use{};
60 INSERT_PADDING_BYTES(2);
61 u32_le node_id{};
62 INSERT_PADDING_WORDS(6);
63 union {
64 // std::array<u8, 0x120> raw{};
65 SinkInfo::DeviceIn device;
66 SinkInfo::CircularBufferIn circular_buffer;
67 };
68 };
69 static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
70};
71
72class SinkContext {
73public:
74 explicit SinkContext(std::size_t sink_count);
75 ~SinkContext();
76
77 std::size_t GetCount() const;
78
79 void UpdateMainSink(SinkInfo::InParams& in);
80 bool InUse() const;
81 std::vector<u8> OutputBuffers() const;
82
83private:
84 bool in_use{false};
85 s32 use_count{};
86 std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
87 std::size_t sink_count{};
88};
89} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp
new file mode 100644
index 000000000..79bb2f516
--- /dev/null
+++ b/src/audio_core/splitter_context.cpp
@@ -0,0 +1,617 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/behavior_info.h"
6#include "audio_core/splitter_context.h"
7#include "common/alignment.h"
8#include "common/assert.h"
9#include "common/logging/log.h"
10
11namespace AudioCore {
12
13ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {}
14ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
15
16void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
17 // Log error as these are not actually failure states
18 if (header.magic != SplitterMagic::DataHeader) {
19 LOG_ERROR(Audio, "Splitter destination header is invalid!");
20 return;
21 }
22
23 // Incorrect splitter id
24 if (header.splitter_id != id) {
25 LOG_ERROR(Audio, "Splitter destination ids do not match!");
26 return;
27 }
28
29 mix_id = header.mix_id;
30 // Copy our mix volumes
31 std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
32 if (!in_use && header.in_use) {
33 // Update mix volumes
34 std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
35 needs_update = false;
36 }
37 in_use = header.in_use;
38}
39
40ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
41 return next;
42}
43
44const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
45 return next;
46}
47
48void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
49 next = dest;
50}
51
52bool ServerSplitterDestinationData::ValidMixId() const {
53 return GetMixId() != AudioCommon::NO_MIX;
54}
55
56s32 ServerSplitterDestinationData::GetMixId() const {
57 return mix_id;
58}
59
60bool ServerSplitterDestinationData::IsConfigured() const {
61 return in_use && ValidMixId();
62}
63
64float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
65 ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
66 return current_mix_volumes.at(i);
67}
68
69const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
70ServerSplitterDestinationData::CurrentMixVolumes() const {
71 return current_mix_volumes;
72}
73
74const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
75ServerSplitterDestinationData::LastMixVolumes() const {
76 return last_mix_volumes;
77}
78
79void ServerSplitterDestinationData::MarkDirty() {
80 needs_update = true;
81}
82
83void ServerSplitterDestinationData::UpdateInternalState() {
84 if (in_use && needs_update) {
85 std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
86 }
87 needs_update = false;
88}
89
90ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {}
91ServerSplitterInfo::~ServerSplitterInfo() = default;
92
93void ServerSplitterInfo::InitializeInfos() {
94 send_length = 0;
95 head = nullptr;
96 new_connection = true;
97}
98
99void ServerSplitterInfo::ClearNewConnectionFlag() {
100 new_connection = false;
101}
102
103std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
104 if (header.send_id != id) {
105 return 0;
106 }
107
108 sample_rate = header.sample_rate;
109 new_connection = true;
110 // We need to update the size here due to the splitter bug being present and providing an
111 // incorrect size. We're suppose to also update the header here but we just ignore and continue
112 return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
113}
114
115ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
116 return head;
117}
118
119const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
120 return head;
121}
122
123ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
124 auto current_head = head;
125 for (std::size_t i = 0; i < depth; i++) {
126 if (current_head == nullptr) {
127 return nullptr;
128 }
129 current_head = current_head->GetNextDestination();
130 }
131 return current_head;
132}
133
134const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
135 auto current_head = head;
136 for (std::size_t i = 0; i < depth; i++) {
137 if (current_head == nullptr) {
138 return nullptr;
139 }
140 current_head = current_head->GetNextDestination();
141 }
142 return current_head;
143}
144
145bool ServerSplitterInfo::HasNewConnection() const {
146 return new_connection;
147}
148
149s32 ServerSplitterInfo::GetLength() const {
150 return send_length;
151}
152
153void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
154 head = new_head;
155}
156
157void ServerSplitterInfo::SetHeadDepth(s32 length) {
158 send_length = length;
159}
160
161SplitterContext::SplitterContext() = default;
162SplitterContext::~SplitterContext() = default;
163
164void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
165 std::size_t _data_count) {
166 if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
167 Setup(0, 0, false);
168 return;
169 }
170 // Only initialize if we're using splitters
171 Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
172}
173
174bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
175 std::size_t& bytes_read) {
176 const auto UpdateOffsets = [&](std::size_t read) {
177 input_offset += read;
178 bytes_read += read;
179 };
180
181 if (info_count == 0 || data_count == 0) {
182 bytes_read = 0;
183 return true;
184 }
185
186 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
187 sizeof(SplitterInfo::InHeader))) {
188 LOG_ERROR(Audio, "Buffer is an invalid size!");
189 return false;
190 }
191 SplitterInfo::InHeader header{};
192 std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
193 UpdateOffsets(sizeof(SplitterInfo::InHeader));
194
195 if (header.magic != SplitterMagic::SplitterHeader) {
196 LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
197 SplitterMagic::SplitterHeader, header.magic);
198 return false;
199 }
200
201 // Clear all connections
202 for (auto& info : infos) {
203 info.ClearNewConnectionFlag();
204 }
205
206 UpdateInfo(input, input_offset, bytes_read, header.info_count);
207 UpdateData(input, input_offset, bytes_read, header.data_count);
208 const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
209 input_offset += aligned_bytes_read - bytes_read;
210 bytes_read = aligned_bytes_read;
211 return true;
212}
213
214bool SplitterContext::UsingSplitter() const {
215 return info_count > 0 && data_count > 0;
216}
217
218ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
219 ASSERT(i < info_count);
220 return infos.at(i);
221}
222
223const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
224 ASSERT(i < info_count);
225 return infos.at(i);
226}
227
228ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
229 ASSERT(i < data_count);
230 return datas.at(i);
231}
232
233const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
234 ASSERT(i < data_count);
235 return datas.at(i);
236}
237
238ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
239 std::size_t data) {
240 ASSERT(info < info_count);
241 auto& cur_info = GetInfo(info);
242 return cur_info.GetData(data);
243}
244
245const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
246 std::size_t data) const {
247 ASSERT(info < info_count);
248 auto& cur_info = GetInfo(info);
249 return cur_info.GetData(data);
250}
251
252void SplitterContext::UpdateInternalState() {
253 if (data_count == 0) {
254 return;
255 }
256
257 for (auto& data : datas) {
258 data.UpdateInternalState();
259 }
260}
261
262std::size_t SplitterContext::GetInfoCount() const {
263 return info_count;
264}
265
266std::size_t SplitterContext::GetDataCount() const {
267 return data_count;
268}
269
270void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count,
271 bool is_splitter_bug_fixed) {
272
273 info_count = _info_count;
274 data_count = _data_count;
275
276 for (std::size_t i = 0; i < info_count; i++) {
277 auto& splitter = infos.emplace_back(static_cast<s32>(i));
278 splitter.InitializeInfos();
279 }
280 for (std::size_t i = 0; i < data_count; i++) {
281 datas.emplace_back(static_cast<s32>(i));
282 }
283
284 bug_fixed = is_splitter_bug_fixed;
285}
286
287bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
288 std::size_t& bytes_read, s32 in_splitter_count) {
289 const auto UpdateOffsets = [&](std::size_t read) {
290 input_offset += read;
291 bytes_read += read;
292 };
293
294 for (s32 i = 0; i < in_splitter_count; i++) {
295 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
296 sizeof(SplitterInfo::InInfoPrams))) {
297 LOG_ERROR(Audio, "Buffer is an invalid size!");
298 return false;
299 }
300 SplitterInfo::InInfoPrams header{};
301 std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
302
303 // Logged as warning as these don't actually cause a bailout for some reason
304 if (header.magic != SplitterMagic::InfoHeader) {
305 LOG_ERROR(Audio, "Bad splitter data header");
306 break;
307 }
308
309 if (header.send_id < 0 || header.send_id > info_count) {
310 LOG_ERROR(Audio, "Bad splitter data id");
311 break;
312 }
313
314 UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
315 auto& info = GetInfo(header.send_id);
316 if (!RecomposeDestination(info, header, input, input_offset)) {
317 LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
318 return false;
319 }
320 const std::size_t read = info.Update(header);
321 bytes_read += read;
322 input_offset += read;
323 }
324 return true;
325}
326
327bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
328 std::size_t& bytes_read, s32 in_data_count) {
329 const auto UpdateOffsets = [&](std::size_t read) {
330 input_offset += read;
331 bytes_read += read;
332 };
333
334 for (s32 i = 0; i < in_data_count; i++) {
335 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
336 sizeof(SplitterInfo::InDestinationParams))) {
337 LOG_ERROR(Audio, "Buffer is an invalid size!");
338 return false;
339 }
340 SplitterInfo::InDestinationParams header{};
341 std::memcpy(&header, input.data() + input_offset,
342 sizeof(SplitterInfo::InDestinationParams));
343 UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
344
345 // Logged as warning as these don't actually cause a bailout for some reason
346 if (header.magic != SplitterMagic::DataHeader) {
347 LOG_ERROR(Audio, "Bad splitter data header");
348 break;
349 }
350
351 if (header.splitter_id < 0 || header.splitter_id > data_count) {
352 LOG_ERROR(Audio, "Bad splitter data id");
353 break;
354 }
355 GetData(header.splitter_id).Update(header);
356 }
357 return true;
358}
359
360bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
361 SplitterInfo::InInfoPrams& header,
362 const std::vector<u8>& input,
363 const std::size_t& input_offset) {
364 // Clear our current destinations
365 auto* current_head = info.GetHead();
366 while (current_head != nullptr) {
367 auto next_head = current_head->GetNextDestination();
368 current_head->SetNextDestination(nullptr);
369 current_head = next_head;
370 }
371 info.SetHead(nullptr);
372
373 s32 size = header.length;
374 // If the splitter bug is present, calculate fixed size
375 if (!bug_fixed) {
376 if (info_count > 0) {
377 const auto factor = data_count / info_count;
378 size = std::min(header.length, static_cast<s32>(factor));
379 } else {
380 size = 0;
381 }
382 }
383
384 if (size < 1) {
385 LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
386 return true;
387 }
388
389 auto* start_head = &GetData(header.resource_id_base);
390 current_head = start_head;
391 std::vector<s32_le> resource_ids(size - 1);
392 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
393 resource_ids.size() * sizeof(s32_le))) {
394 LOG_ERROR(Audio, "Buffer is an invalid size!");
395 return false;
396 }
397 std::memcpy(resource_ids.data(), input.data() + input_offset,
398 resource_ids.size() * sizeof(s32_le));
399
400 for (auto resource_id : resource_ids) {
401 auto* head = &GetData(resource_id);
402 current_head->SetNextDestination(head);
403 current_head = head;
404 }
405
406 info.SetHead(start_head);
407 info.SetHeadDepth(size);
408
409 return true;
410}
411
412NodeStates::NodeStates() = default;
413NodeStates::~NodeStates() = default;
414
415void NodeStates::Initialize(std::size_t node_count_) {
416 // Setup our work parameters
417 node_count = node_count_;
418 was_node_found.resize(node_count);
419 was_node_completed.resize(node_count);
420 index_list.resize(node_count);
421 index_stack.Reset(node_count * node_count);
422}
423
424bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
425 return DepthFirstSearch(edge_matrix);
426}
427
428std::size_t NodeStates::GetIndexPos() const {
429 return index_pos;
430}
431
432const std::vector<s32>& NodeStates::GetIndexList() const {
433 return index_list;
434}
435
436void NodeStates::PushTsortResult(s32 index) {
437 ASSERT(index < node_count);
438 index_list[index_pos++] = index;
439}
440
441bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
442 ResetState();
443 for (std::size_t i = 0; i < node_count; i++) {
444 const auto node_id = static_cast<s32>(i);
445
446 // If we don't have a state, send to our index stack for work
447 if (GetState(i) == NodeStates::State::NoState) {
448 index_stack.push(node_id);
449 }
450
451 // While we have work to do in our stack
452 while (index_stack.Count() > 0) {
453 // Get the current node
454 const auto current_stack_index = index_stack.top();
455 // Check if we've seen the node yet
456 const auto index_state = GetState(current_stack_index);
457 if (index_state == NodeStates::State::NoState) {
458 // Mark the node as seen
459 UpdateState(NodeStates::State::InFound, current_stack_index);
460 } else if (index_state == NodeStates::State::InFound) {
461 // We've seen this node before, mark it as completed
462 UpdateState(NodeStates::State::InCompleted, current_stack_index);
463 // Update our index list
464 PushTsortResult(current_stack_index);
465 // Pop the stack
466 index_stack.pop();
467 continue;
468 } else if (index_state == NodeStates::State::InCompleted) {
469 // If our node is already sorted, clear it
470 index_stack.pop();
471 continue;
472 }
473
474 const auto node_count = edge_matrix.GetNodeCount();
475 for (s32 j = 0; j < static_cast<s32>(node_count); j++) {
476 // Check if our node is connected to our edge matrix
477 if (!edge_matrix.Connected(current_stack_index, j)) {
478 continue;
479 }
480
481 // Check if our node exists
482 const auto node_state = GetState(j);
483 if (node_state == NodeStates::State::NoState) {
484 // Add more work
485 index_stack.push(j);
486 } else if (node_state == NodeStates::State::InFound) {
487 UNREACHABLE_MSG("Node start marked as found");
488 ResetState();
489 return false;
490 }
491 }
492 }
493 }
494 return true;
495}
496
497void NodeStates::ResetState() {
498 // Reset to the start of our index stack
499 index_pos = 0;
500 for (std::size_t i = 0; i < node_count; i++) {
501 // Mark all nodes as not found
502 was_node_found[i] = false;
503 // Mark all nodes as uncompleted
504 was_node_completed[i] = false;
505 // Mark all indexes as invalid
506 index_list[i] = -1;
507 }
508}
509
510void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
511 switch (state) {
512 case NodeStates::State::NoState:
513 was_node_found[i] = false;
514 was_node_completed[i] = false;
515 break;
516 case NodeStates::State::InFound:
517 was_node_found[i] = true;
518 was_node_completed[i] = false;
519 break;
520 case NodeStates::State::InCompleted:
521 was_node_found[i] = false;
522 was_node_completed[i] = true;
523 break;
524 }
525}
526
527NodeStates::State NodeStates::GetState(std::size_t i) {
528 ASSERT(i < node_count);
529 if (was_node_found[i]) {
530 // If our node exists in our found list
531 return NodeStates::State::InFound;
532 } else if (was_node_completed[i]) {
533 // If node is in the completed list
534 return NodeStates::State::InCompleted;
535 } else {
536 // If in neither
537 return NodeStates::State::NoState;
538 }
539}
540
541NodeStates::Stack::Stack() = default;
542NodeStates::Stack::~Stack() = default;
543
544void NodeStates::Stack::Reset(std::size_t size) {
545 // Mark our stack as empty
546 stack.resize(size);
547 stack_size = size;
548 stack_pos = 0;
549 std::fill(stack.begin(), stack.end(), 0);
550}
551
552void NodeStates::Stack::push(s32 val) {
553 ASSERT(stack_pos < stack_size);
554 stack[stack_pos++] = val;
555}
556
557std::size_t NodeStates::Stack::Count() const {
558 return stack_pos;
559}
560
561s32 NodeStates::Stack::top() const {
562 ASSERT(stack_pos > 0);
563 return stack[stack_pos - 1];
564}
565
566s32 NodeStates::Stack::pop() {
567 ASSERT(stack_pos > 0);
568 stack_pos--;
569 return stack[stack_pos];
570}
571
572EdgeMatrix::EdgeMatrix() = default;
573EdgeMatrix::~EdgeMatrix() = default;
574
575void EdgeMatrix::Initialize(std::size_t _node_count) {
576 node_count = _node_count;
577 edge_matrix.resize(node_count * node_count);
578}
579
580bool EdgeMatrix::Connected(s32 a, s32 b) {
581 return GetState(a, b);
582}
583
584void EdgeMatrix::Connect(s32 a, s32 b) {
585 SetState(a, b, true);
586}
587
588void EdgeMatrix::Disconnect(s32 a, s32 b) {
589 SetState(a, b, false);
590}
591
592void EdgeMatrix::RemoveEdges(s32 edge) {
593 for (std::size_t i = 0; i < node_count; i++) {
594 SetState(edge, static_cast<s32>(i), false);
595 }
596}
597
598std::size_t EdgeMatrix::GetNodeCount() const {
599 return node_count;
600}
601
602void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
603 ASSERT(InRange(a, b));
604 edge_matrix.at(a * node_count + b) = state;
605}
606
607bool EdgeMatrix::GetState(s32 a, s32 b) {
608 ASSERT(InRange(a, b));
609 return edge_matrix.at(a * node_count + b);
610}
611
612bool EdgeMatrix::InRange(s32 a, s32 b) const {
613 const std::size_t pos = a * node_count + b;
614 return pos < (node_count * node_count);
615}
616
617} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h
new file mode 100644
index 000000000..ea6239fdb
--- /dev/null
+++ b/src/audio_core/splitter_context.h
@@ -0,0 +1,221 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <stack>
8#include <vector>
9#include "audio_core/common.h"
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "common/swap.h"
13
14namespace AudioCore {
15class BehaviorInfo;
16
17class EdgeMatrix {
18public:
19 EdgeMatrix();
20 ~EdgeMatrix();
21
22 void Initialize(std::size_t _node_count);
23 bool Connected(s32 a, s32 b);
24 void Connect(s32 a, s32 b);
25 void Disconnect(s32 a, s32 b);
26 void RemoveEdges(s32 edge);
27 std::size_t GetNodeCount() const;
28
29private:
30 void SetState(s32 a, s32 b, bool state);
31 bool GetState(s32 a, s32 b);
32
33 bool InRange(s32 a, s32 b) const;
34 std::vector<bool> edge_matrix{};
35 std::size_t node_count{};
36};
37
38class NodeStates {
39public:
40 enum class State {
41 NoState = 0,
42 InFound = 1,
43 InCompleted = 2,
44 };
45
46 // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
47 class Stack {
48 public:
49 Stack();
50 ~Stack();
51
52 void Reset(std::size_t size);
53 void push(s32 val);
54 std::size_t Count() const;
55 s32 top() const;
56 s32 pop();
57
58 private:
59 std::vector<s32> stack{};
60 std::size_t stack_size{};
61 std::size_t stack_pos{};
62 };
63 NodeStates();
64 ~NodeStates();
65
66 void Initialize(std::size_t _node_count);
67 bool Tsort(EdgeMatrix& edge_matrix);
68 std::size_t GetIndexPos() const;
69 const std::vector<s32>& GetIndexList() const;
70
71private:
72 void PushTsortResult(s32 index);
73 bool DepthFirstSearch(EdgeMatrix& edge_matrix);
74 void ResetState();
75 void UpdateState(NodeStates::State state, std::size_t i);
76 NodeStates::State GetState(std::size_t i);
77
78 std::size_t node_count{};
79 std::vector<bool> was_node_found{};
80 std::vector<bool> was_node_completed{};
81 std::size_t index_pos{};
82 std::vector<s32> index_list{};
83 NodeStates::Stack index_stack{};
84};
85
86enum class SplitterMagic : u32_le {
87 SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
88 DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
89 InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
90};
91
92class SplitterInfo {
93public:
94 struct InHeader {
95 SplitterMagic magic{};
96 s32_le info_count{};
97 s32_le data_count{};
98 INSERT_PADDING_WORDS(5);
99 };
100 static_assert(sizeof(SplitterInfo::InHeader) == 0x20,
101 "SplitterInfo::InHeader is an invalid size");
102
103 struct InInfoPrams {
104 SplitterMagic magic{};
105 s32_le send_id{};
106 s32_le sample_rate{};
107 s32_le length{};
108 s32_le resource_id_base{};
109 };
110 static_assert(sizeof(SplitterInfo::InInfoPrams) == 0x14,
111 "SplitterInfo::InInfoPrams is an invalid size");
112
113 struct InDestinationParams {
114 SplitterMagic magic{};
115 s32_le splitter_id{};
116 std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
117 s32_le mix_id{};
118 bool in_use{};
119 INSERT_PADDING_BYTES(3);
120 };
121 static_assert(sizeof(SplitterInfo::InDestinationParams) == 0x70,
122 "SplitterInfo::InDestinationParams is an invalid size");
123};
124
125class ServerSplitterDestinationData {
126public:
127 explicit ServerSplitterDestinationData(s32 id);
128 ~ServerSplitterDestinationData();
129
130 void Update(SplitterInfo::InDestinationParams& header);
131
132 ServerSplitterDestinationData* GetNextDestination();
133 const ServerSplitterDestinationData* GetNextDestination() const;
134 void SetNextDestination(ServerSplitterDestinationData* dest);
135 bool ValidMixId() const;
136 s32 GetMixId() const;
137 bool IsConfigured() const;
138 float GetMixVolume(std::size_t i) const;
139 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
140 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
141 void MarkDirty();
142 void UpdateInternalState();
143
144private:
145 bool needs_update{};
146 bool in_use{};
147 s32 id{};
148 s32 mix_id{};
149 std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
150 std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
151 ServerSplitterDestinationData* next = nullptr;
152};
153
154class ServerSplitterInfo {
155public:
156 explicit ServerSplitterInfo(s32 id);
157 ~ServerSplitterInfo();
158
159 void InitializeInfos();
160 void ClearNewConnectionFlag();
161 std::size_t Update(SplitterInfo::InInfoPrams& header);
162
163 ServerSplitterDestinationData* GetHead();
164 const ServerSplitterDestinationData* GetHead() const;
165 ServerSplitterDestinationData* GetData(std::size_t depth);
166 const ServerSplitterDestinationData* GetData(std::size_t depth) const;
167
168 bool HasNewConnection() const;
169 s32 GetLength() const;
170
171 void SetHead(ServerSplitterDestinationData* new_head);
172 void SetHeadDepth(s32 length);
173
174private:
175 s32 sample_rate{};
176 s32 id{};
177 s32 send_length{};
178 ServerSplitterDestinationData* head = nullptr;
179 bool new_connection{};
180};
181
182class SplitterContext {
183public:
184 SplitterContext();
185 ~SplitterContext();
186
187 void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
188 std::size_t data_count);
189
190 bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
191 bool UsingSplitter() const;
192
193 ServerSplitterInfo& GetInfo(std::size_t i);
194 const ServerSplitterInfo& GetInfo(std::size_t i) const;
195 ServerSplitterDestinationData& GetData(std::size_t i);
196 const ServerSplitterDestinationData& GetData(std::size_t i) const;
197 ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
198 const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
199 std::size_t data) const;
200 void UpdateInternalState();
201
202 std::size_t GetInfoCount() const;
203 std::size_t GetDataCount() const;
204
205private:
206 void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
207 bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
208 std::size_t& bytes_read, s32 in_splitter_count);
209 bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
210 std::size_t& bytes_read, s32 in_data_count);
211 bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
212 const std::vector<u8>& input, const std::size_t& input_offset);
213
214 std::vector<ServerSplitterInfo> infos{};
215 std::vector<ServerSplitterDestinationData> datas{};
216
217 std::size_t info_count{};
218 std::size_t data_count{};
219 bool bug_fixed{};
220};
221} // namespace AudioCore
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 7be5d5087..cb33926bc 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -104,11 +104,7 @@ void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
104 104
105 sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); 105 sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
106 106
107 const auto time_stretch_delta = Settings::values.enable_audio_stretching.GetValue() 107 core_timing.ScheduleEvent(GetBufferReleaseNS(*active_buffer) - ns_late, release_event, {});
108 ? std::chrono::nanoseconds::zero()
109 : ns_late;
110 const auto future_time = GetBufferReleaseNS(*active_buffer) - time_stretch_delta;
111 core_timing.ScheduleEvent(future_time, release_event, {});
112} 108}
113 109
114void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) { 110void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) {
diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp
new file mode 100644
index 000000000..1d8f69844
--- /dev/null
+++ b/src/audio_core/voice_context.cpp
@@ -0,0 +1,526 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/behavior_info.h"
6#include "audio_core/voice_context.h"
7#include "core/memory.h"
8
9namespace AudioCore {
10
11ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {}
12ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
13
14bool ServerVoiceChannelResource::InUse() const {
15 return in_use;
16}
17
18float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
19 ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
20 return mix_volume.at(i);
21}
22
23float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
24 ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
25 return last_mix_volume.at(i);
26}
27
28void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
29 in_use = in_params.in_use;
30 // Update our mix volumes only if it's in use
31 if (in_params.in_use) {
32 mix_volume = in_params.mix_volume;
33 }
34}
35
36void ServerVoiceChannelResource::UpdateLastMixVolumes() {
37 last_mix_volume = mix_volume;
38}
39
40const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
41ServerVoiceChannelResource::GetCurrentMixVolume() const {
42 return mix_volume;
43}
44
45const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
46ServerVoiceChannelResource::GetLastMixVolume() const {
47 return last_mix_volume;
48}
49
50ServerVoiceInfo::ServerVoiceInfo() {
51 Initialize();
52}
53ServerVoiceInfo::~ServerVoiceInfo() = default;
54
55void ServerVoiceInfo::Initialize() {
56 in_params.in_use = false;
57 in_params.node_id = 0;
58 in_params.id = 0;
59 in_params.current_playstate = ServerPlayState::Stop;
60 in_params.priority = 255;
61 in_params.sample_rate = 0;
62 in_params.sample_format = SampleFormat::Invalid;
63 in_params.channel_count = 0;
64 in_params.pitch = 0.0f;
65 in_params.volume = 0.0f;
66 in_params.last_volume = 0.0f;
67 in_params.biquad_filter.fill({});
68 in_params.wave_buffer_count = 0;
69 in_params.wave_bufffer_head = 0;
70 in_params.mix_id = AudioCommon::NO_MIX;
71 in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
72 in_params.additional_params_address = 0;
73 in_params.additional_params_size = 0;
74 in_params.is_new = false;
75 out_params.played_sample_count = 0;
76 out_params.wave_buffer_consumed = 0;
77 in_params.voice_drop_flag = false;
78 in_params.buffer_mapped = false;
79 in_params.wave_buffer_flush_request_count = 0;
80 in_params.was_biquad_filter_enabled.fill(false);
81
82 for (auto& wave_buffer : in_params.wave_buffer) {
83 wave_buffer.start_sample_offset = 0;
84 wave_buffer.end_sample_offset = 0;
85 wave_buffer.is_looping = false;
86 wave_buffer.end_of_stream = false;
87 wave_buffer.buffer_address = 0;
88 wave_buffer.buffer_size = 0;
89 wave_buffer.context_address = 0;
90 wave_buffer.context_size = 0;
91 wave_buffer.sent_to_dsp = true;
92 }
93
94 stored_samples.clear();
95}
96
97void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
98 BehaviorInfo& behavior_info) {
99 in_params.in_use = voice_in.is_in_use;
100 in_params.id = voice_in.id;
101 in_params.node_id = voice_in.node_id;
102 in_params.last_playstate = in_params.current_playstate;
103 switch (voice_in.play_state) {
104 case PlayState::Paused:
105 in_params.current_playstate = ServerPlayState::Paused;
106 break;
107 case PlayState::Stopped:
108 if (in_params.current_playstate != ServerPlayState::Stop) {
109 in_params.current_playstate = ServerPlayState::RequestStop;
110 }
111 break;
112 case PlayState::Started:
113 in_params.current_playstate = ServerPlayState::Play;
114 break;
115 default:
116 UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state);
117 break;
118 }
119
120 in_params.priority = voice_in.priority;
121 in_params.sorting_order = voice_in.sorting_order;
122 in_params.sample_rate = voice_in.sample_rate;
123 in_params.sample_format = voice_in.sample_format;
124 in_params.channel_count = voice_in.channel_count;
125 in_params.pitch = voice_in.pitch;
126 in_params.volume = voice_in.volume;
127 in_params.biquad_filter = voice_in.biquad_filter;
128 in_params.wave_buffer_count = voice_in.wave_buffer_count;
129 in_params.wave_bufffer_head = voice_in.wave_buffer_head;
130 if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
131 in_params.wave_buffer_flush_request_count += voice_in.wave_buffer_flush_request_count;
132 }
133 in_params.mix_id = voice_in.mix_id;
134 if (behavior_info.IsSplitterSupported()) {
135 in_params.splitter_info_id = voice_in.splitter_info_id;
136 } else {
137 in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
138 }
139
140 std::memcpy(in_params.voice_channel_resource_id.data(),
141 voice_in.voice_channel_resource_ids.data(),
142 sizeof(s32) * in_params.voice_channel_resource_id.size());
143
144 if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
145 in_params.behavior_flags.is_played_samples_reset_at_loop_point =
146 voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
147 } else {
148 in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
149 }
150 if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
151 in_params.behavior_flags.is_pitch_and_src_skipped =
152 voice_in.behavior_flags.is_pitch_and_src_skipped;
153 } else {
154 in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
155 }
156
157 if (voice_in.is_voice_drop_flag_clear_requested) {
158 in_params.voice_drop_flag = false;
159 }
160
161 if (in_params.additional_params_address != voice_in.additional_params_address ||
162 in_params.additional_params_size != voice_in.additional_params_size) {
163 in_params.additional_params_address = voice_in.additional_params_address;
164 in_params.additional_params_size = voice_in.additional_params_size;
165 // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
166 // our context is new
167 }
168}
169
170void ServerVoiceInfo::UpdateWaveBuffers(
171 const VoiceInfo::InParams& voice_in,
172 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
173 BehaviorInfo& behavior_info) {
174 if (voice_in.is_new) {
175 // Initialize our wave buffers
176 for (auto& wave_buffer : in_params.wave_buffer) {
177 wave_buffer.start_sample_offset = 0;
178 wave_buffer.end_sample_offset = 0;
179 wave_buffer.is_looping = false;
180 wave_buffer.end_of_stream = false;
181 wave_buffer.buffer_address = 0;
182 wave_buffer.buffer_size = 0;
183 wave_buffer.context_address = 0;
184 wave_buffer.context_size = 0;
185 wave_buffer.sent_to_dsp = true;
186 }
187
188 // Mark all our wave buffers as invalid
189 for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
190 channel++) {
191 for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) {
192 is_valid = false;
193 }
194 }
195 }
196
197 // Update our wave buffers
198 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
199 // Assume that we have at least 1 channel voice state
200 const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
201
202 UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
203 have_valid_wave_buffer, behavior_info);
204 }
205}
206
207void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
208 const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
209 bool is_buffer_valid, BehaviorInfo& behavior_info) {
210 if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
211 out_wavebuffer.buffer_address = 0;
212 out_wavebuffer.buffer_size = 0;
213 }
214
215 if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
216 // Validate sample offset sizings
217 if (sample_format == SampleFormat::Pcm16) {
218 const auto buffer_size = in_wave_buffer.buffer_size;
219 if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 ||
220 (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) ||
221 (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) {
222 // TODO(ogniK): Write error info
223 return;
224 }
225 }
226 // TODO(ogniK): ADPCM Size error
227
228 out_wavebuffer.sent_to_dsp = false;
229 out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
230 out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
231 out_wavebuffer.is_looping = in_wave_buffer.is_looping;
232 out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
233
234 out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
235 out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
236 out_wavebuffer.context_address = in_wave_buffer.context_address;
237 out_wavebuffer.context_size = in_wave_buffer.context_size;
238 in_params.buffer_mapped =
239 in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
240 // TODO(ogniK): Pool mapper attachment
241 // TODO(ogniK): IsAdpcmLoopContextBugFixed
242 }
243}
244
245void ServerVoiceInfo::WriteOutStatus(
246 VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
247 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
248 if (voice_in.is_new) {
249 in_params.is_new = true;
250 voice_out.wave_buffer_consumed = 0;
251 voice_out.played_sample_count = 0;
252 voice_out.voice_dropped = false;
253 } else if (!in_params.is_new) {
254 voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
255 voice_out.played_sample_count = voice_states[0]->played_sample_count;
256 voice_out.voice_dropped = in_params.voice_drop_flag;
257 } else {
258 voice_out.wave_buffer_consumed = 0;
259 voice_out.played_sample_count = 0;
260 voice_out.voice_dropped = false;
261 }
262}
263
264const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
265 return in_params;
266}
267
268ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
269 return in_params;
270}
271
272const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
273 return out_params;
274}
275
276ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
277 return out_params;
278}
279
280bool ServerVoiceInfo::ShouldSkip() const {
281 // TODO(ogniK): Handle unmapped wave buffers or parameters
282 return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag;
283}
284
285bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
286 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
287 if (in_params.is_new) {
288 ResetResources(voice_context);
289 in_params.last_volume = in_params.volume;
290 in_params.is_new = false;
291 }
292
293 const s32 channel_count = in_params.channel_count;
294 for (s32 i = 0; i < channel_count; i++) {
295 const auto channel_resource = in_params.voice_channel_resource_id[i];
296 dsp_voice_states[i] =
297 &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
298 }
299 return UpdateParametersForCommandGeneration(dsp_voice_states);
300}
301
302void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
303 const s32 channel_count = in_params.channel_count;
304 for (s32 i = 0; i < channel_count; i++) {
305 const auto channel_resource = in_params.voice_channel_resource_id[i];
306 auto& dsp_state =
307 voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
308 dsp_state = {};
309 voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
310 .UpdateLastMixVolumes();
311 }
312}
313
314bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
315 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
316 const s32 channel_count = in_params.channel_count;
317 if (in_params.wave_buffer_flush_request_count > 0) {
318 FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
319 channel_count);
320 in_params.wave_buffer_flush_request_count = 0;
321 }
322
323 switch (in_params.current_playstate) {
324 case ServerPlayState::Play: {
325 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
326 if (!in_params.wave_buffer[i].sent_to_dsp) {
327 for (s32 channel = 0; channel < channel_count; channel++) {
328 dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
329 }
330 in_params.wave_buffer[i].sent_to_dsp = true;
331 }
332 }
333 in_params.should_depop = false;
334 return HasValidWaveBuffer(dsp_voice_states[0]);
335 }
336 case ServerPlayState::Paused:
337 case ServerPlayState::Stop: {
338 in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
339 return in_params.should_depop;
340 }
341 case ServerPlayState::RequestStop: {
342 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
343 in_params.wave_buffer[i].sent_to_dsp = true;
344 for (s32 channel = 0; channel < channel_count; channel++) {
345 auto* dsp_state = dsp_voice_states[channel];
346
347 if (dsp_state->is_wave_buffer_valid[i]) {
348 dsp_state->wave_buffer_index =
349 (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
350 dsp_state->wave_buffer_consumed++;
351 }
352
353 dsp_state->is_wave_buffer_valid[i] = false;
354 }
355 }
356
357 for (s32 channel = 0; channel < channel_count; channel++) {
358 auto* dsp_state = dsp_voice_states[channel];
359 dsp_state->offset = 0;
360 dsp_state->played_sample_count = 0;
361 dsp_state->fraction = 0;
362 dsp_state->sample_history.fill(0);
363 dsp_state->context = {};
364 }
365
366 in_params.current_playstate = ServerPlayState::Stop;
367 in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
368 return in_params.should_depop;
369 }
370 default:
371 UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate);
372 }
373
374 return false;
375}
376
377void ServerVoiceInfo::FlushWaveBuffers(
378 u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
379 s32 channel_count) {
380 auto wave_head = in_params.wave_bufffer_head;
381
382 for (u8 i = 0; i < flush_count; i++) {
383 in_params.wave_buffer[wave_head].sent_to_dsp = true;
384 for (s32 channel = 0; channel < channel_count; channel++) {
385 auto* dsp_state = dsp_voice_states[channel];
386 dsp_state->wave_buffer_consumed++;
387 dsp_state->is_wave_buffer_valid[wave_head] = false;
388 dsp_state->wave_buffer_index =
389 (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
390 }
391 wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
392 }
393}
394
395bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
396 const auto& valid_wb = state->is_wave_buffer_valid;
397 return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
398}
399
400VoiceContext::VoiceContext(std::size_t voice_count) : voice_count(voice_count) {
401 for (std::size_t i = 0; i < voice_count; i++) {
402 voice_channel_resources.emplace_back(static_cast<s32>(i));
403 sorted_voice_info.push_back(&voice_info.emplace_back());
404 voice_states.emplace_back();
405 dsp_voice_states.emplace_back();
406 }
407}
408
409VoiceContext::~VoiceContext() {
410 sorted_voice_info.clear();
411}
412
413std::size_t VoiceContext::GetVoiceCount() const {
414 return voice_count;
415}
416
417ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
418 ASSERT(i < voice_count);
419 return voice_channel_resources.at(i);
420}
421
422const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
423 ASSERT(i < voice_count);
424 return voice_channel_resources.at(i);
425}
426
427VoiceState& VoiceContext::GetState(std::size_t i) {
428 ASSERT(i < voice_count);
429 return voice_states.at(i);
430}
431
432const VoiceState& VoiceContext::GetState(std::size_t i) const {
433 ASSERT(i < voice_count);
434 return voice_states.at(i);
435}
436
437VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
438 ASSERT(i < voice_count);
439 return dsp_voice_states.at(i);
440}
441
442const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
443 ASSERT(i < voice_count);
444 return dsp_voice_states.at(i);
445}
446
447ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
448 ASSERT(i < voice_count);
449 return voice_info.at(i);
450}
451
452const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
453 ASSERT(i < voice_count);
454 return voice_info.at(i);
455}
456
457ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
458 ASSERT(i < voice_count);
459 return *sorted_voice_info.at(i);
460}
461
462const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
463 ASSERT(i < voice_count);
464 return *sorted_voice_info.at(i);
465}
466
467s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
468 s32 channel_count, s32 buffer_offset, s32 sample_count,
469 Core::Memory::Memory& memory) {
470 if (wave_buffer->buffer_address == 0) {
471 return 0;
472 }
473 if (wave_buffer->buffer_size == 0) {
474 return 0;
475 }
476 if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) {
477 return 0;
478 }
479
480 const auto samples_remaining =
481 (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset;
482 const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
483 const auto buffer_pos = wave_buffer->buffer_address + start_offset;
484
485 s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
486
487 const auto samples_processed = std::min(sample_count, samples_remaining);
488
489 // Fast path
490 if (channel_count == 1) {
491 for (std::size_t i = 0; i < samples_processed; i++) {
492 output_buffer[i] = buffer_data[i];
493 }
494 } else {
495 for (std::size_t i = 0; i < samples_processed; i++) {
496 output_buffer[i] = buffer_data[i * channel_count + channel];
497 }
498 }
499
500 return samples_processed;
501}
502
503void VoiceContext::SortInfo() {
504 for (std::size_t i = 0; i < voice_count; i++) {
505 sorted_voice_info[i] = &voice_info[i];
506 }
507
508 std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
509 [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
510 const auto& lhs_in = lhs->GetInParams();
511 const auto& rhs_in = rhs->GetInParams();
512 // Sort by priority
513 if (lhs_in.priority != rhs_in.priority) {
514 return lhs_in.priority > rhs_in.priority;
515 } else {
516 // If the priorities match, sort by sorting order
517 return lhs_in.sorting_order > rhs_in.sorting_order;
518 }
519 });
520}
521
522void VoiceContext::UpdateStateByDspShared() {
523 voice_states = dsp_voice_states;
524}
525
526} // namespace AudioCore
diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h
new file mode 100644
index 000000000..59d3d7dfb
--- /dev/null
+++ b/src/audio_core/voice_context.h
@@ -0,0 +1,296 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include "audio_core/algorithm/interpolate.h"
9#include "audio_core/codec.h"
10#include "audio_core/common.h"
11#include "common/bit_field.h"
12#include "common/common_funcs.h"
13#include "common/common_types.h"
14
15namespace Core::Memory {
16class Memory;
17}
18
19namespace AudioCore {
20
21class BehaviorInfo;
22class VoiceContext;
23
24enum class SampleFormat : u8 {
25 Invalid = 0,
26 Pcm8 = 1,
27 Pcm16 = 2,
28 Pcm24 = 3,
29 Pcm32 = 4,
30 PcmFloat = 5,
31 Adpcm = 6,
32};
33
34enum class PlayState : u8 {
35 Started = 0,
36 Stopped = 1,
37 Paused = 2,
38};
39
40enum class ServerPlayState {
41 Play = 0,
42 Stop = 1,
43 RequestStop = 2,
44 Paused = 3,
45};
46
47struct BiquadFilterParameter {
48 bool enabled{};
49 INSERT_PADDING_BYTES(1);
50 std::array<s16, 3> numerator{};
51 std::array<s16, 2> denominator{};
52};
53static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
54
55struct WaveBuffer {
56 u64_le buffer_address{};
57 u64_le buffer_size{};
58 s32_le start_sample_offset{};
59 s32_le end_sample_offset{};
60 u8 is_looping{};
61 u8 end_of_stream{};
62 u8 sent_to_server{};
63 INSERT_PADDING_BYTES(5);
64 u64 context_address{};
65 u64 context_size{};
66 INSERT_PADDING_BYTES(8);
67};
68static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
69
70struct ServerWaveBuffer {
71 VAddr buffer_address{};
72 std::size_t buffer_size{};
73 s32 start_sample_offset{};
74 s32 end_sample_offset{};
75 bool is_looping{};
76 bool end_of_stream{};
77 VAddr context_address{};
78 std::size_t context_size{};
79 bool sent_to_dsp{true};
80};
81
82struct BehaviorFlags {
83 BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
84 BitField<1, 1, u16> is_pitch_and_src_skipped;
85};
86static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
87
88struct ADPCMContext {
89 u16 header{};
90 s16 yn1{};
91 s16 yn2{};
92};
93static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size");
94
95struct VoiceState {
96 s64 played_sample_count{};
97 s32 offset{};
98 s32 wave_buffer_index{};
99 std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid{};
100 s32 wave_buffer_consumed{};
101 std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history{};
102 s32 fraction{};
103 VAddr context_address{};
104 Codec::ADPCM_Coeff coeff{};
105 ADPCMContext context{};
106 std::array<s64, 2> biquad_filter_state{};
107 std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples{};
108 u32 external_context_size{};
109 bool is_external_context_used{};
110 bool voice_dropped{};
111};
112
113class VoiceChannelResource {
114public:
115 struct InParams {
116 s32_le id{};
117 std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
118 bool in_use{};
119 INSERT_PADDING_BYTES(11);
120 };
121 static_assert(sizeof(VoiceChannelResource::InParams) == 0x70, "InParams is an invalid size");
122};
123
124class ServerVoiceChannelResource {
125public:
126 explicit ServerVoiceChannelResource(s32 id);
127 ~ServerVoiceChannelResource();
128
129 bool InUse() const;
130 float GetCurrentMixVolumeAt(std::size_t i) const;
131 float GetLastMixVolumeAt(std::size_t i) const;
132 void Update(VoiceChannelResource::InParams& in_params);
133 void UpdateLastMixVolumes();
134
135 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
136 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
137
138private:
139 s32 id{};
140 std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
141 std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
142 bool in_use{};
143};
144
145class VoiceInfo {
146public:
147 struct InParams {
148 s32_le id{};
149 u32_le node_id{};
150 u8 is_new{};
151 u8 is_in_use{};
152 PlayState play_state{};
153 SampleFormat sample_format{};
154 s32_le sample_rate{};
155 s32_le priority{};
156 s32_le sorting_order{};
157 s32_le channel_count{};
158 float_le pitch{};
159 float_le volume{};
160 std::array<BiquadFilterParameter, 2> biquad_filter{};
161 s32_le wave_buffer_count{};
162 s16_le wave_buffer_head{};
163 INSERT_PADDING_BYTES(6);
164 u64_le additional_params_address{};
165 u64_le additional_params_size{};
166 s32_le mix_id{};
167 s32_le splitter_info_id{};
168 std::array<WaveBuffer, 4> wave_buffer{};
169 std::array<u32_le, 6> voice_channel_resource_ids{};
170 // TODO(ogniK): Remaining flags
171 u8 is_voice_drop_flag_clear_requested{};
172 u8 wave_buffer_flush_request_count{};
173 INSERT_PADDING_BYTES(2);
174 BehaviorFlags behavior_flags{};
175 INSERT_PADDING_BYTES(16);
176 };
177 static_assert(sizeof(VoiceInfo::InParams) == 0x170, "InParams is an invalid size");
178
179 struct OutParams {
180 u64_le played_sample_count{};
181 u32_le wave_buffer_consumed{};
182 u8 voice_dropped{};
183 INSERT_PADDING_BYTES(3);
184 };
185 static_assert(sizeof(VoiceInfo::OutParams) == 0x10, "OutParams is an invalid size");
186};
187
188class ServerVoiceInfo {
189public:
190 struct InParams {
191 bool in_use{};
192 bool is_new{};
193 bool should_depop{};
194 SampleFormat sample_format{};
195 s32 sample_rate{};
196 s32 channel_count{};
197 s32 id{};
198 s32 node_id{};
199 s32 mix_id{};
200 ServerPlayState current_playstate{};
201 ServerPlayState last_playstate{};
202 s32 priority{};
203 s32 sorting_order{};
204 float pitch{};
205 float volume{};
206 float last_volume{};
207 std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
208 s32 wave_buffer_count{};
209 s16 wave_bufffer_head{};
210 INSERT_PADDING_BYTES(2);
211 BehaviorFlags behavior_flags{};
212 VAddr additional_params_address{};
213 std::size_t additional_params_size{};
214 std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
215 std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
216 s32 splitter_info_id{};
217 u8 wave_buffer_flush_request_count{};
218 bool voice_drop_flag{};
219 bool buffer_mapped{};
220 std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
221 };
222
223 struct OutParams {
224 s64 played_sample_count{};
225 s32 wave_buffer_consumed{};
226 };
227
228 ServerVoiceInfo();
229 ~ServerVoiceInfo();
230 void Initialize();
231 void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
232 void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
233 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
234 BehaviorInfo& behavior_info);
235 void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
236 SampleFormat sample_format, bool is_buffer_valid,
237 BehaviorInfo& behavior_info);
238 void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
239 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
240
241 const InParams& GetInParams() const;
242 InParams& GetInParams();
243
244 const OutParams& GetOutParams() const;
245 OutParams& GetOutParams();
246
247 bool ShouldSkip() const;
248 bool UpdateForCommandGeneration(VoiceContext& voice_context);
249 void ResetResources(VoiceContext& voice_context);
250 bool UpdateParametersForCommandGeneration(
251 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
252 void FlushWaveBuffers(u8 flush_count,
253 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
254 s32 channel_count);
255
256private:
257 std::vector<s16> stored_samples;
258 InParams in_params{};
259 OutParams out_params{};
260
261 bool HasValidWaveBuffer(const VoiceState* state) const;
262};
263
264class VoiceContext {
265public:
266 VoiceContext(std::size_t voice_count);
267 ~VoiceContext();
268
269 std::size_t GetVoiceCount() const;
270 ServerVoiceChannelResource& GetChannelResource(std::size_t i);
271 const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
272 VoiceState& GetState(std::size_t i);
273 const VoiceState& GetState(std::size_t i) const;
274 VoiceState& GetDspSharedState(std::size_t i);
275 const VoiceState& GetDspSharedState(std::size_t i) const;
276 ServerVoiceInfo& GetInfo(std::size_t i);
277 const ServerVoiceInfo& GetInfo(std::size_t i) const;
278 ServerVoiceInfo& GetSortedInfo(std::size_t i);
279 const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
280
281 s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
282 s32 channel_count, s32 buffer_offset, s32 sample_count,
283 Core::Memory::Memory& memory);
284 void SortInfo();
285 void UpdateStateByDspShared();
286
287private:
288 std::size_t voice_count{};
289 std::vector<ServerVoiceChannelResource> voice_channel_resources{};
290 std::vector<VoiceState> voice_states{};
291 std::vector<VoiceState> dsp_voice_states{};
292 std::vector<ServerVoiceInfo> voice_info{};
293 std::vector<ServerVoiceInfo*> sorted_voice_info{};
294};
295
296} // namespace AudioCore
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index b96ca9374..d0c405ec7 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -126,6 +126,8 @@ add_library(core STATIC
126 file_sys/vfs_vector.h 126 file_sys/vfs_vector.h
127 file_sys/xts_archive.cpp 127 file_sys/xts_archive.cpp
128 file_sys/xts_archive.h 128 file_sys/xts_archive.h
129 frontend/applets/controller.cpp
130 frontend/applets/controller.h
129 frontend/applets/error.cpp 131 frontend/applets/error.cpp
130 frontend/applets/error.h 132 frontend/applets/error.h
131 frontend/applets/general_frontend.cpp 133 frontend/applets/general_frontend.cpp
@@ -244,6 +246,8 @@ add_library(core STATIC
244 hle/service/am/applet_oe.h 246 hle/service/am/applet_oe.h
245 hle/service/am/applets/applets.cpp 247 hle/service/am/applets/applets.cpp
246 hle/service/am/applets/applets.h 248 hle/service/am/applets/applets.h
249 hle/service/am/applets/controller.cpp
250 hle/service/am/applets/controller.h
247 hle/service/am/applets/error.cpp 251 hle/service/am/applets/error.cpp
248 hle/service/am/applets/error.h 252 hle/service/am/applets/error.h
249 hle/service/am/applets/general_backend.cpp 253 hle/service/am/applets/general_backend.cpp
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
index 7356d252e..dc6f4af3a 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.h
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
@@ -35,8 +35,8 @@ public:
35 std::optional<u8> option) override; 35 std::optional<u8> option) override;
36 36
37 ARM_Dynarmic_32& parent; 37 ARM_Dynarmic_32& parent;
38 u32 uprw; 38 u32 uprw = 0;
39 u32 uro; 39 u32 uro = 0;
40}; 40};
41 41
42} // namespace Core 42} // namespace Core
diff --git a/src/core/core.cpp b/src/core/core.cpp
index df81e8e2c..81e8cc338 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -178,7 +178,7 @@ struct System::Impl {
178 arp_manager.ResetAll(); 178 arp_manager.ResetAll();
179 179
180 telemetry_session = std::make_unique<Core::TelemetrySession>(); 180 telemetry_session = std::make_unique<Core::TelemetrySession>();
181 service_manager = std::make_shared<Service::SM::ServiceManager>(); 181 service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
182 182
183 Service::Init(service_manager, system); 183 Service::Init(service_manager, system);
184 GDBStub::DeferStart(); 184 GDBStub::DeferStart();
@@ -221,7 +221,7 @@ struct System::Impl {
221 telemetry_session->AddInitialInfo(*app_loader); 221 telemetry_session->AddInitialInfo(*app_loader);
222 auto main_process = 222 auto main_process =
223 Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland); 223 Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland);
224 const auto [load_result, load_parameters] = app_loader->Load(*main_process); 224 const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
225 if (load_result != Loader::ResultStatus::Success) { 225 if (load_result != Loader::ResultStatus::Success) {
226 LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result)); 226 LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
227 Shutdown(); 227 Shutdown();
@@ -629,11 +629,11 @@ Loader::AppLoader& System::GetAppLoader() const {
629 return *impl->app_loader; 629 return *impl->app_loader;
630} 630}
631 631
632void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { 632void System::SetFilesystem(FileSys::VirtualFilesystem vfs) {
633 impl->virtual_filesystem = std::move(vfs); 633 impl->virtual_filesystem = std::move(vfs);
634} 634}
635 635
636std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { 636FileSys::VirtualFilesystem System::GetFilesystem() const {
637 return impl->virtual_filesystem; 637 return impl->virtual_filesystem;
638} 638}
639 639
diff --git a/src/core/core.h b/src/core/core.h
index 5c6cfbffe..83ded63a5 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -316,9 +316,9 @@ public:
316 Service::SM::ServiceManager& ServiceManager(); 316 Service::SM::ServiceManager& ServiceManager();
317 const Service::SM::ServiceManager& ServiceManager() const; 317 const Service::SM::ServiceManager& ServiceManager() const;
318 318
319 void SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs); 319 void SetFilesystem(FileSys::VirtualFilesystem vfs);
320 320
321 std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; 321 FileSys::VirtualFilesystem GetFilesystem() const;
322 322
323 void RegisterCheatList(const std::vector<Memory::CheatEntry>& list, 323 void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
324 const std::array<u8, 0x20>& build_id, VAddr main_region_begin, 324 const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index dc591c730..65d246050 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -23,7 +23,6 @@
23#include "common/hex_util.h" 23#include "common/hex_util.h"
24#include "common/logging/log.h" 24#include "common/logging/log.h"
25#include "common/string_util.h" 25#include "common/string_util.h"
26#include "core/core.h"
27#include "core/crypto/aes_util.h" 26#include "core/crypto/aes_util.h"
28#include "core/crypto/key_manager.h" 27#include "core/crypto/key_manager.h"
29#include "core/crypto/partition_data_manager.h" 28#include "core/crypto/partition_data_manager.h"
@@ -1022,10 +1021,10 @@ void KeyManager::DeriveBase() {
1022 } 1021 }
1023} 1022}
1024 1023
1025void KeyManager::DeriveETicket(PartitionDataManager& data) { 1024void KeyManager::DeriveETicket(PartitionDataManager& data,
1025 const FileSys::ContentProvider& provider) {
1026 // ETicket keys 1026 // ETicket keys
1027 const auto es = Core::System::GetInstance().GetContentProvider().GetEntry( 1027 const auto es = provider.GetEntry(0x0100000000000033, FileSys::ContentRecordType::Program);
1028 0x0100000000000033, FileSys::ContentRecordType::Program);
1029 1028
1030 if (es == nullptr) { 1029 if (es == nullptr) {
1031 return; 1030 return;
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 321b75323..0a7220286 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -20,6 +20,10 @@ namespace Common::FS {
20class IOFile; 20class IOFile;
21} 21}
22 22
23namespace FileSys {
24class ContentProvider;
25}
26
23namespace Loader { 27namespace Loader {
24enum class ResultStatus : u16; 28enum class ResultStatus : u16;
25} 29}
@@ -252,7 +256,7 @@ public:
252 256
253 bool BaseDeriveNecessary() const; 257 bool BaseDeriveNecessary() const;
254 void DeriveBase(); 258 void DeriveBase();
255 void DeriveETicket(PartitionDataManager& data); 259 void DeriveETicket(PartitionDataManager& data, const FileSys::ContentProvider& provider);
256 void PopulateTickets(); 260 void PopulateTickets();
257 void SynthesizeTickets(); 261 void SynthesizeTickets();
258 262
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index e04a54c3c..7c6304ff0 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -4,10 +4,10 @@
4 4
5#include <fmt/format.h> 5#include <fmt/format.h>
6#include "common/file_util.h" 6#include "common/file_util.h"
7#include "core/core.h"
8#include "core/file_sys/bis_factory.h" 7#include "core/file_sys/bis_factory.h"
9#include "core/file_sys/mode.h" 8#include "core/file_sys/mode.h"
10#include "core/file_sys/registered_cache.h" 9#include "core/file_sys/registered_cache.h"
10#include "core/file_sys/vfs.h"
11 11
12namespace FileSys { 12namespace FileSys {
13 13
@@ -81,11 +81,11 @@ VirtualDir BISFactory::OpenPartition(BisPartitionId id) const {
81 } 81 }
82} 82}
83 83
84VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const { 84VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id,
85 VirtualFilesystem file_system) const {
85 auto& keys = Core::Crypto::KeyManager::Instance(); 86 auto& keys = Core::Crypto::KeyManager::Instance();
86 Core::Crypto::PartitionDataManager pdm{ 87 Core::Crypto::PartitionDataManager pdm{file_system->OpenDirectory(
87 Core::System::GetInstance().GetFilesystem()->OpenDirectory( 88 Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)};
88 Common::FS::GetUserPath(Common::FS::UserPath::SysDataDir), Mode::Read)};
89 keys.PopulateFromPartitionData(pdm); 89 keys.PopulateFromPartitionData(pdm);
90 90
91 switch (id) { 91 switch (id) {
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 438d3f8d8..136485881 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -52,7 +52,7 @@ public:
52 VirtualDir GetModificationDumpRoot(u64 title_id) const; 52 VirtualDir GetModificationDumpRoot(u64 title_id) const;
53 53
54 VirtualDir OpenPartition(BisPartitionId id) const; 54 VirtualDir OpenPartition(BisPartitionId id) const;
55 VirtualFile OpenPartitionStorage(BisPartitionId id) const; 55 VirtualFile OpenPartitionStorage(BisPartitionId id, VirtualFilesystem file_system) const;
56 56
57 VirtualDir GetImageDirectory() const; 57 VirtualDir GetImageDirectory() const;
58 58
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 9ab86e35b..403c4219a 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -83,7 +83,7 @@ enum class Language : u8 {
83 Italian = 7, 83 Italian = 7,
84 Dutch = 8, 84 Dutch = 8,
85 CanadianFrench = 9, 85 CanadianFrench = 9,
86 Portugese = 10, 86 Portuguese = 10,
87 Russian = 11, 87 Russian = 11,
88 Korean = 12, 88 Korean = 12,
89 Taiwanese = 13, 89 Taiwanese = 13,
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
index fe7375e84..5990a2fd5 100644
--- a/src/core/file_sys/nca_patch.cpp
+++ b/src/core/file_sys/nca_patch.cpp
@@ -12,6 +12,49 @@
12#include "core/file_sys/nca_patch.h" 12#include "core/file_sys/nca_patch.h"
13 13
14namespace FileSys { 14namespace FileSys {
15namespace {
16template <bool Subsection, typename BlockType, typename BucketType>
17std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block,
18 const BucketType& buckets) {
19 if constexpr (Subsection) {
20 const auto& last_bucket = buckets[block.number_buckets - 1];
21 if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) {
22 return {block.number_buckets - 1, last_bucket.number_entries};
23 }
24 } else {
25 ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
26 }
27
28 std::size_t bucket_id = std::count_if(
29 block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
30 [&offset](u64 base_offset) { return base_offset <= offset; });
31
32 const auto& bucket = buckets[bucket_id];
33
34 if (bucket.number_entries == 1) {
35 return {bucket_id, 0};
36 }
37
38 std::size_t low = 0;
39 std::size_t mid = 0;
40 std::size_t high = bucket.number_entries - 1;
41 while (low <= high) {
42 mid = (low + high) / 2;
43 if (bucket.entries[mid].address_patch > offset) {
44 high = mid - 1;
45 } else {
46 if (mid == bucket.number_entries - 1 ||
47 bucket.entries[mid + 1].address_patch > offset) {
48 return {bucket_id, mid};
49 }
50
51 low = mid + 1;
52 }
53 }
54
55 UNREACHABLE_MSG("Offset could not be found in BKTR block.");
56}
57} // Anonymous namespace
15 58
16BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, 59BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
17 std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, 60 std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
@@ -110,46 +153,6 @@ std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const {
110 return raw_read; 153 return raw_read;
111} 154}
112 155
113template <bool Subsection, typename BlockType, typename BucketType>
114std::pair<std::size_t, std::size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block,
115 BucketType buckets) const {
116 if constexpr (Subsection) {
117 const auto last_bucket = buckets[block.number_buckets - 1];
118 if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch)
119 return {block.number_buckets - 1, last_bucket.number_entries};
120 } else {
121 ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
122 }
123
124 std::size_t bucket_id = std::count_if(
125 block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
126 [&offset](u64 base_offset) { return base_offset <= offset; });
127
128 const auto bucket = buckets[bucket_id];
129
130 if (bucket.number_entries == 1)
131 return {bucket_id, 0};
132
133 std::size_t low = 0;
134 std::size_t mid = 0;
135 std::size_t high = bucket.number_entries - 1;
136 while (low <= high) {
137 mid = (low + high) / 2;
138 if (bucket.entries[mid].address_patch > offset) {
139 high = mid - 1;
140 } else {
141 if (mid == bucket.number_entries - 1 ||
142 bucket.entries[mid + 1].address_patch > offset) {
143 return {bucket_id, mid};
144 }
145
146 low = mid + 1;
147 }
148 }
149
150 UNREACHABLE_MSG("Offset could not be found in BKTR block.");
151}
152
153RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { 156RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
154 const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); 157 const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
155 return relocation_buckets[res.first].entries[res.second]; 158 return relocation_buckets[res.first].entries[res.second];
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h
index 8e64e8378..60c544f8e 100644
--- a/src/core/file_sys/nca_patch.h
+++ b/src/core/file_sys/nca_patch.h
@@ -117,10 +117,6 @@ public:
117 bool Rename(std::string_view name) override; 117 bool Rename(std::string_view name) override;
118 118
119private: 119private:
120 template <bool Subsection, typename BlockType, typename BucketType>
121 std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, BlockType block,
122 BucketType buckets) const;
123
124 RelocationEntry GetRelocationEntry(u64 offset) const; 120 RelocationEntry GetRelocationEntry(u64 offset) const;
125 RelocationEntry GetNextRelocationEntry(u64 offset) const; 121 RelocationEntry GetNextRelocationEntry(u64 offset) const;
126 122
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index c228d253e..b9c09b456 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -27,6 +27,7 @@
27#include "core/settings.h" 27#include "core/settings.h"
28 28
29namespace FileSys { 29namespace FileSys {
30namespace {
30 31
31constexpr u64 SINGLE_BYTE_MODULUS = 0x100; 32constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
32constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; 33constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
@@ -36,19 +37,28 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
36 "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9", 37 "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
37}; 38};
38 39
39std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { 40enum class TitleVersionFormat : u8 {
41 ThreeElements, ///< vX.Y.Z
42 FourElements, ///< vX.Y.Z.W
43};
44
45std::string FormatTitleVersion(u32 version,
46 TitleVersionFormat format = TitleVersionFormat::ThreeElements) {
40 std::array<u8, sizeof(u32)> bytes{}; 47 std::array<u8, sizeof(u32)> bytes{};
41 bytes[0] = version % SINGLE_BYTE_MODULUS; 48 bytes[0] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
42 for (std::size_t i = 1; i < bytes.size(); ++i) { 49 for (std::size_t i = 1; i < bytes.size(); ++i) {
43 version /= SINGLE_BYTE_MODULUS; 50 version /= SINGLE_BYTE_MODULUS;
44 bytes[i] = version % SINGLE_BYTE_MODULUS; 51 bytes[i] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
45 } 52 }
46 53
47 if (format == TitleVersionFormat::FourElements) 54 if (format == TitleVersionFormat::FourElements) {
48 return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]); 55 return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
56 }
49 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); 57 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
50} 58}
51 59
60// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
61// doesn't have a directory with name.
52VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) { 62VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
53#ifdef _WIN32 63#ifdef _WIN32
54 return dir->GetSubdirectory(name); 64 return dir->GetSubdirectory(name);
@@ -65,6 +75,43 @@ VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name)
65#endif 75#endif
66} 76}
67 77
78std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
79 u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) {
80 const auto build_id_raw = Common::HexToString(build_id_, upper);
81 const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
82 const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
83
84 if (file == nullptr) {
85 LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
86 title_id, build_id);
87 return std::nullopt;
88 }
89
90 std::vector<u8> data(file->GetSize());
91 if (file->Read(data.data(), data.size()) != data.size()) {
92 LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
93 title_id, build_id);
94 return std::nullopt;
95 }
96
97 const Core::Memory::TextCheatParser parser;
98 return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
99}
100
101void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
102 if (to.empty()) {
103 to += with;
104 } else {
105 to += ", ";
106 to += with;
107 }
108}
109
110bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
111 return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
112}
113} // Anonymous namespace
114
68PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} 115PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
69 116
70PatchManager::~PatchManager() = default; 117PatchManager::~PatchManager() = default;
@@ -245,7 +292,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
245 return out; 292 return out;
246} 293}
247 294
248bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { 295bool PatchManager::HasNSOPatch(const BuildID& build_id_) const {
249 const auto build_id_raw = Common::HexToString(build_id_); 296 const auto build_id_raw = Common::HexToString(build_id_);
250 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); 297 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
251 298
@@ -265,36 +312,8 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
265 return !CollectPatches(patch_dirs, build_id).empty(); 312 return !CollectPatches(patch_dirs, build_id).empty();
266} 313}
267 314
268namespace {
269std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
270 const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
271 const VirtualDir& base_path, bool upper) {
272 const auto build_id_raw = Common::HexToString(build_id_, upper);
273 const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
274 const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
275
276 if (file == nullptr) {
277 LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
278 title_id, build_id);
279 return std::nullopt;
280 }
281
282 std::vector<u8> data(file->GetSize());
283 if (file->Read(data.data(), data.size()) != data.size()) {
284 LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
285 title_id, build_id);
286 return std::nullopt;
287 }
288
289 Core::Memory::TextCheatParser parser;
290 return parser.Parse(system,
291 std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
292}
293
294} // Anonymous namespace
295
296std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList( 315std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
297 const Core::System& system, const std::array<u8, 32>& build_id_) const { 316 const Core::System& system, const BuildID& build_id_) const {
298 const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id); 317 const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
299 if (load_dir == nullptr) { 318 if (load_dir == nullptr) {
300 LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); 319 LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
@@ -314,14 +333,12 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
314 333
315 auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats"); 334 auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats");
316 if (cheats_dir != nullptr) { 335 if (cheats_dir != nullptr) {
317 auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); 336 if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
318 if (res.has_value()) {
319 std::copy(res->begin(), res->end(), std::back_inserter(out)); 337 std::copy(res->begin(), res->end(), std::back_inserter(out));
320 continue; 338 continue;
321 } 339 }
322 340
323 res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); 341 if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
324 if (res.has_value()) {
325 std::copy(res->begin(), res->end(), std::back_inserter(out)); 342 std::copy(res->begin(), res->end(), std::back_inserter(out));
326 } 343 }
327 } 344 }
@@ -435,21 +452,11 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
435 return romfs; 452 return romfs;
436} 453}
437 454
438static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) { 455PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const {
439 if (to.empty()) 456 if (title_id == 0) {
440 to += with;
441 else
442 to += ", " + with;
443}
444
445static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
446 return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
447}
448
449std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
450 VirtualFile update_raw) const {
451 if (title_id == 0)
452 return {}; 457 return {};
458 }
459
453 std::map<std::string, std::string, std::less<>> out; 460 std::map<std::string, std::string, std::less<>> out;
454 const auto& installed = Core::System::GetInstance().GetContentProvider(); 461 const auto& installed = Core::System::GetInstance().GetContentProvider();
455 const auto& disabled = Settings::values.disabled_addons[title_id]; 462 const auto& disabled = Settings::values.disabled_addons[title_id];
@@ -472,8 +479,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
472 if (meta_ver.value_or(0) == 0) { 479 if (meta_ver.value_or(0) == 0) {
473 out.insert_or_assign(update_label, ""); 480 out.insert_or_assign(update_label, "");
474 } else { 481 } else {
475 out.insert_or_assign( 482 out.insert_or_assign(update_label, FormatTitleVersion(*meta_ver));
476 update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
477 } 483 }
478 } else if (update_raw != nullptr) { 484 } else if (update_raw != nullptr) {
479 out.insert_or_assign(update_label, "PACKED"); 485 out.insert_or_assign(update_label, "PACKED");
@@ -562,40 +568,46 @@ std::optional<u32> PatchManager::GetGameVersion() const {
562 return installed.GetEntryVersion(title_id); 568 return installed.GetEntryVersion(title_id);
563} 569}
564 570
565std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { 571PatchManager::Metadata PatchManager::GetControlMetadata() const {
566 const auto& installed = Core::System::GetInstance().GetContentProvider(); 572 const auto& installed = Core::System::GetInstance().GetContentProvider();
567 573
568 const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control); 574 const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
569 if (base_control_nca == nullptr) 575 if (base_control_nca == nullptr) {
570 return {}; 576 return {};
577 }
571 578
572 return ParseControlNCA(*base_control_nca); 579 return ParseControlNCA(*base_control_nca);
573} 580}
574 581
575std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const { 582PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
576 const auto base_romfs = nca.GetRomFS(); 583 const auto base_romfs = nca.GetRomFS();
577 if (base_romfs == nullptr) 584 if (base_romfs == nullptr) {
578 return {}; 585 return {};
586 }
579 587
580 const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); 588 const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
581 if (romfs == nullptr) 589 if (romfs == nullptr) {
582 return {}; 590 return {};
591 }
583 592
584 const auto extracted = ExtractRomFS(romfs); 593 const auto extracted = ExtractRomFS(romfs);
585 if (extracted == nullptr) 594 if (extracted == nullptr) {
586 return {}; 595 return {};
596 }
587 597
588 auto nacp_file = extracted->GetFile("control.nacp"); 598 auto nacp_file = extracted->GetFile("control.nacp");
589 if (nacp_file == nullptr) 599 if (nacp_file == nullptr) {
590 nacp_file = extracted->GetFile("Control.nacp"); 600 nacp_file = extracted->GetFile("Control.nacp");
601 }
591 602
592 auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file); 603 auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
593 604
594 VirtualFile icon_file; 605 VirtualFile icon_file;
595 for (const auto& language : FileSys::LANGUAGE_NAMES) { 606 for (const auto& language : FileSys::LANGUAGE_NAMES) {
596 icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat"); 607 icon_file = extracted->GetFile(std::string("icon_").append(language).append(".dat"));
597 if (icon_file != nullptr) 608 if (icon_file != nullptr) {
598 break; 609 break;
610 }
599 } 611 }
600 612
601 return {std::move(nacp), icon_file}; 613 return {std::move(nacp), icon_file};
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 532f4995f..1f28c6241 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -22,70 +22,62 @@ namespace FileSys {
22class NCA; 22class NCA;
23class NACP; 23class NACP;
24 24
25enum class TitleVersionFormat : u8 {
26 ThreeElements, ///< vX.Y.Z
27 FourElements, ///< vX.Y.Z.W
28};
29
30std::string FormatTitleVersion(u32 version,
31 TitleVersionFormat format = TitleVersionFormat::ThreeElements);
32
33// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
34// doesn't have a directory with name.
35VirtualDir FindSubdirectoryCaseless(VirtualDir dir, std::string_view name);
36
37// A centralized class to manage patches to games. 25// A centralized class to manage patches to games.
38class PatchManager { 26class PatchManager {
39public: 27public:
28 using BuildID = std::array<u8, 0x20>;
29 using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>;
30 using PatchVersionNames = std::map<std::string, std::string, std::less<>>;
31
40 explicit PatchManager(u64 title_id); 32 explicit PatchManager(u64 title_id);
41 ~PatchManager(); 33 ~PatchManager();
42 34
43 u64 GetTitleID() const; 35 [[nodiscard]] u64 GetTitleID() const;
44 36
45 // Currently tracked ExeFS patches: 37 // Currently tracked ExeFS patches:
46 // - Game Updates 38 // - Game Updates
47 VirtualDir PatchExeFS(VirtualDir exefs) const; 39 [[nodiscard]] VirtualDir PatchExeFS(VirtualDir exefs) const;
48 40
49 // Currently tracked NSO patches: 41 // Currently tracked NSO patches:
50 // - IPS 42 // - IPS
51 // - IPSwitch 43 // - IPSwitch
52 std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const; 44 [[nodiscard]] std::vector<u8> PatchNSO(const std::vector<u8>& nso,
45 const std::string& name) const;
53 46
54 // Checks to see if PatchNSO() will have any effect given the NSO's build ID. 47 // Checks to see if PatchNSO() will have any effect given the NSO's build ID.
55 // Used to prevent expensive copies in NSO loader. 48 // Used to prevent expensive copies in NSO loader.
56 bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; 49 [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const;
57 50
58 // Creates a CheatList object with all 51 // Creates a CheatList object with all
59 std::vector<Core::Memory::CheatEntry> CreateCheatList( 52 [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
60 const Core::System& system, const std::array<u8, 0x20>& build_id) const; 53 const Core::System& system, const BuildID& build_id) const;
61 54
62 // Currently tracked RomFS patches: 55 // Currently tracked RomFS patches:
63 // - Game Updates 56 // - Game Updates
64 // - LayeredFS 57 // - LayeredFS
65 VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, 58 [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
66 ContentRecordType type = ContentRecordType::Program, 59 ContentRecordType type = ContentRecordType::Program,
67 VirtualFile update_raw = nullptr) const; 60 VirtualFile update_raw = nullptr) const;
68 61
69 // Returns a vector of pairs between patch names and patch versions. 62 // Returns a vector of pairs between patch names and patch versions.
70 // i.e. Update 3.2.2 will return {"Update", "3.2.2"} 63 // i.e. Update 3.2.2 will return {"Update", "3.2.2"}
71 std::map<std::string, std::string, std::less<>> GetPatchVersionNames( 64 [[nodiscard]] PatchVersionNames GetPatchVersionNames(VirtualFile update_raw = nullptr) const;
72 VirtualFile update_raw = nullptr) const;
73 65
74 // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails, 66 // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
75 // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be 67 // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
76 // std::nullopt 68 // std::nullopt
77 std::optional<u32> GetGameVersion() const; 69 [[nodiscard]] std::optional<u32> GetGameVersion() const;
78 70
79 // Given title_id of the program, attempts to get the control data of the update and parse 71 // Given title_id of the program, attempts to get the control data of the update and parse
80 // it, falling back to the base control data. 72 // it, falling back to the base control data.
81 std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const; 73 [[nodiscard]] Metadata GetControlMetadata() const;
82 74
83 // Version of GetControlMetadata that takes an arbitrary NCA 75 // Version of GetControlMetadata that takes an arbitrary NCA
84 std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const; 76 [[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
85 77
86private: 78private:
87 std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, 79 [[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
88 const std::string& build_id) const; 80 const std::string& build_id) const;
89 81
90 u64 title_id; 82 u64 title_id;
91}; 83};
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 418a39a7e..e967a254e 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -6,7 +6,6 @@
6#include "common/assert.h" 6#include "common/assert.h"
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "core/core.h"
10#include "core/file_sys/card_image.h" 9#include "core/file_sys/card_image.h"
11#include "core/file_sys/content_archive.h" 10#include "core/file_sys/content_archive.h"
12#include "core/file_sys/nca_metadata.h" 11#include "core/file_sys/nca_metadata.h"
@@ -19,7 +18,9 @@
19 18
20namespace FileSys { 19namespace FileSys {
21 20
22RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) { 21RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider,
22 Service::FileSystem::FileSystemController& controller)
23 : content_provider{provider}, filesystem_controller{controller} {
23 // Load the RomFS from the app 24 // Load the RomFS from the app
24 if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) { 25 if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
25 LOG_ERROR(Service_FS, "Unable to read RomFS!"); 26 LOG_ERROR(Service_FS, "Unable to read RomFS!");
@@ -46,39 +47,38 @@ ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_titl
46 47
47ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, 48ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage,
48 ContentRecordType type) const { 49 ContentRecordType type) const {
49 std::shared_ptr<NCA> res; 50 const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type);
50
51 switch (storage) {
52 case StorageId::None:
53 res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
54 break;
55 case StorageId::NandSystem:
56 res =
57 Core::System::GetInstance().GetFileSystemController().GetSystemNANDContents()->GetEntry(
58 title_id, type);
59 break;
60 case StorageId::NandUser:
61 res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->GetEntry(
62 title_id, type);
63 break;
64 case StorageId::SdCard:
65 res = Core::System::GetInstance().GetFileSystemController().GetSDMCContents()->GetEntry(
66 title_id, type);
67 break;
68 default:
69 UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
70 }
71
72 if (res == nullptr) { 51 if (res == nullptr) {
73 // TODO(DarkLordZach): Find the right error code to use here 52 // TODO(DarkLordZach): Find the right error code to use here
74 return RESULT_UNKNOWN; 53 return RESULT_UNKNOWN;
75 } 54 }
55
76 const auto romfs = res->GetRomFS(); 56 const auto romfs = res->GetRomFS();
77 if (romfs == nullptr) { 57 if (romfs == nullptr) {
78 // TODO(DarkLordZach): Find the right error code to use here 58 // TODO(DarkLordZach): Find the right error code to use here
79 return RESULT_UNKNOWN; 59 return RESULT_UNKNOWN;
80 } 60 }
61
81 return MakeResult<VirtualFile>(romfs); 62 return MakeResult<VirtualFile>(romfs);
82} 63}
83 64
65std::shared_ptr<NCA> RomFSFactory::GetEntry(u64 title_id, StorageId storage,
66 ContentRecordType type) const {
67 switch (storage) {
68 case StorageId::None:
69 return content_provider.GetEntry(title_id, type);
70 case StorageId::NandSystem:
71 return filesystem_controller.GetSystemNANDContents()->GetEntry(title_id, type);
72 case StorageId::NandUser:
73 return filesystem_controller.GetUserNANDContents()->GetEntry(title_id, type);
74 case StorageId::SdCard:
75 return filesystem_controller.GetSDMCContents()->GetEntry(title_id, type);
76 case StorageId::Host:
77 case StorageId::GameCard:
78 default:
79 UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
80 return nullptr;
81 }
82}
83
84} // namespace FileSys 84} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index c5d40285c..ec704dfa8 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -13,8 +13,15 @@ namespace Loader {
13class AppLoader; 13class AppLoader;
14} // namespace Loader 14} // namespace Loader
15 15
16namespace Service::FileSystem {
17class FileSystemController;
18}
19
16namespace FileSys { 20namespace FileSys {
17 21
22class ContentProvider;
23class NCA;
24
18enum class ContentRecordType : u8; 25enum class ContentRecordType : u8;
19 26
20enum class StorageId : u8 { 27enum class StorageId : u8 {
@@ -29,18 +36,26 @@ enum class StorageId : u8 {
29/// File system interface to the RomFS archive 36/// File system interface to the RomFS archive
30class RomFSFactory { 37class RomFSFactory {
31public: 38public:
32 explicit RomFSFactory(Loader::AppLoader& app_loader); 39 explicit RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provider,
40 Service::FileSystem::FileSystemController& controller);
33 ~RomFSFactory(); 41 ~RomFSFactory();
34 42
35 void SetPackedUpdate(VirtualFile update_raw); 43 void SetPackedUpdate(VirtualFile update_raw);
36 ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const; 44 [[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const;
37 ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type) const; 45 [[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage,
46 ContentRecordType type) const;
38 47
39private: 48private:
49 [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage,
50 ContentRecordType type) const;
51
40 VirtualFile file; 52 VirtualFile file;
41 VirtualFile update_raw; 53 VirtualFile update_raw;
42 bool updatable; 54 bool updatable;
43 u64 ivfc_offset; 55 u64 ivfc_offset;
56
57 ContentProvider& content_provider;
58 Service::FileSystem::FileSystemController& filesystem_controller;
44}; 59};
45 60
46} // namespace FileSys 61} // namespace FileSys
diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp
new file mode 100644
index 000000000..4505da758
--- /dev/null
+++ b/src/core/frontend/applets/controller.cpp
@@ -0,0 +1,81 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/assert.h"
6#include "common/logging/log.h"
7#include "core/core.h"
8#include "core/frontend/applets/controller.h"
9#include "core/hle/service/hid/controllers/npad.h"
10#include "core/hle/service/hid/hid.h"
11#include "core/hle/service/sm/sm.h"
12
13namespace Core::Frontend {
14
15ControllerApplet::~ControllerApplet() = default;
16
17DefaultControllerApplet::~DefaultControllerApplet() = default;
18
19void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callback,
20 ControllerParameters parameters) const {
21 LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
22
23 auto& npad =
24 Core::System::GetInstance()
25 .ServiceManager()
26 .GetService<Service::HID::Hid>("hid")
27 ->GetAppletResource()
28 ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
29
30 auto& players = Settings::values.players;
31
32 const std::size_t min_supported_players =
33 parameters.enable_single_mode ? 1 : parameters.min_players;
34
35 // Disconnect Handheld first.
36 npad.DisconnectNPadAtIndex(8);
37
38 // Deduce the best configuration based on the input parameters.
39 for (std::size_t index = 0; index < players.size() - 2; ++index) {
40 // First, disconnect all controllers regardless of the value of keep_controllers_connected.
41 // This makes it easy to connect the desired controllers.
42 npad.DisconnectNPadAtIndex(index);
43
44 // Only connect the minimum number of required players.
45 if (index >= min_supported_players) {
46 continue;
47 }
48
49 // Connect controllers based on the following priority list from highest to lowest priority:
50 // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
51 if (parameters.allow_pro_controller) {
52 npad.AddNewControllerAt(
53 npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index);
54 } else if (parameters.allow_dual_joycons) {
55 npad.AddNewControllerAt(
56 npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index);
57 } else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
58 // Assign left joycons to even player indices and right joycons to odd player indices.
59 // We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
60 // a right Joycon for Player 2 in 2 Player Assist mode.
61 if (index % 2 == 0) {
62 npad.AddNewControllerAt(
63 npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index);
64 } else {
65 npad.AddNewControllerAt(
66 npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
67 }
68 } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
69 !Settings::values.use_docked_mode) {
70 // We should *never* reach here under any normal circumstances.
71 npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
72 index);
73 } else {
74 UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
75 }
76 }
77
78 callback();
79}
80
81} // namespace Core::Frontend
diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h
new file mode 100644
index 000000000..a227f15cd
--- /dev/null
+++ b/src/core/frontend/applets/controller.h
@@ -0,0 +1,48 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8
9#include "common/common_types.h"
10
11namespace Core::Frontend {
12
13using BorderColor = std::array<u8, 4>;
14using ExplainText = std::array<char, 0x81>;
15
16struct ControllerParameters {
17 s8 min_players{};
18 s8 max_players{};
19 bool keep_controllers_connected{};
20 bool enable_single_mode{};
21 bool enable_border_color{};
22 std::vector<BorderColor> border_colors{};
23 bool enable_explain_text{};
24 std::vector<ExplainText> explain_text{};
25 bool allow_pro_controller{};
26 bool allow_handheld{};
27 bool allow_dual_joycons{};
28 bool allow_left_joycon{};
29 bool allow_right_joycon{};
30};
31
32class ControllerApplet {
33public:
34 virtual ~ControllerApplet();
35
36 virtual void ReconfigureControllers(std::function<void()> callback,
37 ControllerParameters parameters) const = 0;
38};
39
40class DefaultControllerApplet final : public ControllerApplet {
41public:
42 ~DefaultControllerApplet() override;
43
44 void ReconfigureControllers(std::function<void()> callback,
45 ControllerParameters parameters) const override;
46};
47
48} // namespace Core::Frontend
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
index 2b098b7c6..9da0d2829 100644
--- a/src/core/frontend/input.h
+++ b/src/core/frontend/input.h
@@ -119,11 +119,11 @@ using ButtonDevice = InputDevice<bool>;
119using AnalogDevice = InputDevice<std::tuple<float, float>>; 119using AnalogDevice = InputDevice<std::tuple<float, float>>;
120 120
121/** 121/**
122 * A motion device is an input device that returns a tuple of accelerometer state vector and 122 * A motion status is an object that returns a tuple of accelerometer state vector,
123 * gyroscope state vector. 123 * gyroscope state vector, rotation state vector and orientation state matrix.
124 * 124 *
125 * For both vectors: 125 * For both vectors:
126 * x+ is the same direction as LEFT on D-pad. 126 * x+ is the same direction as RIGHT on D-pad.
127 * y+ is normal to the touch screen, pointing outward. 127 * y+ is normal to the touch screen, pointing outward.
128 * z+ is the same direction as UP on D-pad. 128 * z+ is the same direction as UP on D-pad.
129 * 129 *
@@ -133,8 +133,22 @@ using AnalogDevice = InputDevice<std::tuple<float, float>>;
133 * For gyroscope state vector: 133 * For gyroscope state vector:
134 * Orientation is determined by right-hand rule. 134 * Orientation is determined by right-hand rule.
135 * Units: deg/sec 135 * Units: deg/sec
136 *
137 * For rotation state vector
138 * Units: rotations
139 *
140 * For orientation state matrix
141 * x vector
142 * y vector
143 * z vector
144 */
145using MotionStatus = std::tuple<Common::Vec3<float>, Common::Vec3<float>, Common::Vec3<float>,
146 std::array<Common::Vec3f, 3>>;
147
148/**
149 * A motion device is an input device that returns a motion status object
136 */ 150 */
137using MotionDevice = InputDevice<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>; 151using MotionDevice = InputDevice<MotionStatus>;
138 152
139/** 153/**
140 * A touch device is an input device that returns a tuple of two floats and a bool. The floats are 154 * A touch device is an input device that returns a tuple of two floats and a bool. The floats are
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 5ab204b9b..be9eba519 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -48,14 +48,15 @@ ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kern
48} 48}
49 49
50ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread, 50ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread,
51 Core::Memory::Memory& memory) { 51 Core::Memory::Memory& memory,
52 Core::Timing::CoreTiming& core_timing) {
52 // Keep ServerSession alive until we're done working with it. 53 // Keep ServerSession alive until we're done working with it.
53 if (!parent->Server()) { 54 if (!parent->Server()) {
54 return ERR_SESSION_CLOSED_BY_REMOTE; 55 return ERR_SESSION_CLOSED_BY_REMOTE;
55 } 56 }
56 57
57 // Signal the server session that new data is available 58 // Signal the server session that new data is available
58 return parent->Server()->HandleSyncRequest(std::move(thread), memory); 59 return parent->Server()->HandleSyncRequest(std::move(thread), memory, core_timing);
59} 60}
60 61
61} // namespace Kernel 62} // namespace Kernel
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index c5f760d7d..e5e0690c2 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -16,6 +16,10 @@ namespace Core::Memory {
16class Memory; 16class Memory;
17} 17}
18 18
19namespace Core::Timing {
20class CoreTiming;
21}
22
19namespace Kernel { 23namespace Kernel {
20 24
21class KernelCore; 25class KernelCore;
@@ -42,7 +46,8 @@ public:
42 return HANDLE_TYPE; 46 return HANDLE_TYPE;
43 } 47 }
44 48
45 ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory); 49 ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory,
50 Core::Timing::CoreTiming& core_timing);
46 51
47 bool ShouldWait(const Thread* thread) const override; 52 bool ShouldWait(const Thread* thread) const override;
48 53
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index 36e3c26fb..b6f04dcea 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -188,7 +188,7 @@ private:
188 188
189 /// Scheduler lock mechanisms. 189 /// Scheduler lock mechanisms.
190 bool is_locked{}; 190 bool is_locked{};
191 Common::SpinLock inner_lock{}; 191 std::mutex inner_lock;
192 std::atomic<s64> scope_lock{}; 192 std::atomic<s64> scope_lock{};
193 Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()}; 193 Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()};
194 194
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 7e6391c6c..8c19f2534 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -8,7 +8,6 @@
8#include "common/assert.h" 8#include "common/assert.h"
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/core.h"
12#include "core/core_timing.h" 11#include "core/core_timing.h"
13#include "core/hle/ipc_helpers.h" 12#include "core/hle/ipc_helpers.h"
14#include "core/hle/kernel/client_port.h" 13#include "core/hle/kernel/client_port.h"
@@ -185,10 +184,11 @@ ResultCode ServerSession::CompleteSyncRequest() {
185} 184}
186 185
187ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread, 186ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
188 Core::Memory::Memory& memory) { 187 Core::Memory::Memory& memory,
188 Core::Timing::CoreTiming& core_timing) {
189 const ResultCode result = QueueSyncRequest(std::move(thread), memory); 189 const ResultCode result = QueueSyncRequest(std::move(thread), memory);
190 const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000}; 190 const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000};
191 Core::System::GetInstance().CoreTiming().ScheduleEvent(delay, request_event, {}); 191 core_timing.ScheduleEvent(delay, request_event, {});
192 return result; 192 return result;
193} 193}
194 194
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 403aaf10b..d23e9ec68 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -18,8 +18,9 @@ class Memory;
18} 18}
19 19
20namespace Core::Timing { 20namespace Core::Timing {
21class CoreTiming;
21struct EventType; 22struct EventType;
22} 23} // namespace Core::Timing
23 24
24namespace Kernel { 25namespace Kernel {
25 26
@@ -87,12 +88,14 @@ public:
87 /** 88 /**
88 * Handle a sync request from the emulated application. 89 * Handle a sync request from the emulated application.
89 * 90 *
90 * @param thread Thread that initiated the request. 91 * @param thread Thread that initiated the request.
91 * @param memory Memory context to handle the sync request under. 92 * @param memory Memory context to handle the sync request under.
93 * @param core_timing Core timing context to schedule the request event under.
92 * 94 *
93 * @returns ResultCode from the operation. 95 * @returns ResultCode from the operation.
94 */ 96 */
95 ResultCode HandleSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory); 97 ResultCode HandleSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory,
98 Core::Timing::CoreTiming& core_timing);
96 99
97 bool ShouldWait(const Thread* thread) const override; 100 bool ShouldWait(const Thread* thread) const override;
98 101
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 01ae57053..bafd1ced7 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -346,7 +346,7 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
346 SchedulerLock lock(system.Kernel()); 346 SchedulerLock lock(system.Kernel());
347 thread->InvalidateHLECallback(); 347 thread->InvalidateHLECallback();
348 thread->SetStatus(ThreadStatus::WaitIPC); 348 thread->SetStatus(ThreadStatus::WaitIPC);
349 session->SendSyncRequest(SharedFrom(thread), system.Memory()); 349 session->SendSyncRequest(SharedFrom(thread), system.Memory(), system.CoreTiming());
350 } 350 }
351 351
352 if (thread->HasHLECallback()) { 352 if (thread->HasHLECallback()) {
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 7d92b25a3..d7a81f64a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1192,7 +1192,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1192 {120, nullptr, "ExecuteProgram"}, 1192 {120, nullptr, "ExecuteProgram"},
1193 {121, nullptr, "ClearUserChannel"}, 1193 {121, nullptr, "ClearUserChannel"},
1194 {122, nullptr, "UnpopToUserChannel"}, 1194 {122, nullptr, "UnpopToUserChannel"},
1195 {123, nullptr, "GetPreviousProgramIndex"}, 1195 {123, &IApplicationFunctions::GetPreviousProgramIndex, "GetPreviousProgramIndex"},
1196 {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"}, 1196 {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
1197 {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, 1197 {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"},
1198 {140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"}, 1198 {140, &IApplicationFunctions::GetFriendInvitationStorageChannelEvent, "GetFriendInvitationStorageChannelEvent"},
@@ -1554,6 +1554,14 @@ void IApplicationFunctions::QueryApplicationPlayStatisticsByUid(Kernel::HLEReque
1554 rb.Push<u32>(0); 1554 rb.Push<u32>(0);
1555} 1555}
1556 1556
1557void IApplicationFunctions::GetPreviousProgramIndex(Kernel::HLERequestContext& ctx) {
1558 LOG_WARNING(Service_AM, "(STUBBED) called");
1559
1560 IPC::ResponseBuilder rb{ctx, 3};
1561 rb.Push(RESULT_SUCCESS);
1562 rb.Push<s32>(previous_program_index);
1563}
1564
1557void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) { 1565void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) {
1558 LOG_WARNING(Service_AM, "(STUBBED) called"); 1566 LOG_WARNING(Service_AM, "(STUBBED) called");
1559 1567
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 6e69796ec..bcc06affe 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -288,11 +288,13 @@ private:
288 void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx); 288 void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx);
289 void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx); 289 void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx);
290 void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx); 290 void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx);
291 void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx);
291 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); 292 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
292 void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx); 293 void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx);
293 294
294 bool launch_popped_application_specific = false; 295 bool launch_popped_application_specific = false;
295 bool launch_popped_account_preselect = false; 296 bool launch_popped_account_preselect = false;
297 s32 previous_program_index{-1};
296 Kernel::EventPair gpu_error_detected_event; 298 Kernel::EventPair gpu_error_detected_event;
297 Kernel::EventPair friend_invitation_storage_channel_event; 299 Kernel::EventPair friend_invitation_storage_channel_event;
298 Core::System& system; 300 Core::System& system;
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index c3261f3e6..4e0800f9a 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -5,6 +5,7 @@
5#include <cstring> 5#include <cstring>
6#include "common/assert.h" 6#include "common/assert.h"
7#include "core/core.h" 7#include "core/core.h"
8#include "core/frontend/applets/controller.h"
8#include "core/frontend/applets/error.h" 9#include "core/frontend/applets/error.h"
9#include "core/frontend/applets/general_frontend.h" 10#include "core/frontend/applets/general_frontend.h"
10#include "core/frontend/applets/profile_select.h" 11#include "core/frontend/applets/profile_select.h"
@@ -15,6 +16,7 @@
15#include "core/hle/kernel/writable_event.h" 16#include "core/hle/kernel/writable_event.h"
16#include "core/hle/service/am/am.h" 17#include "core/hle/service/am/am.h"
17#include "core/hle/service/am/applets/applets.h" 18#include "core/hle/service/am/applets/applets.h"
19#include "core/hle/service/am/applets/controller.h"
18#include "core/hle/service/am/applets/error.h" 20#include "core/hle/service/am/applets/error.h"
19#include "core/hle/service/am/applets/general_backend.h" 21#include "core/hle/service/am/applets/general_backend.h"
20#include "core/hle/service/am/applets/profile_select.h" 22#include "core/hle/service/am/applets/profile_select.h"
@@ -140,14 +142,14 @@ void Applet::Initialize() {
140 142
141AppletFrontendSet::AppletFrontendSet() = default; 143AppletFrontendSet::AppletFrontendSet() = default;
142 144
143AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, 145AppletFrontendSet::AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce,
146 ErrorApplet error, ParentalControlsApplet parental_controls,
144 PhotoViewer photo_viewer, ProfileSelect profile_select, 147 PhotoViewer photo_viewer, ProfileSelect profile_select,
145 SoftwareKeyboard software_keyboard, WebBrowser web_browser, 148 SoftwareKeyboard software_keyboard, WebBrowser web_browser)
146 ECommerceApplet e_commerce) 149 : controller{std::move(controller)}, e_commerce{std::move(e_commerce)}, error{std::move(error)},
147 : parental_controls{std::move(parental_controls)}, error{std::move(error)}, 150 parental_controls{std::move(parental_controls)}, photo_viewer{std::move(photo_viewer)},
148 photo_viewer{std::move(photo_viewer)}, profile_select{std::move(profile_select)}, 151 profile_select{std::move(profile_select)}, software_keyboard{std::move(software_keyboard)},
149 software_keyboard{std::move(software_keyboard)}, web_browser{std::move(web_browser)}, 152 web_browser{std::move(web_browser)} {}
150 e_commerce{std::move(e_commerce)} {}
151 153
152AppletFrontendSet::~AppletFrontendSet() = default; 154AppletFrontendSet::~AppletFrontendSet() = default;
153 155
@@ -164,20 +166,37 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
164} 166}
165 167
166void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { 168void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
167 if (set.parental_controls != nullptr) 169 if (set.controller != nullptr) {
168 frontend.parental_controls = std::move(set.parental_controls); 170 frontend.controller = std::move(set.controller);
169 if (set.error != nullptr) 171 }
172
173 if (set.e_commerce != nullptr) {
174 frontend.e_commerce = std::move(set.e_commerce);
175 }
176
177 if (set.error != nullptr) {
170 frontend.error = std::move(set.error); 178 frontend.error = std::move(set.error);
171 if (set.photo_viewer != nullptr) 179 }
180
181 if (set.parental_controls != nullptr) {
182 frontend.parental_controls = std::move(set.parental_controls);
183 }
184
185 if (set.photo_viewer != nullptr) {
172 frontend.photo_viewer = std::move(set.photo_viewer); 186 frontend.photo_viewer = std::move(set.photo_viewer);
173 if (set.profile_select != nullptr) 187 }
188
189 if (set.profile_select != nullptr) {
174 frontend.profile_select = std::move(set.profile_select); 190 frontend.profile_select = std::move(set.profile_select);
175 if (set.software_keyboard != nullptr) 191 }
192
193 if (set.software_keyboard != nullptr) {
176 frontend.software_keyboard = std::move(set.software_keyboard); 194 frontend.software_keyboard = std::move(set.software_keyboard);
177 if (set.web_browser != nullptr) 195 }
196
197 if (set.web_browser != nullptr) {
178 frontend.web_browser = std::move(set.web_browser); 198 frontend.web_browser = std::move(set.web_browser);
179 if (set.e_commerce != nullptr) 199 }
180 frontend.e_commerce = std::move(set.e_commerce);
181} 200}
182 201
183void AppletManager::SetDefaultAppletFrontendSet() { 202void AppletManager::SetDefaultAppletFrontendSet() {
@@ -186,15 +205,23 @@ void AppletManager::SetDefaultAppletFrontendSet() {
186} 205}
187 206
188void AppletManager::SetDefaultAppletsIfMissing() { 207void AppletManager::SetDefaultAppletsIfMissing() {
189 if (frontend.parental_controls == nullptr) { 208 if (frontend.controller == nullptr) {
190 frontend.parental_controls = 209 frontend.controller = std::make_unique<Core::Frontend::DefaultControllerApplet>();
191 std::make_unique<Core::Frontend::DefaultParentalControlsApplet>(); 210 }
211
212 if (frontend.e_commerce == nullptr) {
213 frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
192 } 214 }
193 215
194 if (frontend.error == nullptr) { 216 if (frontend.error == nullptr) {
195 frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>(); 217 frontend.error = std::make_unique<Core::Frontend::DefaultErrorApplet>();
196 } 218 }
197 219
220 if (frontend.parental_controls == nullptr) {
221 frontend.parental_controls =
222 std::make_unique<Core::Frontend::DefaultParentalControlsApplet>();
223 }
224
198 if (frontend.photo_viewer == nullptr) { 225 if (frontend.photo_viewer == nullptr) {
199 frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>(); 226 frontend.photo_viewer = std::make_unique<Core::Frontend::DefaultPhotoViewerApplet>();
200 } 227 }
@@ -211,10 +238,6 @@ void AppletManager::SetDefaultAppletsIfMissing() {
211 if (frontend.web_browser == nullptr) { 238 if (frontend.web_browser == nullptr) {
212 frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>(); 239 frontend.web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
213 } 240 }
214
215 if (frontend.e_commerce == nullptr) {
216 frontend.e_commerce = std::make_unique<Core::Frontend::DefaultECommerceApplet>();
217 }
218} 241}
219 242
220void AppletManager::ClearAll() { 243void AppletManager::ClearAll() {
@@ -225,6 +248,8 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
225 switch (id) { 248 switch (id) {
226 case AppletId::Auth: 249 case AppletId::Auth:
227 return std::make_shared<Auth>(system, *frontend.parental_controls); 250 return std::make_shared<Auth>(system, *frontend.parental_controls);
251 case AppletId::Controller:
252 return std::make_shared<Controller>(system, *frontend.controller);
228 case AppletId::Error: 253 case AppletId::Error:
229 return std::make_shared<Error>(system, *frontend.error); 254 return std::make_shared<Error>(system, *frontend.error);
230 case AppletId::ProfileSelect: 255 case AppletId::ProfileSelect:
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index e75be86a2..a1f4cf897 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -17,6 +17,7 @@ class System;
17} 17}
18 18
19namespace Core::Frontend { 19namespace Core::Frontend {
20class ControllerApplet;
20class ECommerceApplet; 21class ECommerceApplet;
21class ErrorApplet; 22class ErrorApplet;
22class ParentalControlsApplet; 23class ParentalControlsApplet;
@@ -155,19 +156,20 @@ protected:
155}; 156};
156 157
157struct AppletFrontendSet { 158struct AppletFrontendSet {
158 using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>; 159 using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
160 using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
159 using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>; 161 using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
162 using ParentalControlsApplet = std::unique_ptr<Core::Frontend::ParentalControlsApplet>;
160 using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>; 163 using PhotoViewer = std::unique_ptr<Core::Frontend::PhotoViewerApplet>;
161 using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>; 164 using ProfileSelect = std::unique_ptr<Core::Frontend::ProfileSelectApplet>;
162 using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>; 165 using SoftwareKeyboard = std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet>;
163 using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>; 166 using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
164 using ECommerceApplet = std::unique_ptr<Core::Frontend::ECommerceApplet>;
165 167
166 AppletFrontendSet(); 168 AppletFrontendSet();
167 AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, 169 AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, ErrorApplet error,
168 PhotoViewer photo_viewer, ProfileSelect profile_select, 170 ParentalControlsApplet parental_controls, PhotoViewer photo_viewer,
169 SoftwareKeyboard software_keyboard, WebBrowser web_browser, 171 ProfileSelect profile_select, SoftwareKeyboard software_keyboard,
170 ECommerceApplet e_commerce); 172 WebBrowser web_browser);
171 ~AppletFrontendSet(); 173 ~AppletFrontendSet();
172 174
173 AppletFrontendSet(const AppletFrontendSet&) = delete; 175 AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -176,13 +178,14 @@ struct AppletFrontendSet {
176 AppletFrontendSet(AppletFrontendSet&&) noexcept; 178 AppletFrontendSet(AppletFrontendSet&&) noexcept;
177 AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept; 179 AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
178 180
179 ParentalControlsApplet parental_controls; 181 ControllerApplet controller;
182 ECommerceApplet e_commerce;
180 ErrorApplet error; 183 ErrorApplet error;
184 ParentalControlsApplet parental_controls;
181 PhotoViewer photo_viewer; 185 PhotoViewer photo_viewer;
182 ProfileSelect profile_select; 186 ProfileSelect profile_select;
183 SoftwareKeyboard software_keyboard; 187 SoftwareKeyboard software_keyboard;
184 WebBrowser web_browser; 188 WebBrowser web_browser;
185 ECommerceApplet e_commerce;
186}; 189};
187 190
188class AppletManager { 191class AppletManager {
diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp
new file mode 100644
index 000000000..2151da783
--- /dev/null
+++ b/src/core/hle/service/am/applets/controller.cpp
@@ -0,0 +1,210 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <cstring>
7
8#include "common/assert.h"
9#include "common/logging/log.h"
10#include "common/string_util.h"
11#include "core/core.h"
12#include "core/frontend/applets/controller.h"
13#include "core/hle/result.h"
14#include "core/hle/service/am/am.h"
15#include "core/hle/service/am/applets/controller.h"
16#include "core/hle/service/hid/controllers/npad.h"
17
18namespace Service::AM::Applets {
19
20// This error code (0x183ACA) is thrown when the applet fails to initialize.
21[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101};
22// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2.
23[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102};
24
25static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
26 ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
27 std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) {
28 HID::Controller_NPad::NPadType npad_style_set;
29 npad_style_set.raw = private_arg.style_set;
30
31 return {
32 .min_players = std::max(s8(1), header.player_count_min),
33 .max_players = header.player_count_max,
34 .keep_controllers_connected = header.enable_take_over_connection,
35 .enable_single_mode = header.enable_single_mode,
36 .enable_border_color = header.enable_identification_color,
37 .border_colors = identification_colors,
38 .enable_explain_text = enable_text,
39 .explain_text = text,
40 .allow_pro_controller = npad_style_set.pro_controller == 1,
41 .allow_handheld = npad_style_set.handheld == 1,
42 .allow_dual_joycons = npad_style_set.joycon_dual == 1,
43 .allow_left_joycon = npad_style_set.joycon_left == 1,
44 .allow_right_joycon = npad_style_set.joycon_right == 1,
45 };
46}
47
48Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_)
49 : Applet{system_.Kernel()}, frontend(frontend_) {}
50
51Controller::~Controller() = default;
52
53void Controller::Initialize() {
54 Applet::Initialize();
55
56 LOG_INFO(Service_HID, "Initializing Controller Applet.");
57
58 LOG_DEBUG(Service_HID,
59 "Initializing Applet with common_args: arg_version={}, lib_version={}, "
60 "play_startup_sound={}, size={}, system_tick={}, theme_color={}",
61 common_args.arguments_version, common_args.library_version,
62 common_args.play_startup_sound, common_args.size, common_args.system_tick,
63 common_args.theme_color);
64
65 library_applet_version = LibraryAppletVersion{common_args.library_version};
66
67 const auto private_arg_storage = broker.PopNormalDataToApplet();
68 ASSERT(private_arg_storage != nullptr);
69
70 const auto& private_arg = private_arg_storage->GetData();
71 ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate));
72
73 std::memcpy(&controller_private_arg, private_arg.data(), sizeof(ControllerSupportArgPrivate));
74 ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate),
75 "Unknown ControllerSupportArgPrivate revision={} with size={}",
76 library_applet_version, controller_private_arg.arg_private_size);
77
78 switch (controller_private_arg.mode) {
79 case ControllerSupportMode::ShowControllerSupport: {
80 const auto user_arg_storage = broker.PopNormalDataToApplet();
81 ASSERT(user_arg_storage != nullptr);
82
83 const auto& user_arg = user_arg_storage->GetData();
84 switch (library_applet_version) {
85 case LibraryAppletVersion::Version3:
86 case LibraryAppletVersion::Version4:
87 case LibraryAppletVersion::Version5:
88 ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld));
89 std::memcpy(&controller_user_arg_old, user_arg.data(), sizeof(ControllerSupportArgOld));
90 break;
91 case LibraryAppletVersion::Version7:
92 ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew));
93 std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew));
94 break;
95 default:
96 UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}",
97 library_applet_version, controller_private_arg.arg_size);
98 ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew));
99 std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew));
100 break;
101 }
102 break;
103 }
104 case ControllerSupportMode::ShowControllerStrapGuide:
105 case ControllerSupportMode::ShowControllerFirmwareUpdate:
106 default: {
107 UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode);
108 break;
109 }
110 }
111}
112
113bool Controller::TransactionComplete() const {
114 return complete;
115}
116
117ResultCode Controller::GetStatus() const {
118 return status;
119}
120
121void Controller::ExecuteInteractive() {
122 UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
123}
124
125void Controller::Execute() {
126 switch (controller_private_arg.mode) {
127 case ControllerSupportMode::ShowControllerSupport: {
128 const auto parameters = [this] {
129 switch (library_applet_version) {
130 case LibraryAppletVersion::Version3:
131 case LibraryAppletVersion::Version4:
132 case LibraryAppletVersion::Version5:
133 return ConvertToFrontendParameters(
134 controller_private_arg, controller_user_arg_old.header,
135 controller_user_arg_old.enable_explain_text,
136 std::vector<IdentificationColor>(
137 controller_user_arg_old.identification_colors.begin(),
138 controller_user_arg_old.identification_colors.end()),
139 std::vector<ExplainText>(controller_user_arg_old.explain_text.begin(),
140 controller_user_arg_old.explain_text.end()));
141 case LibraryAppletVersion::Version7:
142 default:
143 return ConvertToFrontendParameters(
144 controller_private_arg, controller_user_arg_new.header,
145 controller_user_arg_new.enable_explain_text,
146 std::vector<IdentificationColor>(
147 controller_user_arg_new.identification_colors.begin(),
148 controller_user_arg_new.identification_colors.end()),
149 std::vector<ExplainText>(controller_user_arg_new.explain_text.begin(),
150 controller_user_arg_new.explain_text.end()));
151 }
152 }();
153
154 is_single_mode = parameters.enable_single_mode;
155
156 LOG_DEBUG(Service_HID,
157 "Controller Parameters: min_players={}, max_players={}, "
158 "keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, "
159 "enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, "
160 "allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}",
161 parameters.min_players, parameters.max_players,
162 parameters.keep_controllers_connected, parameters.enable_single_mode,
163 parameters.enable_border_color, parameters.enable_explain_text,
164 parameters.allow_pro_controller, parameters.allow_handheld,
165 parameters.allow_dual_joycons, parameters.allow_left_joycon,
166 parameters.allow_right_joycon);
167
168 frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters);
169 break;
170 }
171 case ControllerSupportMode::ShowControllerStrapGuide:
172 case ControllerSupportMode::ShowControllerFirmwareUpdate:
173 default: {
174 ConfigurationComplete();
175 break;
176 }
177 }
178}
179
180void Controller::ConfigurationComplete() {
181 ControllerSupportResultInfo result_info{};
182
183 const auto& players = Settings::values.players;
184
185 // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
186 // Otherwise, only count connected players from P1-P8.
187 result_info.player_count =
188 is_single_mode ? 1
189 : static_cast<s8>(std::count_if(
190 players.begin(), players.end() - 2,
191 [](Settings::PlayerInput player) { return player.connected; }));
192
193 result_info.selected_id = HID::Controller_NPad::IndexToNPad(
194 std::distance(players.begin(),
195 std::find_if(players.begin(), players.end(),
196 [](Settings::PlayerInput player) { return player.connected; })));
197
198 result_info.result = 0;
199
200 LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}",
201 result_info.player_count, result_info.selected_id, result_info.result);
202
203 complete = true;
204 out_data = std::vector<u8>(sizeof(ControllerSupportResultInfo));
205 std::memcpy(out_data.data(), &result_info, out_data.size());
206 broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(out_data)));
207 broker.SignalStateChanged();
208}
209
210} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h
new file mode 100644
index 000000000..f7bb3fba9
--- /dev/null
+++ b/src/core/hle/service/am/applets/controller.h
@@ -0,0 +1,123 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <vector>
9
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "core/hle/result.h"
13#include "core/hle/service/am/applets/applets.h"
14
15namespace Core {
16class System;
17}
18
19namespace Service::AM::Applets {
20
21using IdentificationColor = std::array<u8, 4>;
22using ExplainText = std::array<char, 0x81>;
23
24enum class LibraryAppletVersion : u32_le {
25 Version3 = 0x3, // 1.0.0 - 2.3.0
26 Version4 = 0x4, // 3.0.0 - 5.1.0
27 Version5 = 0x5, // 6.0.0 - 7.0.1
28 Version7 = 0x7, // 8.0.0+
29};
30
31enum class ControllerSupportMode : u8 {
32 ShowControllerSupport = 0,
33 ShowControllerStrapGuide = 1,
34 ShowControllerFirmwareUpdate = 2,
35};
36
37enum class ControllerSupportCaller : u8 {
38 Application = 0,
39 System = 1,
40};
41
42struct ControllerSupportArgPrivate {
43 u32 arg_private_size{};
44 u32 arg_size{};
45 bool flag_0{};
46 bool flag_1{};
47 ControllerSupportMode mode{};
48 ControllerSupportCaller caller{};
49 u32 style_set{};
50 u32 joy_hold_type{};
51};
52static_assert(sizeof(ControllerSupportArgPrivate) == 0x14,
53 "ControllerSupportArgPrivate has incorrect size.");
54
55struct ControllerSupportArgHeader {
56 s8 player_count_min{};
57 s8 player_count_max{};
58 bool enable_take_over_connection{};
59 bool enable_left_justify{};
60 bool enable_permit_joy_dual{};
61 bool enable_single_mode{};
62 bool enable_identification_color{};
63};
64static_assert(sizeof(ControllerSupportArgHeader) == 0x7,
65 "ControllerSupportArgHeader has incorrect size.");
66
67// LibraryAppletVersion 0x3, 0x4, 0x5
68struct ControllerSupportArgOld {
69 ControllerSupportArgHeader header{};
70 std::array<IdentificationColor, 4> identification_colors{};
71 bool enable_explain_text{};
72 std::array<ExplainText, 4> explain_text{};
73};
74static_assert(sizeof(ControllerSupportArgOld) == 0x21C,
75 "ControllerSupportArgOld has incorrect size.");
76
77// LibraryAppletVersion 0x7
78struct ControllerSupportArgNew {
79 ControllerSupportArgHeader header{};
80 std::array<IdentificationColor, 8> identification_colors{};
81 bool enable_explain_text{};
82 std::array<ExplainText, 8> explain_text{};
83};
84static_assert(sizeof(ControllerSupportArgNew) == 0x430,
85 "ControllerSupportArgNew has incorrect size.");
86
87struct ControllerSupportResultInfo {
88 s8 player_count{};
89 INSERT_PADDING_BYTES(3);
90 u32 selected_id{};
91 u32 result{};
92};
93static_assert(sizeof(ControllerSupportResultInfo) == 0xC,
94 "ControllerSupportResultInfo has incorrect size.");
95
96class Controller final : public Applet {
97public:
98 explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_);
99 ~Controller() override;
100
101 void Initialize() override;
102
103 bool TransactionComplete() const override;
104 ResultCode GetStatus() const override;
105 void ExecuteInteractive() override;
106 void Execute() override;
107
108 void ConfigurationComplete();
109
110private:
111 const Core::Frontend::ControllerApplet& frontend;
112
113 LibraryAppletVersion library_applet_version;
114 ControllerSupportArgPrivate controller_private_arg;
115 ControllerSupportArgOld controller_user_arg_old;
116 ControllerSupportArgNew controller_user_arg_new;
117 bool complete{false};
118 ResultCode status{RESULT_SUCCESS};
119 bool is_single_mode{false};
120 std::vector<u8> out_data;
121};
122
123} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index d8359abaa..a2d3ded7b 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -26,7 +26,7 @@ namespace Service::Audio {
26 26
27class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { 27class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
28public: 28public:
29 explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params, 29 explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params,
30 const std::size_t instance_number) 30 const std::size_t instance_number)
31 : ServiceFramework("IAudioRenderer") { 31 : ServiceFramework("IAudioRenderer") {
32 // clang-format off 32 // clang-format off
@@ -94,14 +94,15 @@ private:
94 void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { 94 void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
95 LOG_DEBUG(Service_Audio, "(STUBBED) called"); 95 LOG_DEBUG(Service_Audio, "(STUBBED) called");
96 96
97 auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer()); 97 std::vector<u8> output_params(ctx.GetWriteBufferSize());
98 auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
98 99
99 if (result.Succeeded()) { 100 if (result.IsSuccess()) {
100 ctx.WriteBuffer(result.Unwrap()); 101 ctx.WriteBuffer(output_params);
101 } 102 }
102 103
103 IPC::ResponseBuilder rb{ctx, 2}; 104 IPC::ResponseBuilder rb{ctx, 2};
104 rb.Push(result.Code()); 105 rb.Push(result);
105 } 106 }
106 107
107 void Start(Kernel::HLERequestContext& ctx) { 108 void Start(Kernel::HLERequestContext& ctx) {
@@ -346,7 +347,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
346 OpenAudioRendererImpl(ctx); 347 OpenAudioRendererImpl(ctx);
347} 348}
348 349
349static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) { 350static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
350 // +1 represents the final mix. 351 // +1 represents the final mix.
351 return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count + 352 return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
352 1; 353 1;
@@ -375,7 +376,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
375 constexpr u64 upsampler_manager_size = 0x48; 376 constexpr u64 upsampler_manager_size = 0x48;
376 377
377 // Calculates the part of the size that relates to mix buffers. 378 // Calculates the part of the size that relates to mix buffers.
378 const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) { 379 const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
379 // As of 8.0.0 this is the maximum on voice channels. 380 // As of 8.0.0 this is the maximum on voice channels.
380 constexpr u64 max_voice_channels = 6; 381 constexpr u64 max_voice_channels = 6;
381 382
@@ -397,7 +398,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
397 }; 398 };
398 399
399 // Calculates the portion of the size related to the mix data (and the sorting thereof). 400 // Calculates the portion of the size related to the mix data (and the sorting thereof).
400 const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) { 401 const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
401 // The size of the mixing info data structure. 402 // The size of the mixing info data structure.
402 constexpr u64 mix_info_size = 0x940; 403 constexpr u64 mix_info_size = 0x940;
403 404
@@ -447,7 +448,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
447 }; 448 };
448 449
449 // Calculates the part of the size related to voice channel info. 450 // Calculates the part of the size related to voice channel info.
450 const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) { 451 const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
451 constexpr u64 voice_info_size = 0x220; 452 constexpr u64 voice_info_size = 0x220;
452 constexpr u64 voice_resource_size = 0xD0; 453 constexpr u64 voice_resource_size = 0xD0;
453 454
@@ -461,7 +462,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
461 }; 462 };
462 463
463 // Calculates the part of the size related to memory pools. 464 // Calculates the part of the size related to memory pools.
464 const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) { 465 const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
465 const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count); 466 const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
466 const u64 memory_pool_info_size = 0x20; 467 const u64 memory_pool_info_size = 0x20;
467 return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size); 468 return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
@@ -469,7 +470,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
469 470
470 // Calculates the part of the size related to the splitter context. 471 // Calculates the part of the size related to the splitter context.
471 const auto calculate_splitter_context_size = 472 const auto calculate_splitter_context_size =
472 [](const AudioCore::AudioRendererParameter& params) -> u64 { 473 [](const AudioCommon::AudioRendererParameter& params) -> u64 {
473 if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { 474 if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
474 return 0; 475 return 0;
475 } 476 }
@@ -488,27 +489,29 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
488 }; 489 };
489 490
490 // Calculates the part of the size related to the upsampler info. 491 // Calculates the part of the size related to the upsampler info.
491 const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) { 492 const auto calculate_upsampler_info_size =
492 constexpr u64 upsampler_info_size = 0x280; 493 [](const AudioCommon::AudioRendererParameter& params) {
493 // Yes, using the buffer size over info alignment size is intentional here. 494 constexpr u64 upsampler_info_size = 0x280;
494 return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count), 495 // Yes, using the buffer size over info alignment size is intentional here.
495 buffer_alignment_size); 496 return Common::AlignUp(upsampler_info_size *
496 }; 497 (u64{params.submix_count} + params.sink_count),
498 buffer_alignment_size);
499 };
497 500
498 // Calculates the part of the size related to effect info. 501 // Calculates the part of the size related to effect info.
499 const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) { 502 const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
500 constexpr u64 effect_info_size = 0x2B0; 503 constexpr u64 effect_info_size = 0x2B0;
501 return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size); 504 return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
502 }; 505 };
503 506
504 // Calculates the part of the size related to audio sink info. 507 // Calculates the part of the size related to audio sink info.
505 const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) { 508 const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
506 const u64 sink_info_size = 0x170; 509 const u64 sink_info_size = 0x170;
507 return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size); 510 return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
508 }; 511 };
509 512
510 // Calculates the part of the size related to voice state info. 513 // Calculates the part of the size related to voice state info.
511 const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) { 514 const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
512 const u64 voice_state_size = 0x100; 515 const u64 voice_state_size = 0x100;
513 const u64 additional_size = buffer_alignment_size - 1; 516 const u64 additional_size = buffer_alignment_size - 1;
514 return Common::AlignUp(voice_state_size * params.voice_count + additional_size, 517 return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
@@ -516,7 +519,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
516 }; 519 };
517 520
518 // Calculates the part of the size related to performance statistics. 521 // Calculates the part of the size related to performance statistics.
519 const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) { 522 const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
520 // Extra size value appended to the end of the calculation. 523 // Extra size value appended to the end of the calculation.
521 constexpr u64 appended = 128; 524 constexpr u64 appended = 128;
522 525
@@ -543,79 +546,81 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
543 }; 546 };
544 547
545 // Calculates the part of the size that relates to the audio command buffer. 548 // Calculates the part of the size that relates to the audio command buffer.
546 const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) { 549 const auto calculate_command_buffer_size =
547 constexpr u64 alignment = (buffer_alignment_size - 1) * 2; 550 [](const AudioCommon::AudioRendererParameter& params) {
551 constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
548 552
549 if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { 553 if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
550 constexpr u64 command_buffer_size = 0x18000; 554 constexpr u64 command_buffer_size = 0x18000;
551 555
552 return command_buffer_size + alignment; 556 return command_buffer_size + alignment;
553 } 557 }
554 558
555 // When the variadic command buffer is supported, this means 559 // When the variadic command buffer is supported, this means
556 // the command generator for the audio renderer can issue commands 560 // the command generator for the audio renderer can issue commands
557 // that are (as one would expect), variable in size. So what we need to do 561 // that are (as one would expect), variable in size. So what we need to do
558 // is determine the maximum possible size for a few command data structures 562 // is determine the maximum possible size for a few command data structures
559 // then multiply them by the amount of present commands indicated by the given 563 // then multiply them by the amount of present commands indicated by the given
560 // respective audio parameters. 564 // respective audio parameters.
561 565
562 constexpr u64 max_biquad_filters = 2; 566 constexpr u64 max_biquad_filters = 2;
563 constexpr u64 max_mix_buffers = 24; 567 constexpr u64 max_mix_buffers = 24;
564 568
565 constexpr u64 biquad_filter_command_size = 0x2C; 569 constexpr u64 biquad_filter_command_size = 0x2C;
566 570
567 constexpr u64 depop_mix_command_size = 0x24; 571 constexpr u64 depop_mix_command_size = 0x24;
568 constexpr u64 depop_setup_command_size = 0x50; 572 constexpr u64 depop_setup_command_size = 0x50;
569 573
570 constexpr u64 effect_command_max_size = 0x540; 574 constexpr u64 effect_command_max_size = 0x540;
571 575
572 constexpr u64 mix_command_size = 0x1C; 576 constexpr u64 mix_command_size = 0x1C;
573 constexpr u64 mix_ramp_command_size = 0x24; 577 constexpr u64 mix_ramp_command_size = 0x24;
574 constexpr u64 mix_ramp_grouped_command_size = 0x13C; 578 constexpr u64 mix_ramp_grouped_command_size = 0x13C;
575 579
576 constexpr u64 perf_command_size = 0x28; 580 constexpr u64 perf_command_size = 0x28;
577 581
578 constexpr u64 sink_command_size = 0x130; 582 constexpr u64 sink_command_size = 0x130;
579 583
580 constexpr u64 submix_command_max_size = 584 constexpr u64 submix_command_max_size =
581 depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; 585 depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
582 586
583 constexpr u64 volume_command_size = 0x1C; 587 constexpr u64 volume_command_size = 0x1C;
584 constexpr u64 volume_ramp_command_size = 0x20; 588 constexpr u64 volume_ramp_command_size = 0x20;
585 589
586 constexpr u64 voice_biquad_filter_command_size = 590 constexpr u64 voice_biquad_filter_command_size =
587 biquad_filter_command_size * max_biquad_filters; 591 biquad_filter_command_size * max_biquad_filters;
588 constexpr u64 voice_data_command_size = 0x9C; 592 constexpr u64 voice_data_command_size = 0x9C;
589 const u64 voice_command_max_size = 593 const u64 voice_command_max_size =
590 (params.splitter_count * depop_setup_command_size) + 594 (params.splitter_count * depop_setup_command_size) +
591 (voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size + 595 (voice_data_command_size + voice_biquad_filter_command_size +
592 mix_ramp_grouped_command_size); 596 volume_ramp_command_size + mix_ramp_grouped_command_size);
593 597
594 // Now calculate the individual elements that comprise the size and add them together. 598 // Now calculate the individual elements that comprise the size and add them together.
595 const u64 effect_commands_size = params.effect_count * effect_command_max_size; 599 const u64 effect_commands_size = params.effect_count * effect_command_max_size;
596 600
597 const u64 final_mix_commands_size = 601 const u64 final_mix_commands_size =
598 depop_mix_command_size + volume_command_size * max_mix_buffers; 602 depop_mix_command_size + volume_command_size * max_mix_buffers;
599 603
600 const u64 perf_commands_size = 604 const u64 perf_commands_size =
601 perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); 605 perf_command_size *
606 (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
602 607
603 const u64 sink_commands_size = params.sink_count * sink_command_size; 608 const u64 sink_commands_size = params.sink_count * sink_command_size;
604 609
605 const u64 splitter_commands_size = 610 const u64 splitter_commands_size =
606 params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; 611 params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
607 612
608 const u64 submix_commands_size = params.submix_count * submix_command_max_size; 613 const u64 submix_commands_size = params.submix_count * submix_command_max_size;
609 614
610 const u64 voice_commands_size = params.voice_count * voice_command_max_size; 615 const u64 voice_commands_size = params.voice_count * voice_command_max_size;
611 616
612 return effect_commands_size + final_mix_commands_size + perf_commands_size + 617 return effect_commands_size + final_mix_commands_size + perf_commands_size +
613 sink_commands_size + splitter_commands_size + submix_commands_size + 618 sink_commands_size + splitter_commands_size + submix_commands_size +
614 voice_commands_size + alignment; 619 voice_commands_size + alignment;
615 }; 620 };
616 621
617 IPC::RequestParser rp{ctx}; 622 IPC::RequestParser rp{ctx};
618 const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); 623 const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
619 624
620 u64 size = 0; 625 u64 size = 0;
621 size += calculate_mix_buffer_sizes(params); 626 size += calculate_mix_buffer_sizes(params);
@@ -681,7 +686,7 @@ void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& c
681 686
682void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { 687void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
683 IPC::RequestParser rp{ctx}; 688 IPC::RequestParser rp{ctx};
684 const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); 689 const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
685 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 690 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
686 691
687 rb.Push(RESULT_SUCCESS); 692 rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 2cee1193c..54a5fb84b 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -379,7 +379,7 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage(
379 return FileSys::ERROR_ENTITY_NOT_FOUND; 379 return FileSys::ERROR_ENTITY_NOT_FOUND;
380 } 380 }
381 381
382 auto part = bis_factory->OpenPartitionStorage(id); 382 auto part = bis_factory->OpenPartitionStorage(id, system.GetFilesystem());
383 if (part == nullptr) { 383 if (part == nullptr) {
384 return FileSys::ERROR_INVALID_ARGUMENT; 384 return FileSys::ERROR_INVALID_ARGUMENT;
385 } 385 }
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index e742497e1..b65d59373 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -193,7 +193,8 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
193 controller.battery_level[0] = BATTERY_FULL; 193 controller.battery_level[0] = BATTERY_FULL;
194 controller.battery_level[1] = BATTERY_FULL; 194 controller.battery_level[1] = BATTERY_FULL;
195 controller.battery_level[2] = BATTERY_FULL; 195 controller.battery_level[2] = BATTERY_FULL;
196 styleset_changed_events[controller_idx].writable->Signal(); 196
197 SignalStyleSetChangedEvent(IndexToNPad(controller_idx));
197} 198}
198 199
199void Controller_NPad::OnInit() { 200void Controller_NPad::OnInit() {
@@ -249,6 +250,9 @@ void Controller_NPad::OnLoadInputDevices() {
249 std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, 250 std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
250 players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, 251 players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
251 sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>); 252 sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>);
253 std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
254 players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END,
255 motions[i].begin(), Input::CreateDevice<Input::MotionDevice>);
252 } 256 }
253} 257}
254 258
@@ -265,6 +269,7 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
265 auto& rstick_entry = npad_pad_states[controller_idx].r_stick; 269 auto& rstick_entry = npad_pad_states[controller_idx].r_stick;
266 const auto& button_state = buttons[controller_idx]; 270 const auto& button_state = buttons[controller_idx];
267 const auto& analog_state = sticks[controller_idx]; 271 const auto& analog_state = sticks[controller_idx];
272 const auto& motion_state = motions[controller_idx];
268 const auto [stick_l_x_f, stick_l_y_f] = 273 const auto [stick_l_x_f, stick_l_y_f] =
269 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); 274 analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
270 const auto [stick_r_x_f, stick_r_y_f] = 275 const auto [stick_r_x_f, stick_r_y_f] =
@@ -359,6 +364,45 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
359 continue; 364 continue;
360 } 365 }
361 const u32 npad_index = static_cast<u32>(i); 366 const u32 npad_index = static_cast<u32>(i);
367
368 const std::array<SixAxisGeneric*, 6> controller_sixaxes{
369 &npad.sixaxis_full, &npad.sixaxis_handheld, &npad.sixaxis_dual_left,
370 &npad.sixaxis_dual_right, &npad.sixaxis_left, &npad.sixaxis_right,
371 };
372
373 for (auto* sixaxis_sensor : controller_sixaxes) {
374 sixaxis_sensor->common.entry_count = 16;
375 sixaxis_sensor->common.total_entry_count = 17;
376
377 const auto& last_entry =
378 sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index];
379
380 sixaxis_sensor->common.timestamp = core_timing.GetCPUTicks();
381 sixaxis_sensor->common.last_entry_index =
382 (sixaxis_sensor->common.last_entry_index + 1) % 17;
383
384 auto& cur_entry = sixaxis_sensor->sixaxis[sixaxis_sensor->common.last_entry_index];
385
386 cur_entry.timestamp = last_entry.timestamp + 1;
387 cur_entry.timestamp2 = cur_entry.timestamp;
388 }
389
390 // Try to read sixaxis sensor states
391 std::array<MotionDevice, 2> motion_devices;
392
393 if (sixaxis_sensors_enabled && Settings::values.motion_enabled) {
394 sixaxis_at_rest = true;
395 for (std::size_t e = 0; e < motion_devices.size(); ++e) {
396 const auto& device = motions[i][e];
397 if (device) {
398 std::tie(motion_devices[e].accel, motion_devices[e].gyro,
399 motion_devices[e].rotation, motion_devices[e].orientation) =
400 device->GetStatus();
401 sixaxis_at_rest = sixaxis_at_rest && motion_devices[e].gyro.Length2() < 0.0001f;
402 }
403 }
404 }
405
362 RequestPadStateUpdate(npad_index); 406 RequestPadStateUpdate(npad_index);
363 auto& pad_state = npad_pad_states[npad_index]; 407 auto& pad_state = npad_pad_states[npad_index];
364 408
@@ -376,6 +420,18 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
376 420
377 libnx_entry.connection_status.raw = 0; 421 libnx_entry.connection_status.raw = 0;
378 libnx_entry.connection_status.IsConnected.Assign(1); 422 libnx_entry.connection_status.IsConnected.Assign(1);
423 auto& full_sixaxis_entry =
424 npad.sixaxis_full.sixaxis[npad.sixaxis_full.common.last_entry_index];
425 auto& handheld_sixaxis_entry =
426 npad.sixaxis_handheld.sixaxis[npad.sixaxis_handheld.common.last_entry_index];
427 auto& dual_left_sixaxis_entry =
428 npad.sixaxis_dual_left.sixaxis[npad.sixaxis_dual_left.common.last_entry_index];
429 auto& dual_right_sixaxis_entry =
430 npad.sixaxis_dual_right.sixaxis[npad.sixaxis_dual_right.common.last_entry_index];
431 auto& left_sixaxis_entry =
432 npad.sixaxis_left.sixaxis[npad.sixaxis_left.common.last_entry_index];
433 auto& right_sixaxis_entry =
434 npad.sixaxis_right.sixaxis[npad.sixaxis_right.common.last_entry_index];
379 435
380 switch (controller_type) { 436 switch (controller_type) {
381 case NPadControllerType::None: 437 case NPadControllerType::None:
@@ -390,6 +446,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
390 main_controller.pad.r_stick = pad_state.r_stick; 446 main_controller.pad.r_stick = pad_state.r_stick;
391 447
392 libnx_entry.connection_status.IsWired.Assign(1); 448 libnx_entry.connection_status.IsWired.Assign(1);
449
450 if (sixaxis_sensors_enabled && motions[i][0]) {
451 full_sixaxis_entry.accel = motion_devices[0].accel;
452 full_sixaxis_entry.gyro = motion_devices[0].gyro;
453 full_sixaxis_entry.rotation = motion_devices[0].rotation;
454 full_sixaxis_entry.orientation = motion_devices[0].orientation;
455 }
393 break; 456 break;
394 case NPadControllerType::Handheld: 457 case NPadControllerType::Handheld:
395 handheld_entry.connection_status.raw = 0; 458 handheld_entry.connection_status.raw = 0;
@@ -408,6 +471,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
408 libnx_entry.connection_status.IsRightJoyConnected.Assign(1); 471 libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
409 libnx_entry.connection_status.IsLeftJoyWired.Assign(1); 472 libnx_entry.connection_status.IsLeftJoyWired.Assign(1);
410 libnx_entry.connection_status.IsRightJoyWired.Assign(1); 473 libnx_entry.connection_status.IsRightJoyWired.Assign(1);
474
475 if (sixaxis_sensors_enabled && motions[i][0]) {
476 handheld_sixaxis_entry.accel = motion_devices[0].accel;
477 handheld_sixaxis_entry.gyro = motion_devices[0].gyro;
478 handheld_sixaxis_entry.rotation = motion_devices[0].rotation;
479 handheld_sixaxis_entry.orientation = motion_devices[0].orientation;
480 }
411 break; 481 break;
412 case NPadControllerType::JoyDual: 482 case NPadControllerType::JoyDual:
413 dual_entry.connection_status.raw = 0; 483 dual_entry.connection_status.raw = 0;
@@ -420,6 +490,21 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
420 490
421 libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); 491 libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
422 libnx_entry.connection_status.IsRightJoyConnected.Assign(1); 492 libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
493
494 if (sixaxis_sensors_enabled && motions[i][0]) {
495 // Set motion for the left joycon
496 dual_left_sixaxis_entry.accel = motion_devices[0].accel;
497 dual_left_sixaxis_entry.gyro = motion_devices[0].gyro;
498 dual_left_sixaxis_entry.rotation = motion_devices[0].rotation;
499 dual_left_sixaxis_entry.orientation = motion_devices[0].orientation;
500 }
501 if (sixaxis_sensors_enabled && motions[i][1]) {
502 // Set motion for the right joycon
503 dual_right_sixaxis_entry.accel = motion_devices[1].accel;
504 dual_right_sixaxis_entry.gyro = motion_devices[1].gyro;
505 dual_right_sixaxis_entry.rotation = motion_devices[1].rotation;
506 dual_right_sixaxis_entry.orientation = motion_devices[1].orientation;
507 }
423 break; 508 break;
424 case NPadControllerType::JoyLeft: 509 case NPadControllerType::JoyLeft:
425 left_entry.connection_status.raw = 0; 510 left_entry.connection_status.raw = 0;
@@ -430,6 +515,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
430 left_entry.pad.r_stick = pad_state.r_stick; 515 left_entry.pad.r_stick = pad_state.r_stick;
431 516
432 libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); 517 libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
518
519 if (sixaxis_sensors_enabled && motions[i][0]) {
520 left_sixaxis_entry.accel = motion_devices[0].accel;
521 left_sixaxis_entry.gyro = motion_devices[0].gyro;
522 left_sixaxis_entry.rotation = motion_devices[0].rotation;
523 left_sixaxis_entry.orientation = motion_devices[0].orientation;
524 }
433 break; 525 break;
434 case NPadControllerType::JoyRight: 526 case NPadControllerType::JoyRight:
435 right_entry.connection_status.raw = 0; 527 right_entry.connection_status.raw = 0;
@@ -440,6 +532,13 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
440 right_entry.pad.r_stick = pad_state.r_stick; 532 right_entry.pad.r_stick = pad_state.r_stick;
441 533
442 libnx_entry.connection_status.IsRightJoyConnected.Assign(1); 534 libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
535
536 if (sixaxis_sensors_enabled && motions[i][1]) {
537 right_sixaxis_entry.accel = motion_devices[1].accel;
538 right_sixaxis_entry.gyro = motion_devices[1].gyro;
539 right_sixaxis_entry.rotation = motion_devices[1].rotation;
540 right_sixaxis_entry.orientation = motion_devices[1].orientation;
541 }
443 break; 542 break;
444 case NPadControllerType::Pokeball: 543 case NPadControllerType::Pokeball:
445 pokeball_entry.connection_status.raw = 0; 544 pokeball_entry.connection_status.raw = 0;
@@ -518,13 +617,17 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
518 last_processed_vibration = vibrations.back(); 617 last_processed_vibration = vibrations.back();
519} 618}
520 619
620Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
621 return last_processed_vibration;
622}
623
521std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const { 624std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const {
522 const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)]; 625 const auto& styleset_event = styleset_changed_events[NPadIdToIndex(npad_id)];
523 return styleset_event.readable; 626 return styleset_event.readable;
524} 627}
525 628
526Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { 629void Controller_NPad::SignalStyleSetChangedEvent(u32 npad_id) const {
527 return last_processed_vibration; 630 styleset_changed_events[NPadIdToIndex(npad_id)].writable->Signal();
528} 631}
529 632
530void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) { 633void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) {
@@ -534,7 +637,7 @@ void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::siz
534void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, 637void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index,
535 bool connected) { 638 bool connected) {
536 if (!connected) { 639 if (!connected) {
537 DisconnectNPad(IndexToNPad(npad_index)); 640 DisconnectNPadAtIndex(npad_index);
538 return; 641 return;
539 } 642 }
540 643
@@ -554,16 +657,19 @@ void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::siz
554} 657}
555 658
556void Controller_NPad::DisconnectNPad(u32 npad_id) { 659void Controller_NPad::DisconnectNPad(u32 npad_id) {
557 const auto npad_index = NPadIdToIndex(npad_id); 660 DisconnectNPadAtIndex(NPadIdToIndex(npad_id));
558 connected_controllers[npad_index].is_connected = false; 661}
662
663void Controller_NPad::DisconnectNPadAtIndex(std::size_t npad_index) {
559 Settings::values.players[npad_index].connected = false; 664 Settings::values.players[npad_index].connected = false;
665 connected_controllers[npad_index].is_connected = false;
560 666
561 auto& controller = shared_memory_entries[npad_index]; 667 auto& controller = shared_memory_entries[npad_index];
562 controller.joy_styles.raw = 0; // Zero out 668 controller.joy_styles.raw = 0; // Zero out
563 controller.device_type.raw = 0; 669 controller.device_type.raw = 0;
564 controller.properties.raw = 0; 670 controller.properties.raw = 0;
565 671
566 styleset_changed_events[npad_index].writable->Signal(); 672 SignalStyleSetChangedEvent(IndexToNPad(npad_index));
567} 673}
568 674
569void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) { 675void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) {
@@ -574,6 +680,14 @@ Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMo
574 return gyroscope_zero_drift_mode; 680 return gyroscope_zero_drift_mode;
575} 681}
576 682
683bool Controller_NPad::IsSixAxisSensorAtRest() const {
684 return sixaxis_at_rest;
685}
686
687void Controller_NPad::SetSixAxisEnabled(bool six_axis_status) {
688 sixaxis_sensors_enabled = six_axis_status;
689}
690
577void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) { 691void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) {
578 const auto npad_index_1 = NPadIdToIndex(npad_id_1); 692 const auto npad_index_1 = NPadIdToIndex(npad_id_1);
579 const auto npad_index_2 = NPadIdToIndex(npad_id_2); 693 const auto npad_index_2 = NPadIdToIndex(npad_id_2);
@@ -666,13 +780,13 @@ void Controller_NPad::ClearAllConnectedControllers() {
666} 780}
667 781
668void Controller_NPad::DisconnectAllConnectedControllers() { 782void Controller_NPad::DisconnectAllConnectedControllers() {
669 for (ControllerHolder& controller : connected_controllers) { 783 for (auto& controller : connected_controllers) {
670 controller.is_connected = false; 784 controller.is_connected = false;
671 } 785 }
672} 786}
673 787
674void Controller_NPad::ConnectAllDisconnectedControllers() { 788void Controller_NPad::ConnectAllDisconnectedControllers() {
675 for (ControllerHolder& controller : connected_controllers) { 789 for (auto& controller : connected_controllers) {
676 if (controller.type != NPadControllerType::None && !controller.is_connected) { 790 if (controller.type != NPadControllerType::None && !controller.is_connected) {
677 controller.is_connected = true; 791 controller.is_connected = true;
678 } 792 }
@@ -680,7 +794,7 @@ void Controller_NPad::ConnectAllDisconnectedControllers() {
680} 794}
681 795
682void Controller_NPad::ClearAllControllers() { 796void Controller_NPad::ClearAllControllers() {
683 for (ControllerHolder& controller : connected_controllers) { 797 for (auto& controller : connected_controllers) {
684 controller.type = NPadControllerType::None; 798 controller.type = NPadControllerType::None;
685 controller.is_connected = false; 799 controller.is_connected = false;
686 } 800 }
@@ -728,92 +842,4 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
728 return false; 842 return false;
729} 843}
730 844
731Controller_NPad::NPadControllerType Controller_NPad::DecideBestController(
732 NPadControllerType priority) const {
733 if (IsControllerSupported(priority)) {
734 return priority;
735 }
736 const auto is_docked = Settings::values.use_docked_mode;
737 if (is_docked && priority == NPadControllerType::Handheld) {
738 priority = NPadControllerType::JoyDual;
739 if (IsControllerSupported(priority)) {
740 return priority;
741 }
742 }
743 std::vector<NPadControllerType> priority_list;
744 switch (priority) {
745 case NPadControllerType::ProController:
746 priority_list.push_back(NPadControllerType::JoyDual);
747 if (!is_docked) {
748 priority_list.push_back(NPadControllerType::Handheld);
749 }
750 priority_list.push_back(NPadControllerType::JoyLeft);
751 priority_list.push_back(NPadControllerType::JoyRight);
752 priority_list.push_back(NPadControllerType::Pokeball);
753 break;
754 case NPadControllerType::Handheld:
755 priority_list.push_back(NPadControllerType::JoyDual);
756 priority_list.push_back(NPadControllerType::ProController);
757 priority_list.push_back(NPadControllerType::JoyLeft);
758 priority_list.push_back(NPadControllerType::JoyRight);
759 priority_list.push_back(NPadControllerType::Pokeball);
760 break;
761 case NPadControllerType::JoyDual:
762 if (!is_docked) {
763 priority_list.push_back(NPadControllerType::Handheld);
764 }
765 priority_list.push_back(NPadControllerType::ProController);
766 priority_list.push_back(NPadControllerType::JoyLeft);
767 priority_list.push_back(NPadControllerType::JoyRight);
768 priority_list.push_back(NPadControllerType::Pokeball);
769 break;
770 case NPadControllerType::JoyLeft:
771 priority_list.push_back(NPadControllerType::JoyRight);
772 priority_list.push_back(NPadControllerType::JoyDual);
773 if (!is_docked) {
774 priority_list.push_back(NPadControllerType::Handheld);
775 }
776 priority_list.push_back(NPadControllerType::ProController);
777 priority_list.push_back(NPadControllerType::Pokeball);
778 break;
779 case NPadControllerType::JoyRight:
780 priority_list.push_back(NPadControllerType::JoyLeft);
781 priority_list.push_back(NPadControllerType::JoyDual);
782 if (!is_docked) {
783 priority_list.push_back(NPadControllerType::Handheld);
784 }
785 priority_list.push_back(NPadControllerType::ProController);
786 priority_list.push_back(NPadControllerType::Pokeball);
787 break;
788 case NPadControllerType::Pokeball:
789 priority_list.push_back(NPadControllerType::JoyLeft);
790 priority_list.push_back(NPadControllerType::JoyRight);
791 priority_list.push_back(NPadControllerType::JoyDual);
792 if (!is_docked) {
793 priority_list.push_back(NPadControllerType::Handheld);
794 }
795 priority_list.push_back(NPadControllerType::ProController);
796 break;
797 default:
798 priority_list.push_back(NPadControllerType::JoyDual);
799 if (!is_docked) {
800 priority_list.push_back(NPadControllerType::Handheld);
801 }
802 priority_list.push_back(NPadControllerType::ProController);
803 priority_list.push_back(NPadControllerType::JoyLeft);
804 priority_list.push_back(NPadControllerType::JoyRight);
805 priority_list.push_back(NPadControllerType::JoyDual);
806 break;
807 }
808
809 const auto iter = std::find_if(priority_list.begin(), priority_list.end(),
810 [this](auto type) { return IsControllerSupported(type); });
811 if (iter == priority_list.end()) {
812 UNIMPLEMENTED_MSG("Could not find supported controller!");
813 return priority;
814 }
815
816 return *iter;
817}
818
819} // namespace Service::HID 845} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index ad25c6fbf..78e7c320b 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -115,17 +115,23 @@ public:
115 void VibrateController(const std::vector<u32>& controller_ids, 115 void VibrateController(const std::vector<u32>& controller_ids,
116 const std::vector<Vibration>& vibrations); 116 const std::vector<Vibration>& vibrations);
117 117
118 std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
119 Vibration GetLastVibration() const; 118 Vibration GetLastVibration() const;
120 119
120 std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
121 void SignalStyleSetChangedEvent(u32 npad_id) const;
122
121 // Adds a new controller at an index. 123 // Adds a new controller at an index.
122 void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index); 124 void AddNewControllerAt(NPadControllerType controller, std::size_t npad_index);
123 // Adds a new controller at an index with connection status. 125 // Adds a new controller at an index with connection status.
124 void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected); 126 void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected);
125 127
126 void DisconnectNPad(u32 npad_id); 128 void DisconnectNPad(u32 npad_id);
129 void DisconnectNPadAtIndex(std::size_t index);
130
127 void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); 131 void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode);
128 GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; 132 GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const;
133 bool IsSixAxisSensorAtRest() const;
134 void SetSixAxisEnabled(bool six_axis_status);
129 LedPattern GetLedPattern(u32 npad_id); 135 LedPattern GetLedPattern(u32 npad_id);
130 void SetVibrationEnabled(bool can_vibrate); 136 void SetVibrationEnabled(bool can_vibrate);
131 bool IsVibrationEnabled() const; 137 bool IsVibrationEnabled() const;
@@ -248,6 +254,24 @@ private:
248 }; 254 };
249 static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size"); 255 static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
250 256
257 struct SixAxisStates {
258 s64_le timestamp{};
259 INSERT_PADDING_WORDS(2);
260 s64_le timestamp2{};
261 Common::Vec3f accel{};
262 Common::Vec3f gyro{};
263 Common::Vec3f rotation{};
264 std::array<Common::Vec3f, 3> orientation{};
265 s64_le always_one{1};
266 };
267 static_assert(sizeof(SixAxisStates) == 0x68, "SixAxisStates is an invalid size");
268
269 struct SixAxisGeneric {
270 CommonHeader common{};
271 std::array<SixAxisStates, 17> sixaxis{};
272 };
273 static_assert(sizeof(SixAxisGeneric) == 0x708, "SixAxisGeneric is an invalid size");
274
251 enum class ColorReadError : u32_le { 275 enum class ColorReadError : u32_le {
252 ReadOk = 0, 276 ReadOk = 0,
253 ColorDoesntExist = 1, 277 ColorDoesntExist = 1,
@@ -277,6 +301,13 @@ private:
277 }; 301 };
278 }; 302 };
279 303
304 struct MotionDevice {
305 Common::Vec3f accel;
306 Common::Vec3f gyro;
307 Common::Vec3f rotation;
308 std::array<Common::Vec3f, 3> orientation;
309 };
310
280 struct NPadEntry { 311 struct NPadEntry {
281 NPadType joy_styles; 312 NPadType joy_styles;
282 NPadAssignments pad_assignment; 313 NPadAssignments pad_assignment;
@@ -296,9 +327,12 @@ private:
296 NPadGeneric pokeball_states; 327 NPadGeneric pokeball_states;
297 NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be 328 NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
298 // relying on this for the time being 329 // relying on this for the time being
299 INSERT_PADDING_BYTES( 330 SixAxisGeneric sixaxis_full;
300 0x708 * 331 SixAxisGeneric sixaxis_handheld;
301 6); // TODO(ogniK): SixAxis states, require more information before implementation 332 SixAxisGeneric sixaxis_dual_left;
333 SixAxisGeneric sixaxis_dual_right;
334 SixAxisGeneric sixaxis_left;
335 SixAxisGeneric sixaxis_right;
302 NPadDevice device_type; 336 NPadDevice device_type;
303 NPadProperties properties; 337 NPadProperties properties;
304 INSERT_PADDING_WORDS(1); 338 INSERT_PADDING_WORDS(1);
@@ -315,21 +349,24 @@ private:
315 349
316 void InitNewlyAddedController(std::size_t controller_idx); 350 void InitNewlyAddedController(std::size_t controller_idx);
317 bool IsControllerSupported(NPadControllerType controller) const; 351 bool IsControllerSupported(NPadControllerType controller) const;
318 NPadControllerType DecideBestController(NPadControllerType priority) const;
319 void RequestPadStateUpdate(u32 npad_id); 352 void RequestPadStateUpdate(u32 npad_id);
320 353
321 u32 press_state{}; 354 u32 press_state{};
322 355
323 NPadType style{}; 356 NPadType style{};
324 std::array<NPadEntry, 10> shared_memory_entries{}; 357 std::array<NPadEntry, 10> shared_memory_entries{};
325 std::array< 358 using ButtonArray = std::array<
326 std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>, 359 std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>,
327 10> 360 10>;
328 buttons; 361 using StickArray = std::array<
329 std::array<
330 std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>, 362 std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
331 10> 363 10>;
332 sticks; 364 using MotionArray = std::array<
365 std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTION_HID>,
366 10>;
367 ButtonArray buttons;
368 StickArray sticks;
369 MotionArray motions;
333 std::vector<u32> supported_npad_id_types{}; 370 std::vector<u32> supported_npad_id_types{};
334 NpadHoldType hold_type{NpadHoldType::Vertical}; 371 NpadHoldType hold_type{NpadHoldType::Vertical};
335 // Each controller should have their own styleset changed event 372 // Each controller should have their own styleset changed event
@@ -338,6 +375,8 @@ private:
338 std::array<ControllerHolder, 10> connected_controllers{}; 375 std::array<ControllerHolder, 10> connected_controllers{};
339 GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; 376 GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
340 bool can_controllers_vibrate{true}; 377 bool can_controllers_vibrate{true};
378 bool sixaxis_sensors_enabled{true};
379 bool sixaxis_at_rest{true};
341 std::array<ControllerPad, 10> npad_pad_states{}; 380 std::array<ControllerPad, 10> npad_pad_states{};
342 bool is_in_lr_assignment_mode{false}; 381 bool is_in_lr_assignment_mode{false};
343 Core::System& system; 382 Core::System& system;
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 9453134bf..1d96f705f 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -164,8 +164,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) {
164 {56, nullptr, "ActivateJoyXpad"}, 164 {56, nullptr, "ActivateJoyXpad"},
165 {58, nullptr, "GetJoyXpadLifoHandle"}, 165 {58, nullptr, "GetJoyXpadLifoHandle"},
166 {59, nullptr, "GetJoyXpadIds"}, 166 {59, nullptr, "GetJoyXpadIds"},
167 {60, nullptr, "ActivateSixAxisSensor"}, 167 {60, &Hid::ActivateSixAxisSensor, "ActivateSixAxisSensor"},
168 {61, nullptr, "DeactivateSixAxisSensor"}, 168 {61, &Hid::DeactivateSixAxisSensor, "DeactivateSixAxisSensor"},
169 {62, nullptr, "GetSixAxisSensorLifoHandle"}, 169 {62, nullptr, "GetSixAxisSensorLifoHandle"},
170 {63, nullptr, "ActivateJoySixAxisSensor"}, 170 {63, nullptr, "ActivateJoySixAxisSensor"},
171 {64, nullptr, "DeactivateJoySixAxisSensor"}, 171 {64, nullptr, "DeactivateJoySixAxisSensor"},
@@ -329,6 +329,31 @@ void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) {
329 rb.Push(0); 329 rb.Push(0);
330} 330}
331 331
332void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
333 IPC::RequestParser rp{ctx};
334 const auto handle{rp.Pop<u32>()};
335 const auto applet_resource_user_id{rp.Pop<u64>()};
336 applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(true);
337 LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle,
338 applet_resource_user_id);
339
340 IPC::ResponseBuilder rb{ctx, 2};
341 rb.Push(RESULT_SUCCESS);
342}
343
344void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) {
345 IPC::RequestParser rp{ctx};
346 const auto handle{rp.Pop<u32>()};
347 const auto applet_resource_user_id{rp.Pop<u64>()};
348 applet_resource->GetController<Controller_NPad>(HidController::NPad).SetSixAxisEnabled(false);
349
350 LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle,
351 applet_resource_user_id);
352
353 IPC::ResponseBuilder rb{ctx, 2};
354 rb.Push(RESULT_SUCCESS);
355}
356
332void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { 357void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) {
333 IPC::RequestParser rp{ctx}; 358 IPC::RequestParser rp{ctx};
334 const auto applet_resource_user_id{rp.Pop<u64>()}; 359 const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -484,13 +509,13 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
484 const auto handle{rp.Pop<u32>()}; 509 const auto handle{rp.Pop<u32>()};
485 const auto applet_resource_user_id{rp.Pop<u64>()}; 510 const auto applet_resource_user_id{rp.Pop<u64>()};
486 511
487 LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, 512 LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle,
488 applet_resource_user_id); 513 applet_resource_user_id);
489 514
490 IPC::ResponseBuilder rb{ctx, 3}; 515 IPC::ResponseBuilder rb{ctx, 3};
491 rb.Push(RESULT_SUCCESS); 516 rb.Push(RESULT_SUCCESS);
492 // TODO (Hexagon12): Properly implement reading gyroscope values from controllers. 517 rb.Push(applet_resource->GetController<Controller_NPad>(HidController::NPad)
493 rb.Push(true); 518 .IsSixAxisSensorAtRest());
494} 519}
495 520
496void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { 521void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index efb07547f..e04aaf1e9 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -86,6 +86,8 @@ private:
86 void CreateAppletResource(Kernel::HLERequestContext& ctx); 86 void CreateAppletResource(Kernel::HLERequestContext& ctx);
87 void ActivateXpad(Kernel::HLERequestContext& ctx); 87 void ActivateXpad(Kernel::HLERequestContext& ctx);
88 void GetXpadIDs(Kernel::HLERequestContext& ctx); 88 void GetXpadIDs(Kernel::HLERequestContext& ctx);
89 void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx);
90 void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx);
89 void ActivateDebugPad(Kernel::HLERequestContext& ctx); 91 void ActivateDebugPad(Kernel::HLERequestContext& ctx);
90 void ActivateTouchScreen(Kernel::HLERequestContext& ctx); 92 void ActivateTouchScreen(Kernel::HLERequestContext& ctx);
91 void ActivateMouse(Kernel::HLERequestContext& ctx); 93 void ActivateMouse(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 5e2d769a4..a0469ffbd 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -2,6 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <array>
5#include <atomic> 6#include <atomic>
6 7
7#include "common/logging/log.h" 8#include "common/logging/log.h"
@@ -72,10 +73,10 @@ private:
72 std::array<u8, 10> uuid; 73 std::array<u8, 10> uuid;
73 u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it 74 u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it
74 // mean something else 75 // mean something else
75 INSERT_PADDING_BYTES(0x15); 76 std::array<u8, 0x15> padding_1;
76 u32_le protocol; 77 u32_le protocol;
77 u32_le tag_type; 78 u32_le tag_type;
78 INSERT_PADDING_BYTES(0x2c); 79 std::array<u8, 0x2c> padding_2;
79 }; 80 };
80 static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size"); 81 static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size");
81 82
@@ -213,13 +214,15 @@ private:
213 LOG_DEBUG(Service_NFP, "called"); 214 LOG_DEBUG(Service_NFP, "called");
214 215
215 IPC::ResponseBuilder rb{ctx, 2}; 216 IPC::ResponseBuilder rb{ctx, 2};
216 auto amiibo = nfp_interface.GetAmiiboBuffer(); 217 const auto& amiibo = nfp_interface.GetAmiiboBuffer();
217 TagInfo tag_info{}; 218 const TagInfo tag_info{
218 tag_info.uuid = amiibo.uuid; 219 .uuid = amiibo.uuid,
219 tag_info.uuid_length = static_cast<u8>(tag_info.uuid.size()); 220 .uuid_length = static_cast<u8>(tag_info.uuid.size()),
220 221 .padding_1 = {},
221 tag_info.protocol = 1; // TODO(ogniK): Figure out actual values 222 .protocol = 1, // TODO(ogniK): Figure out actual values
222 tag_info.tag_type = 2; 223 .tag_type = 2,
224 .padding_2 = {},
225 };
223 ctx.WriteBuffer(tag_info); 226 ctx.WriteBuffer(tag_info);
224 rb.Push(RESULT_SUCCESS); 227 rb.Push(RESULT_SUCCESS);
225 } 228 }
@@ -236,7 +239,7 @@ private:
236 LOG_DEBUG(Service_NFP, "called"); 239 LOG_DEBUG(Service_NFP, "called");
237 240
238 IPC::ResponseBuilder rb{ctx, 2}; 241 IPC::ResponseBuilder rb{ctx, 2};
239 auto amiibo = nfp_interface.GetAmiiboBuffer(); 242 const auto& amiibo = nfp_interface.GetAmiiboBuffer();
240 ctx.WriteBuffer(amiibo.model_info); 243 ctx.WriteBuffer(amiibo.model_info);
241 rb.Push(RESULT_SUCCESS); 244 rb.Push(RESULT_SUCCESS);
242 } 245 }
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 538f28495..76b3533ec 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -72,25 +72,6 @@
72 72
73namespace Service { 73namespace Service {
74 74
75/**
76 * Creates a function string for logging, complete with the name (or header code, depending
77 * on what's passed in) the port name, and all the cmd_buff arguments.
78 */
79[[maybe_unused]] static std::string MakeFunctionString(std::string_view name,
80 std::string_view port_name,
81 const u32* cmd_buff) {
82 // Number of params == bits 0-5 + bits 6-11
83 int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F);
84
85 std::string function_string = fmt::format("function '{}': port={}", name, port_name);
86 for (int i = 1; i <= num_params; ++i) {
87 function_string += fmt::format(", cmd_buff[{}]=0x{:X}", i, cmd_buff[i]);
88 }
89 return function_string;
90}
91
92////////////////////////////////////////////////////////////////////////////////////////////////////
93
94ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions, 75ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions,
95 InvokerFn* handler_invoker) 76 InvokerFn* handler_invoker)
96 : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {} 77 : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {}
@@ -105,10 +86,9 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager)
105 port_installed = true; 86 port_installed = true;
106} 87}
107 88
108void ServiceFrameworkBase::InstallAsNamedPort() { 89void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) {
109 ASSERT(!port_installed); 90 ASSERT(!port_installed);
110 91
111 auto& kernel = Core::System::GetInstance().Kernel();
112 auto [server_port, client_port] = 92 auto [server_port, client_port] =
113 Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); 93 Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
114 server_port->SetHleHandler(shared_from_this()); 94 server_port->SetHleHandler(shared_from_this());
@@ -116,10 +96,9 @@ void ServiceFrameworkBase::InstallAsNamedPort() {
116 port_installed = true; 96 port_installed = true;
117} 97}
118 98
119std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { 99std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel) {
120 ASSERT(!port_installed); 100 ASSERT(!port_installed);
121 101
122 auto& kernel = Core::System::GetInstance().Kernel();
123 auto [server_port, client_port] = 102 auto [server_port, client_port] =
124 Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); 103 Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
125 auto port = MakeResult(std::move(server_port)).Unwrap(); 104 auto port = MakeResult(std::move(server_port)).Unwrap();
@@ -191,9 +170,6 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
191 return RESULT_SUCCESS; 170 return RESULT_SUCCESS;
192} 171}
193 172
194////////////////////////////////////////////////////////////////////////////////////////////////////
195// Module interface
196
197/// Initialize ServiceManager 173/// Initialize ServiceManager
198void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) { 174void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
199 // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it 175 // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 022d885b6..a01ef3353 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -63,9 +63,9 @@ public:
63 /// Creates a port pair and registers this service with the given ServiceManager. 63 /// Creates a port pair and registers this service with the given ServiceManager.
64 void InstallAsService(SM::ServiceManager& service_manager); 64 void InstallAsService(SM::ServiceManager& service_manager);
65 /// Creates a port pair and registers it on the kernel's global port registry. 65 /// Creates a port pair and registers it on the kernel's global port registry.
66 void InstallAsNamedPort(); 66 void InstallAsNamedPort(Kernel::KernelCore& kernel);
67 /// Creates and returns an unregistered port for the service. 67 /// Creates and returns an unregistered port for the service.
68 std::shared_ptr<Kernel::ClientPort> CreatePort(); 68 std::shared_ptr<Kernel::ClientPort> CreatePort(Kernel::KernelCore& kernel);
69 69
70 void InvokeRequest(Kernel::HLERequestContext& ctx); 70 void InvokeRequest(Kernel::HLERequestContext& ctx);
71 71
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index d872de16c..9c1da361b 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -19,7 +19,7 @@ constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::SM, 4);
19constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6); 19constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6);
20constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7); 20constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7);
21 21
22ServiceManager::ServiceManager() = default; 22ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} {}
23ServiceManager::~ServiceManager() = default; 23ServiceManager::~ServiceManager() = default;
24 24
25void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { 25void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
@@ -27,11 +27,11 @@ void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
27} 27}
28 28
29static ResultCode ValidateServiceName(const std::string& name) { 29static ResultCode ValidateServiceName(const std::string& name) {
30 if (name.size() <= 0 || name.size() > 8) { 30 if (name.empty() || name.size() > 8) {
31 LOG_ERROR(Service_SM, "Invalid service name! service={}", name); 31 LOG_ERROR(Service_SM, "Invalid service name! service={}", name);
32 return ERR_INVALID_NAME; 32 return ERR_INVALID_NAME;
33 } 33 }
34 if (name.find('\0') != std::string::npos) { 34 if (name.rfind('\0') != std::string::npos) {
35 LOG_ERROR(Service_SM, "A non null terminated service was passed"); 35 LOG_ERROR(Service_SM, "A non null terminated service was passed");
36 return ERR_INVALID_NAME; 36 return ERR_INVALID_NAME;
37 } 37 }
@@ -43,13 +43,13 @@ void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self,
43 ASSERT(self->sm_interface.expired()); 43 ASSERT(self->sm_interface.expired());
44 44
45 auto sm = std::make_shared<SM>(self, kernel); 45 auto sm = std::make_shared<SM>(self, kernel);
46 sm->InstallAsNamedPort(); 46 sm->InstallAsNamedPort(kernel);
47 self->sm_interface = sm; 47 self->sm_interface = sm;
48 self->controller_interface = std::make_unique<Controller>(); 48 self->controller_interface = std::make_unique<Controller>();
49} 49}
50 50
51ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService( 51ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(std::string name,
52 std::string name, unsigned int max_sessions) { 52 u32 max_sessions) {
53 53
54 CASCADE_CODE(ValidateServiceName(name)); 54 CASCADE_CODE(ValidateServiceName(name));
55 55
@@ -58,7 +58,6 @@ ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(
58 return ERR_ALREADY_REGISTERED; 58 return ERR_ALREADY_REGISTERED;
59 } 59 }
60 60
61 auto& kernel = Core::System::GetInstance().Kernel();
62 auto [server_port, client_port] = 61 auto [server_port, client_port] =
63 Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name); 62 Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name);
64 63
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index aabf166b7..6790c86f0 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -48,11 +48,11 @@ class ServiceManager {
48public: 48public:
49 static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel); 49 static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel);
50 50
51 ServiceManager(); 51 explicit ServiceManager(Kernel::KernelCore& kernel_);
52 ~ServiceManager(); 52 ~ServiceManager();
53 53
54 ResultVal<std::shared_ptr<Kernel::ServerPort>> RegisterService(std::string name, 54 ResultVal<std::shared_ptr<Kernel::ServerPort>> RegisterService(std::string name,
55 unsigned int max_sessions); 55 u32 max_sessions);
56 ResultCode UnregisterService(const std::string& name); 56 ResultCode UnregisterService(const std::string& name);
57 ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name); 57 ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name);
58 ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name); 58 ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name);
@@ -79,6 +79,9 @@ private:
79 79
80 /// Map of registered services, retrieved using GetServicePort or ConnectToService. 80 /// Map of registered services, retrieved using GetServicePort or ConnectToService.
81 std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services; 81 std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services;
82
83 /// Kernel context
84 Kernel::KernelCore& kernel;
82}; 85};
83 86
84} // namespace Service::SM 87} // namespace Service::SM
diff --git a/src/core/hle/service/sockets/blocking_worker.h b/src/core/hle/service/sockets/blocking_worker.h
index 31ef6b821..2d53e52b6 100644
--- a/src/core/hle/service/sockets/blocking_worker.h
+++ b/src/core/hle/service/sockets/blocking_worker.h
@@ -29,7 +29,7 @@ namespace Service::Sockets {
29 * Worker abstraction to execute blocking calls on host without blocking the guest thread 29 * Worker abstraction to execute blocking calls on host without blocking the guest thread
30 * 30 *
31 * @tparam Service Service where the work is executed 31 * @tparam Service Service where the work is executed
32 * @tparam ...Types Types of work to execute 32 * @tparam Types Types of work to execute
33 */ 33 */
34template <class Service, class... Types> 34template <class Service, class... Types>
35class BlockingWorker { 35class BlockingWorker {
@@ -109,9 +109,8 @@ private:
109 while (keep_running) { 109 while (keep_running) {
110 work_event.Wait(); 110 work_event.Wait();
111 111
112 const auto visit_fn = [service, &keep_running](auto&& w) { 112 const auto visit_fn = [service, &keep_running]<typename T>(T&& w) {
113 using T = std::decay_t<decltype(w)>; 113 if constexpr (std::is_same_v<std::decay_t<T>, std::monostate>) {
114 if constexpr (std::is_same_v<T, std::monostate>) {
115 keep_running = false; 114 keep_running = false;
116 } else { 115 } else {
117 w.Execute(service); 116 w.Execute(service);
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index 803505452..7b9dd42d8 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -491,7 +491,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u
491 for (PollFD& pollfd : fds) { 491 for (PollFD& pollfd : fds) {
492 ASSERT(pollfd.revents == 0); 492 ASSERT(pollfd.revents == 0);
493 493
494 if (pollfd.fd > MAX_FD || pollfd.fd < 0) { 494 if (pollfd.fd > static_cast<s32>(MAX_FD) || pollfd.fd < 0) {
495 LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd); 495 LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd);
496 pollfd.revents = 0; 496 pollfd.revents = 0;
497 return {0, Errno::SUCCESS}; 497 return {0, Errno::SUCCESS};
@@ -764,6 +764,7 @@ std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, const std::vector<u8>&
764 SockAddrIn guest_addr_in; 764 SockAddrIn guest_addr_in;
765 std::memcpy(&guest_addr_in, addr.data(), sizeof(guest_addr_in)); 765 std::memcpy(&guest_addr_in, addr.data(), sizeof(guest_addr_in));
766 addr_in = Translate(guest_addr_in); 766 addr_in = Translate(guest_addr_in);
767 p_addr_in = &addr_in;
767 } 768 }
768 769
769 return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in)); 770 return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in));
@@ -795,7 +796,7 @@ s32 BSD::FindFreeFileDescriptorHandle() noexcept {
795} 796}
796 797
797bool BSD::IsFileDescriptorValid(s32 fd) const noexcept { 798bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
798 if (fd > MAX_FD || fd < 0) { 799 if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
799 LOG_ERROR(Service, "Invalid file descriptor handle={}", fd); 800 LOG_ERROR(Service, "Invalid file descriptor handle={}", fd);
800 return false; 801 return false;
801 } 802 }
@@ -809,7 +810,7 @@ bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
809bool BSD::IsBlockingSocket(s32 fd) const noexcept { 810bool BSD::IsBlockingSocket(s32 fd) const noexcept {
810 // Inform invalid sockets as non-blocking 811 // Inform invalid sockets as non-blocking
811 // This way we avoid using a worker thread as it will fail without blocking host 812 // This way we avoid using a worker thread as it will fail without blocking host
812 if (fd > MAX_FD || fd < 0) { 813 if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
813 return false; 814 return false;
814 } 815 }
815 if (!file_descriptors[fd]) { 816 if (!file_descriptors[fd]) {
diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp
index 2be8f642d..139743e1d 100644
--- a/src/core/hle/service/sockets/sockets_translate.cpp
+++ b/src/core/hle/service/sockets/sockets_translate.cpp
@@ -131,21 +131,21 @@ u16 TranslatePollEventsToGuest(u16 flags) {
131Network::SockAddrIn Translate(SockAddrIn value) { 131Network::SockAddrIn Translate(SockAddrIn value) {
132 ASSERT(value.len == 0 || value.len == sizeof(value)); 132 ASSERT(value.len == 0 || value.len == sizeof(value));
133 133
134 Network::SockAddrIn result; 134 return {
135 result.family = Translate(static_cast<Domain>(value.family)); 135 .family = Translate(static_cast<Domain>(value.family)),
136 result.ip = value.ip; 136 .ip = value.ip,
137 result.portno = value.portno >> 8 | value.portno << 8; 137 .portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
138 return result; 138 };
139} 139}
140 140
141SockAddrIn Translate(Network::SockAddrIn value) { 141SockAddrIn Translate(Network::SockAddrIn value) {
142 SockAddrIn result; 142 return {
143 result.len = sizeof(result); 143 .len = sizeof(SockAddrIn),
144 result.family = static_cast<u8>(Translate(value.family)); 144 .family = static_cast<u8>(Translate(value.family)),
145 result.portno = value.portno >> 8 | value.portno << 8; 145 .portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
146 result.ip = value.ip; 146 .ip = value.ip,
147 result.zeroes = {}; 147 .zeroes = {},
148 return result; 148 };
149} 149}
150 150
151Network::ShutdownHow Translate(ShutdownHow how) { 151Network::ShutdownHow Translate(ShutdownHow how) {
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 134e83412..394a1bf26 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -89,7 +89,7 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::Virtua
89} 89}
90 90
91AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirectory::Load( 91AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirectory::Load(
92 Kernel::Process& process) { 92 Kernel::Process& process, Core::System& system) {
93 if (is_loaded) { 93 if (is_loaded) {
94 return {ResultStatus::ErrorAlreadyLoaded, {}}; 94 return {ResultStatus::ErrorAlreadyLoaded, {}};
95 } 95 }
@@ -141,9 +141,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
141 continue; 141 continue;
142 } 142 }
143 143
144 const bool should_pass_arguments{std::strcmp(module, "rtld") == 0}; 144 const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
145 const auto tentative_next_load_addr{AppLoader_NSO::LoadModule( 145 const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
146 process, *module_file, code_size, should_pass_arguments, false)}; 146 process, system, *module_file, code_size, should_pass_arguments, false);
147 if (!tentative_next_load_addr) { 147 if (!tentative_next_load_addr) {
148 return {ResultStatus::ErrorLoadingNSO, {}}; 148 return {ResultStatus::ErrorLoadingNSO, {}};
149 } 149 }
@@ -168,9 +168,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
168 } 168 }
169 169
170 const VAddr load_addr{next_load_addr}; 170 const VAddr load_addr{next_load_addr};
171 const bool should_pass_arguments{std::strcmp(module, "rtld") == 0}; 171 const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
172 const auto tentative_next_load_addr{AppLoader_NSO::LoadModule( 172 const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
173 process, *module_file, load_addr, should_pass_arguments, true, pm)}; 173 process, system, *module_file, load_addr, should_pass_arguments, true, pm);
174 if (!tentative_next_load_addr) { 174 if (!tentative_next_load_addr) {
175 return {ResultStatus::ErrorLoadingNSO, {}}; 175 return {ResultStatus::ErrorLoadingNSO, {}};
176 } 176 }
@@ -192,8 +192,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
192 // Register the RomFS if a ".romfs" file was found 192 // Register the RomFS if a ".romfs" file was found
193 if (romfs_iter != files.end() && *romfs_iter != nullptr) { 193 if (romfs_iter != files.end() && *romfs_iter != nullptr) {
194 romfs = *romfs_iter; 194 romfs = *romfs_iter;
195 Core::System::GetInstance().GetFileSystemController().RegisterRomFS( 195 system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
196 std::make_unique<FileSys::RomFSFactory>(*this)); 196 *this, system.GetContentProvider(), system.GetFileSystemController()));
197 } 197 }
198 198
199 is_loaded = true; 199 is_loaded = true;
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index 1c0a354a4..35d340317 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -9,6 +9,10 @@
9#include "core/file_sys/program_metadata.h" 9#include "core/file_sys/program_metadata.h"
10#include "core/loader/loader.h" 10#include "core/loader/loader.h"
11 11
12namespace Core {
13class System;
14}
15
12namespace Loader { 16namespace Loader {
13 17
14/** 18/**
@@ -37,7 +41,7 @@ public:
37 return IdentifyType(file); 41 return IdentifyType(file);
38 } 42 }
39 43
40 LoadResult Load(Kernel::Process& process) override; 44 LoadResult Load(Kernel::Process& process, Core::System& system) override;
41 45
42 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 46 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
43 ResultStatus ReadIcon(std::vector<u8>& buffer) override; 47 ResultStatus ReadIcon(std::vector<u8>& buffer) override;
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index 8f7615115..dca1fcb18 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -383,7 +383,8 @@ FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& file) {
383 return FileType::Error; 383 return FileType::Error;
384} 384}
385 385
386AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process) { 386AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process,
387 [[maybe_unused]] Core::System& system) {
387 if (is_loaded) { 388 if (is_loaded) {
388 return {ResultStatus::ErrorAlreadyLoaded, {}}; 389 return {ResultStatus::ErrorAlreadyLoaded, {}};
389 } 390 }
diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h
index 7ef7770a6..3527933ad 100644
--- a/src/core/loader/elf.h
+++ b/src/core/loader/elf.h
@@ -8,6 +8,10 @@
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "core/loader/loader.h" 9#include "core/loader/loader.h"
10 10
11namespace Core {
12class System;
13}
14
11namespace Loader { 15namespace Loader {
12 16
13/// Loads an ELF/AXF file 17/// Loads an ELF/AXF file
@@ -26,7 +30,7 @@ public:
26 return IdentifyType(file); 30 return IdentifyType(file);
27 } 31 }
28 32
29 LoadResult Load(Kernel::Process& process) override; 33 LoadResult Load(Kernel::Process& process, Core::System& system) override;
30}; 34};
31 35
32} // namespace Loader 36} // namespace Loader
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index 40fa03ad1..5981bcd21 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -43,7 +43,8 @@ FileType AppLoader_KIP::GetFileType() const {
43 : FileType::Error; 43 : FileType::Error;
44} 44}
45 45
46AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) { 46AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process,
47 [[maybe_unused]] Core::System& system) {
47 if (is_loaded) { 48 if (is_loaded) {
48 return {ResultStatus::ErrorAlreadyLoaded, {}}; 49 return {ResultStatus::ErrorAlreadyLoaded, {}};
49 } 50 }
diff --git a/src/core/loader/kip.h b/src/core/loader/kip.h
index 12ca40269..dee05a7b5 100644
--- a/src/core/loader/kip.h
+++ b/src/core/loader/kip.h
@@ -6,6 +6,10 @@
6 6
7#include "core/loader/loader.h" 7#include "core/loader/loader.h"
8 8
9namespace Core {
10class System;
11}
12
9namespace FileSys { 13namespace FileSys {
10class KIP; 14class KIP;
11} 15}
@@ -26,7 +30,7 @@ public:
26 30
27 FileType GetFileType() const override; 31 FileType GetFileType() const override;
28 32
29 LoadResult Load(Kernel::Process& process) override; 33 LoadResult Load(Kernel::Process& process, Core::System& system) override;
30 34
31private: 35private:
32 std::unique_ptr<FileSys::KIP> kip; 36 std::unique_ptr<FileSys::KIP> kip;
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 227ecc704..ac60b097a 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -15,6 +15,10 @@
15#include "core/file_sys/control_metadata.h" 15#include "core/file_sys/control_metadata.h"
16#include "core/file_sys/vfs.h" 16#include "core/file_sys/vfs.h"
17 17
18namespace Core {
19class System;
20}
21
18namespace FileSys { 22namespace FileSys {
19class NACP; 23class NACP;
20} // namespace FileSys 24} // namespace FileSys
@@ -154,9 +158,10 @@ public:
154 /** 158 /**
155 * Load the application and return the created Process instance 159 * Load the application and return the created Process instance
156 * @param process The newly created process. 160 * @param process The newly created process.
161 * @param system The system that this process is being loaded under.
157 * @return The status result of the operation. 162 * @return The status result of the operation.
158 */ 163 */
159 virtual LoadResult Load(Kernel::Process& process) = 0; 164 virtual LoadResult Load(Kernel::Process& process, Core::System& system) = 0;
160 165
161 /** 166 /**
162 * Get the code (typically .code section) of the application 167 * Get the code (typically .code section) of the application
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index a152981a0..49028177b 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -41,7 +41,8 @@ FileType AppLoader_NAX::GetFileType() const {
41 return IdentifyTypeImpl(*nax); 41 return IdentifyTypeImpl(*nax);
42} 42}
43 43
44AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) { 44AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process,
45 [[maybe_unused]] Core::System& system) {
45 if (is_loaded) { 46 if (is_loaded) {
46 return {ResultStatus::ErrorAlreadyLoaded, {}}; 47 return {ResultStatus::ErrorAlreadyLoaded, {}};
47 } 48 }
@@ -65,7 +66,7 @@ AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) {
65 return {nca_status, {}}; 66 return {nca_status, {}};
66 } 67 }
67 68
68 const auto result = nca_loader->Load(process); 69 const auto result = nca_loader->Load(process, system);
69 if (result.first != ResultStatus::Success) { 70 if (result.first != ResultStatus::Success) {
70 return result; 71 return result;
71 } 72 }
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index eaec9bf58..c2b7722b5 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -8,10 +8,12 @@
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "core/loader/loader.h" 9#include "core/loader/loader.h"
10 10
11namespace FileSys { 11namespace Core {
12class System;
13}
12 14
15namespace FileSys {
13class NAX; 16class NAX;
14
15} // namespace FileSys 17} // namespace FileSys
16 18
17namespace Loader { 19namespace Loader {
@@ -33,7 +35,7 @@ public:
33 35
34 FileType GetFileType() const override; 36 FileType GetFileType() const override;
35 37
36 LoadResult Load(Kernel::Process& process) override; 38 LoadResult Load(Kernel::Process& process, Core::System& system) override;
37 39
38 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 40 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
39 u64 ReadRomFSIVFCOffset() const override; 41 u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 5a0469978..fa694de37 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -31,7 +31,7 @@ FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) {
31 return FileType::Error; 31 return FileType::Error;
32} 32}
33 33
34AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) { 34AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process, Core::System& system) {
35 if (is_loaded) { 35 if (is_loaded) {
36 return {ResultStatus::ErrorAlreadyLoaded, {}}; 36 return {ResultStatus::ErrorAlreadyLoaded, {}};
37 } 37 }
@@ -52,14 +52,14 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
52 52
53 directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); 53 directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
54 54
55 const auto load_result = directory_loader->Load(process); 55 const auto load_result = directory_loader->Load(process, system);
56 if (load_result.first != ResultStatus::Success) { 56 if (load_result.first != ResultStatus::Success) {
57 return load_result; 57 return load_result;
58 } 58 }
59 59
60 if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) { 60 if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) {
61 Core::System::GetInstance().GetFileSystemController().RegisterRomFS( 61 system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
62 std::make_unique<FileSys::RomFSFactory>(*this)); 62 *this, system.GetContentProvider(), system.GetFileSystemController()));
63 } 63 }
64 64
65 is_loaded = true; 65 is_loaded = true;
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index e47dc0e47..711070294 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -8,6 +8,10 @@
8#include "core/file_sys/vfs.h" 8#include "core/file_sys/vfs.h"
9#include "core/loader/loader.h" 9#include "core/loader/loader.h"
10 10
11namespace Core {
12class System;
13}
14
11namespace FileSys { 15namespace FileSys {
12class NCA; 16class NCA;
13} 17}
@@ -33,7 +37,7 @@ public:
33 return IdentifyType(file); 37 return IdentifyType(file);
34 } 38 }
35 39
36 LoadResult Load(Kernel::Process& process) override; 40 LoadResult Load(Kernel::Process& process, Core::System& system) override;
37 41
38 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 42 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
39 u64 ReadRomFSIVFCOffset() const override; 43 u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 906544bc9..9fb5eddad 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -208,7 +208,7 @@ bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& fi
208 return LoadNroImpl(process, file.ReadAllBytes(), file.GetName()); 208 return LoadNroImpl(process, file.ReadAllBytes(), file.GetName());
209} 209}
210 210
211AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) { 211AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process, Core::System& system) {
212 if (is_loaded) { 212 if (is_loaded) {
213 return {ResultStatus::ErrorAlreadyLoaded, {}}; 213 return {ResultStatus::ErrorAlreadyLoaded, {}};
214 } 214 }
@@ -218,8 +218,8 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) {
218 } 218 }
219 219
220 if (romfs != nullptr) { 220 if (romfs != nullptr) {
221 Core::System::GetInstance().GetFileSystemController().RegisterRomFS( 221 system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
222 std::make_unique<FileSys::RomFSFactory>(*this)); 222 *this, system.GetContentProvider(), system.GetFileSystemController()));
223 } 223 }
224 224
225 is_loaded = true; 225 is_loaded = true;
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 4593d48fb..a2aab2ecc 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -10,6 +10,10 @@
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/loader/loader.h" 11#include "core/loader/loader.h"
12 12
13namespace Core {
14class System;
15}
16
13namespace FileSys { 17namespace FileSys {
14class NACP; 18class NACP;
15} 19}
@@ -37,7 +41,7 @@ public:
37 return IdentifyType(file); 41 return IdentifyType(file);
38 } 42 }
39 43
40 LoadResult Load(Kernel::Process& process) override; 44 LoadResult Load(Kernel::Process& process, Core::System& system) override;
41 45
42 ResultStatus ReadIcon(std::vector<u8>& buffer) override; 46 ResultStatus ReadIcon(std::vector<u8>& buffer) override;
43 ResultStatus ReadProgramId(u64& out_program_id) override; 47 ResultStatus ReadProgramId(u64& out_program_id) override;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 575330a86..60373cc5f 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -71,7 +71,7 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
71 return FileType::NSO; 71 return FileType::NSO;
72} 72}
73 73
74std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, 74std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, Core::System& system,
75 const FileSys::VfsFile& file, VAddr load_base, 75 const FileSys::VfsFile& file, VAddr load_base,
76 bool should_pass_arguments, bool load_into_process, 76 bool should_pass_arguments, bool load_into_process,
77 std::optional<FileSys::PatchManager> pm) { 77 std::optional<FileSys::PatchManager> pm) {
@@ -148,7 +148,6 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
148 148
149 // Apply cheats if they exist and the program has a valid title ID 149 // Apply cheats if they exist and the program has a valid title ID
150 if (pm) { 150 if (pm) {
151 auto& system = Core::System::GetInstance();
152 system.SetCurrentProcessBuildID(nso_header.build_id); 151 system.SetCurrentProcessBuildID(nso_header.build_id);
153 const auto cheats = pm->CreateCheatList(system, nso_header.build_id); 152 const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
154 if (!cheats.empty()) { 153 if (!cheats.empty()) {
@@ -166,7 +165,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
166 return load_base + image_size; 165 return load_base + image_size;
167} 166}
168 167
169AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) { 168AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process, Core::System& system) {
170 if (is_loaded) { 169 if (is_loaded) {
171 return {ResultStatus::ErrorAlreadyLoaded, {}}; 170 return {ResultStatus::ErrorAlreadyLoaded, {}};
172 } 171 }
@@ -175,7 +174,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
175 174
176 // Load module 175 // Load module
177 const VAddr base_address = process.PageTable().GetCodeRegionStart(); 176 const VAddr base_address = process.PageTable().GetCodeRegionStart();
178 if (!LoadModule(process, *file, base_address, true, true)) { 177 if (!LoadModule(process, system, *file, base_address, true, true)) {
179 return {ResultStatus::ErrorLoadingNSO, {}}; 178 return {ResultStatus::ErrorLoadingNSO, {}};
180 } 179 }
181 180
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index b210830f0..4bd47787d 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -12,6 +12,10 @@
12#include "core/file_sys/patch_manager.h" 12#include "core/file_sys/patch_manager.h"
13#include "core/loader/loader.h" 13#include "core/loader/loader.h"
14 14
15namespace Core {
16class System;
17}
18
15namespace Kernel { 19namespace Kernel {
16class Process; 20class Process;
17} 21}
@@ -80,12 +84,12 @@ public:
80 return IdentifyType(file); 84 return IdentifyType(file);
81 } 85 }
82 86
83 static std::optional<VAddr> LoadModule(Kernel::Process& process, const FileSys::VfsFile& file, 87 static std::optional<VAddr> LoadModule(Kernel::Process& process, Core::System& system,
84 VAddr load_base, bool should_pass_arguments, 88 const FileSys::VfsFile& file, VAddr load_base,
85 bool load_into_process, 89 bool should_pass_arguments, bool load_into_process,
86 std::optional<FileSys::PatchManager> pm = {}); 90 std::optional<FileSys::PatchManager> pm = {});
87 91
88 LoadResult Load(Kernel::Process& process) override; 92 LoadResult Load(Kernel::Process& process, Core::System& system) override;
89 93
90 ResultStatus ReadNSOModules(Modules& modules) override; 94 ResultStatus ReadNSOModules(Modules& modules) override;
91 95
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 13950fc08..15e528fa8 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -71,7 +71,7 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
71 return FileType::Error; 71 return FileType::Error;
72} 72}
73 73
74AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) { 74AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process, Core::System& system) {
75 if (is_loaded) { 75 if (is_loaded) {
76 return {ResultStatus::ErrorAlreadyLoaded, {}}; 76 return {ResultStatus::ErrorAlreadyLoaded, {}};
77 } 77 }
@@ -99,15 +99,14 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
99 return {ResultStatus::ErrorNSPMissingProgramNCA, {}}; 99 return {ResultStatus::ErrorNSPMissingProgramNCA, {}};
100 } 100 }
101 101
102 const auto result = secondary_loader->Load(process); 102 const auto result = secondary_loader->Load(process, system);
103 if (result.first != ResultStatus::Success) { 103 if (result.first != ResultStatus::Success) {
104 return result; 104 return result;
105 } 105 }
106 106
107 FileSys::VirtualFile update_raw; 107 FileSys::VirtualFile update_raw;
108 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) { 108 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
109 Core::System::GetInstance().GetFileSystemController().SetPackedUpdate( 109 system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
110 std::move(update_raw));
111 } 110 }
112 111
113 is_loaded = true; 112 is_loaded = true;
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 868b028d3..b27deb686 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -9,6 +9,10 @@
9#include "core/file_sys/vfs.h" 9#include "core/file_sys/vfs.h"
10#include "core/loader/loader.h" 10#include "core/loader/loader.h"
11 11
12namespace Core {
13class System;
14}
15
12namespace FileSys { 16namespace FileSys {
13class NACP; 17class NACP;
14class NSP; 18class NSP;
@@ -35,7 +39,7 @@ public:
35 return IdentifyType(file); 39 return IdentifyType(file);
36 } 40 }
37 41
38 LoadResult Load(Kernel::Process& process) override; 42 LoadResult Load(Kernel::Process& process, Core::System& system) override;
39 43
40 ResultStatus ReadRomFS(FileSys::VirtualFile& file) override; 44 ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
41 u64 ReadRomFSIVFCOffset() const override; 45 u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 7186ad1ff..25e83af0f 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -49,7 +49,7 @@ FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) {
49 return FileType::Error; 49 return FileType::Error;
50} 50}
51 51
52AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) { 52AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process, Core::System& system) {
53 if (is_loaded) { 53 if (is_loaded) {
54 return {ResultStatus::ErrorAlreadyLoaded, {}}; 54 return {ResultStatus::ErrorAlreadyLoaded, {}};
55 } 55 }
@@ -66,15 +66,14 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
66 return {ResultStatus::ErrorMissingProductionKeyFile, {}}; 66 return {ResultStatus::ErrorMissingProductionKeyFile, {}};
67 } 67 }
68 68
69 const auto result = nca_loader->Load(process); 69 const auto result = nca_loader->Load(process, system);
70 if (result.first != ResultStatus::Success) { 70 if (result.first != ResultStatus::Success) {
71 return result; 71 return result;
72 } 72 }
73 73
74 FileSys::VirtualFile update_raw; 74 FileSys::VirtualFile update_raw;
75 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) { 75 if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
76 Core::System::GetInstance().GetFileSystemController().SetPackedUpdate( 76 system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
77 std::move(update_raw));
78 } 77 }
79 78
80 is_loaded = true; 79 is_loaded = true;
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 618ae2f47..04aea286f 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -9,6 +9,10 @@
9#include "core/file_sys/vfs.h" 9#include "core/file_sys/vfs.h"
10#include "core/loader/loader.h" 10#include "core/loader/loader.h"
11 11
12namespace Core {
13class System;
14}
15
12namespace FileSys { 16namespace FileSys {
13class NACP; 17class NACP;
14class XCI; 18class XCI;
@@ -35,7 +39,7 @@ public:
35 return IdentifyType(file); 39 return IdentifyType(file);
36 } 40 }
37 41
38 LoadResult Load(Kernel::Process& process) override; 42 LoadResult Load(Kernel::Process& process, Core::System& system) override;
39 43
40 ResultStatus ReadRomFS(FileSys::VirtualFile& file) override; 44 ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
41 u64 ReadRomFSIVFCOffset() const override; 45 u64 ReadRomFSIVFCOffset() const override;
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index e503118dd..29284a42d 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -19,10 +19,24 @@
19#include "core/memory/cheat_engine.h" 19#include "core/memory/cheat_engine.h"
20 20
21namespace Core::Memory { 21namespace Core::Memory {
22 22namespace {
23constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; 23constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
24constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; 24constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
25 25
26std::string_view ExtractName(std::string_view data, std::size_t start_index, char match) {
27 auto end_index = start_index;
28 while (data[end_index] != match) {
29 ++end_index;
30 if (end_index > data.size() ||
31 (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
32 return {};
33 }
34 }
35
36 return data.substr(start_index, end_index - start_index);
37}
38} // Anonymous namespace
39
26StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata) 40StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata)
27 : metadata(metadata), system(system) {} 41 : metadata(metadata), system(system) {}
28 42
@@ -82,26 +96,9 @@ CheatParser::~CheatParser() = default;
82 96
83TextCheatParser::~TextCheatParser() = default; 97TextCheatParser::~TextCheatParser() = default;
84 98
85namespace { 99std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
86template <char match>
87std::string_view ExtractName(std::string_view data, std::size_t start_index) {
88 auto end_index = start_index;
89 while (data[end_index] != match) {
90 ++end_index;
91 if (end_index > data.size() ||
92 (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
93 return {};
94 }
95 }
96
97 return data.substr(start_index, end_index - start_index);
98}
99} // Anonymous namespace
100
101std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
102 std::string_view data) const {
103 std::vector<CheatEntry> out(1); 100 std::vector<CheatEntry> out(1);
104 std::optional<u64> current_entry = std::nullopt; 101 std::optional<u64> current_entry;
105 102
106 for (std::size_t i = 0; i < data.size(); ++i) { 103 for (std::size_t i = 0; i < data.size(); ++i) {
107 if (::isspace(data[i])) { 104 if (::isspace(data[i])) {
@@ -115,7 +112,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
115 return {}; 112 return {};
116 } 113 }
117 114
118 const auto name = ExtractName<'}'>(data, i + 1); 115 const auto name = ExtractName(data, i + 1, '}');
119 if (name.empty()) { 116 if (name.empty()) {
120 return {}; 117 return {};
121 } 118 }
@@ -132,7 +129,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
132 current_entry = out.size(); 129 current_entry = out.size();
133 out.emplace_back(); 130 out.emplace_back();
134 131
135 const auto name = ExtractName<']'>(data, i + 1); 132 const auto name = ExtractName(data, i + 1, ']');
136 if (name.empty()) { 133 if (name.empty()) {
137 return {}; 134 return {};
138 } 135 }
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h
index fa039a831..a31002346 100644
--- a/src/core/memory/cheat_engine.h
+++ b/src/core/memory/cheat_engine.h
@@ -47,8 +47,7 @@ class CheatParser {
47public: 47public:
48 virtual ~CheatParser(); 48 virtual ~CheatParser();
49 49
50 virtual std::vector<CheatEntry> Parse(const Core::System& system, 50 [[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0;
51 std::string_view data) const = 0;
52}; 51};
53 52
54// CheatParser implementation that parses text files 53// CheatParser implementation that parses text files
@@ -56,7 +55,7 @@ class TextCheatParser final : public CheatParser {
56public: 55public:
57 ~TextCheatParser() override; 56 ~TextCheatParser() override;
58 57
59 std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override; 58 [[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override;
60}; 59};
61 60
62// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming 61// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
diff --git a/src/core/settings.h b/src/core/settings.h
index 80f0d95a7..9834f44bb 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -152,6 +152,7 @@ struct Values {
152 152
153 bool vibration_enabled; 153 bool vibration_enabled;
154 154
155 bool motion_enabled;
155 std::string motion_device; 156 std::string motion_device;
156 std::string touch_device; 157 std::string touch_device;
157 TouchscreenInput touchscreen; 158 TouchscreenInput touchscreen;
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp
index c6c423c4b..c507c9891 100644
--- a/src/input_common/gcadapter/gc_adapter.cpp
+++ b/src/input_common/gcadapter/gc_adapter.cpp
@@ -4,7 +4,16 @@
4 4
5#include <chrono> 5#include <chrono>
6#include <thread> 6#include <thread>
7
8#ifdef _MSC_VER
9#pragma warning(push)
10#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union
11#endif
7#include <libusb.h> 12#include <libusb.h>
13#ifdef _MSC_VER
14#pragma warning(pop)
15#endif
16
8#include "common/logging/log.h" 17#include "common/logging/log.h"
9#include "input_common/gcadapter/gc_adapter.h" 18#include "input_common/gcadapter/gc_adapter.h"
10 19
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index ea1a1cee6..062ec66b5 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -12,6 +12,7 @@
12#include "input_common/main.h" 12#include "input_common/main.h"
13#include "input_common/motion_emu.h" 13#include "input_common/motion_emu.h"
14#include "input_common/touch_from_button.h" 14#include "input_common/touch_from_button.h"
15#include "input_common/udp/client.h"
15#include "input_common/udp/udp.h" 16#include "input_common/udp/udp.h"
16#ifdef HAVE_SDL2 17#ifdef HAVE_SDL2
17#include "input_common/sdl/sdl.h" 18#include "input_common/sdl/sdl.h"
@@ -40,7 +41,11 @@ struct InputSubsystem::Impl {
40 sdl = SDL::Init(); 41 sdl = SDL::Init();
41#endif 42#endif
42 43
43 udp = CemuhookUDP::Init(); 44 udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
45 udpmotion = std::make_shared<UDPMotionFactory>(udp);
46 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
47 udptouch = std::make_shared<UDPTouchFactory>(udp);
48 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
44 } 49 }
45 50
46 void Shutdown() { 51 void Shutdown() {
@@ -53,12 +58,17 @@ struct InputSubsystem::Impl {
53#ifdef HAVE_SDL2 58#ifdef HAVE_SDL2
54 sdl.reset(); 59 sdl.reset();
55#endif 60#endif
56 udp.reset();
57 Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); 61 Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
58 Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); 62 Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
59 63
60 gcbuttons.reset(); 64 gcbuttons.reset();
61 gcanalog.reset(); 65 gcanalog.reset();
66
67 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
68 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
69
70 udpmotion.reset();
71 udptouch.reset();
62 } 72 }
63 73
64 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { 74 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
@@ -109,14 +119,28 @@ struct InputSubsystem::Impl {
109 return {}; 119 return {};
110 } 120 }
111 121
122 [[nodiscard]] MotionMapping GetMotionMappingForDevice(
123 const Common::ParamPackage& params) const {
124 if (!params.Has("class") || params.Get("class", "") == "any") {
125 return {};
126 }
127 if (params.Get("class", "") == "cemuhookudp") {
128 // TODO return the correct motion device
129 return {};
130 }
131 return {};
132 }
133
112 std::shared_ptr<Keyboard> keyboard; 134 std::shared_ptr<Keyboard> keyboard;
113 std::shared_ptr<MotionEmu> motion_emu; 135 std::shared_ptr<MotionEmu> motion_emu;
114#ifdef HAVE_SDL2 136#ifdef HAVE_SDL2
115 std::unique_ptr<SDL::State> sdl; 137 std::unique_ptr<SDL::State> sdl;
116#endif 138#endif
117 std::unique_ptr<CemuhookUDP::State> udp;
118 std::shared_ptr<GCButtonFactory> gcbuttons; 139 std::shared_ptr<GCButtonFactory> gcbuttons;
119 std::shared_ptr<GCAnalogFactory> gcanalog; 140 std::shared_ptr<GCAnalogFactory> gcanalog;
141 std::shared_ptr<UDPMotionFactory> udpmotion;
142 std::shared_ptr<UDPTouchFactory> udptouch;
143 std::shared_ptr<CemuhookUDP::Client> udp;
120}; 144};
121 145
122InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} 146InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -175,6 +199,22 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const {
175 return impl->gcbuttons.get(); 199 return impl->gcbuttons.get();
176} 200}
177 201
202UDPMotionFactory* InputSubsystem::GetUDPMotions() {
203 return impl->udpmotion.get();
204}
205
206const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
207 return impl->udpmotion.get();
208}
209
210UDPTouchFactory* InputSubsystem::GetUDPTouch() {
211 return impl->udptouch.get();
212}
213
214const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
215 return impl->udptouch.get();
216}
217
178void InputSubsystem::ReloadInputDevices() { 218void InputSubsystem::ReloadInputDevices() {
179 if (!impl->udp) { 219 if (!impl->udp) {
180 return; 220 return;
diff --git a/src/input_common/main.h b/src/input_common/main.h
index f3fbf696e..dded3f1ef 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -21,10 +21,14 @@ namespace Settings::NativeButton {
21enum Values : int; 21enum Values : int;
22} 22}
23 23
24namespace Settings::NativeMotion {
25enum Values : int;
26}
27
24namespace InputCommon { 28namespace InputCommon {
25namespace Polling { 29namespace Polling {
26 30
27enum class DeviceType { Button, AnalogPreferred }; 31enum class DeviceType { Button, AnalogPreferred, Motion };
28 32
29/** 33/**
30 * A class that can be used to get inputs from an input device like controllers without having to 34 * A class that can be used to get inputs from an input device like controllers without having to
@@ -50,6 +54,8 @@ public:
50 54
51class GCAnalogFactory; 55class GCAnalogFactory;
52class GCButtonFactory; 56class GCButtonFactory;
57class UDPMotionFactory;
58class UDPTouchFactory;
53class Keyboard; 59class Keyboard;
54class MotionEmu; 60class MotionEmu;
55 61
@@ -59,6 +65,7 @@ class MotionEmu;
59 */ 65 */
60using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; 66using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
61using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; 67using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
68using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>;
62 69
63class InputSubsystem { 70class InputSubsystem {
64public: 71public:
@@ -103,6 +110,9 @@ public:
103 /// Retrieves the button mappings for the given device. 110 /// Retrieves the button mappings for the given device.
104 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const; 111 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const;
105 112
113 /// Retrieves the motion mappings for the given device.
114 [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
115
106 /// Retrieves the underlying GameCube analog handler. 116 /// Retrieves the underlying GameCube analog handler.
107 [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); 117 [[nodiscard]] GCAnalogFactory* GetGCAnalogs();
108 118
@@ -115,6 +125,18 @@ public:
115 /// Retrieves the underlying GameCube button handler. 125 /// Retrieves the underlying GameCube button handler.
116 [[nodiscard]] const GCButtonFactory* GetGCButtons() const; 126 [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
117 127
128 /// Retrieves the underlying udp motion handler.
129 [[nodiscard]] UDPMotionFactory* GetUDPMotions();
130
131 /// Retrieves the underlying udp motion handler.
132 [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
133
134 /// Retrieves the underlying udp touch handler.
135 [[nodiscard]] UDPTouchFactory* GetUDPTouch();
136
137 /// Retrieves the underlying udp touch handler.
138 [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
139
118 /// Reloads the input devices 140 /// Reloads the input devices
119 void ReloadInputDevices(); 141 void ReloadInputDevices();
120 142
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
index d4cdf76a3..69fd3c1d2 100644
--- a/src/input_common/motion_emu.cpp
+++ b/src/input_common/motion_emu.cpp
@@ -56,7 +56,7 @@ public:
56 is_tilting = false; 56 is_tilting = false;
57 } 57 }
58 58
59 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() { 59 Input::MotionStatus GetStatus() {
60 std::lock_guard guard{status_mutex}; 60 std::lock_guard guard{status_mutex};
61 return status; 61 return status;
62 } 62 }
@@ -76,7 +76,7 @@ private:
76 76
77 Common::Event shutdown_event; 77 Common::Event shutdown_event;
78 78
79 std::tuple<Common::Vec3<float>, Common::Vec3<float>> status; 79 Input::MotionStatus status;
80 std::mutex status_mutex; 80 std::mutex status_mutex;
81 81
82 // Note: always keep the thread declaration at the end so that other objects are initialized 82 // Note: always keep the thread declaration at the end so that other objects are initialized
@@ -113,10 +113,19 @@ private:
113 gravity = QuaternionRotate(inv_q, gravity); 113 gravity = QuaternionRotate(inv_q, gravity);
114 angular_rate = QuaternionRotate(inv_q, angular_rate); 114 angular_rate = QuaternionRotate(inv_q, angular_rate);
115 115
116 // TODO: Calculate the correct rotation vector and orientation matrix
117 const auto matrix4x4 = q.ToMatrix();
118 const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f);
119 const std::array orientation{
120 Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
121 Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
122 Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]),
123 };
124
116 // Update the sensor state 125 // Update the sensor state
117 { 126 {
118 std::lock_guard guard{status_mutex}; 127 std::lock_guard guard{status_mutex};
119 status = std::make_tuple(gravity, angular_rate); 128 status = std::make_tuple(gravity, angular_rate, rotation, orientation);
120 } 129 }
121 } 130 }
122 } 131 }
@@ -131,7 +140,7 @@ public:
131 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); 140 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
132 } 141 }
133 142
134 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { 143 Input::MotionStatus GetStatus() const override {
135 return device->GetStatus(); 144 return device->GetStatus();
136 } 145 }
137 146
diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp
index 80c719cf4..b66c05856 100644
--- a/src/input_common/settings.cpp
+++ b/src/input_common/settings.cpp
@@ -14,6 +14,13 @@ const std::array<const char*, NumButtons> mapping = {{
14}}; 14}};
15} 15}
16 16
17namespace NativeMotion {
18const std::array<const char*, NumMotions> mapping = {{
19 "motionleft",
20 "motionright",
21}};
22}
23
17namespace NativeAnalog { 24namespace NativeAnalog {
18const std::array<const char*, NumAnalogs> mapping = {{ 25const std::array<const char*, NumAnalogs> mapping = {{
19 "lstick", 26 "lstick",
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
index 2d258960b..ab0b95cf1 100644
--- a/src/input_common/settings.h
+++ b/src/input_common/settings.h
@@ -66,6 +66,21 @@ constexpr int NUM_STICKS_HID = NumAnalogs;
66extern const std::array<const char*, NumAnalogs> mapping; 66extern const std::array<const char*, NumAnalogs> mapping;
67} // namespace NativeAnalog 67} // namespace NativeAnalog
68 68
69namespace NativeMotion {
70enum Values : int {
71 MOTIONLEFT,
72 MOTIONRIGHT,
73
74 NumMotions,
75};
76
77constexpr int MOTION_HID_BEGIN = MOTIONLEFT;
78constexpr int MOTION_HID_END = NumMotions;
79constexpr int NUM_MOTION_HID = NumMotions;
80
81extern const std::array<const char*, NumMotions> mapping;
82} // namespace NativeMotion
83
69namespace NativeMouseButton { 84namespace NativeMouseButton {
70enum Values { 85enum Values {
71 Left, 86 Left,
@@ -292,6 +307,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
292 307
293using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; 308using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
294using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; 309using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
310using MotionRaw = std::array<std::string, NativeMotion::NumMotions>;
295using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; 311using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
296using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; 312using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
297using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; 313using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
@@ -314,6 +330,7 @@ struct PlayerInput {
314 ControllerType controller_type; 330 ControllerType controller_type;
315 ButtonsRaw buttons; 331 ButtonsRaw buttons;
316 AnalogsRaw analogs; 332 AnalogsRaw analogs;
333 MotionRaw motions;
317 std::string lstick_mod; 334 std::string lstick_mod;
318 std::string rstick_mod; 335 std::string rstick_mod;
319 336
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
index 3f4eaf448..2b6a68d4b 100644
--- a/src/input_common/udp/client.cpp
+++ b/src/input_common/udp/client.cpp
@@ -2,14 +2,13 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
6#include <array>
7#include <chrono> 5#include <chrono>
8#include <cstring> 6#include <cstring>
9#include <functional> 7#include <functional>
10#include <thread> 8#include <thread>
11#include <boost/asio.hpp> 9#include <boost/asio.hpp>
12#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/settings.h"
13#include "input_common/udp/client.h" 12#include "input_common/udp/client.h"
14#include "input_common/udp/protocol.h" 13#include "input_common/udp/protocol.h"
15 14
@@ -131,21 +130,59 @@ static void SocketLoop(Socket* socket) {
131 socket->Loop(); 130 socket->Loop();
132} 131}
133 132
134Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, 133Client::Client() {
135 u8 pad_index, u32 client_id) 134 LOG_INFO(Input, "Udp Initialization started");
136 : status(std::move(status)) { 135 for (std::size_t client = 0; client < clients.size(); client++) {
137 StartCommunication(host, port, pad_index, client_id); 136 u8 pad = client % 4;
137 StartCommunication(client, Settings::values.udp_input_address,
138 Settings::values.udp_input_port, pad, 24872);
139 // Set motion parameters
140 // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
141 // Real HW values are unknown, 0.0001 is an approximate to Standard
142 clients[client].motion.SetGyroThreshold(0.0001f);
143 }
138} 144}
139 145
140Client::~Client() { 146Client::~Client() {
141 socket->Stop(); 147 Reset();
142 thread.join(); 148}
149
150std::vector<Common::ParamPackage> Client::GetInputDevices() const {
151 std::vector<Common::ParamPackage> devices;
152 for (std::size_t client = 0; client < clients.size(); client++) {
153 if (!DeviceConnected(client)) {
154 continue;
155 }
156 std::string name = fmt::format("UDP Controller {}", client);
157 devices.emplace_back(Common::ParamPackage{
158 {"class", "cemuhookudp"},
159 {"display", std::move(name)},
160 {"port", std::to_string(client)},
161 });
162 }
163 return devices;
143} 164}
144 165
166bool Client::DeviceConnected(std::size_t pad) const {
167 // Use last timestamp to detect if the socket has stopped sending data
168 const auto now = std::chrono::system_clock::now();
169 u64 time_difference =
170 std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update)
171 .count();
172 return time_difference < 1000 && clients[pad].active == 1;
173}
174
175void Client::ReloadUDPClient() {
176 for (std::size_t client = 0; client < clients.size(); client++) {
177 ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client);
178 }
179}
145void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { 180void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
146 socket->Stop(); 181 // client number must be determined from host / port and pad index
147 thread.join(); 182 std::size_t client = pad_index;
148 StartCommunication(host, port, pad_index, client_id); 183 clients[client].socket->Stop();
184 clients[client].thread.join();
185 StartCommunication(client, host, port, pad_index, client_id);
149} 186}
150 187
151void Client::OnVersion(Response::Version data) { 188void Client::OnVersion(Response::Version data) {
@@ -157,23 +194,39 @@ void Client::OnPortInfo(Response::PortInfo data) {
157} 194}
158 195
159void Client::OnPadData(Response::PadData data) { 196void Client::OnPadData(Response::PadData data) {
197 // client number must be determined from host / port and pad index
198 std::size_t client = data.info.id;
160 LOG_TRACE(Input, "PadData packet received"); 199 LOG_TRACE(Input, "PadData packet received");
161 if (data.packet_counter <= packet_sequence) { 200 if (data.packet_counter == clients[client].packet_sequence) {
162 LOG_WARNING( 201 LOG_WARNING(
163 Input, 202 Input,
164 "PadData packet dropped because its stale info. Current count: {} Packet count: {}", 203 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
165 packet_sequence, data.packet_counter); 204 clients[client].packet_sequence, data.packet_counter);
166 return; 205 return;
167 } 206 }
168 packet_sequence = data.packet_counter; 207 clients[client].active = data.info.is_pad_active;
169 // TODO: Check how the Switch handles motions and how the CemuhookUDP motion 208 clients[client].packet_sequence = data.packet_counter;
170 // directions correspond to the ones of the Switch 209 const auto now = std::chrono::system_clock::now();
171 Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); 210 u64 time_difference = std::chrono::duration_cast<std::chrono::microseconds>(
172 Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); 211 now - clients[client].last_motion_update)
173 { 212 .count();
174 std::lock_guard guard(status->update_mutex); 213 clients[client].last_motion_update = now;
214 Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
215 clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
216 // Gyroscope values are not it the correct scale from better joy.
217 // Dividing by 312 allows us to make one full turn = 1 turn
218 // This must be a configurable valued called sensitivity
219 clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f);
220 clients[client].motion.UpdateRotation(time_difference);
221 clients[client].motion.UpdateOrientation(time_difference);
222 Common::Vec3f gyroscope = clients[client].motion.GetGyroscope();
223 Common::Vec3f accelerometer = clients[client].motion.GetAcceleration();
224 Common::Vec3f rotation = clients[client].motion.GetRotations();
225 std::array<Common::Vec3f, 3> orientation = clients[client].motion.GetOrientation();
175 226
176 status->motion_status = {accel, gyro}; 227 {
228 std::lock_guard guard(clients[client].status.update_mutex);
229 clients[client].status.motion_status = {accelerometer, gyroscope, rotation, orientation};
177 230
178 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates 231 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
179 // between a simple "tap" and a hard press that causes the touch screen to click. 232 // between a simple "tap" and a hard press that causes the touch screen to click.
@@ -182,11 +235,11 @@ void Client::OnPadData(Response::PadData data) {
182 float x = 0; 235 float x = 0;
183 float y = 0; 236 float y = 0;
184 237
185 if (is_active && status->touch_calibration) { 238 if (is_active && clients[client].status.touch_calibration) {
186 const u16 min_x = status->touch_calibration->min_x; 239 const u16 min_x = clients[client].status.touch_calibration->min_x;
187 const u16 max_x = status->touch_calibration->max_x; 240 const u16 max_x = clients[client].status.touch_calibration->max_x;
188 const u16 min_y = status->touch_calibration->min_y; 241 const u16 min_y = clients[client].status.touch_calibration->min_y;
189 const u16 max_y = status->touch_calibration->max_y; 242 const u16 max_y = clients[client].status.touch_calibration->max_y;
190 243
191 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / 244 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
192 static_cast<float>(max_x - min_x); 245 static_cast<float>(max_x - min_x);
@@ -194,17 +247,80 @@ void Client::OnPadData(Response::PadData data) {
194 static_cast<float>(max_y - min_y); 247 static_cast<float>(max_y - min_y);
195 } 248 }
196 249
197 status->touch_status = {x, y, is_active}; 250 clients[client].status.touch_status = {x, y, is_active};
251
252 if (configuring) {
253 UpdateYuzuSettings(client, accelerometer, gyroscope, is_active);
254 }
198 } 255 }
199} 256}
200 257
201void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { 258void Client::StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
259 u32 client_id) {
202 SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, 260 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
203 [this](Response::PortInfo info) { OnPortInfo(info); }, 261 [this](Response::PortInfo info) { OnPortInfo(info); },
204 [this](Response::PadData data) { OnPadData(data); }}; 262 [this](Response::PadData data) { OnPadData(data); }};
205 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); 263 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
206 socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); 264 clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
207 thread = std::thread{SocketLoop, this->socket.get()}; 265 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
266}
267
268void Client::Reset() {
269 for (std::size_t client = 0; client < clients.size(); client++) {
270 clients[client].socket->Stop();
271 clients[client].thread.join();
272 }
273}
274
275void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
276 const Common::Vec3<float>& gyro, bool touch) {
277 UDPPadStatus pad;
278 if (touch) {
279 pad.touch = PadTouch::Click;
280 pad_queue[client].Push(pad);
281 }
282 for (size_t i = 0; i < 3; ++i) {
283 if (gyro[i] > 6.0f || gyro[i] < -6.0f) {
284 pad.motion = static_cast<PadMotion>(i);
285 pad.motion_value = gyro[i];
286 pad_queue[client].Push(pad);
287 }
288 if (acc[i] > 2.0f || acc[i] < -2.0f) {
289 pad.motion = static_cast<PadMotion>(i + 3);
290 pad.motion_value = acc[i];
291 pad_queue[client].Push(pad);
292 }
293 }
294}
295
296void Client::BeginConfiguration() {
297 for (auto& pq : pad_queue) {
298 pq.Clear();
299 }
300 configuring = true;
301}
302
303void Client::EndConfiguration() {
304 for (auto& pq : pad_queue) {
305 pq.Clear();
306 }
307 configuring = false;
308}
309
310DeviceStatus& Client::GetPadState(std::size_t pad) {
311 return clients[pad].status;
312}
313
314const DeviceStatus& Client::GetPadState(std::size_t pad) const {
315 return clients[pad].status;
316}
317
318std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() {
319 return pad_queue;
320}
321
322const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const {
323 return pad_queue;
208} 324}
209 325
210void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, 326void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index b8c654755..523dc6a7a 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/udp/client.h
@@ -12,8 +12,12 @@
12#include <thread> 12#include <thread>
13#include <tuple> 13#include <tuple>
14#include "common/common_types.h" 14#include "common/common_types.h"
15#include "common/param_package.h"
15#include "common/thread.h" 16#include "common/thread.h"
17#include "common/threadsafe_queue.h"
16#include "common/vector_math.h" 18#include "common/vector_math.h"
19#include "core/frontend/input.h"
20#include "input_common/motion_input.h"
17 21
18namespace InputCommon::CemuhookUDP { 22namespace InputCommon::CemuhookUDP {
19 23
@@ -28,9 +32,30 @@ struct PortInfo;
28struct Version; 32struct Version;
29} // namespace Response 33} // namespace Response
30 34
35enum class PadMotion {
36 GyroX,
37 GyroY,
38 GyroZ,
39 AccX,
40 AccY,
41 AccZ,
42 Undefined,
43};
44
45enum class PadTouch {
46 Click,
47 Undefined,
48};
49
50struct UDPPadStatus {
51 PadTouch touch{PadTouch::Undefined};
52 PadMotion motion{PadMotion::Undefined};
53 f32 motion_value{0.0f};
54};
55
31struct DeviceStatus { 56struct DeviceStatus {
32 std::mutex update_mutex; 57 std::mutex update_mutex;
33 std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status; 58 Input::MotionStatus motion_status;
34 std::tuple<float, float, bool> touch_status; 59 std::tuple<float, float, bool> touch_status;
35 60
36 // calibration data for scaling the device's touch area to 3ds 61 // calibration data for scaling the device's touch area to 3ds
@@ -45,22 +70,58 @@ struct DeviceStatus {
45 70
46class Client { 71class Client {
47public: 72public:
48 explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, 73 // Initialize the UDP client capture and read sequence
49 u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); 74 Client();
75
76 // Close and release the client
50 ~Client(); 77 ~Client();
78
79 // Used for polling
80 void BeginConfiguration();
81 void EndConfiguration();
82
83 std::vector<Common::ParamPackage> GetInputDevices() const;
84
85 bool DeviceConnected(std::size_t pad) const;
86 void ReloadUDPClient();
51 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, 87 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
52 u32 client_id = 24872); 88 u32 client_id = 24872);
53 89
90 std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue();
91 const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const;
92
93 DeviceStatus& GetPadState(std::size_t pad);
94 const DeviceStatus& GetPadState(std::size_t pad) const;
95
54private: 96private:
97 struct ClientData {
98 std::unique_ptr<Socket> socket;
99 DeviceStatus status;
100 std::thread thread;
101 u64 packet_sequence = 0;
102 u8 active;
103
104 // Realtime values
105 // motion is initalized with PID values for drift correction on joycons
106 InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
107 std::chrono::time_point<std::chrono::system_clock> last_motion_update;
108 };
109
110 // For shutting down, clear all data, join all threads, release usb
111 void Reset();
112
55 void OnVersion(Response::Version); 113 void OnVersion(Response::Version);
56 void OnPortInfo(Response::PortInfo); 114 void OnPortInfo(Response::PortInfo);
57 void OnPadData(Response::PadData); 115 void OnPadData(Response::PadData);
58 void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); 116 void StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
117 u32 client_id);
118 void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
119 const Common::Vec3<float>& gyro, bool touch);
120
121 bool configuring = false;
59 122
60 std::unique_ptr<Socket> socket; 123 std::array<ClientData, 4> clients;
61 std::shared_ptr<DeviceStatus> status; 124 std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue;
62 std::thread thread;
63 u64 packet_sequence = 0;
64}; 125};
65 126
66/// An async job allowing configuration of the touchpad calibration. 127/// An async job allowing configuration of the touchpad calibration.
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index 4b347e47e..eba077a36 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -1,105 +1,144 @@
1// Copyright 2018 Citra Emulator Project 1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <atomic>
6#include <list>
5#include <mutex> 7#include <mutex>
6#include <optional> 8#include <utility>
7#include <tuple> 9#include "common/assert.h"
8 10#include "common/threadsafe_queue.h"
9#include "common/param_package.h"
10#include "core/frontend/input.h"
11#include "core/settings.h"
12#include "input_common/udp/client.h" 11#include "input_common/udp/client.h"
13#include "input_common/udp/udp.h" 12#include "input_common/udp/udp.h"
14 13
15namespace InputCommon::CemuhookUDP { 14namespace InputCommon {
16 15
17class UDPTouchDevice final : public Input::TouchDevice { 16class UDPMotion final : public Input::MotionDevice {
18public: 17public:
19 explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 18 UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
20 std::tuple<float, float, bool> GetStatus() const override { 19 : ip(ip_), port(port_), pad(pad_), client(client_) {}
21 std::lock_guard guard(status->update_mutex); 20
22 return status->touch_status; 21 Input::MotionStatus GetStatus() const override {
22 return client->GetPadState(pad).motion_status;
23 } 23 }
24 24
25private: 25private:
26 std::shared_ptr<DeviceStatus> status; 26 const std::string ip;
27 const int port;
28 const int pad;
29 CemuhookUDP::Client* client;
30 mutable std::mutex mutex;
27}; 31};
28 32
29class UDPMotionDevice final : public Input::MotionDevice { 33/// A motion device factory that creates motion devices from JC Adapter
30public: 34UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
31 explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 35 : client(std::move(client_)) {}
32 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { 36
33 std::lock_guard guard(status->update_mutex); 37/**
34 return status->motion_status; 38 * Creates motion device
35 } 39 * @param params contains parameters for creating the device:
40 * - "port": the nth jcpad on the adapter
41 */
42std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
43 const std::string ip = params.Get("ip", "127.0.0.1");
44 const int port = params.Get("port", 26760);
45 const int pad = params.Get("pad_index", 0);
46
47 return std::make_unique<UDPMotion>(ip, port, pad, client.get());
48}
36 49
37private: 50void UDPMotionFactory::BeginConfiguration() {
38 std::shared_ptr<DeviceStatus> status; 51 polling = true;
39}; 52 client->BeginConfiguration();
53}
40 54
41class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { 55void UDPMotionFactory::EndConfiguration() {
42public: 56 polling = false;
43 explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 57 client->EndConfiguration();
44 58}
45 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { 59
46 { 60Common::ParamPackage UDPMotionFactory::GetNextInput() {
47 std::lock_guard guard(status->update_mutex); 61 Common::ParamPackage params;
48 status->touch_calibration = DeviceStatus::CalibrationData{}; 62 CemuhookUDP::UDPPadStatus pad;
49 // These default values work well for DS4 but probably not other touch inputs 63 auto& queue = client->GetPadQueue();
50 status->touch_calibration->min_x = params.Get("min_x", 100); 64 for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
51 status->touch_calibration->min_y = params.Get("min_y", 50); 65 while (queue[pad_number].Pop(pad)) {
52 status->touch_calibration->max_x = params.Get("max_x", 1800); 66 if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
53 status->touch_calibration->max_y = params.Get("max_y", 850); 67 continue;
68 }
69 params.Set("engine", "cemuhookudp");
70 params.Set("ip", "127.0.0.1");
71 params.Set("port", 26760);
72 params.Set("pad_index", static_cast<int>(pad_number));
73 params.Set("motion", static_cast<u16>(pad.motion));
74 return params;
54 } 75 }
55 return std::make_unique<UDPTouchDevice>(status);
56 } 76 }
77 return params;
78}
57 79
58private: 80class UDPTouch final : public Input::TouchDevice {
59 std::shared_ptr<DeviceStatus> status;
60};
61
62class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
63public: 81public:
64 explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 82 UDPTouch(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
83 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
65 84
66 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { 85 std::tuple<float, float, bool> GetStatus() const override {
67 return std::make_unique<UDPMotionDevice>(status); 86 return client->GetPadState(pad).touch_status;
68 } 87 }
69 88
70private: 89private:
71 std::shared_ptr<DeviceStatus> status; 90 const std::string ip;
91 const int port;
92 const int pad;
93 CemuhookUDP::Client* client;
94 mutable std::mutex mutex;
72}; 95};
73 96
74State::State() { 97/// A motion device factory that creates motion devices from JC Adapter
75 auto status = std::make_shared<DeviceStatus>(); 98UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
76 client = 99 : client(std::move(client_)) {}
77 std::make_unique<Client>(status, Settings::values.udp_input_address, 100
78 Settings::values.udp_input_port, Settings::values.udp_pad_index); 101/**
79 102 * Creates motion device
80 motion_factory = std::make_shared<UDPMotionFactory>(status); 103 * @param params contains parameters for creating the device:
81 touch_factory = std::make_shared<UDPTouchFactory>(status); 104 * - "port": the nth jcpad on the adapter
82 105 */
83 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory); 106std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
84 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory); 107 const std::string ip = params.Get("ip", "127.0.0.1");
108 const int port = params.Get("port", 26760);
109 const int pad = params.Get("pad_index", 0);
110
111 return std::make_unique<UDPTouch>(ip, port, pad, client.get());
85} 112}
86 113
87State::~State() { 114void UDPTouchFactory::BeginConfiguration() {
88 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); 115 polling = true;
89 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); 116 client->BeginConfiguration();
90} 117}
91 118
92std::vector<Common::ParamPackage> State::GetInputDevices() const { 119void UDPTouchFactory::EndConfiguration() {
93 // TODO support binding udp devices 120 polling = false;
94 return {}; 121 client->EndConfiguration();
95} 122}
96 123
97void State::ReloadUDPClient() { 124Common::ParamPackage UDPTouchFactory::GetNextInput() {
98 client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, 125 Common::ParamPackage params;
99 Settings::values.udp_pad_index); 126 CemuhookUDP::UDPPadStatus pad;
127 auto& queue = client->GetPadQueue();
128 for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
129 while (queue[pad_number].Pop(pad)) {
130 if (pad.touch == CemuhookUDP::PadTouch::Undefined) {
131 continue;
132 }
133 params.Set("engine", "cemuhookudp");
134 params.Set("ip", "127.0.0.1");
135 params.Set("port", 26760);
136 params.Set("pad_index", static_cast<int>(pad_number));
137 params.Set("touch", static_cast<u16>(pad.touch));
138 return params;
139 }
140 }
141 return params;
100} 142}
101 143
102std::unique_ptr<State> Init() { 144} // namespace InputCommon
103 return std::make_unique<State>();
104}
105} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
index 672a5c812..ea3fd4175 100644
--- a/src/input_common/udp/udp.h
+++ b/src/input_common/udp/udp.h
@@ -1,32 +1,57 @@
1// Copyright 2018 Citra Emulator Project 1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include <vector> 8#include "core/frontend/input.h"
9#include "common/param_package.h" 9#include "input_common/udp/client.h"
10 10
11namespace InputCommon::CemuhookUDP { 11namespace InputCommon {
12 12
13class Client; 13/// A motion device factory that creates motion devices from udp clients
14class UDPMotionFactory; 14class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
15class UDPTouchFactory;
16
17class State {
18public: 15public:
19 State(); 16 explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
20 ~State(); 17
21 void ReloadUDPClient(); 18 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
22 std::vector<Common::ParamPackage> GetInputDevices() const; 19
20 Common::ParamPackage GetNextInput();
21
22 /// For device input configuration/polling
23 void BeginConfiguration();
24 void EndConfiguration();
25
26 bool IsPolling() const {
27 return polling;
28 }
23 29
24private: 30private:
25 std::unique_ptr<Client> client; 31 std::shared_ptr<CemuhookUDP::Client> client;
26 std::shared_ptr<UDPMotionFactory> motion_factory; 32 bool polling = false;
27 std::shared_ptr<UDPTouchFactory> touch_factory;
28}; 33};
29 34
30std::unique_ptr<State> Init(); 35/// A touch device factory that creates touch devices from udp clients
36class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
37public:
38 explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
39
40 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
41
42 Common::ParamPackage GetNextInput();
43
44 /// For device input configuration/polling
45 void BeginConfiguration();
46 void EndConfiguration();
47
48 bool IsPolling() const {
49 return polling;
50 }
51
52private:
53 std::shared_ptr<CemuhookUDP::Client> client;
54 bool polling = false;
55};
31 56
32} // namespace InputCommon::CemuhookUDP 57} // namespace InputCommon
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index d85f1e9d1..f52b55ef3 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -269,5 +269,5 @@ endif()
269if (MSVC) 269if (MSVC)
270 target_compile_options(video_core PRIVATE /we4267) 270 target_compile_options(video_core PRIVATE /we4267)
271else() 271else()
272 target_compile_options(video_core PRIVATE -Werror=conversion -Wno-error=sign-conversion) 272 target_compile_options(video_core PRIVATE -Werror=conversion -Wno-error=sign-conversion -Werror=switch)
273endif() 273endif()
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index 6e50661a3..9409c4075 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -87,12 +87,12 @@ void Fermi2D::HandleSurfaceCopy() {
87 const Common::Rectangle<u32> src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2}; 87 const Common::Rectangle<u32> src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2};
88 const Common::Rectangle<u32> dst_rect{regs.blit_dst_x, regs.blit_dst_y, dst_blit_x2, 88 const Common::Rectangle<u32> dst_rect{regs.blit_dst_x, regs.blit_dst_y, dst_blit_x2,
89 dst_blit_y2}; 89 dst_blit_y2};
90 Config copy_config; 90 const Config copy_config{
91 copy_config.operation = regs.operation; 91 .operation = regs.operation,
92 copy_config.filter = regs.blit_control.filter; 92 .filter = regs.blit_control.filter,
93 copy_config.src_rect = src_rect; 93 .src_rect = src_rect,
94 copy_config.dst_rect = dst_rect; 94 .dst_rect = dst_rect,
95 95 };
96 if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) { 96 if (!rasterizer->AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) {
97 UNIMPLEMENTED(); 97 UNIMPLEMENTED();
98 } 98 }
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 213abfaae..0909709ec 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -145,8 +145,8 @@ public:
145 } regs{}; 145 } regs{};
146 146
147 struct Config { 147 struct Config {
148 Operation operation; 148 Operation operation{};
149 Filter filter; 149 Filter filter{};
150 Common::Rectangle<u32> src_rect; 150 Common::Rectangle<u32> src_rect;
151 Common::Rectangle<u32> dst_rect; 151 Common::Rectangle<u32> dst_rect;
152 }; 152 };
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 3f75fcd2b..ce3a65122 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -1443,8 +1443,10 @@ private:
1443 return expr + ", vec2(0.0), vec2(0.0))"; 1443 return expr + ", vec2(0.0), vec2(0.0))";
1444 case TextureType::TextureCube: 1444 case TextureType::TextureCube:
1445 return expr + ", vec3(0.0), vec3(0.0))"; 1445 return expr + ", vec3(0.0), vec3(0.0))";
1446 default:
1447 UNREACHABLE();
1448 break;
1446 } 1449 }
1447 UNREACHABLE();
1448 } 1450 }
1449 1451
1450 for (const auto& variant : extras) { 1452 for (const auto& variant : extras) {
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index fe9bd4b5a..a8be2aa37 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -47,6 +47,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
47 return GL_UNSIGNED_INT; 47 return GL_UNSIGNED_INT;
48 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 48 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
49 return GL_UNSIGNED_INT_2_10_10_10_REV; 49 return GL_UNSIGNED_INT_2_10_10_10_REV;
50 default:
51 break;
50 } 52 }
51 break; 53 break;
52 case Maxwell::VertexAttribute::Type::SignedNorm: 54 case Maxwell::VertexAttribute::Type::SignedNorm:
@@ -70,6 +72,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
70 return GL_INT; 72 return GL_INT;
71 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 73 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
72 return GL_INT_2_10_10_10_REV; 74 return GL_INT_2_10_10_10_REV;
75 default:
76 break;
73 } 77 }
74 break; 78 break;
75 case Maxwell::VertexAttribute::Type::Float: 79 case Maxwell::VertexAttribute::Type::Float:
@@ -84,6 +88,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
84 case Maxwell::VertexAttribute::Size::Size_32_32_32: 88 case Maxwell::VertexAttribute::Size::Size_32_32_32:
85 case Maxwell::VertexAttribute::Size::Size_32_32_32_32: 89 case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
86 return GL_FLOAT; 90 return GL_FLOAT;
91 default:
92 break;
87 } 93 }
88 break; 94 break;
89 } 95 }
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index f8c77f4fa..d22de1d81 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -78,9 +78,10 @@ VkSamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode w
78 case Tegra::Texture::WrapMode::MirrorOnceBorder: 78 case Tegra::Texture::WrapMode::MirrorOnceBorder:
79 UNIMPLEMENTED(); 79 UNIMPLEMENTED();
80 return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; 80 return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
81 default:
82 UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode));
83 return {};
81 } 84 }
82 UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode));
83 return {};
84} 85}
85 86
86VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) { 87VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) {
@@ -298,9 +299,10 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const VKDevice& device,
298 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; 299 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
299 case Maxwell::PrimitiveTopology::Patches: 300 case Maxwell::PrimitiveTopology::Patches:
300 return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; 301 return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
302 default:
303 UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology));
304 return {};
301 } 305 }
302 UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology));
303 return {};
304} 306}
305 307
306VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) { 308VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) {
@@ -325,6 +327,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
325 return VK_FORMAT_R16G16B16A16_UNORM; 327 return VK_FORMAT_R16G16B16A16_UNORM;
326 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 328 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
327 return VK_FORMAT_A2B10G10R10_UNORM_PACK32; 329 return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
330 default:
331 break;
328 } 332 }
329 break; 333 break;
330 case Maxwell::VertexAttribute::Type::SignedNorm: 334 case Maxwell::VertexAttribute::Type::SignedNorm:
@@ -347,6 +351,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
347 return VK_FORMAT_R16G16B16A16_SNORM; 351 return VK_FORMAT_R16G16B16A16_SNORM;
348 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 352 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
349 return VK_FORMAT_A2B10G10R10_SNORM_PACK32; 353 return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
354 default:
355 break;
350 } 356 }
351 break; 357 break;
352 case Maxwell::VertexAttribute::Type::UnsignedScaled: 358 case Maxwell::VertexAttribute::Type::UnsignedScaled:
@@ -369,6 +375,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
369 return VK_FORMAT_R16G16B16A16_USCALED; 375 return VK_FORMAT_R16G16B16A16_USCALED;
370 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 376 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
371 return VK_FORMAT_A2B10G10R10_USCALED_PACK32; 377 return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
378 default:
379 break;
372 } 380 }
373 break; 381 break;
374 case Maxwell::VertexAttribute::Type::SignedScaled: 382 case Maxwell::VertexAttribute::Type::SignedScaled:
@@ -391,6 +399,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
391 return VK_FORMAT_R16G16B16A16_SSCALED; 399 return VK_FORMAT_R16G16B16A16_SSCALED;
392 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 400 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
393 return VK_FORMAT_A2B10G10R10_SSCALED_PACK32; 401 return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
402 default:
403 break;
394 } 404 }
395 break; 405 break;
396 case Maxwell::VertexAttribute::Type::UnsignedInt: 406 case Maxwell::VertexAttribute::Type::UnsignedInt:
@@ -421,6 +431,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
421 return VK_FORMAT_R32G32B32A32_UINT; 431 return VK_FORMAT_R32G32B32A32_UINT;
422 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 432 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
423 return VK_FORMAT_A2B10G10R10_UINT_PACK32; 433 return VK_FORMAT_A2B10G10R10_UINT_PACK32;
434 default:
435 break;
424 } 436 }
425 break; 437 break;
426 case Maxwell::VertexAttribute::Type::SignedInt: 438 case Maxwell::VertexAttribute::Type::SignedInt:
@@ -451,6 +463,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
451 return VK_FORMAT_R32G32B32A32_SINT; 463 return VK_FORMAT_R32G32B32A32_SINT;
452 case Maxwell::VertexAttribute::Size::Size_10_10_10_2: 464 case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
453 return VK_FORMAT_A2B10G10R10_SINT_PACK32; 465 return VK_FORMAT_A2B10G10R10_SINT_PACK32;
466 default:
467 break;
454 } 468 }
455 break; 469 break;
456 case Maxwell::VertexAttribute::Type::Float: 470 case Maxwell::VertexAttribute::Type::Float:
@@ -471,6 +485,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
471 return VK_FORMAT_R32G32B32_SFLOAT; 485 return VK_FORMAT_R32G32B32_SFLOAT;
472 case Maxwell::VertexAttribute::Size::Size_32_32_32_32: 486 case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
473 return VK_FORMAT_R32G32B32A32_SFLOAT; 487 return VK_FORMAT_R32G32B32A32_SFLOAT;
488 default:
489 break;
474 } 490 }
475 break; 491 break;
476 } 492 }
diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp
index 013865aa4..fe291a148 100644
--- a/src/video_core/renderer_vulkan/wrapper.cpp
+++ b/src/video_core/renderer_vulkan/wrapper.cpp
@@ -262,6 +262,22 @@ const char* ToString(VkResult result) noexcept {
262 return "VK_ERROR_INVALID_DEVICE_ADDRESS_EXT"; 262 return "VK_ERROR_INVALID_DEVICE_ADDRESS_EXT";
263 case VkResult::VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: 263 case VkResult::VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
264 return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; 264 return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
265 case VkResult::VK_ERROR_UNKNOWN:
266 return "VK_ERROR_UNKNOWN";
267 case VkResult::VK_ERROR_INCOMPATIBLE_VERSION_KHR:
268 return "VK_ERROR_INCOMPATIBLE_VERSION_KHR";
269 case VkResult::VK_THREAD_IDLE_KHR:
270 return "VK_THREAD_IDLE_KHR";
271 case VkResult::VK_THREAD_DONE_KHR:
272 return "VK_THREAD_DONE_KHR";
273 case VkResult::VK_OPERATION_DEFERRED_KHR:
274 return "VK_OPERATION_DEFERRED_KHR";
275 case VkResult::VK_OPERATION_NOT_DEFERRED_KHR:
276 return "VK_OPERATION_NOT_DEFERRED_KHR";
277 case VkResult::VK_PIPELINE_COMPILE_REQUIRED_EXT:
278 return "VK_PIPELINE_COMPILE_REQUIRED_EXT";
279 case VkResult::VK_RESULT_MAX_ENUM:
280 return "VK_RESULT_MAX_ENUM";
265 } 281 }
266 return "Unknown"; 282 return "Unknown";
267} 283}
diff --git a/src/video_core/shader/decode/arithmetic_half.cpp b/src/video_core/shader/decode/arithmetic_half.cpp
index a276aee44..88103fede 100644
--- a/src/video_core/shader/decode/arithmetic_half.cpp
+++ b/src/video_core/shader/decode/arithmetic_half.cpp
@@ -53,6 +53,9 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
53 absolute_a = ((instr.value >> 44) & 1) != 0; 53 absolute_a = ((instr.value >> 44) & 1) != 0;
54 absolute_b = ((instr.value >> 54) & 1) != 0; 54 absolute_b = ((instr.value >> 54) & 1) != 0;
55 break; 55 break;
56 default:
57 UNREACHABLE();
58 break;
56 } 59 }
57 60
58 Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a); 61 Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a);
diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp
index e75ca4fdb..618d309d2 100644
--- a/src/video_core/shader/decode/image.cpp
+++ b/src/video_core/shader/decode/image.cpp
@@ -119,6 +119,8 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor,
119 return descriptor.r_type; 119 return descriptor.r_type;
120 } 120 }
121 break; 121 break;
122 default:
123 break;
122 } 124 }
123 UNIMPLEMENTED_MSG("Texture format not implemented={}", format); 125 UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
124 return ComponentType::FLOAT; 126 return ComponentType::FLOAT;
@@ -220,9 +222,10 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) {
220 return (component == 0 || component == 1) ? 8 : 0; 222 return (component == 0 || component == 1) ? 8 : 0;
221 case TextureFormat::G4R4: 223 case TextureFormat::G4R4:
222 return (component == 0 || component == 1) ? 4 : 0; 224 return (component == 0 || component == 1) ? 4 : 0;
225 default:
226 UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
227 return 0;
223 } 228 }
224 UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
225 return 0;
226} 229}
227 230
228std::size_t GetImageComponentMask(TextureFormat format) { 231std::size_t GetImageComponentMask(TextureFormat format) {
@@ -257,9 +260,10 @@ std::size_t GetImageComponentMask(TextureFormat format) {
257 case TextureFormat::R8: 260 case TextureFormat::R8:
258 case TextureFormat::R1: 261 case TextureFormat::R1:
259 return std::size_t{R}; 262 return std::size_t{R};
263 default:
264 UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
265 return std::size_t{R | G | B | A};
260 } 266 }
261 UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
262 return std::size_t{R | G | B | A};
263} 267}
264 268
265std::size_t GetImageTypeNumCoordinates(Tegra::Shader::ImageType image_type) { 269std::size_t GetImageTypeNumCoordinates(Tegra::Shader::ImageType image_type) {
@@ -463,7 +467,10 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
463 return OperationCode::AtomicImageXor; 467 return OperationCode::AtomicImageXor;
464 case Tegra::Shader::ImageAtomicOperation::Exch: 468 case Tegra::Shader::ImageAtomicOperation::Exch:
465 return OperationCode::AtomicImageExchange; 469 return OperationCode::AtomicImageExchange;
470 default:
471 break;
466 } 472 }
473 break;
467 default: 474 default:
468 break; 475 break;
469 } 476 }
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp
index 29ebf65ba..a03b50e39 100644
--- a/src/video_core/shader/decode/texture.cpp
+++ b/src/video_core/shader/decode/texture.cpp
@@ -763,7 +763,7 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
763 763
764Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) { 764Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
765 const auto texture_type{instr.tld.texture_type}; 765 const auto texture_type{instr.tld.texture_type};
766 const bool is_array{instr.tld.is_array}; 766 const bool is_array{instr.tld.is_array != 0};
767 const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL}; 767 const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL};
768 const std::size_t coord_count{GetCoordCount(texture_type)}; 768 const std::size_t coord_count{GetCoordCount(texture_type)};
769 769
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 3ea4e5601..cc0291b15 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -9,6 +9,9 @@ add_executable(yuzu
9 about_dialog.cpp 9 about_dialog.cpp
10 about_dialog.h 10 about_dialog.h
11 aboutdialog.ui 11 aboutdialog.ui
12 applets/controller.cpp
13 applets/controller.h
14 applets/controller.ui
12 applets/error.cpp 15 applets/error.cpp
13 applets/error.h 16 applets/error.h
14 applets/profile_select.cpp 17 applets/profile_select.cpp
@@ -62,12 +65,15 @@ add_executable(yuzu
62 configuration/configure_input.cpp 65 configuration/configure_input.cpp
63 configuration/configure_input.h 66 configuration/configure_input.h
64 configuration/configure_input.ui 67 configuration/configure_input.ui
65 configuration/configure_input_player.cpp
66 configuration/configure_input_player.h
67 configuration/configure_input_player.ui
68 configuration/configure_input_advanced.cpp 68 configuration/configure_input_advanced.cpp
69 configuration/configure_input_advanced.h 69 configuration/configure_input_advanced.h
70 configuration/configure_input_advanced.ui 70 configuration/configure_input_advanced.ui
71 configuration/configure_input_dialog.cpp
72 configuration/configure_input_dialog.h
73 configuration/configure_input_dialog.ui
74 configuration/configure_input_player.cpp
75 configuration/configure_input_player.h
76 configuration/configure_input_player.ui
71 configuration/configure_motion_touch.cpp 77 configuration/configure_motion_touch.cpp
72 configuration/configure_motion_touch.h 78 configuration/configure_motion_touch.h
73 configuration/configure_motion_touch.ui 79 configuration/configure_motion_touch.ui
diff --git a/src/yuzu/applets/controller.cpp b/src/yuzu/applets/controller.cpp
new file mode 100644
index 000000000..9d45f2a01
--- /dev/null
+++ b/src/yuzu/applets/controller.cpp
@@ -0,0 +1,601 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6
7#include "common/assert.h"
8#include "common/string_util.h"
9#include "core/core.h"
10#include "core/hle/lock.h"
11#include "core/hle/service/hid/controllers/npad.h"
12#include "core/hle/service/hid/hid.h"
13#include "core/hle/service/sm/sm.h"
14#include "ui_controller.h"
15#include "yuzu/applets/controller.h"
16#include "yuzu/configuration/configure_input_dialog.h"
17#include "yuzu/main.h"
18
19namespace {
20
21constexpr std::array<std::array<bool, 4>, 8> led_patterns = {{
22 {1, 0, 0, 0},
23 {1, 1, 0, 0},
24 {1, 1, 1, 0},
25 {1, 1, 1, 1},
26 {1, 0, 0, 1},
27 {1, 0, 1, 0},
28 {1, 0, 1, 1},
29 {0, 1, 1, 0},
30}};
31
32void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
33 bool connected) {
34 Core::System& system{Core::System::GetInstance()};
35
36 if (!system.IsPoweredOn()) {
37 return;
38 }
39
40 Service::SM::ServiceManager& sm = system.ServiceManager();
41
42 auto& npad =
43 sm.GetService<Service::HID::Hid>("hid")
44 ->GetAppletResource()
45 ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
46
47 npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
48}
49
50// Returns true if the given controller type is compatible with the given parameters.
51bool IsControllerCompatible(Settings::ControllerType controller_type,
52 Core::Frontend::ControllerParameters parameters) {
53 switch (controller_type) {
54 case Settings::ControllerType::ProController:
55 return parameters.allow_pro_controller;
56 case Settings::ControllerType::DualJoyconDetached:
57 return parameters.allow_dual_joycons;
58 case Settings::ControllerType::LeftJoycon:
59 return parameters.allow_left_joycon;
60 case Settings::ControllerType::RightJoycon:
61 return parameters.allow_right_joycon;
62 case Settings::ControllerType::Handheld:
63 return parameters.enable_single_mode && parameters.allow_handheld;
64 default:
65 return false;
66 }
67}
68
69/// Maps the controller type combobox index to Controller Type enum
70constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) {
71 switch (index) {
72 case 0:
73 default:
74 return Settings::ControllerType::ProController;
75 case 1:
76 return Settings::ControllerType::DualJoyconDetached;
77 case 2:
78 return Settings::ControllerType::LeftJoycon;
79 case 3:
80 return Settings::ControllerType::RightJoycon;
81 case 4:
82 return Settings::ControllerType::Handheld;
83 }
84}
85
86/// Maps the Controller Type enum to controller type combobox index
87constexpr int GetIndexFromControllerType(Settings::ControllerType type) {
88 switch (type) {
89 case Settings::ControllerType::ProController:
90 default:
91 return 0;
92 case Settings::ControllerType::DualJoyconDetached:
93 return 1;
94 case Settings::ControllerType::LeftJoycon:
95 return 2;
96 case Settings::ControllerType::RightJoycon:
97 return 3;
98 case Settings::ControllerType::Handheld:
99 return 4;
100 }
101}
102
103} // namespace
104
105QtControllerSelectorDialog::QtControllerSelectorDialog(
106 QWidget* parent, Core::Frontend::ControllerParameters parameters_,
107 InputCommon::InputSubsystem* input_subsystem_)
108 : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
109 parameters(std::move(parameters_)), input_subsystem(input_subsystem_) {
110 ui->setupUi(this);
111
112 player_widgets = {
113 ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4,
114 ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8,
115 };
116
117 player_groupboxes = {
118 ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected,
119 ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected,
120 ui->groupPlayer7Connected, ui->groupPlayer8Connected,
121 };
122
123 connected_controller_icons = {
124 ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4,
125 ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8,
126 };
127
128 led_patterns_boxes = {{
129 {ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3,
130 ui->checkboxPlayer1LED4},
131 {ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3,
132 ui->checkboxPlayer2LED4},
133 {ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3,
134 ui->checkboxPlayer3LED4},
135 {ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3,
136 ui->checkboxPlayer4LED4},
137 {ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3,
138 ui->checkboxPlayer5LED4},
139 {ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3,
140 ui->checkboxPlayer6LED4},
141 {ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3,
142 ui->checkboxPlayer7LED4},
143 {ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3,
144 ui->checkboxPlayer8LED4},
145 }};
146
147 explain_text_labels = {
148 ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain,
149 ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain,
150 ui->labelPlayer7Explain, ui->labelPlayer8Explain,
151 };
152
153 emulated_controllers = {
154 ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated,
155 ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated,
156 ui->comboPlayer7Emulated, ui->comboPlayer8Emulated,
157 };
158
159 player_labels = {
160 ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4,
161 ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8,
162 };
163
164 connected_controller_labels = {
165 ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3,
166 ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6,
167 ui->labelConnectedPlayer7, ui->labelConnectedPlayer8,
168 };
169
170 connected_controller_checkboxes = {
171 ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
172 ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
173 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
174 };
175
176 // Setup/load everything prior to setting up connections.
177 // This avoids unintentionally changing the states of elements while loading them in.
178 SetSupportedControllers();
179 DisableUnsupportedPlayers();
180 LoadConfiguration();
181
182 for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
183 SetExplainText(i);
184 UpdateControllerIcon(i);
185 UpdateLEDPattern(i);
186 UpdateBorderColor(i);
187
188 connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
189 if (checked) {
190 for (std::size_t index = 0; index <= i; ++index) {
191 connected_controller_checkboxes[index]->setChecked(checked);
192 }
193 } else {
194 for (std::size_t index = i; index < NUM_PLAYERS; ++index) {
195 connected_controller_checkboxes[index]->setChecked(checked);
196 }
197 }
198 });
199
200 connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
201 [this, i](int) {
202 UpdateControllerIcon(i);
203 UpdateControllerState(i);
204 UpdateLEDPattern(i);
205 CheckIfParametersMet();
206 });
207
208 connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
209 player_groupboxes[i]->setChecked(state == Qt::Checked);
210 UpdateControllerIcon(i);
211 UpdateControllerState(i);
212 UpdateLEDPattern(i);
213 UpdateBorderColor(i);
214 CheckIfParametersMet();
215 });
216
217 if (i == 0) {
218 connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
219 [this](int index) {
220 UpdateDockedState(GetControllerTypeFromIndex(index) ==
221 Settings::ControllerType::Handheld);
222 });
223 }
224 }
225
226 connect(ui->inputConfigButton, &QPushButton::clicked, this,
227 &QtControllerSelectorDialog::CallConfigureInputDialog);
228
229 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
230 &QtControllerSelectorDialog::ApplyConfiguration);
231
232 // If keep_controllers_connected is false, forcefully disconnect all controllers
233 if (!parameters.keep_controllers_connected) {
234 for (auto player : player_groupboxes) {
235 player->setChecked(false);
236 }
237 }
238
239 CheckIfParametersMet();
240
241 resize(0, 0);
242}
243
244QtControllerSelectorDialog::~QtControllerSelectorDialog() = default;
245
246void QtControllerSelectorDialog::ApplyConfiguration() {
247 // Update the controller state once more, just to be sure they are properly applied.
248 for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
249 UpdateControllerState(index);
250 }
251
252 const bool pre_docked_mode = Settings::values.use_docked_mode;
253 Settings::values.use_docked_mode = ui->radioDocked->isChecked();
254 OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
255
256 Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
257}
258
259void QtControllerSelectorDialog::LoadConfiguration() {
260 for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
261 const auto connected = Settings::values.players[index].connected ||
262 (index == 0 && Settings::values.players[8].connected);
263 player_groupboxes[index]->setChecked(connected);
264 connected_controller_checkboxes[index]->setChecked(connected);
265 emulated_controllers[index]->setCurrentIndex(
266 GetIndexFromControllerType(Settings::values.players[index].controller_type));
267 }
268
269 UpdateDockedState(Settings::values.players[8].connected);
270
271 ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
272}
273
274void QtControllerSelectorDialog::CallConfigureInputDialog() {
275 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
276
277 ConfigureInputDialog dialog(this, max_supported_players, input_subsystem);
278
279 dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
280 Qt::WindowSystemMenuHint);
281 dialog.setWindowModality(Qt::WindowModal);
282 dialog.exec();
283
284 dialog.ApplyConfiguration();
285
286 LoadConfiguration();
287 CheckIfParametersMet();
288}
289
290void QtControllerSelectorDialog::CheckIfParametersMet() {
291 // Here, we check and validate the current configuration against all applicable parameters.
292 const auto num_connected_players = static_cast<int>(
293 std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
294 [this](const QGroupBox* player) { return player->isChecked(); }));
295
296 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
297 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
298
299 // First, check against the number of connected players.
300 if (num_connected_players < min_supported_players ||
301 num_connected_players > max_supported_players) {
302 parameters_met = false;
303 ui->buttonBox->setEnabled(parameters_met);
304 return;
305 }
306
307 // Next, check against all connected controllers.
308 const auto all_controllers_compatible = [this] {
309 for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
310 // Skip controllers that are not used, we only care about the currently connected ones.
311 if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) {
312 continue;
313 }
314
315 const auto compatible = IsControllerCompatible(
316 GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex()),
317 parameters);
318
319 // If any controller is found to be incompatible, return false early.
320 if (!compatible) {
321 return false;
322 }
323 }
324
325 // Reaching here means all currently connected controllers are compatible.
326 return true;
327 }();
328
329 if (!all_controllers_compatible) {
330 parameters_met = false;
331 ui->buttonBox->setEnabled(parameters_met);
332 return;
333 }
334
335 parameters_met = true;
336 ui->buttonBox->setEnabled(parameters_met);
337}
338
339void QtControllerSelectorDialog::SetSupportedControllers() {
340 const QString theme = [this] {
341 if (QIcon::themeName().contains(QStringLiteral("dark"))) {
342 return QStringLiteral("_dark");
343 } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
344 return QStringLiteral("_midnight");
345 } else {
346 return QString{};
347 }
348 }();
349
350 if (parameters.enable_single_mode && parameters.allow_handheld) {
351 ui->controllerSupported1->setStyleSheet(
352 QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme));
353 } else {
354 ui->controllerSupported1->setStyleSheet(
355 QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme));
356 }
357
358 if (parameters.allow_dual_joycons) {
359 ui->controllerSupported2->setStyleSheet(
360 QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme));
361 } else {
362 ui->controllerSupported2->setStyleSheet(
363 QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme));
364 }
365
366 if (parameters.allow_left_joycon) {
367 ui->controllerSupported3->setStyleSheet(
368 QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme));
369 } else {
370 ui->controllerSupported3->setStyleSheet(
371 QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme));
372 }
373
374 if (parameters.allow_right_joycon) {
375 ui->controllerSupported4->setStyleSheet(
376 QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme));
377 } else {
378 ui->controllerSupported4->setStyleSheet(
379 QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
380 }
381
382 if (parameters.allow_pro_controller) {
383 ui->controllerSupported5->setStyleSheet(
384 QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
385 } else {
386 ui->controllerSupported5->setStyleSheet(
387 QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ")
388 .arg(theme));
389 }
390
391 // enable_single_mode overrides min_players and max_players.
392 if (parameters.enable_single_mode) {
393 ui->numberSupportedLabel->setText(QStringLiteral("1"));
394 return;
395 }
396
397 if (parameters.min_players == parameters.max_players) {
398 ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players));
399 } else {
400 ui->numberSupportedLabel->setText(
401 QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players));
402 }
403}
404
405void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) {
406 if (!player_groupboxes[player_index]->isChecked()) {
407 connected_controller_icons[player_index]->setStyleSheet(QString{});
408 player_labels[player_index]->show();
409 return;
410 }
411
412 const QString stylesheet = [this, player_index] {
413 switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex())) {
414 case Settings::ControllerType::ProController:
415 return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
416 case Settings::ControllerType::DualJoyconDetached:
417 return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
418 case Settings::ControllerType::LeftJoycon:
419 return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
420 case Settings::ControllerType::RightJoycon:
421 return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
422 case Settings::ControllerType::Handheld:
423 return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
424 default:
425 return QString{};
426 }
427 }();
428
429 const QString theme = [this] {
430 if (QIcon::themeName().contains(QStringLiteral("dark"))) {
431 return QStringLiteral("_dark");
432 } else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
433 return QStringLiteral("_midnight");
434 } else {
435 return QString{};
436 }
437 }();
438
439 connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme));
440 player_labels[player_index]->hide();
441}
442
443void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
444 auto& player = Settings::values.players[player_index];
445
446 player.controller_type =
447 GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex());
448 player.connected = player_groupboxes[player_index]->isChecked();
449
450 // Player 2-8
451 if (player_index != 0) {
452 UpdateController(player.controller_type, player_index, player.connected);
453 return;
454 }
455
456 // Player 1 and Handheld
457 auto& handheld = Settings::values.players[8];
458 // If Handheld is selected, copy all the settings from Player 1 to Handheld.
459 if (player.controller_type == Settings::ControllerType::Handheld) {
460 handheld = player;
461 handheld.connected = player_groupboxes[player_index]->isChecked();
462 player.connected = false; // Disconnect Player 1
463 } else {
464 player.connected = player_groupboxes[player_index]->isChecked();
465 handheld.connected = false; // Disconnect Handheld
466 }
467
468 UpdateController(player.controller_type, player_index, player.connected);
469 UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
470}
471
472void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
473 if (!player_groupboxes[player_index]->isChecked() ||
474 GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()) ==
475 Settings::ControllerType::Handheld) {
476 led_patterns_boxes[player_index][0]->setChecked(false);
477 led_patterns_boxes[player_index][1]->setChecked(false);
478 led_patterns_boxes[player_index][2]->setChecked(false);
479 led_patterns_boxes[player_index][3]->setChecked(false);
480 return;
481 }
482
483 led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]);
484 led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]);
485 led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]);
486 led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]);
487}
488
489void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
490 if (!parameters.enable_border_color ||
491 player_index >= static_cast<std::size_t>(parameters.max_players) ||
492 player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) {
493 return;
494 }
495
496 player_groupboxes[player_index]->setStyleSheet(
497 player_groupboxes[player_index]->styleSheet().append(
498 QStringLiteral("QGroupBox#groupPlayer%1Connected:checked "
499 "{ border: 1px solid rgba(%2, %3, %4, %5); }")
500 .arg(player_index + 1)
501 .arg(parameters.border_colors[player_index][0])
502 .arg(parameters.border_colors[player_index][1])
503 .arg(parameters.border_colors[player_index][2])
504 .arg(parameters.border_colors[player_index][3])));
505}
506
507void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) {
508 if (!parameters.enable_explain_text ||
509 player_index >= static_cast<std::size_t>(parameters.max_players)) {
510 return;
511 }
512
513 explain_text_labels[player_index]->setText(QString::fromStdString(
514 Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(),
515 parameters.explain_text[player_index].size())));
516}
517
518void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
519 // Disallow changing the console mode if the controller type is handheld.
520 ui->radioDocked->setEnabled(!is_handheld);
521 ui->radioUndocked->setEnabled(!is_handheld);
522
523 ui->radioDocked->setChecked(Settings::values.use_docked_mode);
524 ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
525
526 // Also force into undocked mode if the controller type is handheld.
527 if (is_handheld) {
528 ui->radioUndocked->setChecked(true);
529 }
530}
531
532void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
533 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
534
535 switch (max_supported_players) {
536 case 0:
537 default:
538 UNREACHABLE();
539 return;
540 case 1:
541 ui->widgetSpacer->hide();
542 ui->widgetSpacer2->hide();
543 ui->widgetSpacer3->hide();
544 ui->widgetSpacer4->hide();
545 break;
546 case 2:
547 ui->widgetSpacer->hide();
548 ui->widgetSpacer2->hide();
549 ui->widgetSpacer3->hide();
550 break;
551 case 3:
552 ui->widgetSpacer->hide();
553 ui->widgetSpacer2->hide();
554 break;
555 case 4:
556 ui->widgetSpacer->hide();
557 break;
558 case 5:
559 case 6:
560 case 7:
561 case 8:
562 break;
563 }
564
565 for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
566 // Disconnect any unsupported players here and disable or hide them if applicable.
567 Settings::values.players[index].connected = false;
568 UpdateController(Settings::values.players[index].controller_type, index, false);
569 // Hide the player widgets when max_supported_controllers is less than or equal to 4.
570 if (max_supported_players <= 4) {
571 player_widgets[index]->hide();
572 }
573
574 // Disable and hide the following to prevent these from interaction.
575 player_widgets[index]->setDisabled(true);
576 connected_controller_checkboxes[index]->setDisabled(true);
577 connected_controller_labels[index]->hide();
578 connected_controller_checkboxes[index]->hide();
579 }
580}
581
582QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
583 connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
584 &GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
585 connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
586 &QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
587}
588
589QtControllerSelector::~QtControllerSelector() = default;
590
591void QtControllerSelector::ReconfigureControllers(
592 std::function<void()> callback, Core::Frontend::ControllerParameters parameters) const {
593 this->callback = std::move(callback);
594 emit MainWindowReconfigureControllers(parameters);
595}
596
597void QtControllerSelector::MainWindowReconfigureFinished() {
598 // Acquire the HLE mutex
599 std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
600 callback();
601}
diff --git a/src/yuzu/applets/controller.h b/src/yuzu/applets/controller.h
new file mode 100644
index 000000000..2d6d588c6
--- /dev/null
+++ b/src/yuzu/applets/controller.h
@@ -0,0 +1,133 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <QDialog>
10#include "core/frontend/applets/controller.h"
11
12class GMainWindow;
13class QCheckBox;
14class QComboBox;
15class QDialogButtonBox;
16class QGroupBox;
17class QLabel;
18
19namespace InputCommon {
20class InputSubsystem;
21}
22
23namespace Ui {
24class QtControllerSelectorDialog;
25}
26
27class QtControllerSelectorDialog final : public QDialog {
28 Q_OBJECT
29
30public:
31 explicit QtControllerSelectorDialog(QWidget* parent,
32 Core::Frontend::ControllerParameters parameters_,
33 InputCommon::InputSubsystem* input_subsystem_);
34 ~QtControllerSelectorDialog() override;
35
36private:
37 // Applies the current configuration.
38 void ApplyConfiguration();
39
40 // Loads the current input configuration into the frontend applet.
41 void LoadConfiguration();
42
43 // Initializes the "Configure Input" Dialog.
44 void CallConfigureInputDialog();
45
46 // Checks the current configuration against the given parameters and
47 // sets the value of parameters_met.
48 void CheckIfParametersMet();
49
50 // Sets the controller icons for "Supported Controller Types".
51 void SetSupportedControllers();
52
53 // Updates the controller icons per player.
54 void UpdateControllerIcon(std::size_t player_index);
55
56 // Updates the controller state (type and connection status) per player.
57 void UpdateControllerState(std::size_t player_index);
58
59 // Updates the LED pattern per player.
60 void UpdateLEDPattern(std::size_t player_index);
61
62 // Updates the border color per player.
63 void UpdateBorderColor(std::size_t player_index);
64
65 // Sets the "Explain Text" per player.
66 void SetExplainText(std::size_t player_index);
67
68 // Updates the console mode.
69 void UpdateDockedState(bool is_handheld);
70
71 // Disables and disconnects unsupported players based on the given parameters.
72 void DisableUnsupportedPlayers();
73
74 std::unique_ptr<Ui::QtControllerSelectorDialog> ui;
75
76 // Parameters sent in from the backend HLE applet.
77 Core::Frontend::ControllerParameters parameters;
78
79 InputCommon::InputSubsystem* input_subsystem;
80
81 // This is true if and only if all parameters are met. Otherwise, this is false.
82 // This determines whether the "OK" button can be clicked to exit the applet.
83 bool parameters_met{false};
84
85 static constexpr std::size_t NUM_PLAYERS = 8;
86
87 // Widgets encapsulating the groupboxes and comboboxes per player.
88 std::array<QWidget*, NUM_PLAYERS> player_widgets;
89
90 // Groupboxes encapsulating the controller icons and LED patterns per player.
91 std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes;
92
93 // Icons for currently connected controllers/players.
94 std::array<QWidget*, NUM_PLAYERS> connected_controller_icons;
95
96 // Labels that represent the player numbers in place of the controller icons.
97 std::array<QLabel*, NUM_PLAYERS> player_labels;
98
99 // LED patterns for currently connected controllers/players.
100 std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes;
101
102 // Labels representing additional information known as "Explain Text" per player.
103 std::array<QLabel*, NUM_PLAYERS> explain_text_labels;
104
105 // Comboboxes with a list of emulated controllers per player.
106 std::array<QComboBox*, NUM_PLAYERS> emulated_controllers;
107
108 // Labels representing the number of connected controllers
109 // above the "Connected Controllers" checkboxes.
110 std::array<QLabel*, NUM_PLAYERS> connected_controller_labels;
111
112 // Checkboxes representing the "Connected Controllers".
113 std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes;
114};
115
116class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet {
117 Q_OBJECT
118
119public:
120 explicit QtControllerSelector(GMainWindow& parent);
121 ~QtControllerSelector() override;
122
123 void ReconfigureControllers(std::function<void()> callback,
124 Core::Frontend::ControllerParameters parameters) const override;
125
126signals:
127 void MainWindowReconfigureControllers(Core::Frontend::ControllerParameters parameters) const;
128
129private:
130 void MainWindowReconfigureFinished();
131
132 mutable std::function<void()> callback;
133};
diff --git a/src/yuzu/applets/controller.ui b/src/yuzu/applets/controller.ui
new file mode 100644
index 000000000..c4108a979
--- /dev/null
+++ b/src/yuzu/applets/controller.ui
@@ -0,0 +1,2672 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>QtControllerSelectorDialog</class>
4 <widget class="QDialog" name="QtControllerSelectorDialog">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>839</width>
10 <height>630</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Controller Applet</string>
15 </property>
16 <property name="styleSheet">
17 <string notr="true"/>
18 </property>
19 <layout class="QVBoxLayout" name="verticalLayout" stretch="0">
20 <property name="leftMargin">
21 <number>0</number>
22 </property>
23 <property name="topMargin">
24 <number>0</number>
25 </property>
26 <property name="rightMargin">
27 <number>0</number>
28 </property>
29 <property name="bottomMargin">
30 <number>0</number>
31 </property>
32 <item>
33 <widget class="QWidget" name="mainControllerApplet" native="true">
34 <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,3,0">
35 <property name="spacing">
36 <number>0</number>
37 </property>
38 <property name="leftMargin">
39 <number>0</number>
40 </property>
41 <property name="topMargin">
42 <number>0</number>
43 </property>
44 <property name="rightMargin">
45 <number>0</number>
46 </property>
47 <property name="bottomMargin">
48 <number>0</number>
49 </property>
50 <item>
51 <widget class="QWidget" name="topControllerApplet" native="true">
52 <layout class="QHBoxLayout" name="horizontalLayout">
53 <property name="spacing">
54 <number>10</number>
55 </property>
56 <property name="leftMargin">
57 <number>0</number>
58 </property>
59 <property name="topMargin">
60 <number>10</number>
61 </property>
62 <property name="rightMargin">
63 <number>0</number>
64 </property>
65 <property name="bottomMargin">
66 <number>10</number>
67 </property>
68 <item>
69 <spacer name="controllerAppletHorizontalSpacer2">
70 <property name="orientation">
71 <enum>Qt::Horizontal</enum>
72 </property>
73 <property name="sizeHint" stdset="0">
74 <size>
75 <width>40</width>
76 <height>20</height>
77 </size>
78 </property>
79 </spacer>
80 </item>
81 <item>
82 <widget class="QWidget" name="controllersSupported" native="true">
83 <property name="minimumSize">
84 <size>
85 <width>70</width>
86 <height>70</height>
87 </size>
88 </property>
89 <property name="maximumSize">
90 <size>
91 <width>70</width>
92 <height>70</height>
93 </size>
94 </property>
95 <layout class="QVBoxLayout" name="verticalLayout_21">
96 <property name="leftMargin">
97 <number>0</number>
98 </property>
99 <property name="topMargin">
100 <number>0</number>
101 </property>
102 <property name="rightMargin">
103 <number>0</number>
104 </property>
105 <property name="bottomMargin">
106 <number>0</number>
107 </property>
108 <item>
109 <widget class="QLabel" name="controllersSupportedLabel">
110 <property name="minimumSize">
111 <size>
112 <width>70</width>
113 <height>70</height>
114 </size>
115 </property>
116 <property name="maximumSize">
117 <size>
118 <width>70</width>
119 <height>70</height>
120 </size>
121 </property>
122 <property name="font">
123 <font>
124 <weight>75</weight>
125 <bold>true</bold>
126 </font>
127 </property>
128 <property name="text">
129 <string>Supported Controller Types:</string>
130 </property>
131 <property name="alignment">
132 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
133 </property>
134 <property name="wordWrap">
135 <bool>true</bool>
136 </property>
137 </widget>
138 </item>
139 </layout>
140 </widget>
141 </item>
142 <item>
143 <widget class="QWidget" name="controllerSupported1" native="true">
144 <property name="sizePolicy">
145 <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
146 <horstretch>0</horstretch>
147 <verstretch>0</verstretch>
148 </sizepolicy>
149 </property>
150 <property name="minimumSize">
151 <size>
152 <width>70</width>
153 <height>70</height>
154 </size>
155 </property>
156 <property name="maximumSize">
157 <size>
158 <width>70</width>
159 <height>70</height>
160 </size>
161 </property>
162 <property name="styleSheet">
163 <string notr="true"/>
164 </property>
165 </widget>
166 </item>
167 <item>
168 <widget class="QWidget" name="controllerSupported2" native="true">
169 <property name="minimumSize">
170 <size>
171 <width>70</width>
172 <height>70</height>
173 </size>
174 </property>
175 <property name="maximumSize">
176 <size>
177 <width>70</width>
178 <height>70</height>
179 </size>
180 </property>
181 <property name="styleSheet">
182 <string notr="true"/>
183 </property>
184 </widget>
185 </item>
186 <item>
187 <widget class="QWidget" name="controllerSupported3" native="true">
188 <property name="minimumSize">
189 <size>
190 <width>70</width>
191 <height>70</height>
192 </size>
193 </property>
194 <property name="maximumSize">
195 <size>
196 <width>70</width>
197 <height>70</height>
198 </size>
199 </property>
200 <property name="styleSheet">
201 <string notr="true"/>
202 </property>
203 </widget>
204 </item>
205 <item>
206 <widget class="QWidget" name="controllerSupported4" native="true">
207 <property name="minimumSize">
208 <size>
209 <width>70</width>
210 <height>70</height>
211 </size>
212 </property>
213 <property name="maximumSize">
214 <size>
215 <width>70</width>
216 <height>70</height>
217 </size>
218 </property>
219 <property name="styleSheet">
220 <string notr="true"/>
221 </property>
222 </widget>
223 </item>
224 <item>
225 <widget class="QWidget" name="controllerSupported5" native="true">
226 <property name="minimumSize">
227 <size>
228 <width>70</width>
229 <height>70</height>
230 </size>
231 </property>
232 <property name="maximumSize">
233 <size>
234 <width>70</width>
235 <height>70</height>
236 </size>
237 </property>
238 <property name="styleSheet">
239 <string notr="true"/>
240 </property>
241 </widget>
242 </item>
243 <item>
244 <widget class="QWidget" name="playersSupported" native="true">
245 <property name="minimumSize">
246 <size>
247 <width>70</width>
248 <height>70</height>
249 </size>
250 </property>
251 <property name="maximumSize">
252 <size>
253 <width>70</width>
254 <height>70</height>
255 </size>
256 </property>
257 <layout class="QVBoxLayout" name="verticalLayout_20">
258 <property name="spacing">
259 <number>0</number>
260 </property>
261 <property name="leftMargin">
262 <number>0</number>
263 </property>
264 <property name="topMargin">
265 <number>16</number>
266 </property>
267 <property name="rightMargin">
268 <number>14</number>
269 </property>
270 <property name="bottomMargin">
271 <number>16</number>
272 </property>
273 <item>
274 <widget class="QLabel" name="maxSupportedLabel">
275 <property name="font">
276 <font>
277 <weight>75</weight>
278 <bold>true</bold>
279 </font>
280 </property>
281 <property name="text">
282 <string>Players:</string>
283 </property>
284 <property name="alignment">
285 <set>Qt::AlignCenter</set>
286 </property>
287 <property name="wordWrap">
288 <bool>false</bool>
289 </property>
290 </widget>
291 </item>
292 <item>
293 <widget class="QLabel" name="numberSupportedLabel">
294 <property name="font">
295 <font>
296 <pointsize>14</pointsize>
297 </font>
298 </property>
299 <property name="text">
300 <string>1 - 8</string>
301 </property>
302 <property name="alignment">
303 <set>Qt::AlignCenter</set>
304 </property>
305 </widget>
306 </item>
307 </layout>
308 </widget>
309 </item>
310 <item>
311 <spacer name="controllerAppletHorizontalSpacer3">
312 <property name="orientation">
313 <enum>Qt::Horizontal</enum>
314 </property>
315 <property name="sizeHint" stdset="0">
316 <size>
317 <width>40</width>
318 <height>20</height>
319 </size>
320 </property>
321 </spacer>
322 </item>
323 </layout>
324 </widget>
325 </item>
326 <item>
327 <widget class="QWidget" name="middleControllerApplet" native="true">
328 <layout class="QVBoxLayout" name="verticalLayout_3">
329 <property name="spacing">
330 <number>0</number>
331 </property>
332 <property name="leftMargin">
333 <number>0</number>
334 </property>
335 <property name="topMargin">
336 <number>0</number>
337 </property>
338 <property name="rightMargin">
339 <number>0</number>
340 </property>
341 <property name="bottomMargin">
342 <number>0</number>
343 </property>
344 <item>
345 <layout class="QGridLayout" name="gridLayout">
346 <property name="spacing">
347 <number>5</number>
348 </property>
349 <item row="1" column="7">
350 <widget class="QWidget" name="widgetPlayer4" native="true">
351 <layout class="QVBoxLayout" name="verticalLayout_27">
352 <property name="spacing">
353 <number>5</number>
354 </property>
355 <property name="leftMargin">
356 <number>0</number>
357 </property>
358 <property name="topMargin">
359 <number>0</number>
360 </property>
361 <property name="rightMargin">
362 <number>0</number>
363 </property>
364 <property name="bottomMargin">
365 <number>0</number>
366 </property>
367 <item alignment="Qt::AlignHCenter">
368 <widget class="QGroupBox" name="groupPlayer4Connected">
369 <property name="minimumSize">
370 <size>
371 <width>100</width>
372 <height>100</height>
373 </size>
374 </property>
375 <property name="maximumSize">
376 <size>
377 <width>100</width>
378 <height>100</height>
379 </size>
380 </property>
381 <property name="title">
382 <string/>
383 </property>
384 <property name="checkable">
385 <bool>true</bool>
386 </property>
387 <property name="checked">
388 <bool>false</bool>
389 </property>
390 <layout class="QVBoxLayout" name="verticalLayout_7" stretch="1,0">
391 <property name="spacing">
392 <number>7</number>
393 </property>
394 <property name="leftMargin">
395 <number>14</number>
396 </property>
397 <property name="topMargin">
398 <number>7</number>
399 </property>
400 <property name="rightMargin">
401 <number>14</number>
402 </property>
403 <property name="bottomMargin">
404 <number>4</number>
405 </property>
406 <item>
407 <widget class="QWidget" name="controllerPlayer4" native="true">
408 <property name="styleSheet">
409 <string notr="true"/>
410 </property>
411 <layout class="QVBoxLayout" name="verticalLayout_15">
412 <property name="topMargin">
413 <number>16</number>
414 </property>
415 <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
416 <widget class="QLabel" name="labelPlayer4">
417 <property name="text">
418 <string>P4</string>
419 </property>
420 </widget>
421 </item>
422 </layout>
423 </widget>
424 </item>
425 <item alignment="Qt::AlignHCenter">
426 <widget class="QWidget" name="Player4LEDs" native="true">
427 <property name="enabled">
428 <bool>false</bool>
429 </property>
430 <property name="minimumSize">
431 <size>
432 <width>0</width>
433 <height>10</height>
434 </size>
435 </property>
436 <layout class="QHBoxLayout" name="horizontalLayout_10">
437 <property name="spacing">
438 <number>4</number>
439 </property>
440 <property name="leftMargin">
441 <number>0</number>
442 </property>
443 <property name="topMargin">
444 <number>0</number>
445 </property>
446 <property name="rightMargin">
447 <number>0</number>
448 </property>
449 <property name="bottomMargin">
450 <number>0</number>
451 </property>
452 <item>
453 <widget class="QCheckBox" name="checkboxPlayer4LED1"/>
454 </item>
455 <item>
456 <widget class="QCheckBox" name="checkboxPlayer4LED2"/>
457 </item>
458 <item>
459 <widget class="QCheckBox" name="checkboxPlayer4LED3"/>
460 </item>
461 <item>
462 <widget class="QCheckBox" name="checkboxPlayer4LED4"/>
463 </item>
464 </layout>
465 </widget>
466 </item>
467 </layout>
468 </widget>
469 </item>
470 <item>
471 <widget class="QWidget" name="Player4Explain" native="true">
472 <property name="minimumSize">
473 <size>
474 <width>0</width>
475 <height>10</height>
476 </size>
477 </property>
478 <property name="maximumSize">
479 <size>
480 <width>150</width>
481 <height>16777215</height>
482 </size>
483 </property>
484 <layout class="QVBoxLayout" name="verticalLayout_39">
485 <property name="spacing">
486 <number>0</number>
487 </property>
488 <property name="leftMargin">
489 <number>0</number>
490 </property>
491 <property name="topMargin">
492 <number>0</number>
493 </property>
494 <property name="rightMargin">
495 <number>0</number>
496 </property>
497 <property name="bottomMargin">
498 <number>0</number>
499 </property>
500 <item>
501 <widget class="QLabel" name="labelPlayer4Explain">
502 <property name="alignment">
503 <set>Qt::AlignCenter</set>
504 </property>
505 </widget>
506 </item>
507 </layout>
508 </widget>
509 </item>
510 <item>
511 <widget class="QComboBox" name="comboPlayer4Emulated">
512 <item>
513 <property name="text">
514 <string>Pro Controller</string>
515 </property>
516 </item>
517 <item>
518 <property name="text">
519 <string>Dual Joycons</string>
520 </property>
521 </item>
522 <item>
523 <property name="text">
524 <string>Left Joycon</string>
525 </property>
526 </item>
527 <item>
528 <property name="text">
529 <string>Right Joycon</string>
530 </property>
531 </item>
532 </widget>
533 </item>
534 <item>
535 <widget class="QComboBox" name="comboPlayer4Profile">
536 <item>
537 <property name="text">
538 <string>Use Current Config</string>
539 </property>
540 </item>
541 </widget>
542 </item>
543 </layout>
544 </widget>
545 </item>
546 <item row="1" column="3">
547 <widget class="QWidget" name="widgetPlayer2" native="true">
548 <layout class="QVBoxLayout" name="verticalLayout_29">
549 <property name="spacing">
550 <number>5</number>
551 </property>
552 <property name="leftMargin">
553 <number>0</number>
554 </property>
555 <property name="topMargin">
556 <number>0</number>
557 </property>
558 <property name="rightMargin">
559 <number>0</number>
560 </property>
561 <property name="bottomMargin">
562 <number>0</number>
563 </property>
564 <item alignment="Qt::AlignHCenter">
565 <widget class="QGroupBox" name="groupPlayer2Connected">
566 <property name="minimumSize">
567 <size>
568 <width>100</width>
569 <height>100</height>
570 </size>
571 </property>
572 <property name="maximumSize">
573 <size>
574 <width>100</width>
575 <height>100</height>
576 </size>
577 </property>
578 <property name="title">
579 <string/>
580 </property>
581 <property name="checkable">
582 <bool>true</bool>
583 </property>
584 <property name="checked">
585 <bool>false</bool>
586 </property>
587 <layout class="QVBoxLayout" name="verticalLayout_5" stretch="1,0">
588 <property name="spacing">
589 <number>7</number>
590 </property>
591 <property name="leftMargin">
592 <number>14</number>
593 </property>
594 <property name="topMargin">
595 <number>7</number>
596 </property>
597 <property name="rightMargin">
598 <number>14</number>
599 </property>
600 <property name="bottomMargin">
601 <number>4</number>
602 </property>
603 <item>
604 <widget class="QWidget" name="controllerPlayer2" native="true">
605 <property name="styleSheet">
606 <string notr="true"/>
607 </property>
608 <layout class="QVBoxLayout" name="verticalLayout_13">
609 <property name="topMargin">
610 <number>16</number>
611 </property>
612 <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
613 <widget class="QLabel" name="labelPlayer2">
614 <property name="text">
615 <string>P2</string>
616 </property>
617 </widget>
618 </item>
619 </layout>
620 </widget>
621 </item>
622 <item alignment="Qt::AlignHCenter">
623 <widget class="QWidget" name="Player2LEDs" native="true">
624 <property name="enabled">
625 <bool>false</bool>
626 </property>
627 <property name="minimumSize">
628 <size>
629 <width>0</width>
630 <height>10</height>
631 </size>
632 </property>
633 <layout class="QHBoxLayout" name="horizontalLayout_8">
634 <property name="spacing">
635 <number>4</number>
636 </property>
637 <property name="leftMargin">
638 <number>0</number>
639 </property>
640 <property name="topMargin">
641 <number>0</number>
642 </property>
643 <property name="rightMargin">
644 <number>0</number>
645 </property>
646 <property name="bottomMargin">
647 <number>0</number>
648 </property>
649 <item>
650 <widget class="QCheckBox" name="checkboxPlayer2LED1"/>
651 </item>
652 <item>
653 <widget class="QCheckBox" name="checkboxPlayer2LED2"/>
654 </item>
655 <item>
656 <widget class="QCheckBox" name="checkboxPlayer2LED3"/>
657 </item>
658 <item>
659 <widget class="QCheckBox" name="checkboxPlayer2LED4"/>
660 </item>
661 </layout>
662 </widget>
663 </item>
664 </layout>
665 </widget>
666 </item>
667 <item>
668 <widget class="QWidget" name="Player2Explain" native="true">
669 <property name="minimumSize">
670 <size>
671 <width>0</width>
672 <height>10</height>
673 </size>
674 </property>
675 <property name="maximumSize">
676 <size>
677 <width>150</width>
678 <height>16777215</height>
679 </size>
680 </property>
681 <layout class="QVBoxLayout" name="verticalLayout_37">
682 <property name="spacing">
683 <number>0</number>
684 </property>
685 <property name="leftMargin">
686 <number>0</number>
687 </property>
688 <property name="topMargin">
689 <number>0</number>
690 </property>
691 <property name="rightMargin">
692 <number>0</number>
693 </property>
694 <property name="bottomMargin">
695 <number>0</number>
696 </property>
697 <item>
698 <widget class="QLabel" name="labelPlayer2Explain">
699 <property name="alignment">
700 <set>Qt::AlignCenter</set>
701 </property>
702 </widget>
703 </item>
704 </layout>
705 </widget>
706 </item>
707 <item>
708 <widget class="QComboBox" name="comboPlayer2Emulated">
709 <item>
710 <property name="text">
711 <string>Pro Controller</string>
712 </property>
713 </item>
714 <item>
715 <property name="text">
716 <string>Dual Joycons</string>
717 </property>
718 </item>
719 <item>
720 <property name="text">
721 <string>Left Joycon</string>
722 </property>
723 </item>
724 <item>
725 <property name="text">
726 <string>Right Joycon</string>
727 </property>
728 </item>
729 </widget>
730 </item>
731 <item>
732 <widget class="QComboBox" name="comboPlayer2Profile">
733 <item>
734 <property name="text">
735 <string>Use Current Config</string>
736 </property>
737 </item>
738 </widget>
739 </item>
740 </layout>
741 </widget>
742 </item>
743 <item row="1" column="1">
744 <widget class="QWidget" name="widgetPlayer1" native="true">
745 <layout class="QVBoxLayout" name="verticalLayout_30">
746 <property name="spacing">
747 <number>5</number>
748 </property>
749 <property name="leftMargin">
750 <number>0</number>
751 </property>
752 <property name="topMargin">
753 <number>0</number>
754 </property>
755 <property name="rightMargin">
756 <number>0</number>
757 </property>
758 <property name="bottomMargin">
759 <number>0</number>
760 </property>
761 <item alignment="Qt::AlignHCenter">
762 <widget class="QGroupBox" name="groupPlayer1Connected">
763 <property name="minimumSize">
764 <size>
765 <width>100</width>
766 <height>100</height>
767 </size>
768 </property>
769 <property name="maximumSize">
770 <size>
771 <width>100</width>
772 <height>100</height>
773 </size>
774 </property>
775 <property name="title">
776 <string/>
777 </property>
778 <property name="checkable">
779 <bool>true</bool>
780 </property>
781 <property name="checked">
782 <bool>false</bool>
783 </property>
784 <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0">
785 <property name="spacing">
786 <number>7</number>
787 </property>
788 <property name="leftMargin">
789 <number>14</number>
790 </property>
791 <property name="topMargin">
792 <number>7</number>
793 </property>
794 <property name="rightMargin">
795 <number>14</number>
796 </property>
797 <property name="bottomMargin">
798 <number>4</number>
799 </property>
800 <item>
801 <widget class="QWidget" name="controllerPlayer1" native="true">
802 <property name="styleSheet">
803 <string notr="true"/>
804 </property>
805 <layout class="QVBoxLayout" name="verticalLayout_12">
806 <property name="topMargin">
807 <number>16</number>
808 </property>
809 <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
810 <widget class="QLabel" name="labelPlayer1">
811 <property name="text">
812 <string>P1</string>
813 </property>
814 </widget>
815 </item>
816 </layout>
817 </widget>
818 </item>
819 <item alignment="Qt::AlignHCenter">
820 <widget class="QWidget" name="Player1LEDs" native="true">
821 <property name="enabled">
822 <bool>false</bool>
823 </property>
824 <property name="minimumSize">
825 <size>
826 <width>0</width>
827 <height>10</height>
828 </size>
829 </property>
830 <layout class="QHBoxLayout" name="horizontalLayout_2">
831 <property name="spacing">
832 <number>4</number>
833 </property>
834 <property name="leftMargin">
835 <number>0</number>
836 </property>
837 <property name="topMargin">
838 <number>0</number>
839 </property>
840 <property name="rightMargin">
841 <number>0</number>
842 </property>
843 <property name="bottomMargin">
844 <number>0</number>
845 </property>
846 <item>
847 <widget class="QCheckBox" name="checkboxPlayer1LED1">
848 <property name="layoutDirection">
849 <enum>Qt::LeftToRight</enum>
850 </property>
851 </widget>
852 </item>
853 <item>
854 <widget class="QCheckBox" name="checkboxPlayer1LED2"/>
855 </item>
856 <item>
857 <widget class="QCheckBox" name="checkboxPlayer1LED3"/>
858 </item>
859 <item>
860 <widget class="QCheckBox" name="checkboxPlayer1LED4"/>
861 </item>
862 </layout>
863 </widget>
864 </item>
865 </layout>
866 </widget>
867 </item>
868 <item>
869 <widget class="QWidget" name="Player1Explain" native="true">
870 <property name="minimumSize">
871 <size>
872 <width>0</width>
873 <height>10</height>
874 </size>
875 </property>
876 <property name="maximumSize">
877 <size>
878 <width>150</width>
879 <height>16777215</height>
880 </size>
881 </property>
882 <layout class="QVBoxLayout" name="verticalLayout_36">
883 <property name="spacing">
884 <number>0</number>
885 </property>
886 <property name="leftMargin">
887 <number>0</number>
888 </property>
889 <property name="topMargin">
890 <number>0</number>
891 </property>
892 <property name="rightMargin">
893 <number>0</number>
894 </property>
895 <property name="bottomMargin">
896 <number>0</number>
897 </property>
898 <item>
899 <widget class="QLabel" name="labelPlayer1Explain">
900 <property name="alignment">
901 <set>Qt::AlignCenter</set>
902 </property>
903 </widget>
904 </item>
905 </layout>
906 </widget>
907 </item>
908 <item>
909 <widget class="QComboBox" name="comboPlayer1Emulated">
910 <item>
911 <property name="text">
912 <string>Pro Controller</string>
913 </property>
914 </item>
915 <item>
916 <property name="text">
917 <string>Dual Joycons</string>
918 </property>
919 </item>
920 <item>
921 <property name="text">
922 <string>Left Joycon</string>
923 </property>
924 </item>
925 <item>
926 <property name="text">
927 <string>Right Joycon</string>
928 </property>
929 </item>
930 <item>
931 <property name="text">
932 <string>Handheld</string>
933 </property>
934 </item>
935 </widget>
936 </item>
937 <item>
938 <widget class="QComboBox" name="comboPlayer1Profile">
939 <item>
940 <property name="text">
941 <string>Use Current Config</string>
942 </property>
943 </item>
944 </widget>
945 </item>
946 </layout>
947 </widget>
948 </item>
949 <item row="1" column="8">
950 <widget class="QWidget" name="widgetSpacer2" native="true">
951 <property name="minimumSize">
952 <size>
953 <width>25</width>
954 <height>0</height>
955 </size>
956 </property>
957 <layout class="QVBoxLayout" name="verticalLayout_31">
958 <property name="spacing">
959 <number>0</number>
960 </property>
961 <property name="leftMargin">
962 <number>0</number>
963 </property>
964 <property name="topMargin">
965 <number>0</number>
966 </property>
967 <property name="rightMargin">
968 <number>0</number>
969 </property>
970 <property name="bottomMargin">
971 <number>0</number>
972 </property>
973 <item>
974 <spacer name="controllerAppletHorizontalSpacer8">
975 <property name="orientation">
976 <enum>Qt::Horizontal</enum>
977 </property>
978 <property name="sizeHint" stdset="0">
979 <size>
980 <width>25</width>
981 <height>20</height>
982 </size>
983 </property>
984 </spacer>
985 </item>
986 </layout>
987 </widget>
988 </item>
989 <item row="1" column="4">
990 <widget class="QWidget" name="widgetSpacer4" native="true">
991 <layout class="QVBoxLayout" name="verticalLayout_33">
992 <property name="spacing">
993 <number>0</number>
994 </property>
995 <property name="leftMargin">
996 <number>0</number>
997 </property>
998 <property name="topMargin">
999 <number>0</number>
1000 </property>
1001 <property name="rightMargin">
1002 <number>0</number>
1003 </property>
1004 <property name="bottomMargin">
1005 <number>0</number>
1006 </property>
1007 <item>
1008 <spacer name="controllerAppletHorizontalSpacer6">
1009 <property name="orientation">
1010 <enum>Qt::Horizontal</enum>
1011 </property>
1012 <property name="sizeHint" stdset="0">
1013 <size>
1014 <width>0</width>
1015 <height>20</height>
1016 </size>
1017 </property>
1018 </spacer>
1019 </item>
1020 </layout>
1021 </widget>
1022 </item>
1023 <item row="1" column="6">
1024 <widget class="QWidget" name="widgetSpacer3" native="true">
1025 <layout class="QVBoxLayout" name="verticalLayout_32">
1026 <property name="spacing">
1027 <number>0</number>
1028 </property>
1029 <property name="leftMargin">
1030 <number>0</number>
1031 </property>
1032 <property name="topMargin">
1033 <number>0</number>
1034 </property>
1035 <property name="rightMargin">
1036 <number>0</number>
1037 </property>
1038 <property name="bottomMargin">
1039 <number>0</number>
1040 </property>
1041 <item>
1042 <spacer name="controllerAppletHorizontalSpacer7">
1043 <property name="orientation">
1044 <enum>Qt::Horizontal</enum>
1045 </property>
1046 <property name="sizeHint" stdset="0">
1047 <size>
1048 <width>0</width>
1049 <height>20</height>
1050 </size>
1051 </property>
1052 </spacer>
1053 </item>
1054 </layout>
1055 </widget>
1056 </item>
1057 <item row="1" column="5">
1058 <widget class="QWidget" name="widgetPlayer3" native="true">
1059 <layout class="QVBoxLayout" name="verticalLayout_28">
1060 <property name="spacing">
1061 <number>5</number>
1062 </property>
1063 <property name="leftMargin">
1064 <number>0</number>
1065 </property>
1066 <property name="topMargin">
1067 <number>0</number>
1068 </property>
1069 <property name="rightMargin">
1070 <number>0</number>
1071 </property>
1072 <property name="bottomMargin">
1073 <number>0</number>
1074 </property>
1075 <item alignment="Qt::AlignHCenter">
1076 <widget class="QGroupBox" name="groupPlayer3Connected">
1077 <property name="minimumSize">
1078 <size>
1079 <width>100</width>
1080 <height>100</height>
1081 </size>
1082 </property>
1083 <property name="maximumSize">
1084 <size>
1085 <width>100</width>
1086 <height>100</height>
1087 </size>
1088 </property>
1089 <property name="title">
1090 <string/>
1091 </property>
1092 <property name="checkable">
1093 <bool>true</bool>
1094 </property>
1095 <property name="checked">
1096 <bool>false</bool>
1097 </property>
1098 <layout class="QVBoxLayout" name="verticalLayout_6" stretch="1,0">
1099 <property name="spacing">
1100 <number>7</number>
1101 </property>
1102 <property name="leftMargin">
1103 <number>14</number>
1104 </property>
1105 <property name="topMargin">
1106 <number>7</number>
1107 </property>
1108 <property name="rightMargin">
1109 <number>14</number>
1110 </property>
1111 <property name="bottomMargin">
1112 <number>4</number>
1113 </property>
1114 <item>
1115 <widget class="QWidget" name="controllerPlayer3" native="true">
1116 <property name="styleSheet">
1117 <string notr="true"/>
1118 </property>
1119 <layout class="QVBoxLayout" name="verticalLayout_14">
1120 <property name="topMargin">
1121 <number>16</number>
1122 </property>
1123 <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
1124 <widget class="QLabel" name="labelPlayer3">
1125 <property name="text">
1126 <string>P3</string>
1127 </property>
1128 </widget>
1129 </item>
1130 </layout>
1131 </widget>
1132 </item>
1133 <item alignment="Qt::AlignHCenter">
1134 <widget class="QWidget" name="Player3LEDs" native="true">
1135 <property name="enabled">
1136 <bool>false</bool>
1137 </property>
1138 <property name="minimumSize">
1139 <size>
1140 <width>0</width>
1141 <height>10</height>
1142 </size>
1143 </property>
1144 <layout class="QHBoxLayout" name="horizontalLayout_9">
1145 <property name="spacing">
1146 <number>4</number>
1147 </property>
1148 <property name="leftMargin">
1149 <number>0</number>
1150 </property>
1151 <property name="topMargin">
1152 <number>0</number>
1153 </property>
1154 <property name="rightMargin">
1155 <number>0</number>
1156 </property>
1157 <property name="bottomMargin">
1158 <number>0</number>
1159 </property>
1160 <item>
1161 <widget class="QCheckBox" name="checkboxPlayer3LED1"/>
1162 </item>
1163 <item>
1164 <widget class="QCheckBox" name="checkboxPlayer3LED2"/>
1165 </item>
1166 <item>
1167 <widget class="QCheckBox" name="checkboxPlayer3LED3"/>
1168 </item>
1169 <item>
1170 <widget class="QCheckBox" name="checkboxPlayer3LED4"/>
1171 </item>
1172 </layout>
1173 </widget>
1174 </item>
1175 </layout>
1176 </widget>
1177 </item>
1178 <item>
1179 <widget class="QWidget" name="Player3Explain" native="true">
1180 <property name="minimumSize">
1181 <size>
1182 <width>0</width>
1183 <height>10</height>
1184 </size>
1185 </property>
1186 <property name="maximumSize">
1187 <size>
1188 <width>150</width>
1189 <height>16777215</height>
1190 </size>
1191 </property>
1192 <layout class="QVBoxLayout" name="verticalLayout_38">
1193 <property name="spacing">
1194 <number>0</number>
1195 </property>
1196 <property name="leftMargin">
1197 <number>0</number>
1198 </property>
1199 <property name="topMargin">
1200 <number>0</number>
1201 </property>
1202 <property name="rightMargin">
1203 <number>0</number>
1204 </property>
1205 <property name="bottomMargin">
1206 <number>0</number>
1207 </property>
1208 <item>
1209 <widget class="QLabel" name="labelPlayer3Explain">
1210 <property name="alignment">
1211 <set>Qt::AlignCenter</set>
1212 </property>
1213 </widget>
1214 </item>
1215 </layout>
1216 </widget>
1217 </item>
1218 <item>
1219 <widget class="QComboBox" name="comboPlayer3Emulated">
1220 <property name="editable">
1221 <bool>false</bool>
1222 </property>
1223 <item>
1224 <property name="text">
1225 <string>Pro Controller</string>
1226 </property>
1227 </item>
1228 <item>
1229 <property name="text">
1230 <string>Dual Joycons</string>
1231 </property>
1232 </item>
1233 <item>
1234 <property name="text">
1235 <string>Left Joycon</string>
1236 </property>
1237 </item>
1238 <item>
1239 <property name="text">
1240 <string>Right Joycon</string>
1241 </property>
1242 </item>
1243 </widget>
1244 </item>
1245 <item>
1246 <widget class="QComboBox" name="comboPlayer3Profile">
1247 <item>
1248 <property name="text">
1249 <string>Use Current Config</string>
1250 </property>
1251 </item>
1252 </widget>
1253 </item>
1254 </layout>
1255 </widget>
1256 </item>
1257 <item row="0" column="1">
1258 <widget class="QWidget" name="widgetSpacer5" native="true">
1259 <property name="minimumSize">
1260 <size>
1261 <width>0</width>
1262 <height>25</height>
1263 </size>
1264 </property>
1265 <layout class="QVBoxLayout" name="verticalLayout_34">
1266 <property name="spacing">
1267 <number>0</number>
1268 </property>
1269 <property name="leftMargin">
1270 <number>0</number>
1271 </property>
1272 <property name="topMargin">
1273 <number>0</number>
1274 </property>
1275 <property name="rightMargin">
1276 <number>0</number>
1277 </property>
1278 <property name="bottomMargin">
1279 <number>0</number>
1280 </property>
1281 <item>
1282 <spacer name="controllerAppletVerticalSpacer3">
1283 <property name="orientation">
1284 <enum>Qt::Vertical</enum>
1285 </property>
1286 <property name="sizeHint" stdset="0">
1287 <size>
1288 <width>20</width>
1289 <height>25</height>
1290 </size>
1291 </property>
1292 </spacer>
1293 </item>
1294 </layout>
1295 </widget>
1296 </item>
1297 <item row="6" column="5">
1298 <widget class="QWidget" name="widgetPlayer7" native="true">
1299 <layout class="QVBoxLayout" name="verticalLayout_25">
1300 <property name="spacing">
1301 <number>5</number>
1302 </property>
1303 <property name="leftMargin">
1304 <number>0</number>
1305 </property>
1306 <property name="topMargin">
1307 <number>0</number>
1308 </property>
1309 <property name="rightMargin">
1310 <number>0</number>
1311 </property>
1312 <property name="bottomMargin">
1313 <number>0</number>
1314 </property>
1315 <item alignment="Qt::AlignHCenter">
1316 <widget class="QGroupBox" name="groupPlayer7Connected">
1317 <property name="minimumSize">
1318 <size>
1319 <width>100</width>
1320 <height>100</height>
1321 </size>
1322 </property>
1323 <property name="maximumSize">
1324 <size>
1325 <width>100</width>
1326 <height>100</height>
1327 </size>
1328 </property>
1329 <property name="title">
1330 <string/>
1331 </property>
1332 <property name="checkable">
1333 <bool>true</bool>
1334 </property>
1335 <property name="checked">
1336 <bool>false</bool>
1337 </property>
1338 <layout class="QVBoxLayout" name="verticalLayout_10" stretch="1,0">
1339 <property name="spacing">
1340 <number>7</number>
1341 </property>
1342 <property name="leftMargin">
1343 <number>14</number>
1344 </property>
1345 <property name="topMargin">
1346 <number>7</number>
1347 </property>
1348 <property name="rightMargin">
1349 <number>14</number>
1350 </property>
1351 <property name="bottomMargin">
1352 <number>4</number>
1353 </property>
1354 <item>
1355 <widget class="QWidget" name="controllerPlayer7" native="true">
1356 <property name="styleSheet">
1357 <string notr="true"/>
1358 </property>
1359 <layout class="QVBoxLayout" name="verticalLayout_18">
1360 <property name="topMargin">
1361 <number>16</number>
1362 </property>
1363 <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
1364 <widget class="QLabel" name="labelPlayer7">
1365 <property name="text">
1366 <string>P7</string>
1367 </property>
1368 </widget>
1369 </item>
1370 </layout>
1371 </widget>
1372 </item>
1373 <item alignment="Qt::AlignHCenter">
1374 <widget class="QWidget" name="Player7LEDs" native="true">
1375 <property name="enabled">
1376 <bool>false</bool>
1377 </property>
1378 <property name="minimumSize">
1379 <size>
1380 <width>0</width>
1381 <height>10</height>
1382 </size>
1383 </property>
1384 <layout class="QHBoxLayout" name="horizontalLayout_13">
1385 <property name="spacing">
1386 <number>4</number>
1387 </property>
1388 <property name="leftMargin">
1389 <number>0</number>
1390 </property>
1391 <property name="topMargin">
1392 <number>0</number>
1393 </property>
1394 <property name="rightMargin">
1395 <number>0</number>
1396 </property>
1397 <property name="bottomMargin">
1398 <number>0</number>
1399 </property>
1400 <item>
1401 <widget class="QCheckBox" name="checkboxPlayer7LED1"/>
1402 </item>
1403 <item>
1404 <widget class="QCheckBox" name="checkboxPlayer7LED2"/>
1405 </item>
1406 <item>
1407 <widget class="QCheckBox" name="checkboxPlayer7LED3"/>
1408 </item>
1409 <item>
1410 <widget class="QCheckBox" name="checkboxPlayer7LED4"/>
1411 </item>
1412 </layout>
1413 </widget>
1414 </item>
1415 </layout>
1416 </widget>
1417 </item>
1418 <item>
1419 <widget class="QWidget" name="Player7Explain" native="true">
1420 <property name="minimumSize">
1421 <size>
1422 <width>0</width>
1423 <height>10</height>
1424 </size>
1425 </property>
1426 <property name="maximumSize">
1427 <size>
1428 <width>150</width>
1429 <height>16777215</height>
1430 </size>
1431 </property>
1432 <layout class="QVBoxLayout" name="verticalLayout_42">
1433 <property name="spacing">
1434 <number>0</number>
1435 </property>
1436 <property name="leftMargin">
1437 <number>0</number>
1438 </property>
1439 <property name="topMargin">
1440 <number>0</number>
1441 </property>
1442 <property name="rightMargin">
1443 <number>0</number>
1444 </property>
1445 <property name="bottomMargin">
1446 <number>0</number>
1447 </property>
1448 <item>
1449 <widget class="QLabel" name="labelPlayer7Explain">
1450 <property name="alignment">
1451 <set>Qt::AlignCenter</set>
1452 </property>
1453 </widget>
1454 </item>
1455 </layout>
1456 </widget>
1457 </item>
1458 <item>
1459 <widget class="QComboBox" name="comboPlayer7Emulated">
1460 <item>
1461 <property name="text">
1462 <string>Pro Controller</string>
1463 </property>
1464 </item>
1465 <item>
1466 <property name="text">
1467 <string>Dual Joycons</string>
1468 </property>
1469 </item>
1470 <item>
1471 <property name="text">
1472 <string>Left Joycon</string>
1473 </property>
1474 </item>
1475 <item>
1476 <property name="text">
1477 <string>Right Joycon</string>
1478 </property>
1479 </item>
1480 </widget>
1481 </item>
1482 <item>
1483 <widget class="QComboBox" name="comboPlayer7Profile">
1484 <item>
1485 <property name="text">
1486 <string>Use Current Config</string>
1487 </property>
1488 </item>
1489 </widget>
1490 </item>
1491 </layout>
1492 </widget>
1493 </item>
1494 <item row="6" column="7">
1495 <widget class="QWidget" name="widgetPlayer8" native="true">
1496 <layout class="QVBoxLayout" name="verticalLayout_26">
1497 <property name="spacing">
1498 <number>5</number>
1499 </property>
1500 <property name="leftMargin">
1501 <number>0</number>
1502 </property>
1503 <property name="topMargin">
1504 <number>0</number>
1505 </property>
1506 <property name="rightMargin">
1507 <number>0</number>
1508 </property>
1509 <property name="bottomMargin">
1510 <number>0</number>
1511 </property>
1512 <item alignment="Qt::AlignHCenter">
1513 <widget class="QGroupBox" name="groupPlayer8Connected">
1514 <property name="minimumSize">
1515 <size>
1516 <width>100</width>
1517 <height>100</height>
1518 </size>
1519 </property>
1520 <property name="maximumSize">
1521 <size>
1522 <width>100</width>
1523 <height>100</height>
1524 </size>
1525 </property>
1526 <property name="title">
1527 <string/>
1528 </property>
1529 <property name="checkable">
1530 <bool>true</bool>
1531 </property>
1532 <property name="checked">
1533 <bool>false</bool>
1534 </property>
1535 <layout class="QVBoxLayout" name="verticalLayout_11" stretch="1,0">
1536 <property name="spacing">
1537 <number>7</number>
1538 </property>
1539 <property name="leftMargin">
1540 <number>14</number>
1541 </property>
1542 <property name="topMargin">
1543 <number>7</number>
1544 </property>
1545 <property name="rightMargin">
1546 <number>14</number>
1547 </property>
1548 <property name="bottomMargin">
1549 <number>4</number>
1550 </property>
1551 <item>
1552 <widget class="QWidget" name="controllerPlayer8" native="true">
1553 <property name="styleSheet">
1554 <string notr="true"/>
1555 </property>
1556 <layout class="QVBoxLayout" name="verticalLayout_19">
1557 <property name="topMargin">
1558 <number>16</number>
1559 </property>
1560 <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
1561 <widget class="QLabel" name="labelPlayer8">
1562 <property name="text">
1563 <string>P8</string>
1564 </property>
1565 </widget>
1566 </item>
1567 </layout>
1568 </widget>
1569 </item>
1570 <item alignment="Qt::AlignHCenter">
1571 <widget class="QWidget" name="Player8LEDs" native="true">
1572 <property name="enabled">
1573 <bool>false</bool>
1574 </property>
1575 <property name="minimumSize">
1576 <size>
1577 <width>0</width>
1578 <height>10</height>
1579 </size>
1580 </property>
1581 <layout class="QHBoxLayout" name="horizontalLayout_14">
1582 <property name="spacing">
1583 <number>4</number>
1584 </property>
1585 <property name="leftMargin">
1586 <number>0</number>
1587 </property>
1588 <property name="topMargin">
1589 <number>0</number>
1590 </property>
1591 <property name="rightMargin">
1592 <number>0</number>
1593 </property>
1594 <property name="bottomMargin">
1595 <number>0</number>
1596 </property>
1597 <item>
1598 <widget class="QCheckBox" name="checkboxPlayer8LED1"/>
1599 </item>
1600 <item>
1601 <widget class="QCheckBox" name="checkboxPlayer8LED2"/>
1602 </item>
1603 <item>
1604 <widget class="QCheckBox" name="checkboxPlayer8LED3"/>
1605 </item>
1606 <item>
1607 <widget class="QCheckBox" name="checkboxPlayer8LED4"/>
1608 </item>
1609 </layout>
1610 </widget>
1611 </item>
1612 </layout>
1613 </widget>
1614 </item>
1615 <item>
1616 <widget class="QWidget" name="Player8Explain" native="true">
1617 <property name="minimumSize">
1618 <size>
1619 <width>0</width>
1620 <height>10</height>
1621 </size>
1622 </property>
1623 <property name="maximumSize">
1624 <size>
1625 <width>150</width>
1626 <height>16777215</height>
1627 </size>
1628 </property>
1629 <layout class="QVBoxLayout" name="verticalLayout_35">
1630 <property name="spacing">
1631 <number>0</number>
1632 </property>
1633 <property name="leftMargin">
1634 <number>0</number>
1635 </property>
1636 <property name="topMargin">
1637 <number>0</number>
1638 </property>
1639 <property name="rightMargin">
1640 <number>0</number>
1641 </property>
1642 <property name="bottomMargin">
1643 <number>0</number>
1644 </property>
1645 <item>
1646 <widget class="QLabel" name="labelPlayer8Explain">
1647 <property name="alignment">
1648 <set>Qt::AlignCenter</set>
1649 </property>
1650 </widget>
1651 </item>
1652 </layout>
1653 </widget>
1654 </item>
1655 <item>
1656 <widget class="QComboBox" name="comboPlayer8Emulated">
1657 <item>
1658 <property name="text">
1659 <string>Pro Controller</string>
1660 </property>
1661 </item>
1662 <item>
1663 <property name="text">
1664 <string>Dual Joycons</string>
1665 </property>
1666 </item>
1667 <item>
1668 <property name="text">
1669 <string>Left Joycon</string>
1670 </property>
1671 </item>
1672 <item>
1673 <property name="text">
1674 <string>Right Joycon</string>
1675 </property>
1676 </item>
1677 </widget>
1678 </item>
1679 <item>
1680 <widget class="QComboBox" name="comboPlayer8Profile">
1681 <item>
1682 <property name="text">
1683 <string>Use Current Config</string>
1684 </property>
1685 </item>
1686 </widget>
1687 </item>
1688 </layout>
1689 </widget>
1690 </item>
1691 <item row="6" column="1">
1692 <widget class="QWidget" name="widgetPlayer5" native="true">
1693 <layout class="QVBoxLayout" name="verticalLayout_23">
1694 <property name="spacing">
1695 <number>5</number>
1696 </property>
1697 <property name="leftMargin">
1698 <number>0</number>
1699 </property>
1700 <property name="topMargin">
1701 <number>0</number>
1702 </property>
1703 <property name="rightMargin">
1704 <number>0</number>
1705 </property>
1706 <property name="bottomMargin">
1707 <number>0</number>
1708 </property>
1709 <item alignment="Qt::AlignHCenter">
1710 <widget class="QGroupBox" name="groupPlayer5Connected">
1711 <property name="minimumSize">
1712 <size>
1713 <width>100</width>
1714 <height>100</height>
1715 </size>
1716 </property>
1717 <property name="maximumSize">
1718 <size>
1719 <width>100</width>
1720 <height>100</height>
1721 </size>
1722 </property>
1723 <property name="title">
1724 <string/>
1725 </property>
1726 <property name="checkable">
1727 <bool>true</bool>
1728 </property>
1729 <property name="checked">
1730 <bool>false</bool>
1731 </property>
1732 <layout class="QVBoxLayout" name="verticalLayout_8" stretch="1,0">
1733 <property name="spacing">
1734 <number>7</number>
1735 </property>
1736 <property name="leftMargin">
1737 <number>14</number>
1738 </property>
1739 <property name="topMargin">
1740 <number>7</number>
1741 </property>
1742 <property name="rightMargin">
1743 <number>14</number>
1744 </property>
1745 <property name="bottomMargin">
1746 <number>4</number>
1747 </property>
1748 <item>
1749 <widget class="QWidget" name="controllerPlayer5" native="true">
1750 <property name="styleSheet">
1751 <string notr="true"/>
1752 </property>
1753 <layout class="QVBoxLayout" name="verticalLayout_16">
1754 <property name="topMargin">
1755 <number>16</number>
1756 </property>
1757 <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
1758 <widget class="QLabel" name="labelPlayer5">
1759 <property name="text">
1760 <string>P5</string>
1761 </property>
1762 </widget>
1763 </item>
1764 </layout>
1765 </widget>
1766 </item>
1767 <item alignment="Qt::AlignHCenter">
1768 <widget class="QWidget" name="Player5LEDs" native="true">
1769 <property name="enabled">
1770 <bool>false</bool>
1771 </property>
1772 <property name="minimumSize">
1773 <size>
1774 <width>0</width>
1775 <height>10</height>
1776 </size>
1777 </property>
1778 <layout class="QHBoxLayout" name="horizontalLayout_11">
1779 <property name="spacing">
1780 <number>4</number>
1781 </property>
1782 <property name="leftMargin">
1783 <number>0</number>
1784 </property>
1785 <property name="topMargin">
1786 <number>0</number>
1787 </property>
1788 <property name="rightMargin">
1789 <number>0</number>
1790 </property>
1791 <property name="bottomMargin">
1792 <number>0</number>
1793 </property>
1794 <item>
1795 <widget class="QCheckBox" name="checkboxPlayer5LED1">
1796 <property name="layoutDirection">
1797 <enum>Qt::LeftToRight</enum>
1798 </property>
1799 </widget>
1800 </item>
1801 <item>
1802 <widget class="QCheckBox" name="checkboxPlayer5LED2"/>
1803 </item>
1804 <item>
1805 <widget class="QCheckBox" name="checkboxPlayer5LED3"/>
1806 </item>
1807 <item>
1808 <widget class="QCheckBox" name="checkboxPlayer5LED4"/>
1809 </item>
1810 </layout>
1811 </widget>
1812 </item>
1813 </layout>
1814 </widget>
1815 </item>
1816 <item>
1817 <widget class="QWidget" name="Player5Explain" native="true">
1818 <property name="minimumSize">
1819 <size>
1820 <width>0</width>
1821 <height>10</height>
1822 </size>
1823 </property>
1824 <property name="maximumSize">
1825 <size>
1826 <width>150</width>
1827 <height>16777215</height>
1828 </size>
1829 </property>
1830 <layout class="QVBoxLayout" name="verticalLayout_40">
1831 <property name="spacing">
1832 <number>0</number>
1833 </property>
1834 <property name="leftMargin">
1835 <number>0</number>
1836 </property>
1837 <property name="topMargin">
1838 <number>0</number>
1839 </property>
1840 <property name="rightMargin">
1841 <number>0</number>
1842 </property>
1843 <property name="bottomMargin">
1844 <number>0</number>
1845 </property>
1846 <item>
1847 <widget class="QLabel" name="labelPlayer5Explain">
1848 <property name="alignment">
1849 <set>Qt::AlignCenter</set>
1850 </property>
1851 </widget>
1852 </item>
1853 </layout>
1854 </widget>
1855 </item>
1856 <item>
1857 <widget class="QComboBox" name="comboPlayer5Emulated">
1858 <item>
1859 <property name="text">
1860 <string>Pro Controller</string>
1861 </property>
1862 </item>
1863 <item>
1864 <property name="text">
1865 <string>Dual Joycons</string>
1866 </property>
1867 </item>
1868 <item>
1869 <property name="text">
1870 <string>Left Joycon</string>
1871 </property>
1872 </item>
1873 <item>
1874 <property name="text">
1875 <string>Right Joycon</string>
1876 </property>
1877 </item>
1878 </widget>
1879 </item>
1880 <item>
1881 <widget class="QComboBox" name="comboPlayer5Profile">
1882 <item>
1883 <property name="text">
1884 <string>Use Current Config</string>
1885 </property>
1886 </item>
1887 </widget>
1888 </item>
1889 </layout>
1890 </widget>
1891 </item>
1892 <item row="6" column="3">
1893 <widget class="QWidget" name="widgetPlayer6" native="true">
1894 <layout class="QVBoxLayout" name="verticalLayout_24">
1895 <property name="spacing">
1896 <number>5</number>
1897 </property>
1898 <property name="leftMargin">
1899 <number>0</number>
1900 </property>
1901 <property name="topMargin">
1902 <number>0</number>
1903 </property>
1904 <property name="rightMargin">
1905 <number>0</number>
1906 </property>
1907 <property name="bottomMargin">
1908 <number>0</number>
1909 </property>
1910 <item alignment="Qt::AlignHCenter">
1911 <widget class="QGroupBox" name="groupPlayer6Connected">
1912 <property name="minimumSize">
1913 <size>
1914 <width>100</width>
1915 <height>100</height>
1916 </size>
1917 </property>
1918 <property name="maximumSize">
1919 <size>
1920 <width>100</width>
1921 <height>100</height>
1922 </size>
1923 </property>
1924 <property name="title">
1925 <string/>
1926 </property>
1927 <property name="checkable">
1928 <bool>true</bool>
1929 </property>
1930 <property name="checked">
1931 <bool>false</bool>
1932 </property>
1933 <layout class="QVBoxLayout" name="verticalLayout_9" stretch="1,0">
1934 <property name="spacing">
1935 <number>7</number>
1936 </property>
1937 <property name="leftMargin">
1938 <number>14</number>
1939 </property>
1940 <property name="topMargin">
1941 <number>7</number>
1942 </property>
1943 <property name="rightMargin">
1944 <number>14</number>
1945 </property>
1946 <property name="bottomMargin">
1947 <number>4</number>
1948 </property>
1949 <item>
1950 <widget class="QWidget" name="controllerPlayer6" native="true">
1951 <property name="styleSheet">
1952 <string notr="true"/>
1953 </property>
1954 <layout class="QVBoxLayout" name="verticalLayout_17">
1955 <property name="topMargin">
1956 <number>16</number>
1957 </property>
1958 <item alignment="Qt::AlignHCenter|Qt::AlignVCenter">
1959 <widget class="QLabel" name="labelPlayer6">
1960 <property name="text">
1961 <string>P6</string>
1962 </property>
1963 </widget>
1964 </item>
1965 </layout>
1966 </widget>
1967 </item>
1968 <item alignment="Qt::AlignHCenter">
1969 <widget class="QWidget" name="Player6LEDs" native="true">
1970 <property name="enabled">
1971 <bool>false</bool>
1972 </property>
1973 <property name="minimumSize">
1974 <size>
1975 <width>0</width>
1976 <height>10</height>
1977 </size>
1978 </property>
1979 <layout class="QHBoxLayout" name="horizontalLayout_12">
1980 <property name="spacing">
1981 <number>4</number>
1982 </property>
1983 <property name="leftMargin">
1984 <number>0</number>
1985 </property>
1986 <property name="topMargin">
1987 <number>0</number>
1988 </property>
1989 <property name="rightMargin">
1990 <number>0</number>
1991 </property>
1992 <property name="bottomMargin">
1993 <number>0</number>
1994 </property>
1995 <item>
1996 <widget class="QCheckBox" name="checkboxPlayer6LED1"/>
1997 </item>
1998 <item>
1999 <widget class="QCheckBox" name="checkboxPlayer6LED2"/>
2000 </item>
2001 <item>
2002 <widget class="QCheckBox" name="checkboxPlayer6LED3"/>
2003 </item>
2004 <item>
2005 <widget class="QCheckBox" name="checkboxPlayer6LED4"/>
2006 </item>
2007 </layout>
2008 </widget>
2009 </item>
2010 </layout>
2011 </widget>
2012 </item>
2013 <item>
2014 <widget class="QWidget" name="Player6Explain" native="true">
2015 <property name="minimumSize">
2016 <size>
2017 <width>0</width>
2018 <height>10</height>
2019 </size>
2020 </property>
2021 <property name="maximumSize">
2022 <size>
2023 <width>150</width>
2024 <height>16777215</height>
2025 </size>
2026 </property>
2027 <layout class="QVBoxLayout" name="verticalLayout_41">
2028 <property name="spacing">
2029 <number>0</number>
2030 </property>
2031 <property name="leftMargin">
2032 <number>0</number>
2033 </property>
2034 <property name="topMargin">
2035 <number>0</number>
2036 </property>
2037 <property name="rightMargin">
2038 <number>0</number>
2039 </property>
2040 <property name="bottomMargin">
2041 <number>0</number>
2042 </property>
2043 <item>
2044 <widget class="QLabel" name="labelPlayer6Explain">
2045 <property name="alignment">
2046 <set>Qt::AlignCenter</set>
2047 </property>
2048 </widget>
2049 </item>
2050 </layout>
2051 </widget>
2052 </item>
2053 <item>
2054 <widget class="QComboBox" name="comboPlayer6Emulated">
2055 <item>
2056 <property name="text">
2057 <string>Pro Controller</string>
2058 </property>
2059 </item>
2060 <item>
2061 <property name="text">
2062 <string>Dual Joycons</string>
2063 </property>
2064 </item>
2065 <item>
2066 <property name="text">
2067 <string>Left Joycon</string>
2068 </property>
2069 </item>
2070 <item>
2071 <property name="text">
2072 <string>Right Joycon</string>
2073 </property>
2074 </item>
2075 </widget>
2076 </item>
2077 <item>
2078 <widget class="QComboBox" name="comboPlayer6Profile">
2079 <item>
2080 <property name="text">
2081 <string>Use Current Config</string>
2082 </property>
2083 </item>
2084 </widget>
2085 </item>
2086 </layout>
2087 </widget>
2088 </item>
2089 <item row="10" column="1">
2090 <widget class="QWidget" name="widgetSpacer" native="true">
2091 <property name="minimumSize">
2092 <size>
2093 <width>0</width>
2094 <height>25</height>
2095 </size>
2096 </property>
2097 <layout class="QVBoxLayout" name="verticalLayout_22">
2098 <property name="spacing">
2099 <number>0</number>
2100 </property>
2101 <property name="leftMargin">
2102 <number>0</number>
2103 </property>
2104 <property name="topMargin">
2105 <number>0</number>
2106 </property>
2107 <property name="rightMargin">
2108 <number>0</number>
2109 </property>
2110 <property name="bottomMargin">
2111 <number>0</number>
2112 </property>
2113 <item>
2114 <spacer name="controllerAppletVerticalSpacer">
2115 <property name="orientation">
2116 <enum>Qt::Vertical</enum>
2117 </property>
2118 <property name="sizeHint" stdset="0">
2119 <size>
2120 <width>20</width>
2121 <height>25</height>
2122 </size>
2123 </property>
2124 </spacer>
2125 </item>
2126 </layout>
2127 </widget>
2128 </item>
2129 <item row="1" column="2">
2130 <widget class="QWidget" name="widgetSpacer6" native="true">
2131 <layout class="QHBoxLayout" name="horizontalLayout_15">
2132 <property name="spacing">
2133 <number>0</number>
2134 </property>
2135 <property name="leftMargin">
2136 <number>0</number>
2137 </property>
2138 <property name="topMargin">
2139 <number>0</number>
2140 </property>
2141 <property name="rightMargin">
2142 <number>0</number>
2143 </property>
2144 <property name="bottomMargin">
2145 <number>0</number>
2146 </property>
2147 <item>
2148 <spacer name="controllerAppletHorizontalSpacer5">
2149 <property name="orientation">
2150 <enum>Qt::Horizontal</enum>
2151 </property>
2152 <property name="sizeHint" stdset="0">
2153 <size>
2154 <width>0</width>
2155 <height>20</height>
2156 </size>
2157 </property>
2158 </spacer>
2159 </item>
2160 </layout>
2161 </widget>
2162 </item>
2163 <item row="1" column="0">
2164 <widget class="QWidget" name="widgetSpacer7" native="true">
2165 <property name="minimumSize">
2166 <size>
2167 <width>25</width>
2168 <height>0</height>
2169 </size>
2170 </property>
2171 <layout class="QHBoxLayout" name="horizontalLayout_16">
2172 <property name="spacing">
2173 <number>0</number>
2174 </property>
2175 <property name="leftMargin">
2176 <number>0</number>
2177 </property>
2178 <property name="topMargin">
2179 <number>0</number>
2180 </property>
2181 <property name="rightMargin">
2182 <number>0</number>
2183 </property>
2184 <property name="bottomMargin">
2185 <number>0</number>
2186 </property>
2187 <item>
2188 <spacer name="controllerAppletHorizontalSpacer4">
2189 <property name="orientation">
2190 <enum>Qt::Horizontal</enum>
2191 </property>
2192 <property name="sizeHint" stdset="0">
2193 <size>
2194 <width>25</width>
2195 <height>20</height>
2196 </size>
2197 </property>
2198 </spacer>
2199 </item>
2200 </layout>
2201 </widget>
2202 </item>
2203 <item row="2" column="1">
2204 <widget class="QWidget" name="widgetSpacer9" native="true">
2205 <property name="minimumSize">
2206 <size>
2207 <width>0</width>
2208 <height>25</height>
2209 </size>
2210 </property>
2211 <layout class="QHBoxLayout" name="horizontalLayout_17">
2212 <property name="spacing">
2213 <number>0</number>
2214 </property>
2215 <property name="leftMargin">
2216 <number>0</number>
2217 </property>
2218 <property name="topMargin">
2219 <number>0</number>
2220 </property>
2221 <property name="rightMargin">
2222 <number>0</number>
2223 </property>
2224 <property name="bottomMargin">
2225 <number>0</number>
2226 </property>
2227 <item>
2228 <spacer name="controllerAppletVerticalSpacer2">
2229 <property name="orientation">
2230 <enum>Qt::Vertical</enum>
2231 </property>
2232 <property name="sizeHint" stdset="0">
2233 <size>
2234 <width>20</width>
2235 <height>25</height>
2236 </size>
2237 </property>
2238 </spacer>
2239 </item>
2240 </layout>
2241 </widget>
2242 </item>
2243 </layout>
2244 </item>
2245 </layout>
2246 </widget>
2247 </item>
2248 <item>
2249 <widget class="QWidget" name="bottomControllerApplet" native="true">
2250 <layout class="QHBoxLayout" name="horizontalLayout_6">
2251 <property name="spacing">
2252 <number>15</number>
2253 </property>
2254 <property name="leftMargin">
2255 <number>15</number>
2256 </property>
2257 <property name="topMargin">
2258 <number>8</number>
2259 </property>
2260 <property name="rightMargin">
2261 <number>15</number>
2262 </property>
2263 <property name="bottomMargin">
2264 <number>15</number>
2265 </property>
2266 <item>
2267 <widget class="QGroupBox" name="handheldGroup">
2268 <property name="maximumSize">
2269 <size>
2270 <width>16777215</width>
2271 <height>16777215</height>
2272 </size>
2273 </property>
2274 <property name="title">
2275 <string>Console Mode</string>
2276 </property>
2277 <layout class="QHBoxLayout" name="horizontalLayout_3">
2278 <property name="spacing">
2279 <number>6</number>
2280 </property>
2281 <property name="leftMargin">
2282 <number>6</number>
2283 </property>
2284 <property name="topMargin">
2285 <number>6</number>
2286 </property>
2287 <property name="rightMargin">
2288 <number>3</number>
2289 </property>
2290 <property name="bottomMargin">
2291 <number>6</number>
2292 </property>
2293 <item>
2294 <widget class="QRadioButton" name="radioDocked">
2295 <property name="text">
2296 <string>Docked</string>
2297 </property>
2298 <property name="checked">
2299 <bool>true</bool>
2300 </property>
2301 </widget>
2302 </item>
2303 <item>
2304 <widget class="QRadioButton" name="radioUndocked">
2305 <property name="text">
2306 <string>Undocked</string>
2307 </property>
2308 </widget>
2309 </item>
2310 </layout>
2311 </widget>
2312 </item>
2313 <item>
2314 <widget class="QGroupBox" name="vibrationGroup">
2315 <property name="title">
2316 <string>Vibration</string>
2317 </property>
2318 <property name="checkable">
2319 <bool>true</bool>
2320 </property>
2321 <layout class="QHBoxLayout" name="horizontalLayout_5">
2322 <property name="leftMargin">
2323 <number>3</number>
2324 </property>
2325 <property name="topMargin">
2326 <number>3</number>
2327 </property>
2328 <property name="rightMargin">
2329 <number>3</number>
2330 </property>
2331 <property name="bottomMargin">
2332 <number>3</number>
2333 </property>
2334 <item>
2335 <widget class="QSpinBox" name="vibrationSpin">
2336 <property name="minimumSize">
2337 <size>
2338 <width>65</width>
2339 <height>0</height>
2340 </size>
2341 </property>
2342 <property name="maximumSize">
2343 <size>
2344 <width>65</width>
2345 <height>16777215</height>
2346 </size>
2347 </property>
2348 <property name="suffix">
2349 <string>%</string>
2350 </property>
2351 <property name="minimum">
2352 <number>1</number>
2353 </property>
2354 <property name="maximum">
2355 <number>200</number>
2356 </property>
2357 <property name="value">
2358 <number>100</number>
2359 </property>
2360 </widget>
2361 </item>
2362 </layout>
2363 </widget>
2364 </item>
2365 <item>
2366 <widget class="QGroupBox" name="motionGroup">
2367 <property name="title">
2368 <string>Motion</string>
2369 </property>
2370 <property name="checkable">
2371 <bool>true</bool>
2372 </property>
2373 <layout class="QHBoxLayout" name="horizontalLayout_4">
2374 <property name="leftMargin">
2375 <number>3</number>
2376 </property>
2377 <property name="topMargin">
2378 <number>3</number>
2379 </property>
2380 <property name="rightMargin">
2381 <number>3</number>
2382 </property>
2383 <property name="bottomMargin">
2384 <number>3</number>
2385 </property>
2386 <item>
2387 <widget class="QPushButton" name="motionButton">
2388 <property name="minimumSize">
2389 <size>
2390 <width>57</width>
2391 <height>0</height>
2392 </size>
2393 </property>
2394 <property name="maximumSize">
2395 <size>
2396 <width>55</width>
2397 <height>16777215</height>
2398 </size>
2399 </property>
2400 <property name="styleSheet">
2401 <string notr="true">min-width: 55px;</string>
2402 </property>
2403 <property name="text">
2404 <string>Configure</string>
2405 </property>
2406 </widget>
2407 </item>
2408 </layout>
2409 </widget>
2410 </item>
2411 <item>
2412 <widget class="QGroupBox" name="inputConfigGroup">
2413 <property name="title">
2414 <string>Input Config</string>
2415 </property>
2416 <layout class="QHBoxLayout" name="horizontalLayout_7">
2417 <property name="leftMargin">
2418 <number>3</number>
2419 </property>
2420 <property name="topMargin">
2421 <number>3</number>
2422 </property>
2423 <property name="rightMargin">
2424 <number>3</number>
2425 </property>
2426 <property name="bottomMargin">
2427 <number>3</number>
2428 </property>
2429 <item>
2430 <widget class="QPushButton" name="inputConfigButton">
2431 <property name="maximumSize">
2432 <size>
2433 <width>65</width>
2434 <height>16777215</height>
2435 </size>
2436 </property>
2437 <property name="styleSheet">
2438 <string notr="true">min-width: 55px;</string>
2439 </property>
2440 <property name="text">
2441 <string>Open</string>
2442 </property>
2443 </widget>
2444 </item>
2445 </layout>
2446 </widget>
2447 </item>
2448 <item>
2449 <widget class="QWidget" name="connectedControllers" native="true">
2450 <layout class="QGridLayout" name="gridLayout_2">
2451 <property name="leftMargin">
2452 <number>5</number>
2453 </property>
2454 <property name="topMargin">
2455 <number>0</number>
2456 </property>
2457 <property name="rightMargin">
2458 <number>0</number>
2459 </property>
2460 <property name="bottomMargin">
2461 <number>0</number>
2462 </property>
2463 <property name="spacing">
2464 <number>3</number>
2465 </property>
2466 <item row="1" column="4">
2467 <widget class="QCheckBox" name="checkboxPlayer4Connected">
2468 <property name="text">
2469 <string/>
2470 </property>
2471 </widget>
2472 </item>
2473 <item row="1" column="0">
2474 <widget class="QLabel" name="labelControllers">
2475 <property name="text">
2476 <string>Controllers</string>
2477 </property>
2478 </widget>
2479 </item>
2480 <item row="1" column="2">
2481 <widget class="QCheckBox" name="checkboxPlayer2Connected">
2482 <property name="text">
2483 <string/>
2484 </property>
2485 </widget>
2486 </item>
2487 <item row="0" column="1">
2488 <widget class="QLabel" name="labelConnectedPlayer1">
2489 <property name="text">
2490 <string>1</string>
2491 </property>
2492 <property name="alignment">
2493 <set>Qt::AlignCenter</set>
2494 </property>
2495 </widget>
2496 </item>
2497 <item row="1" column="3">
2498 <widget class="QCheckBox" name="checkboxPlayer3Connected">
2499 <property name="text">
2500 <string/>
2501 </property>
2502 </widget>
2503 </item>
2504 <item row="1" column="1">
2505 <widget class="QCheckBox" name="checkboxPlayer1Connected">
2506 <property name="layoutDirection">
2507 <enum>Qt::LeftToRight</enum>
2508 </property>
2509 <property name="checked">
2510 <bool>false</bool>
2511 </property>
2512 </widget>
2513 </item>
2514 <item row="0" column="2">
2515 <widget class="QLabel" name="labelConnectedPlayer2">
2516 <property name="text">
2517 <string>2</string>
2518 </property>
2519 <property name="alignment">
2520 <set>Qt::AlignCenter</set>
2521 </property>
2522 </widget>
2523 </item>
2524 <item row="0" column="4">
2525 <widget class="QLabel" name="labelConnectedPlayer4">
2526 <property name="text">
2527 <string>4</string>
2528 </property>
2529 <property name="alignment">
2530 <set>Qt::AlignCenter</set>
2531 </property>
2532 </widget>
2533 </item>
2534 <item row="0" column="3">
2535 <widget class="QLabel" name="labelConnectedPlayer3">
2536 <property name="text">
2537 <string>3</string>
2538 </property>
2539 <property name="alignment">
2540 <set>Qt::AlignCenter</set>
2541 </property>
2542 </widget>
2543 </item>
2544 <item row="0" column="0">
2545 <widget class="QLabel" name="labelConnected">
2546 <property name="text">
2547 <string>Connected</string>
2548 </property>
2549 </widget>
2550 </item>
2551 <item row="1" column="7">
2552 <widget class="QCheckBox" name="checkboxPlayer7Connected">
2553 <property name="text">
2554 <string/>
2555 </property>
2556 </widget>
2557 </item>
2558 <item row="0" column="5">
2559 <widget class="QLabel" name="labelConnectedPlayer5">
2560 <property name="text">
2561 <string>5</string>
2562 </property>
2563 <property name="alignment">
2564 <set>Qt::AlignCenter</set>
2565 </property>
2566 </widget>
2567 </item>
2568 <item row="1" column="6">
2569 <widget class="QCheckBox" name="checkboxPlayer6Connected">
2570 <property name="text">
2571 <string/>
2572 </property>
2573 </widget>
2574 </item>
2575 <item row="0" column="7">
2576 <widget class="QLabel" name="labelConnectedPlayer7">
2577 <property name="text">
2578 <string>7</string>
2579 </property>
2580 <property name="alignment">
2581 <set>Qt::AlignCenter</set>
2582 </property>
2583 </widget>
2584 </item>
2585 <item row="1" column="5">
2586 <widget class="QCheckBox" name="checkboxPlayer5Connected">
2587 <property name="text">
2588 <string/>
2589 </property>
2590 </widget>
2591 </item>
2592 <item row="0" column="6">
2593 <widget class="QLabel" name="labelConnectedPlayer6">
2594 <property name="text">
2595 <string>6</string>
2596 </property>
2597 <property name="alignment">
2598 <set>Qt::AlignCenter</set>
2599 </property>
2600 </widget>
2601 </item>
2602 <item row="0" column="8">
2603 <widget class="QLabel" name="labelConnectedPlayer8">
2604 <property name="text">
2605 <string>8</string>
2606 </property>
2607 <property name="alignment">
2608 <set>Qt::AlignCenter</set>
2609 </property>
2610 </widget>
2611 </item>
2612 <item row="1" column="8">
2613 <widget class="QCheckBox" name="checkboxPlayer8Connected">
2614 <property name="text">
2615 <string/>
2616 </property>
2617 </widget>
2618 </item>
2619 </layout>
2620 </widget>
2621 </item>
2622 <item>
2623 <spacer name="controllerAppletHorizontalSpacer">
2624 <property name="orientation">
2625 <enum>Qt::Horizontal</enum>
2626 </property>
2627 <property name="sizeHint" stdset="0">
2628 <size>
2629 <width>0</width>
2630 <height>20</height>
2631 </size>
2632 </property>
2633 </spacer>
2634 </item>
2635 <item alignment="Qt::AlignBottom">
2636 <widget class="QDialogButtonBox" name="buttonBox">
2637 <property name="enabled">
2638 <bool>true</bool>
2639 </property>
2640 <property name="standardButtons">
2641 <set>QDialogButtonBox::Ok</set>
2642 </property>
2643 </widget>
2644 </item>
2645 </layout>
2646 </widget>
2647 </item>
2648 </layout>
2649 </widget>
2650 </item>
2651 </layout>
2652 </widget>
2653 <resources/>
2654 <connections>
2655 <connection>
2656 <sender>buttonBox</sender>
2657 <signal>accepted()</signal>
2658 <receiver>QtControllerSelectorDialog</receiver>
2659 <slot>accept()</slot>
2660 <hints>
2661 <hint type="sourcelabel">
2662 <x>20</x>
2663 <y>20</y>
2664 </hint>
2665 <hint type="destinationlabel">
2666 <x>20</x>
2667 <y>20</y>
2668 </hint>
2669 </hints>
2670 </connection>
2671 </connections>
2672</ui>
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 2bc55a26a..d2913d613 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -36,6 +36,11 @@ const std::array<int, Settings::NativeButton::NumButtons> Config::default_button
36 Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V, 36 Qt::Key_H, Qt::Key_G, Qt::Key_D, Qt::Key_C, Qt::Key_B, Qt::Key_V,
37}; 37};
38 38
39const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = {
40 Qt::Key_7,
41 Qt::Key_8,
42};
43
39const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ 44const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
40 { 45 {
41 Qt::Key_Up, 46 Qt::Key_Up,
@@ -284,6 +289,22 @@ void Config::ReadPlayerValues() {
284 } 289 }
285 } 290 }
286 291
292 for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
293 const std::string default_param =
294 InputCommon::GenerateKeyboardParam(default_motions[i]);
295 auto& player_motions = player.motions[i];
296
297 player_motions = qt_config
298 ->value(QStringLiteral("player_%1_").arg(p) +
299 QString::fromUtf8(Settings::NativeMotion::mapping[i]),
300 QString::fromStdString(default_param))
301 .toString()
302 .toStdString();
303 if (player_motions.empty()) {
304 player_motions = default_param;
305 }
306 }
307
287 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 308 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
288 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 309 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
289 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 310 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
@@ -424,6 +445,7 @@ void Config::ReadControlValues() {
424 445
425 Settings::values.vibration_enabled = 446 Settings::values.vibration_enabled =
426 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); 447 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool();
448 Settings::values.motion_enabled = ReadSetting(QStringLiteral("motion_enabled"), true).toBool();
427 Settings::values.use_docked_mode = 449 Settings::values.use_docked_mode =
428 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); 450 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
429 451
@@ -922,6 +944,14 @@ void Config::SavePlayerValues() {
922 QString::fromStdString(player.buttons[i]), 944 QString::fromStdString(player.buttons[i]),
923 QString::fromStdString(default_param)); 945 QString::fromStdString(default_param));
924 } 946 }
947 for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
948 const std::string default_param =
949 InputCommon::GenerateKeyboardParam(default_motions[i]);
950 WriteSetting(QStringLiteral("player_%1_").arg(p) +
951 QString::fromStdString(Settings::NativeMotion::mapping[i]),
952 QString::fromStdString(player.motions[i]),
953 QString::fromStdString(default_param));
954 }
925 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 955 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
926 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 956 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
927 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 957 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
@@ -1062,6 +1092,7 @@ void Config::SaveControlValues() {
1062 SaveMotionTouchValues(); 1092 SaveMotionTouchValues();
1063 1093
1064 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); 1094 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true);
1095 WriteSetting(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true);
1065 WriteSetting(QStringLiteral("motion_device"), 1096 WriteSetting(QStringLiteral("motion_device"),
1066 QString::fromStdString(Settings::values.motion_device), 1097 QString::fromStdString(Settings::values.motion_device),
1067 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); 1098 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index ca0d29c6c..5d8e45d78 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -23,6 +23,7 @@ public:
23 void Save(); 23 void Save();
24 24
25 static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; 25 static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
26 static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
26 static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; 27 static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
27 static const std::array<int, 2> default_stick_mod; 28 static const std::array<int, 2> default_stick_mod;
28 static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> 29 static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index ae3e31762..2725fcb2b 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -70,7 +70,8 @@ ConfigureInput::ConfigureInput(QWidget* parent)
70 70
71ConfigureInput::~ConfigureInput() = default; 71ConfigureInput::~ConfigureInput() = default;
72 72
73void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { 73void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
74 std::size_t max_players) {
74 player_controllers = { 75 player_controllers = {
75 new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem), 76 new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem),
76 new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem), 77 new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem),
@@ -93,6 +94,11 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
93 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, 94 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
94 }; 95 };
95 96
97 std::array<QLabel*, 8> player_connected_labels = {
98 ui->label, ui->label_3, ui->label_4, ui->label_5,
99 ui->label_6, ui->label_7, ui->label_8, ui->label_9,
100 };
101
96 for (std::size_t i = 0; i < player_tabs.size(); ++i) { 102 for (std::size_t i = 0; i < player_tabs.size(); ++i) {
97 player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); 103 player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
98 player_tabs[i]->layout()->addWidget(player_controllers[i]); 104 player_tabs[i]->layout()->addWidget(player_controllers[i]);
@@ -112,6 +118,13 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
112 connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) { 118 connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
113 player_controllers[i]->ConnectPlayer(state == Qt::Checked); 119 player_controllers[i]->ConnectPlayer(state == Qt::Checked);
114 }); 120 });
121
122 // Remove/hide all the elements that exceed max_players, if applicable.
123 if (i >= max_players) {
124 ui->tabWidget->removeTab(static_cast<int>(max_players));
125 player_connected[i]->hide();
126 player_connected_labels[i]->hide();
127 }
115 } 128 }
116 // Only the first player can choose handheld mode so connect the signal just to player 1 129 // Only the first player can choose handheld mode so connect the signal just to player 1
117 connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged, 130 connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged,
@@ -133,6 +146,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
133 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); 146 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
134 }); 147 });
135 148
149 connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] {
150 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
151 });
152
136 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); 153 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
137 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); 154 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
138 155
@@ -159,6 +176,7 @@ void ConfigureInput::ApplyConfiguration() {
159 OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); 176 OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
160 177
161 Settings::values.vibration_enabled = ui->vibrationGroup->isChecked(); 178 Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
179 Settings::values.motion_enabled = ui->motionGroup->isChecked();
162} 180}
163 181
164void ConfigureInput::changeEvent(QEvent* event) { 182void ConfigureInput::changeEvent(QEvent* event) {
@@ -175,10 +193,10 @@ void ConfigureInput::RetranslateUI() {
175 193
176void ConfigureInput::LoadConfiguration() { 194void ConfigureInput::LoadConfiguration() {
177 LoadPlayerControllerIndices(); 195 LoadPlayerControllerIndices();
178 UpdateDockedState(Settings::values.players[0].controller_type == 196 UpdateDockedState(Settings::values.players[8].connected);
179 Settings::ControllerType::Handheld);
180 197
181 ui->vibrationGroup->setChecked(Settings::values.vibration_enabled); 198 ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
199 ui->motionGroup->setChecked(Settings::values.motion_enabled);
182} 200}
183 201
184void ConfigureInput::LoadPlayerControllerIndices() { 202void ConfigureInput::LoadPlayerControllerIndices() {
@@ -205,17 +223,18 @@ void ConfigureInput::RestoreDefaults() {
205 ui->radioDocked->setChecked(true); 223 ui->radioDocked->setChecked(true);
206 ui->radioUndocked->setChecked(false); 224 ui->radioUndocked->setChecked(false);
207 ui->vibrationGroup->setChecked(true); 225 ui->vibrationGroup->setChecked(true);
226 ui->motionGroup->setChecked(true);
208} 227}
209 228
210void ConfigureInput::UpdateDockedState(bool is_handheld) { 229void ConfigureInput::UpdateDockedState(bool is_handheld) {
211 // If the controller type is handheld only, disallow changing docked mode 230 // Disallow changing the console mode if the controller type is handheld.
212 ui->radioDocked->setEnabled(!is_handheld); 231 ui->radioDocked->setEnabled(!is_handheld);
213 ui->radioUndocked->setEnabled(!is_handheld); 232 ui->radioUndocked->setEnabled(!is_handheld);
214 233
215 ui->radioDocked->setChecked(Settings::values.use_docked_mode); 234 ui->radioDocked->setChecked(Settings::values.use_docked_mode);
216 ui->radioUndocked->setChecked(!Settings::values.use_docked_mode); 235 ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
217 236
218 // If its handheld only, force docked mode off (since you can't play handheld in a dock) 237 // Also force into undocked mode if the controller type is handheld.
219 if (is_handheld) { 238 if (is_handheld) {
220 ui->radioUndocked->setChecked(true); 239 ui->radioUndocked->setChecked(true);
221 } 240 }
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index d08a24f96..0e8b2fd4e 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -37,7 +37,7 @@ public:
37 ~ConfigureInput() override; 37 ~ConfigureInput() override;
38 38
39 /// Initializes the input dialog with the given input subsystem. 39 /// Initializes the input dialog with the given input subsystem.
40 void Initialize(InputCommon::InputSubsystem* input_subsystem_); 40 void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8);
41 41
42 /// Save all button configurations to settings file. 42 /// Save all button configurations to settings file.
43 void ApplyConfiguration(); 43 void ApplyConfiguration();
diff --git a/src/yuzu/configuration/configure_input_dialog.cpp b/src/yuzu/configuration/configure_input_dialog.cpp
new file mode 100644
index 000000000..1866003c2
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_dialog.cpp
@@ -0,0 +1,37 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "ui_configure_input_dialog.h"
6#include "yuzu/configuration/configure_input_dialog.h"
7
8ConfigureInputDialog::ConfigureInputDialog(QWidget* parent, std::size_t max_players,
9 InputCommon::InputSubsystem* input_subsystem)
10 : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputDialog>()),
11 input_widget(new ConfigureInput(this)) {
12 ui->setupUi(this);
13
14 input_widget->Initialize(input_subsystem, max_players);
15
16 ui->inputLayout->addWidget(input_widget);
17
18 RetranslateUI();
19}
20
21ConfigureInputDialog::~ConfigureInputDialog() = default;
22
23void ConfigureInputDialog::ApplyConfiguration() {
24 input_widget->ApplyConfiguration();
25}
26
27void ConfigureInputDialog::changeEvent(QEvent* event) {
28 if (event->type() == QEvent::LanguageChange) {
29 RetranslateUI();
30 }
31
32 QDialog::changeEvent(event);
33}
34
35void ConfigureInputDialog::RetranslateUI() {
36 ui->retranslateUi(this);
37}
diff --git a/src/yuzu/configuration/configure_input_dialog.h b/src/yuzu/configuration/configure_input_dialog.h
new file mode 100644
index 000000000..d1bd865f9
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_dialog.h
@@ -0,0 +1,38 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QDialog>
9#include "yuzu/configuration/configure_input.h"
10
11class QPushButton;
12
13namespace InputCommon {
14class InputSubsystem;
15}
16
17namespace Ui {
18class ConfigureInputDialog;
19}
20
21class ConfigureInputDialog : public QDialog {
22 Q_OBJECT
23
24public:
25 explicit ConfigureInputDialog(QWidget* parent, std::size_t max_players,
26 InputCommon::InputSubsystem* input_subsystem);
27 ~ConfigureInputDialog() override;
28
29 void ApplyConfiguration();
30
31private:
32 void changeEvent(QEvent* event) override;
33 void RetranslateUI();
34
35 std::unique_ptr<Ui::ConfigureInputDialog> ui;
36
37 ConfigureInput* input_widget;
38};
diff --git a/src/yuzu/configuration/configure_input_dialog.ui b/src/yuzu/configuration/configure_input_dialog.ui
new file mode 100644
index 000000000..b92ddb200
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_dialog.ui
@@ -0,0 +1,57 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureInputDialog</class>
4 <widget class="QDialog" name="ConfigureInputDialog">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>70</width>
10 <height>540</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Configure Input</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <property name="spacing">
18 <number>2</number>
19 </property>
20 <property name="leftMargin">
21 <number>9</number>
22 </property>
23 <property name="topMargin">
24 <number>9</number>
25 </property>
26 <property name="rightMargin">
27 <number>9</number>
28 </property>
29 <property name="bottomMargin">
30 <number>9</number>
31 </property>
32 <item>
33 <layout class="QHBoxLayout" name="inputLayout"/>
34 </item>
35 <item>
36 <layout class="QHBoxLayout" name="horizontalLayout">
37 <item>
38 <widget class="QDialogButtonBox" name="buttonBox">
39 <property name="standardButtons">
40 <set>QDialogButtonBox::Ok</set>
41 </property>
42 </widget>
43 </item>
44 </layout>
45 </item>
46 </layout>
47 </widget>
48 <resources/>
49 <connections>
50 <connection>
51 <sender>buttonBox</sender>
52 <signal>accepted()</signal>
53 <receiver>ConfigureInputDialog</receiver>
54 <slot>accept()</slot>
55 </connection>
56 </connections>
57</ui>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 13ecb3dc5..698cb1940 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -18,6 +18,7 @@
18#include "core/hle/service/sm/sm.h" 18#include "core/hle/service/sm/sm.h"
19#include "input_common/gcadapter/gc_poller.h" 19#include "input_common/gcadapter/gc_poller.h"
20#include "input_common/main.h" 20#include "input_common/main.h"
21#include "input_common/udp/udp.h"
21#include "ui_configure_input_player.h" 22#include "ui_configure_input_player.h"
22#include "yuzu/configuration/config.h" 23#include "yuzu/configuration/config.h"
23#include "yuzu/configuration/configure_input_player.h" 24#include "yuzu/configuration/configure_input_player.h"
@@ -149,6 +150,14 @@ QString ButtonToText(const Common::ParamPackage& param) {
149 return GetKeyName(param.Get("code", 0)); 150 return GetKeyName(param.Get("code", 0));
150 } 151 }
151 152
153 if (param.Get("engine", "") == "cemuhookudp") {
154 if (param.Has("pad_index")) {
155 const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
156 return QObject::tr("Motion %1").arg(motion_str);
157 }
158 return GetKeyName(param.Get("code", 0));
159 }
160
152 if (param.Get("engine", "") == "sdl") { 161 if (param.Get("engine", "") == "sdl") {
153 if (param.Has("hat")) { 162 if (param.Has("hat")) {
154 const QString hat_str = QString::fromStdString(param.Get("hat", "")); 163 const QString hat_str = QString::fromStdString(param.Get("hat", ""));
@@ -247,6 +256,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
247 ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot, 256 ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot,
248 }; 257 };
249 258
259 mod_buttons = {
260 ui->buttonLStickMod,
261 ui->buttonRStickMod,
262 };
263
250 analog_map_buttons = {{ 264 analog_map_buttons = {{
251 { 265 {
252 ui->buttonLStickUp, 266 ui->buttonLStickUp,
@@ -262,6 +276,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
262 }, 276 },
263 }}; 277 }};
264 278
279 motion_map = {
280 ui->buttonMotionLeft,
281 ui->buttonMotionRight,
282 };
283
265 analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone}; 284 analog_map_deadzone_label = {ui->labelLStickDeadzone, ui->labelRStickDeadzone};
266 analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone}; 285 analog_map_deadzone_slider = {ui->sliderLStickDeadzone, ui->sliderRStickDeadzone};
267 analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup}; 286 analog_map_modifier_groupbox = {ui->buttonLStickModGroup, ui->buttonRStickModGroup};
@@ -271,7 +290,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
271 analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange}; 290 analog_map_range_spinbox = {ui->spinboxLStickRange, ui->spinboxRStickRange};
272 291
273 const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param, 292 const auto ConfigureButtonClick = [&](QPushButton* button, Common::ParamPackage* param,
274 int default_val) { 293 int default_val, InputCommon::Polling::DeviceType type) {
275 connect(button, &QPushButton::clicked, [=, this] { 294 connect(button, &QPushButton::clicked, [=, this] {
276 HandleClick( 295 HandleClick(
277 button, 296 button,
@@ -291,22 +310,56 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
291 } 310 }
292 *param = std::move(params); 311 *param = std::move(params);
293 }, 312 },
294 InputCommon::Polling::DeviceType::Button); 313 type);
295 }); 314 });
296 }; 315 };
297 316
298 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { 317 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
299 auto* const button = button_map[button_id]; 318 auto* const button = button_map[button_id];
319
300 if (button == nullptr) { 320 if (button == nullptr) {
301 continue; 321 continue;
302 } 322 }
323
303 ConfigureButtonClick(button_map[button_id], &buttons_param[button_id], 324 ConfigureButtonClick(button_map[button_id], &buttons_param[button_id],
304 Config::default_buttons[button_id]); 325 Config::default_buttons[button_id],
326 InputCommon::Polling::DeviceType::Button);
327
328 button->setContextMenuPolicy(Qt::CustomContextMenu);
329
330 connect(button, &QPushButton::customContextMenuRequested,
331 [=, this](const QPoint& menu_location) {
332 QMenu context_menu;
333 context_menu.addAction(tr("Clear"), [&] {
334 buttons_param[button_id].Clear();
335 button_map[button_id]->setText(tr("[not set]"));
336 });
337 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
338 });
305 } 339 }
306 340
307 // Handle clicks for the modifier buttons as well. 341 for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
308 ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]); 342 auto* const button = motion_map[motion_id];
309 ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]); 343 if (button == nullptr) {
344 continue;
345 }
346
347 ConfigureButtonClick(motion_map[motion_id], &motions_param[motion_id],
348 Config::default_motions[motion_id],
349 InputCommon::Polling::DeviceType::Motion);
350
351 button->setContextMenuPolicy(Qt::CustomContextMenu);
352
353 connect(button, &QPushButton::customContextMenuRequested,
354 [=, this](const QPoint& menu_location) {
355 QMenu context_menu;
356 context_menu.addAction(tr("Clear"), [&] {
357 motions_param[motion_id].Clear();
358 motion_map[motion_id]->setText(tr("[not set]"));
359 });
360 context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
361 });
362 }
310 363
311 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 364 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
312 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 365 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
@@ -325,8 +378,38 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
325 }, 378 },
326 InputCommon::Polling::DeviceType::AnalogPreferred); 379 InputCommon::Polling::DeviceType::AnalogPreferred);
327 }); 380 });
381
382 analog_button->setContextMenuPolicy(Qt::CustomContextMenu);
383
384 connect(analog_button, &QPushButton::customContextMenuRequested,
385 [=, this](const QPoint& menu_location) {
386 QMenu context_menu;
387 context_menu.addAction(tr("Clear"), [&] {
388 analogs_param[analog_id].Clear();
389 analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
390 });
391 context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
392 menu_location));
393 });
328 } 394 }
329 395
396 // Handle clicks for the modifier buttons as well.
397 ConfigureButtonClick(mod_buttons[analog_id], &stick_mod_param[analog_id],
398 Config::default_stick_mod[analog_id],
399 InputCommon::Polling::DeviceType::Button);
400
401 mod_buttons[analog_id]->setContextMenuPolicy(Qt::CustomContextMenu);
402
403 connect(mod_buttons[analog_id], &QPushButton::customContextMenuRequested,
404 [=, this](const QPoint& menu_location) {
405 QMenu context_menu;
406 context_menu.addAction(tr("Clear"), [&] {
407 stick_mod_param[analog_id].Clear();
408 mod_buttons[analog_id]->setText(tr("[not set]"));
409 });
410 context_menu.exec(mod_buttons[analog_id]->mapToGlobal(menu_location));
411 });
412
330 connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged), 413 connect(analog_map_range_spinbox[analog_id], qOverload<int>(&QSpinBox::valueChanged),
331 [=, this] { 414 [=, this] {
332 const auto spinbox_value = analog_map_range_spinbox[analog_id]->value(); 415 const auto spinbox_value = analog_map_range_spinbox[analog_id]->value();
@@ -385,9 +468,11 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
385 468
386 UpdateControllerIcon(); 469 UpdateControllerIcon();
387 UpdateControllerAvailableButtons(); 470 UpdateControllerAvailableButtons();
471 UpdateMotionButtons();
388 connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) { 472 connect(ui->comboControllerType, qOverload<int>(&QComboBox::currentIndexChanged), [this](int) {
389 UpdateControllerIcon(); 473 UpdateControllerIcon();
390 UpdateControllerAvailableButtons(); 474 UpdateControllerAvailableButtons();
475 UpdateMotionButtons();
391 }); 476 });
392 477
393 connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this, 478 connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this,
@@ -417,6 +502,13 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
417 return; 502 return;
418 } 503 }
419 } 504 }
505 if (input_subsystem->GetUDPMotions()->IsPolling()) {
506 params = input_subsystem->GetUDPMotions()->GetNextInput();
507 if (params.Has("engine")) {
508 SetPollingResult(params, false);
509 return;
510 }
511 }
420 for (auto& poller : device_pollers) { 512 for (auto& poller : device_pollers) {
421 params = poller->GetNextInput(); 513 params = poller->GetNextInput();
422 if (params.Has("engine")) { 514 if (params.Has("engine")) {
@@ -448,6 +540,10 @@ void ConfigureInputPlayer::ApplyConfiguration() {
448 return; 540 return;
449 } 541 }
450 542
543 auto& motions = player.motions;
544 std::transform(motions_param.begin(), motions_param.end(), motions.begin(),
545 [](const Common::ParamPackage& param) { return param.Serialize(); });
546
451 player.controller_type = 547 player.controller_type =
452 static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex()); 548 static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex());
453 player.connected = ui->groupConnectedController->isChecked(); 549 player.connected = ui->groupConnectedController->isChecked();
@@ -501,6 +597,8 @@ void ConfigureInputPlayer::LoadConfiguration() {
501 [](const std::string& str) { return Common::ParamPackage(str); }); 597 [](const std::string& str) { return Common::ParamPackage(str); });
502 std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(), 598 std::transform(player.analogs.begin(), player.analogs.end(), analogs_param.begin(),
503 [](const std::string& str) { return Common::ParamPackage(str); }); 599 [](const std::string& str) { return Common::ParamPackage(str); });
600 std::transform(player.motions.begin(), player.motions.end(), motions_param.begin(),
601 [](const std::string& str) { return Common::ParamPackage(str); });
504 } 602 }
505 603
506 UpdateUI(); 604 UpdateUI();
@@ -530,20 +628,23 @@ void ConfigureInputPlayer::RestoreDefaults() {
530 InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; 628 InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
531 } 629 }
532 630
533 // Reset Modifier Buttons 631 // Reset Analogs and Modifier Buttons
534 lstick_mod =
535 Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[0]));
536 rstick_mod =
537 Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[1]));
538
539 // Reset Analogs
540 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 632 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
541 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 633 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
542 Common::ParamPackage params{InputCommon::GenerateKeyboardParam( 634 Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
543 Config::default_analogs[analog_id][sub_button_id])}; 635 Config::default_analogs[analog_id][sub_button_id])};
544 SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); 636 SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
545 } 637 }
638
639 stick_mod_param[analog_id] = Common::ParamPackage(
640 InputCommon::GenerateKeyboardParam(Config::default_stick_mod[analog_id]));
641 }
642
643 for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
644 motions_param[motion_id] = Common::ParamPackage{
645 InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
546 } 646 }
647
547 UpdateUI(); 648 UpdateUI();
548 UpdateInputDevices(); 649 UpdateInputDevices();
549 ui->comboControllerType->setCurrentIndex(0); 650 ui->comboControllerType->setCurrentIndex(0);
@@ -552,25 +653,33 @@ void ConfigureInputPlayer::RestoreDefaults() {
552void ConfigureInputPlayer::ClearAll() { 653void ConfigureInputPlayer::ClearAll() {
553 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { 654 for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
554 const auto* const button = button_map[button_id]; 655 const auto* const button = button_map[button_id];
555 if (button == nullptr || !button->isEnabled()) { 656 if (button == nullptr) {
556 continue; 657 continue;
557 } 658 }
558 659
559 buttons_param[button_id].Clear(); 660 buttons_param[button_id].Clear();
560 } 661 }
561 662
562 lstick_mod.Clear();
563 rstick_mod.Clear();
564
565 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 663 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
566 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 664 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
567 const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id]; 665 const auto* const analog_button = analog_map_buttons[analog_id][sub_button_id];
568 if (analog_button == nullptr || !analog_button->isEnabled()) { 666 if (analog_button == nullptr) {
569 continue; 667 continue;
570 } 668 }
571 669
572 analogs_param[analog_id].Clear(); 670 analogs_param[analog_id].Clear();
573 } 671 }
672
673 stick_mod_param[analog_id].Clear();
674 }
675
676 for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
677 const auto* const motion_button = motion_map[motion_id];
678 if (motion_button == nullptr) {
679 continue;
680 }
681
682 motions_param[motion_id].Clear();
574 } 683 }
575 684
576 UpdateUI(); 685 UpdateUI();
@@ -582,8 +691,9 @@ void ConfigureInputPlayer::UpdateUI() {
582 button_map[button]->setText(ButtonToText(buttons_param[button])); 691 button_map[button]->setText(ButtonToText(buttons_param[button]));
583 } 692 }
584 693
585 ui->buttonLStickMod->setText(ButtonToText(lstick_mod)); 694 for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
586 ui->buttonRStickMod->setText(ButtonToText(rstick_mod)); 695 motion_map[motion_id]->setText(ButtonToText(motions_param[motion_id]));
696 }
587 697
588 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 698 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
589 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 699 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
@@ -597,6 +707,8 @@ void ConfigureInputPlayer::UpdateUI() {
597 AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); 707 AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
598 } 708 }
599 709
710 mod_buttons[analog_id]->setText(ButtonToText(stick_mod_param[analog_id]));
711
600 const auto deadzone_label = analog_map_deadzone_label[analog_id]; 712 const auto deadzone_label = analog_map_deadzone_label[analog_id];
601 const auto deadzone_slider = analog_map_deadzone_slider[analog_id]; 713 const auto deadzone_slider = analog_map_deadzone_slider[analog_id];
602 const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id]; 714 const auto modifier_groupbox = analog_map_modifier_groupbox[analog_id];
@@ -646,10 +758,10 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
646 const auto& device = input_devices[ui->comboDevices->currentIndex()]; 758 const auto& device = input_devices[ui->comboDevices->currentIndex()];
647 auto button_mapping = input_subsystem->GetButtonMappingForDevice(device); 759 auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
648 auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device); 760 auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
649 for (int i = 0; i < buttons_param.size(); ++i) { 761 for (std::size_t i = 0; i < buttons_param.size(); ++i) {
650 buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; 762 buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
651 } 763 }
652 for (int i = 0; i < analogs_param.size(); ++i) { 764 for (std::size_t i = 0; i < analogs_param.size(); ++i) {
653 analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)]; 765 analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
654 } 766 }
655 767
@@ -659,7 +771,11 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
659void ConfigureInputPlayer::HandleClick( 771void ConfigureInputPlayer::HandleClick(
660 QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, 772 QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
661 InputCommon::Polling::DeviceType type) { 773 InputCommon::Polling::DeviceType type) {
662 button->setText(tr("[waiting]")); 774 if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
775 button->setText(tr("Shake!"));
776 } else {
777 button->setText(tr("[waiting]"));
778 }
663 button->setFocus(); 779 button->setFocus();
664 780
665 // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a 781 // The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
@@ -683,6 +799,10 @@ void ConfigureInputPlayer::HandleClick(
683 input_subsystem->GetGCAnalogs()->BeginConfiguration(); 799 input_subsystem->GetGCAnalogs()->BeginConfiguration();
684 } 800 }
685 801
802 if (type == InputCommon::Polling::DeviceType::Motion) {
803 input_subsystem->GetUDPMotions()->BeginConfiguration();
804 }
805
686 timeout_timer->start(2500); // Cancel after 2.5 seconds 806 timeout_timer->start(2500); // Cancel after 2.5 seconds
687 poll_timer->start(50); // Check for new inputs every 50ms 807 poll_timer->start(50); // Check for new inputs every 50ms
688} 808}
@@ -700,6 +820,8 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params,
700 input_subsystem->GetGCButtons()->EndConfiguration(); 820 input_subsystem->GetGCButtons()->EndConfiguration();
701 input_subsystem->GetGCAnalogs()->EndConfiguration(); 821 input_subsystem->GetGCAnalogs()->EndConfiguration();
702 822
823 input_subsystem->GetUDPMotions()->EndConfiguration();
824
703 if (!abort) { 825 if (!abort) {
704 (*input_setter)(params); 826 (*input_setter)(params);
705 } 827 }
@@ -832,6 +954,37 @@ void ConfigureInputPlayer::UpdateControllerAvailableButtons() {
832 } 954 }
833} 955}
834 956
957void ConfigureInputPlayer::UpdateMotionButtons() {
958 if (debug) {
959 // Motion isn't used with the debug controller, hide both groupboxes.
960 ui->buttonMotionLeftGroup->hide();
961 ui->buttonMotionRightGroup->hide();
962 return;
963 }
964
965 // Show/hide the "Motion 1/2" groupboxes depending on the currently selected controller.
966 switch (GetControllerTypeFromIndex(ui->comboControllerType->currentIndex())) {
967 case Settings::ControllerType::ProController:
968 case Settings::ControllerType::LeftJoycon:
969 case Settings::ControllerType::Handheld:
970 // Show "Motion 1" and hide "Motion 2".
971 ui->buttonMotionLeftGroup->show();
972 ui->buttonMotionRightGroup->hide();
973 break;
974 case Settings::ControllerType::RightJoycon:
975 // Show "Motion 2" and hide "Motion 1".
976 ui->buttonMotionLeftGroup->hide();
977 ui->buttonMotionRightGroup->show();
978 break;
979 case Settings::ControllerType::DualJoyconDetached:
980 default:
981 // Show both "Motion 1/2".
982 ui->buttonMotionLeftGroup->show();
983 ui->buttonMotionRightGroup->show();
984 break;
985 }
986}
987
835void ConfigureInputPlayer::showEvent(QShowEvent* event) { 988void ConfigureInputPlayer::showEvent(QShowEvent* event) {
836 if (bottom_row == nullptr) { 989 if (bottom_row == nullptr) {
837 return; 990 return;
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index a25bc3bd9..ce443dec5 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -107,6 +107,9 @@ private:
107 /// Hides and disables controller settings based on the current controller type. 107 /// Hides and disables controller settings based on the current controller type.
108 void UpdateControllerAvailableButtons(); 108 void UpdateControllerAvailableButtons();
109 109
110 /// Shows or hides motion groupboxes based on the current controller type.
111 void UpdateMotionButtons();
112
110 /// Gets the default controller mapping for this device and auto configures the input to match. 113 /// Gets the default controller mapping for this device and auto configures the input to match.
111 void UpdateMappingWithDefaults(); 114 void UpdateMappingWithDefaults();
112 115
@@ -128,14 +131,17 @@ private:
128 131
129 std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; 132 std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
130 std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; 133 std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
134 std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> stick_mod_param;
135 std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param;
131 136
132 static constexpr int ANALOG_SUB_BUTTONS_NUM = 4; 137 static constexpr int ANALOG_SUB_BUTTONS_NUM = 4;
133 138
134 /// Each button input is represented by a QPushButton. 139 /// Each button input is represented by a QPushButton.
135 std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; 140 std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
141 /// Each motion input is represented by a QPushButton.
142 std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map;
136 /// Extra buttons for the modifiers. 143 /// Extra buttons for the modifiers.
137 Common::ParamPackage lstick_mod; 144 std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> mod_buttons;
138 Common::ParamPackage rstick_mod;
139 145
140 /// A group of four QPushButtons represent one analog input. The buttons each represent up, 146 /// A group of four QPushButtons represent one analog input. The buttons each represent up,
141 /// down, left, right, respectively. 147 /// down, left, right, respectively.
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index 9bc681894..e03461d9d 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -1983,6 +1983,9 @@
1983 <property name="spacing"> 1983 <property name="spacing">
1984 <number>3</number> 1984 <number>3</number>
1985 </property> 1985 </property>
1986 <property name="topMargin">
1987 <number>0</number>
1988 </property>
1986 <item> 1989 <item>
1987 <spacer name="horizontalSpacerMiscButtons1"> 1990 <spacer name="horizontalSpacerMiscButtons1">
1988 <property name="orientation"> 1991 <property name="orientation">
@@ -1990,21 +1993,119 @@
1990 </property> 1993 </property>
1991 <property name="sizeHint" stdset="0"> 1994 <property name="sizeHint" stdset="0">
1992 <size> 1995 <size>
1993 <width>40</width> 1996 <width>20</width>
1994 <height>0</height> 1997 <height>20</height>
1995 </size> 1998 </size>
1996 </property> 1999 </property>
1997 </spacer> 2000 </spacer>
1998 </item> 2001 </item>
1999 <item> 2002 <item>
2003 <widget class="QGroupBox" name="buttonMotionLeftGroup">
2004 <property name="title">
2005 <string>Motion 1</string>
2006 </property>
2007 <property name="alignment">
2008 <set>Qt::AlignCenter</set>
2009 </property>
2010 <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2">
2011 <property name="spacing">
2012 <number>3</number>
2013 </property>
2014 <property name="leftMargin">
2015 <number>3</number>
2016 </property>
2017 <property name="topMargin">
2018 <number>3</number>
2019 </property>
2020 <property name="rightMargin">
2021 <number>3</number>
2022 </property>
2023 <property name="bottomMargin">
2024 <number>3</number>
2025 </property>
2026 <item>
2027 <widget class="QPushButton" name="buttonMotionLeft">
2028 <property name="minimumSize">
2029 <size>
2030 <width>57</width>
2031 <height>0</height>
2032 </size>
2033 </property>
2034 <property name="maximumSize">
2035 <size>
2036 <width>55</width>
2037 <height>16777215</height>
2038 </size>
2039 </property>
2040 <property name="styleSheet">
2041 <string notr="true">min-width: 55px;</string>
2042 </property>
2043 <property name="text">
2044 <string>Left</string>
2045 </property>
2046 </widget>
2047 </item>
2048 </layout>
2049 </widget>
2050 </item>
2051 <item>
2052 <widget class="QGroupBox" name="buttonMotionRightGroup">
2053 <property name="title">
2054 <string>Motion 2</string>
2055 </property>
2056 <property name="alignment">
2057 <set>Qt::AlignCenter</set>
2058 </property>
2059 <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2">
2060 <property name="spacing">
2061 <number>3</number>
2062 </property>
2063 <property name="leftMargin">
2064 <number>3</number>
2065 </property>
2066 <property name="topMargin">
2067 <number>3</number>
2068 </property>
2069 <property name="rightMargin">
2070 <number>3</number>
2071 </property>
2072 <property name="bottomMargin">
2073 <number>3</number>
2074 </property>
2075 <item>
2076 <widget class="QPushButton" name="buttonMotionRight">
2077 <property name="minimumSize">
2078 <size>
2079 <width>57</width>
2080 <height>0</height>
2081 </size>
2082 </property>
2083 <property name="maximumSize">
2084 <size>
2085 <width>55</width>
2086 <height>16777215</height>
2087 </size>
2088 </property>
2089 <property name="styleSheet">
2090 <string notr="true">min-width: 55px;</string>
2091 </property>
2092 <property name="text">
2093 <string>Right</string>
2094 </property>
2095 </widget>
2096 </item>
2097 </layout>
2098 </widget>
2099 </item>
2100 <item>
2000 <spacer name="horizontalSpacerMiscButtons4"> 2101 <spacer name="horizontalSpacerMiscButtons4">
2001 <property name="orientation"> 2102 <property name="orientation">
2002 <enum>Qt::Horizontal</enum> 2103 <enum>Qt::Horizontal</enum>
2003 </property> 2104 </property>
2004 <property name="sizeHint" stdset="0"> 2105 <property name="sizeHint" stdset="0">
2005 <size> 2106 <size>
2006 <width>40</width> 2107 <width>20</width>
2007 <height>0</height> 2108 <height>20</height>
2008 </size> 2109 </size>
2009 </property> 2110 </property>
2010 </spacer> 2111 </spacer>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index a1b61d119..bb3a08ac7 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -11,6 +11,7 @@
11#endif 11#endif
12 12
13// VFS includes must be before glad as they will conflict with Windows file api, which uses defines. 13// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
14#include "applets/controller.h"
14#include "applets/error.h" 15#include "applets/error.h"
15#include "applets/profile_select.h" 16#include "applets/profile_select.h"
16#include "applets/software_keyboard.h" 17#include "applets/software_keyboard.h"
@@ -19,7 +20,9 @@
19#include "configuration/configure_per_game.h" 20#include "configuration/configure_per_game.h"
20#include "core/file_sys/vfs.h" 21#include "core/file_sys/vfs.h"
21#include "core/file_sys/vfs_real.h" 22#include "core/file_sys/vfs_real.h"
23#include "core/frontend/applets/controller.h"
22#include "core/frontend/applets/general_frontend.h" 24#include "core/frontend/applets/general_frontend.h"
25#include "core/frontend/applets/software_keyboard.h"
23#include "core/hle/service/acc/profile_manager.h" 26#include "core/hle/service/acc/profile_manager.h"
24#include "core/hle/service/am/applet_ae.h" 27#include "core/hle/service/am/applet_ae.h"
25#include "core/hle/service/am/applet_oe.h" 28#include "core/hle/service/am/applet_oe.h"
@@ -84,7 +87,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
84#include "core/file_sys/romfs.h" 87#include "core/file_sys/romfs.h"
85#include "core/file_sys/savedata_factory.h" 88#include "core/file_sys/savedata_factory.h"
86#include "core/file_sys/submission_package.h" 89#include "core/file_sys/submission_package.h"
87#include "core/frontend/applets/software_keyboard.h"
88#include "core/hle/kernel/process.h" 90#include "core/hle/kernel/process.h"
89#include "core/hle/service/am/am.h" 91#include "core/hle/service/am/am.h"
90#include "core/hle/service/filesystem/filesystem.h" 92#include "core/hle/service/filesystem/filesystem.h"
@@ -283,6 +285,23 @@ GMainWindow::~GMainWindow() {
283 delete render_window; 285 delete render_window;
284} 286}
285 287
288void GMainWindow::ControllerSelectorReconfigureControllers(
289 const Core::Frontend::ControllerParameters& parameters) {
290 QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get());
291 dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
292 Qt::WindowSystemMenuHint);
293 dialog.setWindowModality(Qt::WindowModal);
294 dialog.exec();
295
296 emit ControllerSelectorReconfigureFinished();
297
298 // Don't forget to apply settings.
299 Settings::Apply();
300 config->Save();
301
302 UpdateStatusButtons();
303}
304
286void GMainWindow::ProfileSelectorSelectProfile() { 305void GMainWindow::ProfileSelectorSelectProfile() {
287 const Service::Account::ProfileManager manager; 306 const Service::Account::ProfileManager manager;
288 int index = 0; 307 int index = 0;
@@ -291,10 +310,12 @@ void GMainWindow::ProfileSelectorSelectProfile() {
291 dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | 310 dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
292 Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); 311 Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
293 dialog.setWindowModality(Qt::WindowModal); 312 dialog.setWindowModality(Qt::WindowModal);
313
294 if (dialog.exec() == QDialog::Rejected) { 314 if (dialog.exec() == QDialog::Rejected) {
295 emit ProfileSelectorFinishedSelection(std::nullopt); 315 emit ProfileSelectorFinishedSelection(std::nullopt);
296 return; 316 return;
297 } 317 }
318
298 index = dialog.GetIndex(); 319 index = dialog.GetIndex();
299 } 320 }
300 321
@@ -966,13 +987,14 @@ bool GMainWindow::LoadROM(const QString& filename) {
966 system.SetFilesystem(vfs); 987 system.SetFilesystem(vfs);
967 988
968 system.SetAppletFrontendSet({ 989 system.SetAppletFrontendSet({
969 nullptr, // Parental Controls 990 std::make_unique<QtControllerSelector>(*this), // Controller Selector
970 std::make_unique<QtErrorDisplay>(*this), // 991 nullptr, // E-Commerce
971 nullptr, // Photo Viewer 992 std::make_unique<QtErrorDisplay>(*this), // Error Display
972 std::make_unique<QtProfileSelector>(*this), // 993 nullptr, // Parental Controls
973 std::make_unique<QtSoftwareKeyboard>(*this), // 994 nullptr, // Photo Viewer
974 std::make_unique<QtWebBrowser>(*this), // 995 std::make_unique<QtProfileSelector>(*this), // Profile Selector
975 nullptr, // E-Commerce 996 std::make_unique<QtSoftwareKeyboard>(*this), // Software Keyboard
997 std::make_unique<QtWebBrowser>(*this), // Web Browser
976 }); 998 });
977 999
978 system.RegisterHostThread(); 1000 system.RegisterHostThread();
@@ -2047,6 +2069,7 @@ void GMainWindow::OnStartGame() {
2047 2069
2048 emu_thread->SetRunning(true); 2070 emu_thread->SetRunning(true);
2049 2071
2072 qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters");
2050 qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( 2073 qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>(
2051 "Core::Frontend::SoftwareKeyboardParameters"); 2074 "Core::Frontend::SoftwareKeyboardParameters");
2052 qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); 2075 qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
@@ -2569,8 +2592,10 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
2569 2592
2570 const auto function = [this, &keys, &pdm] { 2593 const auto function = [this, &keys, &pdm] {
2571 keys.PopulateFromPartitionData(pdm); 2594 keys.PopulateFromPartitionData(pdm);
2572 Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs); 2595
2573 keys.DeriveETicket(pdm); 2596 auto& system = Core::System::GetInstance();
2597 system.GetFileSystemController().CreateFactories(*vfs);
2598 keys.DeriveETicket(pdm, system.GetContentProvider());
2574 }; 2599 };
2575 2600
2576 QString errors; 2601 QString errors;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 0ce66a1ca..afcfa68a9 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -37,6 +37,7 @@ enum class InstalledEntryType;
37class GameListPlaceholder; 37class GameListPlaceholder;
38 38
39namespace Core::Frontend { 39namespace Core::Frontend {
40struct ControllerParameters;
40struct SoftwareKeyboardParameters; 41struct SoftwareKeyboardParameters;
41} // namespace Core::Frontend 42} // namespace Core::Frontend
42 43
@@ -116,9 +117,12 @@ signals:
116 117
117 void UpdateInstallProgress(); 118 void UpdateInstallProgress();
118 119
120 void ControllerSelectorReconfigureFinished();
121
119 void ErrorDisplayFinished(); 122 void ErrorDisplayFinished();
120 123
121 void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid); 124 void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
125
122 void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); 126 void SoftwareKeyboardFinishedText(std::optional<std::u16string> text);
123 void SoftwareKeyboardFinishedCheckDialog(); 127 void SoftwareKeyboardFinishedCheckDialog();
124 128
@@ -127,6 +131,8 @@ signals:
127 131
128public slots: 132public slots:
129 void OnLoadComplete(); 133 void OnLoadComplete();
134 void ControllerSelectorReconfigureControllers(
135 const Core::Frontend::ControllerParameters& parameters);
130 void ErrorDisplayDisplayError(QString body); 136 void ErrorDisplayDisplayError(QString body);
131 void ProfileSelectorSelectProfile(); 137 void ProfileSelectorSelectProfile();
132 void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); 138 void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters);
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index e9f1c6500..23448e747 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -290,6 +290,8 @@ void Config::ReadValues() {
290 290
291 Settings::values.vibration_enabled = 291 Settings::values.vibration_enabled =
292 sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true); 292 sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true);
293 Settings::values.motion_enabled =
294 sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true);
293 Settings::values.touchscreen.enabled = 295 Settings::values.touchscreen.enabled =
294 sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); 296 sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
295 Settings::values.touchscreen.device = 297 Settings::values.touchscreen.device =
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
index aaf59129a..bc273fb51 100644
--- a/src/yuzu_tester/config.cpp
+++ b/src/yuzu_tester/config.cpp
@@ -76,6 +76,7 @@ void Config::ReadValues() {
76 } 76 }
77 77
78 Settings::values.vibration_enabled = true; 78 Settings::values.vibration_enabled = true;
79 Settings::values.motion_enabled = true;
79 Settings::values.touchscreen.enabled = ""; 80 Settings::values.touchscreen.enabled = "";
80 Settings::values.touchscreen.device = ""; 81 Settings::values.touchscreen.device = "";
81 Settings::values.touchscreen.finger = 0; 82 Settings::values.touchscreen.finger = 0;