diff options
168 files changed, 11380 insertions, 1484 deletions
| @@ -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 | ||
| 44 | QWidget#bottomPerGameInput, | ||
| 45 | QWidget#topControllerApplet, | ||
| 46 | QWidget#bottomControllerApplet, | ||
| 47 | QGroupBox#groupPlayer1Connected:checked, | ||
| 48 | QGroupBox#groupPlayer2Connected:checked, | ||
| 49 | QGroupBox#groupPlayer3Connected:checked, | ||
| 50 | QGroupBox#groupPlayer4Connected:checked, | ||
| 51 | QGroupBox#groupPlayer5Connected:checked, | ||
| 52 | QGroupBox#groupPlayer6Connected:checked, | ||
| 53 | QGroupBox#groupPlayer7Connected:checked, | ||
| 54 | QGroupBox#groupPlayer8Connected:checked { | ||
| 55 | background-color: #f5f5f5; | ||
| 56 | } | ||
| 57 | |||
| 58 | QWidget#topControllerApplet { | ||
| 59 | border-bottom: 1px solid #828790 | ||
| 60 | } | ||
| 61 | |||
| 62 | QWidget#bottomPerGameInput, | ||
| 63 | QWidget#bottomControllerApplet { | ||
| 64 | border-top: 1px solid #828790 | ||
| 65 | } | ||
| 66 | |||
| 67 | QWidget#topPerGameInput, | ||
| 68 | QWidget#middleControllerApplet { | ||
| 69 | background-color: #fff; | ||
| 70 | } | ||
| 71 | |||
| 72 | QWidget#topPerGameInput QComboBox, | ||
| 73 | QWidget#middleControllerApplet QComboBox { | ||
| 74 | width: 123px; | ||
| 75 | } | ||
| 76 | |||
| 77 | QWidget#connectedControllers { | ||
| 78 | background: transparent; | ||
| 79 | } | ||
| 80 | |||
| 81 | QWidget#playersSupported, | ||
| 82 | QWidget#controllersSupported, | ||
| 83 | QWidget#controllerSupported1, | ||
| 84 | QWidget#controllerSupported2, | ||
| 85 | QWidget#controllerSupported3, | ||
| 86 | QWidget#controllerSupported4, | ||
| 87 | QWidget#controllerSupported5, | ||
| 88 | QWidget#controllerSupported6 { | ||
| 89 | border: none; | ||
| 90 | background: transparent; | ||
| 91 | } | ||
| 92 | |||
| 93 | QGroupBox#groupPlayer1Connected, | ||
| 94 | QGroupBox#groupPlayer2Connected, | ||
| 95 | QGroupBox#groupPlayer3Connected, | ||
| 96 | QGroupBox#groupPlayer4Connected, | ||
| 97 | QGroupBox#groupPlayer5Connected, | ||
| 98 | QGroupBox#groupPlayer6Connected, | ||
| 99 | QGroupBox#groupPlayer7Connected, | ||
| 100 | QGroupBox#groupPlayer8Connected { | ||
| 101 | border: 1px solid #828790; | ||
| 102 | border-radius: 3px; | ||
| 103 | padding: 0px; | ||
| 104 | min-height: 98px; | ||
| 105 | max-height: 98px; | ||
| 106 | } | ||
| 107 | |||
| 108 | QGroupBox#groupPlayer1Connected:unchecked, | ||
| 109 | QGroupBox#groupPlayer2Connected:unchecked, | ||
| 110 | QGroupBox#groupPlayer3Connected:unchecked, | ||
| 111 | QGroupBox#groupPlayer4Connected:unchecked, | ||
| 112 | QGroupBox#groupPlayer5Connected:unchecked, | ||
| 113 | QGroupBox#groupPlayer6Connected:unchecked, | ||
| 114 | QGroupBox#groupPlayer7Connected:unchecked, | ||
| 115 | QGroupBox#groupPlayer8Connected:unchecked { | ||
| 116 | border: 1px solid #d9d9d9; | ||
| 117 | } | ||
| 118 | |||
| 119 | QGroupBox#groupPlayer1Connected::title, | ||
| 120 | QGroupBox#groupPlayer2Connected::title, | ||
| 121 | QGroupBox#groupPlayer3Connected::title, | ||
| 122 | QGroupBox#groupPlayer4Connected::title, | ||
| 123 | QGroupBox#groupPlayer5Connected::title, | ||
| 124 | QGroupBox#groupPlayer6Connected::title, | ||
| 125 | QGroupBox#groupPlayer7Connected::title, | ||
| 126 | QGroupBox#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 | |||
| 44 | QCheckBox#checkboxPlayer1Connected, | 137 | QCheckBox#checkboxPlayer1Connected, |
| 45 | QCheckBox#checkboxPlayer2Connected, | 138 | QCheckBox#checkboxPlayer2Connected, |
| 46 | QCheckBox#checkboxPlayer3Connected, | 139 | QCheckBox#checkboxPlayer3Connected, |
| @@ -52,6 +145,42 @@ QCheckBox#checkboxPlayer8Connected { | |||
| 52 | spacing: 0px; | 145 | spacing: 0px; |
| 53 | } | 146 | } |
| 54 | 147 | ||
| 148 | QWidget#Player1LEDs QCheckBox, | ||
| 149 | QWidget#Player2LEDs QCheckBox, | ||
| 150 | QWidget#Player3LEDs QCheckBox, | ||
| 151 | QWidget#Player4LEDs QCheckBox, | ||
| 152 | QWidget#Player5LEDs QCheckBox, | ||
| 153 | QWidget#Player6LEDs QCheckBox, | ||
| 154 | QWidget#Player7LEDs QCheckBox, | ||
| 155 | QWidget#Player8LEDs QCheckBox { | ||
| 156 | spacing: 0px; | ||
| 157 | } | ||
| 158 | |||
| 159 | QWidget#Player1LEDs QCheckBox::indicator, | ||
| 160 | QWidget#Player2LEDs QCheckBox::indicator, | ||
| 161 | QWidget#Player3LEDs QCheckBox::indicator, | ||
| 162 | QWidget#Player4LEDs QCheckBox::indicator, | ||
| 163 | QWidget#Player5LEDs QCheckBox::indicator, | ||
| 164 | QWidget#Player6LEDs QCheckBox::indicator, | ||
| 165 | QWidget#Player7LEDs QCheckBox::indicator, | ||
| 166 | QWidget#Player8LEDs QCheckBox::indicator { | ||
| 167 | width: 6px; | ||
| 168 | height: 6px; | ||
| 169 | margin-left: 0px; | ||
| 170 | } | ||
| 171 | |||
| 172 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, | ||
| 173 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator, | ||
| 174 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator, | ||
| 175 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator, | ||
| 176 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, | ||
| 177 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, | ||
| 178 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, | ||
| 179 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { | ||
| 180 | width: 12px; | ||
| 181 | height: 12px; | ||
| 182 | } | ||
| 183 | |||
| 55 | QCheckBox#checkboxPlayer1Connected::indicator, | 184 | QCheckBox#checkboxPlayer1Connected::indicator, |
| 56 | QCheckBox#checkboxPlayer2Connected::indicator, | 185 | QCheckBox#checkboxPlayer2Connected::indicator, |
| 57 | QCheckBox#checkboxPlayer3Connected::indicator, | 186 | QCheckBox#checkboxPlayer3Connected::indicator, |
| @@ -64,6 +193,34 @@ QCheckBox#checkboxPlayer8Connected::indicator { | |||
| 64 | height: 14px; | 193 | height: 14px; |
| 65 | } | 194 | } |
| 66 | 195 | ||
| 196 | QGroupBox#groupPlayer1Connected::indicator, | ||
| 197 | QGroupBox#groupPlayer2Connected::indicator, | ||
| 198 | QGroupBox#groupPlayer3Connected::indicator, | ||
| 199 | QGroupBox#groupPlayer4Connected::indicator, | ||
| 200 | QGroupBox#groupPlayer5Connected::indicator, | ||
| 201 | QGroupBox#groupPlayer6Connected::indicator, | ||
| 202 | QGroupBox#groupPlayer7Connected::indicator, | ||
| 203 | QGroupBox#groupPlayer8Connected::indicator { | ||
| 204 | width: 16px; | ||
| 205 | height: 16px; | ||
| 206 | } | ||
| 207 | |||
| 208 | QWidget#Player1LEDs QCheckBox::indicator:checked, | ||
| 209 | QWidget#Player2LEDs QCheckBox::indicator:checked, | ||
| 210 | QWidget#Player3LEDs QCheckBox::indicator:checked, | ||
| 211 | QWidget#Player4LEDs QCheckBox::indicator:checked, | ||
| 212 | QWidget#Player5LEDs QCheckBox::indicator:checked, | ||
| 213 | QWidget#Player6LEDs QCheckBox::indicator:checked, | ||
| 214 | QWidget#Player7LEDs QCheckBox::indicator:checked, | ||
| 215 | QWidget#Player8LEDs QCheckBox::indicator:checked, | ||
| 216 | QGroupBox#groupPlayer1Connected::indicator:checked, | ||
| 217 | QGroupBox#groupPlayer2Connected::indicator:checked, | ||
| 218 | QGroupBox#groupPlayer3Connected::indicator:checked, | ||
| 219 | QGroupBox#groupPlayer4Connected::indicator:checked, | ||
| 220 | QGroupBox#groupPlayer5Connected::indicator:checked, | ||
| 221 | QGroupBox#groupPlayer6Connected::indicator:checked, | ||
| 222 | QGroupBox#groupPlayer7Connected::indicator:checked, | ||
| 223 | QGroupBox#groupPlayer8Connected::indicator:checked, | ||
| 67 | QCheckBox#checkboxPlayer1Connected::indicator:checked, | 224 | QCheckBox#checkboxPlayer1Connected::indicator:checked, |
| 68 | QCheckBox#checkboxPlayer2Connected::indicator:checked, | 225 | QCheckBox#checkboxPlayer2Connected::indicator:checked, |
| 69 | QCheckBox#checkboxPlayer3Connected::indicator:checked, | 226 | QCheckBox#checkboxPlayer3Connected::indicator:checked, |
| @@ -74,11 +231,27 @@ QCheckBox#checkboxPlayer7Connected::indicator:checked, | |||
| 74 | QCheckBox#checkboxPlayer8Connected::indicator:checked, | 231 | QCheckBox#checkboxPlayer8Connected::indicator:checked, |
| 75 | QGroupBox#groupConnectedController::indicator:checked { | 232 | QGroupBox#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 | ||
| 239 | QWidget#Player1LEDs QCheckBox::indicator:unchecked, | ||
| 240 | QWidget#Player2LEDs QCheckBox::indicator:unchecked, | ||
| 241 | QWidget#Player3LEDs QCheckBox::indicator:unchecked, | ||
| 242 | QWidget#Player4LEDs QCheckBox::indicator:unchecked, | ||
| 243 | QWidget#Player5LEDs QCheckBox::indicator:unchecked, | ||
| 244 | QWidget#Player6LEDs QCheckBox::indicator:unchecked, | ||
| 245 | QWidget#Player7LEDs QCheckBox::indicator:unchecked, | ||
| 246 | QWidget#Player8LEDs QCheckBox::indicator:unchecked, | ||
| 247 | QGroupBox#groupPlayer1Connected::indicator:unchecked, | ||
| 248 | QGroupBox#groupPlayer2Connected::indicator:unchecked, | ||
| 249 | QGroupBox#groupPlayer3Connected::indicator:unchecked, | ||
| 250 | QGroupBox#groupPlayer4Connected::indicator:unchecked, | ||
| 251 | QGroupBox#groupPlayer5Connected::indicator:unchecked, | ||
| 252 | QGroupBox#groupPlayer6Connected::indicator:unchecked, | ||
| 253 | QGroupBox#groupPlayer7Connected::indicator:unchecked, | ||
| 254 | QGroupBox#groupPlayer8Connected::indicator:unchecked, | ||
| 82 | QCheckBox#checkboxPlayer1Connected::indicator:unchecked, | 255 | QCheckBox#checkboxPlayer1Connected::indicator:unchecked, |
| 83 | QCheckBox#checkboxPlayer2Connected::indicator:unchecked, | 256 | QCheckBox#checkboxPlayer2Connected::indicator:unchecked, |
| 84 | QCheckBox#checkboxPlayer3Connected::indicator:unchecked, | 257 | QCheckBox#checkboxPlayer3Connected::indicator:unchecked, |
| @@ -89,7 +262,18 @@ QCheckBox#checkboxPlayer7Connected::indicator:unchecked, | |||
| 89 | QCheckBox#checkboxPlayer8Connected::indicator:unchecked, | 262 | QCheckBox#checkboxPlayer8Connected::indicator:unchecked, |
| 90 | QGroupBox#groupConnectedController::indicator:unchecked { | 263 | QGroupBox#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 | |||
| 270 | QWidget#controllerPlayer1, | ||
| 271 | QWidget#controllerPlayer2, | ||
| 272 | QWidget#controllerPlayer3, | ||
| 273 | QWidget#controllerPlayer4, | ||
| 274 | QWidget#controllerPlayer5, | ||
| 275 | QWidget#controllerPlayer6, | ||
| 276 | QWidget#controllerPlayer7, | ||
| 277 | QWidget#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 | ||
| 1287 | QSpinBox#spinboxLStickRange, | ||
| 1288 | QSpinBox#spinboxRStickRange { | ||
| 1289 | padding: 4px 0px 5px 0px; | ||
| 1290 | min-width: 63px; | ||
| 1291 | } | ||
| 1292 | |||
| 1293 | QSpinBox#vibrationSpin { | ||
| 1294 | padding: 4px 0px 5px 0px; | ||
| 1295 | min-width: 63px; | ||
| 1296 | } | ||
| 1297 | |||
| 1298 | QSpinBox#spinboxLStickRange:up-button, | ||
| 1299 | QSpinBox#spinboxRStickRange:up-button, | ||
| 1300 | QSpinBox#vibrationSpin:up-button { | ||
| 1301 | left: -2px; | ||
| 1302 | } | ||
| 1303 | |||
| 1304 | QSpinBox#spinboxLStickRange:down-button, | ||
| 1305 | QSpinBox#spinboxRStickRange:down-button, | ||
| 1306 | QSpinBox#vibrationSpin:down-button { | ||
| 1307 | right: -1px; | ||
| 1308 | } | ||
| 1309 | |||
| 1310 | QGroupBox#motionGroup::indicator, | ||
| 1311 | QGroupBox#vibrationGroup::indicator { | ||
| 1312 | margin-left: 0px; | ||
| 1313 | } | ||
| 1314 | |||
| 1315 | QGroupBox#motionGroup::title, | ||
| 1316 | QGroupBox#vibrationGroup::title { | ||
| 1317 | spacing: 2px; | ||
| 1318 | padding-left: 1px; | ||
| 1319 | padding-right: 1px; | ||
| 1320 | } | ||
| 1321 | |||
| 1322 | QWidget#bottomPerGameInput, | ||
| 1323 | QWidget#topControllerApplet, | ||
| 1324 | QWidget#bottomControllerApplet, | ||
| 1325 | QGroupBox#groupPlayer1Connected:checked, | ||
| 1326 | QGroupBox#groupPlayer2Connected:checked, | ||
| 1327 | QGroupBox#groupPlayer3Connected:checked, | ||
| 1328 | QGroupBox#groupPlayer4Connected:checked, | ||
| 1329 | QGroupBox#groupPlayer5Connected:checked, | ||
| 1330 | QGroupBox#groupPlayer6Connected:checked, | ||
| 1331 | QGroupBox#groupPlayer7Connected:checked, | ||
| 1332 | QGroupBox#groupPlayer8Connected:checked { | ||
| 1333 | background-color: #232629; | ||
| 1334 | } | ||
| 1335 | |||
| 1336 | QWidget#topPerGameInput, | ||
| 1337 | QWidget#middleControllerApplet { | ||
| 1338 | background-color: #31363b; | ||
| 1339 | } | ||
| 1340 | |||
| 1341 | QWidget#topPerGameInput QComboBox, | ||
| 1342 | QWidget#middleControllerApplet QComboBox { | ||
| 1343 | width: 119px; | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | QRadioButton#radioDocked { | ||
| 1347 | margin-left: -3px; | ||
| 1348 | } | ||
| 1349 | |||
| 1350 | |||
| 1351 | QRadioButton#radioUndocked { | ||
| 1352 | margin-right: 5px; | ||
| 1353 | } | ||
| 1354 | |||
| 1355 | QWidget#connectedControllers { | ||
| 1356 | background: transparent; | ||
| 1357 | } | ||
| 1358 | |||
| 1359 | QWidget#playersSupported, | ||
| 1360 | QWidget#controllersSupported, | ||
| 1361 | QWidget#controllerSupported1, | ||
| 1362 | QWidget#controllerSupported2, | ||
| 1363 | QWidget#controllerSupported3, | ||
| 1364 | QWidget#controllerSupported4, | ||
| 1365 | QWidget#controllerSupported5, | ||
| 1366 | QWidget#controllerSupported6 { | ||
| 1367 | border: none; | ||
| 1368 | background: transparent; | ||
| 1369 | } | ||
| 1370 | |||
| 1371 | QGroupBox#groupPlayer1Connected, | ||
| 1372 | QGroupBox#groupPlayer2Connected, | ||
| 1373 | QGroupBox#groupPlayer3Connected, | ||
| 1374 | QGroupBox#groupPlayer4Connected, | ||
| 1375 | QGroupBox#groupPlayer5Connected, | ||
| 1376 | QGroupBox#groupPlayer6Connected, | ||
| 1377 | QGroupBox#groupPlayer7Connected, | ||
| 1378 | QGroupBox#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 | |||
| 1387 | QGroupBox#groupPlayer1Connected:unchecked, | ||
| 1388 | QGroupBox#groupPlayer2Connected:unchecked, | ||
| 1389 | QGroupBox#groupPlayer3Connected:unchecked, | ||
| 1390 | QGroupBox#groupPlayer4Connected:unchecked, | ||
| 1391 | QGroupBox#groupPlayer5Connected:unchecked, | ||
| 1392 | QGroupBox#groupPlayer6Connected:unchecked, | ||
| 1393 | QGroupBox#groupPlayer7Connected:unchecked, | ||
| 1394 | QGroupBox#groupPlayer8Connected:unchecked { | ||
| 1395 | border: 1px solid #54575b; | ||
| 1396 | } | ||
| 1397 | |||
| 1398 | QGroupBox#groupPlayer1Connected::title, | ||
| 1399 | QGroupBox#groupPlayer2Connected::title, | ||
| 1400 | QGroupBox#groupPlayer3Connected::title, | ||
| 1401 | QGroupBox#groupPlayer4Connected::title, | ||
| 1402 | QGroupBox#groupPlayer5Connected::title, | ||
| 1403 | QGroupBox#groupPlayer6Connected::title, | ||
| 1404 | QGroupBox#groupPlayer7Connected::title, | ||
| 1405 | QGroupBox#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 | |||
| 1287 | QCheckBox#checkboxPlayer1Connected, | 1416 | QCheckBox#checkboxPlayer1Connected, |
| 1288 | QCheckBox#checkboxPlayer2Connected, | 1417 | QCheckBox#checkboxPlayer2Connected, |
| 1289 | QCheckBox#checkboxPlayer3Connected, | 1418 | QCheckBox#checkboxPlayer3Connected, |
| @@ -1295,6 +1424,55 @@ QCheckBox#checkboxPlayer8Connected { | |||
| 1295 | spacing: 0px; | 1424 | spacing: 0px; |
| 1296 | } | 1425 | } |
| 1297 | 1426 | ||
| 1427 | QWidget#Player1LEDs, | ||
| 1428 | QWidget#Player2LEDs, | ||
| 1429 | QWidget#Player3LEDs, | ||
| 1430 | QWidget#Player4LEDs, | ||
| 1431 | QWidget#Player5LEDs, | ||
| 1432 | QWidget#Player6LEDs, | ||
| 1433 | QWidget#Player7LEDs, | ||
| 1434 | QWidget#Player8LEDs { | ||
| 1435 | background: transparent; | ||
| 1436 | } | ||
| 1437 | |||
| 1438 | QWidget#Player1LEDs QCheckBox, | ||
| 1439 | QWidget#Player2LEDs QCheckBox, | ||
| 1440 | QWidget#Player3LEDs QCheckBox, | ||
| 1441 | QWidget#Player4LEDs QCheckBox, | ||
| 1442 | QWidget#Player5LEDs QCheckBox, | ||
| 1443 | QWidget#Player6LEDs QCheckBox, | ||
| 1444 | QWidget#Player7LEDs QCheckBox, | ||
| 1445 | QWidget#Player8LEDs QCheckBox { | ||
| 1446 | spacing: 0px; | ||
| 1447 | margin-bottom: 0px; | ||
| 1448 | margin-right: 0px; | ||
| 1449 | } | ||
| 1450 | |||
| 1451 | QWidget#Player1LEDs QCheckBox::indicator, | ||
| 1452 | QWidget#Player2LEDs QCheckBox::indicator, | ||
| 1453 | QWidget#Player3LEDs QCheckBox::indicator, | ||
| 1454 | QWidget#Player4LEDs QCheckBox::indicator, | ||
| 1455 | QWidget#Player5LEDs QCheckBox::indicator, | ||
| 1456 | QWidget#Player6LEDs QCheckBox::indicator, | ||
| 1457 | QWidget#Player7LEDs QCheckBox::indicator, | ||
| 1458 | QWidget#Player8LEDs QCheckBox::indicator { | ||
| 1459 | width: 6px; | ||
| 1460 | height: 6px; | ||
| 1461 | margin-left: 0px; | ||
| 1462 | } | ||
| 1463 | |||
| 1464 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, | ||
| 1465 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator, | ||
| 1466 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator, | ||
| 1467 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator, | ||
| 1468 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, | ||
| 1469 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, | ||
| 1470 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, | ||
| 1471 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { | ||
| 1472 | width: 12px; | ||
| 1473 | height: 12px; | ||
| 1474 | } | ||
| 1475 | |||
| 1298 | QCheckBox#checkboxPlayer1Connected::indicator, | 1476 | QCheckBox#checkboxPlayer1Connected::indicator, |
| 1299 | QCheckBox#checkboxPlayer2Connected::indicator, | 1477 | QCheckBox#checkboxPlayer2Connected::indicator, |
| 1300 | QCheckBox#checkboxPlayer3Connected::indicator, | 1478 | QCheckBox#checkboxPlayer3Connected::indicator, |
| @@ -1307,6 +1485,34 @@ QCheckBox#checkboxPlayer8Connected::indicator { | |||
| 1307 | height: 14px; | 1485 | height: 14px; |
| 1308 | } | 1486 | } |
| 1309 | 1487 | ||
| 1488 | QGroupBox#groupPlayer1Connected::indicator, | ||
| 1489 | QGroupBox#groupPlayer2Connected::indicator, | ||
| 1490 | QGroupBox#groupPlayer3Connected::indicator, | ||
| 1491 | QGroupBox#groupPlayer4Connected::indicator, | ||
| 1492 | QGroupBox#groupPlayer5Connected::indicator, | ||
| 1493 | QGroupBox#groupPlayer6Connected::indicator, | ||
| 1494 | QGroupBox#groupPlayer7Connected::indicator, | ||
| 1495 | QGroupBox#groupPlayer8Connected::indicator { | ||
| 1496 | width: 16px; | ||
| 1497 | height: 16px; | ||
| 1498 | } | ||
| 1499 | |||
| 1500 | QWidget#Player1LEDs QCheckBox::indicator:checked, | ||
| 1501 | QWidget#Player2LEDs QCheckBox::indicator:checked, | ||
| 1502 | QWidget#Player3LEDs QCheckBox::indicator:checked, | ||
| 1503 | QWidget#Player4LEDs QCheckBox::indicator:checked, | ||
| 1504 | QWidget#Player5LEDs QCheckBox::indicator:checked, | ||
| 1505 | QWidget#Player6LEDs QCheckBox::indicator:checked, | ||
| 1506 | QWidget#Player7LEDs QCheckBox::indicator:checked, | ||
| 1507 | QWidget#Player8LEDs QCheckBox::indicator:checked, | ||
| 1508 | QGroupBox#groupPlayer1Connected::indicator:checked, | ||
| 1509 | QGroupBox#groupPlayer2Connected::indicator:checked, | ||
| 1510 | QGroupBox#groupPlayer3Connected::indicator:checked, | ||
| 1511 | QGroupBox#groupPlayer4Connected::indicator:checked, | ||
| 1512 | QGroupBox#groupPlayer5Connected::indicator:checked, | ||
| 1513 | QGroupBox#groupPlayer6Connected::indicator:checked, | ||
| 1514 | QGroupBox#groupPlayer7Connected::indicator:checked, | ||
| 1515 | QGroupBox#groupPlayer8Connected::indicator:checked, | ||
| 1310 | QCheckBox#checkboxPlayer1Connected::indicator:checked, | 1516 | QCheckBox#checkboxPlayer1Connected::indicator:checked, |
| 1311 | QCheckBox#checkboxPlayer2Connected::indicator:checked, | 1517 | QCheckBox#checkboxPlayer2Connected::indicator:checked, |
| 1312 | QCheckBox#checkboxPlayer3Connected::indicator:checked, | 1518 | QCheckBox#checkboxPlayer3Connected::indicator:checked, |
| @@ -1322,6 +1528,22 @@ QGroupBox#groupConnectedController::indicator:checked { | |||
| 1322 | image: none; | 1528 | image: none; |
| 1323 | } | 1529 | } |
| 1324 | 1530 | ||
| 1531 | QWidget#Player1LEDs QCheckBox::indicator:unchecked, | ||
| 1532 | QWidget#Player2LEDs QCheckBox::indicator:unchecked, | ||
| 1533 | QWidget#Player3LEDs QCheckBox::indicator:unchecked, | ||
| 1534 | QWidget#Player4LEDs QCheckBox::indicator:unchecked, | ||
| 1535 | QWidget#Player5LEDs QCheckBox::indicator:unchecked, | ||
| 1536 | QWidget#Player6LEDs QCheckBox::indicator:unchecked, | ||
| 1537 | QWidget#Player7LEDs QCheckBox::indicator:unchecked, | ||
| 1538 | QWidget#Player8LEDs QCheckBox::indicator:unchecked, | ||
| 1539 | QGroupBox#groupPlayer1Connected::indicator:unchecked, | ||
| 1540 | QGroupBox#groupPlayer2Connected::indicator:unchecked, | ||
| 1541 | QGroupBox#groupPlayer3Connected::indicator:unchecked, | ||
| 1542 | QGroupBox#groupPlayer4Connected::indicator:unchecked, | ||
| 1543 | QGroupBox#groupPlayer5Connected::indicator:unchecked, | ||
| 1544 | QGroupBox#groupPlayer6Connected::indicator:unchecked, | ||
| 1545 | QGroupBox#groupPlayer7Connected::indicator:unchecked, | ||
| 1546 | QGroupBox#groupPlayer8Connected::indicator:unchecked, | ||
| 1325 | QCheckBox#checkboxPlayer1Connected::indicator:unchecked, | 1547 | QCheckBox#checkboxPlayer1Connected::indicator:unchecked, |
| 1326 | QCheckBox#checkboxPlayer2Connected::indicator:unchecked, | 1548 | QCheckBox#checkboxPlayer2Connected::indicator:unchecked, |
| 1327 | QCheckBox#checkboxPlayer3Connected::indicator:unchecked, | 1549 | QCheckBox#checkboxPlayer3Connected::indicator:unchecked, |
| @@ -1337,39 +1559,15 @@ QGroupBox#groupConnectedController::indicator:unchecked { | |||
| 1337 | image: none; | 1559 | image: none; |
| 1338 | } | 1560 | } |
| 1339 | 1561 | ||
| 1340 | QSpinBox#spinboxLStickRange, | 1562 | QWidget#controllerPlayer1, |
| 1341 | QSpinBox#spinboxRStickRange { | 1563 | QWidget#controllerPlayer2, |
| 1342 | padding: 4px 0px 5px 0px; | 1564 | QWidget#controllerPlayer3, |
| 1343 | min-width: 63px; | 1565 | QWidget#controllerPlayer4, |
| 1344 | } | 1566 | QWidget#controllerPlayer5, |
| 1345 | 1567 | QWidget#controllerPlayer6, | |
| 1346 | QSpinBox#vibrationSpin { | 1568 | QWidget#controllerPlayer7, |
| 1347 | padding: 4px 0px 5px 0px; | 1569 | QWidget#controllerPlayer8 { |
| 1348 | min-width: 63px; | 1570 | background: transparent; |
| 1349 | } | ||
| 1350 | |||
| 1351 | QSpinBox#spinboxLStickRange:up-button, | ||
| 1352 | QSpinBox#spinboxRStickRange:up-button, | ||
| 1353 | QSpinBox#vibrationSpin:up-button { | ||
| 1354 | left: -2px; | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | QSpinBox#spinboxLStickRange:down-button, | ||
| 1358 | QSpinBox#spinboxRStickRange:down-button, | ||
| 1359 | QSpinBox#vibrationSpin:down-button { | ||
| 1360 | right: -1px; | ||
| 1361 | } | ||
| 1362 | |||
| 1363 | QGroupBox#motionGroup::indicator, | ||
| 1364 | QGroupBox#vibrationGroup::indicator { | ||
| 1365 | margin-left: 0px; | ||
| 1366 | } | ||
| 1367 | |||
| 1368 | QGroupBox#motionGroup::title, | ||
| 1369 | QGroupBox#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 | --------------------------------------------------------------------------- */ |
| 237 | QGroupBox { | 237 | QGroupBox { |
| 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 | ||
| 245 | QGroupBox::title { | 245 | QGroupBox::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 | ||
| 253 | QGroupBox::indicator { | 253 | QGroupBox::indicator { |
| @@ -2205,6 +2205,144 @@ QPushButton#buttonRefreshDevices { | |||
| 2205 | padding: 0px 0px; | 2205 | padding: 0px 0px; |
| 2206 | } | 2206 | } |
| 2207 | 2207 | ||
| 2208 | QSpinBox#spinboxLStickRange, | ||
| 2209 | QSpinBox#spinboxRStickRange { | ||
| 2210 | min-width: 38px; | ||
| 2211 | } | ||
| 2212 | |||
| 2213 | QGroupBox#motionGroup::indicator, | ||
| 2214 | QGroupBox#vibrationGroup::indicator { | ||
| 2215 | margin-left: 0px; | ||
| 2216 | } | ||
| 2217 | |||
| 2218 | QWidget#bottomPerGameInput QGroupBox#motionGroup, | ||
| 2219 | QWidget#bottomPerGameInput QGroupBox#vibrationGroup, | ||
| 2220 | QWidget#bottomPerGameInput QGroupBox#inputConfigGroup { | ||
| 2221 | padding: 0px; | ||
| 2222 | } | ||
| 2223 | |||
| 2224 | QGroupBox#motionGroup::title, | ||
| 2225 | QGroupBox#vibrationGroup::title { | ||
| 2226 | spacing: 2px; | ||
| 2227 | padding-left: 1px; | ||
| 2228 | padding-right: 1px; | ||
| 2229 | } | ||
| 2230 | |||
| 2231 | QListWidget#selectorList { | ||
| 2232 | background-color: #0f1922; | ||
| 2233 | } | ||
| 2234 | |||
| 2235 | QSpinBox, | ||
| 2236 | QLineEdit, | ||
| 2237 | QTreeView#hotkey_list, | ||
| 2238 | QScrollArea#scrollArea QTreeView { | ||
| 2239 | background-color: #0f1922; | ||
| 2240 | } | ||
| 2241 | |||
| 2242 | QWidget#bottomPerGameInput, | ||
| 2243 | QWidget#topControllerApplet, | ||
| 2244 | QWidget#bottomControllerApplet, | ||
| 2245 | QGroupBox#groupPlayer1Connected:checked, | ||
| 2246 | QGroupBox#groupPlayer2Connected:checked, | ||
| 2247 | QGroupBox#groupPlayer3Connected:checked, | ||
| 2248 | QGroupBox#groupPlayer4Connected:checked, | ||
| 2249 | QGroupBox#groupPlayer5Connected:checked, | ||
| 2250 | QGroupBox#groupPlayer6Connected:checked, | ||
| 2251 | QGroupBox#groupPlayer7Connected:checked, | ||
| 2252 | QGroupBox#groupPlayer8Connected:checked { | ||
| 2253 | background-color: #0f1922; | ||
| 2254 | } | ||
| 2255 | |||
| 2256 | QWidget#topPerGameInput, | ||
| 2257 | QWidget#middleControllerApplet { | ||
| 2258 | background-color: #19232d; | ||
| 2259 | } | ||
| 2260 | |||
| 2261 | QWidget#topPerGameInput QComboBox, | ||
| 2262 | QWidget#middleControllerApplet QComboBox { | ||
| 2263 | padding-right: 2px; | ||
| 2264 | width: 127px; | ||
| 2265 | } | ||
| 2266 | |||
| 2267 | QGroupBox#handheldGroup { | ||
| 2268 | padding-left: 0px; | ||
| 2269 | } | ||
| 2270 | |||
| 2271 | QRadioButton#radioDocked { | ||
| 2272 | margin-left: -1px; | ||
| 2273 | padding-left: 0px; | ||
| 2274 | } | ||
| 2275 | |||
| 2276 | QRadioButton#radioDocked::indicator { | ||
| 2277 | margin-left: 0px; | ||
| 2278 | } | ||
| 2279 | |||
| 2280 | |||
| 2281 | QRadioButton#radioUndocked { | ||
| 2282 | margin-right: 2px; | ||
| 2283 | } | ||
| 2284 | |||
| 2285 | QWidget#connectedControllers { | ||
| 2286 | background: transparent; | ||
| 2287 | } | ||
| 2288 | |||
| 2289 | QWidget#playersSupported, | ||
| 2290 | QWidget#controllersSupported, | ||
| 2291 | QWidget#controllerSupported1, | ||
| 2292 | QWidget#controllerSupported2, | ||
| 2293 | QWidget#controllerSupported3, | ||
| 2294 | QWidget#controllerSupported4, | ||
| 2295 | QWidget#controllerSupported5, | ||
| 2296 | QWidget#controllerSupported6 { | ||
| 2297 | border: none; | ||
| 2298 | background: transparent; | ||
| 2299 | } | ||
| 2300 | |||
| 2301 | QGroupBox#groupPlayer1Connected, | ||
| 2302 | QGroupBox#groupPlayer2Connected, | ||
| 2303 | QGroupBox#groupPlayer3Connected, | ||
| 2304 | QGroupBox#groupPlayer4Connected, | ||
| 2305 | QGroupBox#groupPlayer5Connected, | ||
| 2306 | QGroupBox#groupPlayer6Connected, | ||
| 2307 | QGroupBox#groupPlayer7Connected, | ||
| 2308 | QGroupBox#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 | |||
| 2318 | QGroupBox#groupPlayer1Connected:unchecked, | ||
| 2319 | QGroupBox#groupPlayer2Connected:unchecked, | ||
| 2320 | QGroupBox#groupPlayer3Connected:unchecked, | ||
| 2321 | QGroupBox#groupPlayer4Connected:unchecked, | ||
| 2322 | QGroupBox#groupPlayer5Connected:unchecked, | ||
| 2323 | QGroupBox#groupPlayer6Connected:unchecked, | ||
| 2324 | QGroupBox#groupPlayer7Connected:unchecked, | ||
| 2325 | QGroupBox#groupPlayer8Connected:unchecked { | ||
| 2326 | border: 1px solid #32414b; | ||
| 2327 | } | ||
| 2328 | |||
| 2329 | QGroupBox#groupPlayer1Connected::title, | ||
| 2330 | QGroupBox#groupPlayer2Connected::title, | ||
| 2331 | QGroupBox#groupPlayer3Connected::title, | ||
| 2332 | QGroupBox#groupPlayer4Connected::title, | ||
| 2333 | QGroupBox#groupPlayer5Connected::title, | ||
| 2334 | QGroupBox#groupPlayer6Connected::title, | ||
| 2335 | QGroupBox#groupPlayer7Connected::title, | ||
| 2336 | QGroupBox#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 | ||
| 2209 | QCheckBox#checkboxPlayer1Connected, | 2347 | QCheckBox#checkboxPlayer1Connected, |
| 2210 | QCheckBox#checkboxPlayer2Connected, | 2348 | QCheckBox#checkboxPlayer2Connected, |
| @@ -2214,7 +2352,69 @@ QCheckBox#checkboxPlayer5Connected, | |||
| 2214 | QCheckBox#checkboxPlayer6Connected, | 2352 | QCheckBox#checkboxPlayer6Connected, |
| 2215 | QCheckBox#checkboxPlayer7Connected, | 2353 | QCheckBox#checkboxPlayer7Connected, |
| 2216 | QCheckBox#checkboxPlayer8Connected { | 2354 | QCheckBox#checkboxPlayer8Connected { |
| 2355 | spacing: 0px; | ||
| 2356 | } | ||
| 2357 | |||
| 2358 | QWidget#connectedControllers QLabel { | ||
| 2359 | padding: 0px; | ||
| 2360 | } | ||
| 2361 | |||
| 2362 | QWidget#Player1LEDs, | ||
| 2363 | QWidget#Player2LEDs, | ||
| 2364 | QWidget#Player3LEDs, | ||
| 2365 | QWidget#Player4LEDs, | ||
| 2366 | QWidget#Player5LEDs, | ||
| 2367 | QWidget#Player6LEDs, | ||
| 2368 | QWidget#Player7LEDs, | ||
| 2369 | QWidget#Player8LEDs { | ||
| 2370 | background: transparent; | ||
| 2371 | } | ||
| 2372 | |||
| 2373 | QWidget#Player1LEDs QCheckBox, | ||
| 2374 | QWidget#Player2LEDs QCheckBox, | ||
| 2375 | QWidget#Player3LEDs QCheckBox, | ||
| 2376 | QWidget#Player4LEDs QCheckBox, | ||
| 2377 | QWidget#Player5LEDs QCheckBox, | ||
| 2378 | QWidget#Player6LEDs QCheckBox, | ||
| 2379 | QWidget#Player7LEDs QCheckBox, | ||
| 2380 | QWidget#Player8LEDs QCheckBox, | ||
| 2381 | QCheckBox#checkboxPlayer1Connected, | ||
| 2382 | QCheckBox#checkboxPlayer2Connected, | ||
| 2383 | QCheckBox#checkboxPlayer3Connected, | ||
| 2384 | QCheckBox#checkboxPlayer4Connected, | ||
| 2385 | QCheckBox#checkboxPlayer5Connected, | ||
| 2386 | QCheckBox#checkboxPlayer6Connected, | ||
| 2387 | QCheckBox#checkboxPlayer7Connected, | ||
| 2388 | QCheckBox#checkboxPlayer8Connected { | ||
| 2217 | spacing: 0px; | 2389 | spacing: 0px; |
| 2390 | padding-top: 0px; | ||
| 2391 | padding-bottom: 0px; | ||
| 2392 | background: transparent; | ||
| 2393 | } | ||
| 2394 | |||
| 2395 | QWidget#Player1LEDs QCheckBox::indicator, | ||
| 2396 | QWidget#Player2LEDs QCheckBox::indicator, | ||
| 2397 | QWidget#Player3LEDs QCheckBox::indicator, | ||
| 2398 | QWidget#Player4LEDs QCheckBox::indicator, | ||
| 2399 | QWidget#Player5LEDs QCheckBox::indicator, | ||
| 2400 | QWidget#Player6LEDs QCheckBox::indicator, | ||
| 2401 | QWidget#Player7LEDs QCheckBox::indicator, | ||
| 2402 | QWidget#Player8LEDs QCheckBox::indicator { | ||
| 2403 | width: 6px; | ||
| 2404 | height: 6px; | ||
| 2405 | margin-left: 0px; | ||
| 2406 | } | ||
| 2407 | |||
| 2408 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer1Connected::indicator, | ||
| 2409 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer2Connected::indicator, | ||
| 2410 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer3Connected::indicator, | ||
| 2411 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer4Connected::indicator, | ||
| 2412 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer5Connected::indicator, | ||
| 2413 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer6Connected::indicator, | ||
| 2414 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer7Connected::indicator, | ||
| 2415 | QWidget#bottomPerGameInput QCheckBox#checkboxPlayer8Connected::indicator { | ||
| 2416 | width: 12px; | ||
| 2417 | height: 12px; | ||
| 2218 | } | 2418 | } |
| 2219 | 2419 | ||
| 2220 | QCheckBox#checkboxPlayer1Connected::indicator, | 2420 | QCheckBox#checkboxPlayer1Connected::indicator, |
| @@ -2227,8 +2427,25 @@ QCheckBox#checkboxPlayer7Connected::indicator, | |||
| 2227 | QCheckBox#checkboxPlayer8Connected::indicator { | 2427 | QCheckBox#checkboxPlayer8Connected::indicator { |
| 2228 | width: 14px; | 2428 | width: 14px; |
| 2229 | height: 14px; | 2429 | height: 14px; |
| 2430 | margin-left: 2px; | ||
| 2230 | } | 2431 | } |
| 2231 | 2432 | ||
| 2433 | QWidget#Player1LEDs QCheckBox::indicator:checked, | ||
| 2434 | QWidget#Player2LEDs QCheckBox::indicator:checked, | ||
| 2435 | QWidget#Player3LEDs QCheckBox::indicator:checked, | ||
| 2436 | QWidget#Player4LEDs QCheckBox::indicator:checked, | ||
| 2437 | QWidget#Player5LEDs QCheckBox::indicator:checked, | ||
| 2438 | QWidget#Player6LEDs QCheckBox::indicator:checked, | ||
| 2439 | QWidget#Player7LEDs QCheckBox::indicator:checked, | ||
| 2440 | QWidget#Player8LEDs QCheckBox::indicator:checked, | ||
| 2441 | QGroupBox#groupPlayer1Connected::indicator:checked, | ||
| 2442 | QGroupBox#groupPlayer2Connected::indicator:checked, | ||
| 2443 | QGroupBox#groupPlayer3Connected::indicator:checked, | ||
| 2444 | QGroupBox#groupPlayer4Connected::indicator:checked, | ||
| 2445 | QGroupBox#groupPlayer5Connected::indicator:checked, | ||
| 2446 | QGroupBox#groupPlayer6Connected::indicator:checked, | ||
| 2447 | QGroupBox#groupPlayer7Connected::indicator:checked, | ||
| 2448 | QGroupBox#groupPlayer8Connected::indicator:checked, | ||
| 2232 | QCheckBox#checkboxPlayer1Connected::indicator:checked, | 2449 | QCheckBox#checkboxPlayer1Connected::indicator:checked, |
| 2233 | QCheckBox#checkboxPlayer2Connected::indicator:checked, | 2450 | QCheckBox#checkboxPlayer2Connected::indicator:checked, |
| 2234 | QCheckBox#checkboxPlayer3Connected::indicator:checked, | 2451 | QCheckBox#checkboxPlayer3Connected::indicator:checked, |
| @@ -2244,6 +2461,22 @@ QGroupBox#groupConnectedController::indicator:checked { | |||
| 2244 | image: none; | 2461 | image: none; |
| 2245 | } | 2462 | } |
| 2246 | 2463 | ||
| 2464 | QWidget#Player1LEDs QCheckBox::indicator:unchecked, | ||
| 2465 | QWidget#Player2LEDs QCheckBox::indicator:unchecked, | ||
| 2466 | QWidget#Player3LEDs QCheckBox::indicator:unchecked, | ||
| 2467 | QWidget#Player4LEDs QCheckBox::indicator:unchecked, | ||
| 2468 | QWidget#Player5LEDs QCheckBox::indicator:unchecked, | ||
| 2469 | QWidget#Player6LEDs QCheckBox::indicator:unchecked, | ||
| 2470 | QWidget#Player7LEDs QCheckBox::indicator:unchecked, | ||
| 2471 | QWidget#Player8LEDs QCheckBox::indicator:unchecked, | ||
| 2472 | QGroupBox#groupPlayer1Connected::indicator:unchecked, | ||
| 2473 | QGroupBox#groupPlayer2Connected::indicator:unchecked, | ||
| 2474 | QGroupBox#groupPlayer3Connected::indicator:unchecked, | ||
| 2475 | QGroupBox#groupPlayer4Connected::indicator:unchecked, | ||
| 2476 | QGroupBox#groupPlayer5Connected::indicator:unchecked, | ||
| 2477 | QGroupBox#groupPlayer6Connected::indicator:unchecked, | ||
| 2478 | QGroupBox#groupPlayer7Connected::indicator:unchecked, | ||
| 2479 | QGroupBox#groupPlayer8Connected::indicator:unchecked, | ||
| 2247 | QCheckBox#checkboxPlayer1Connected::indicator:unchecked, | 2480 | QCheckBox#checkboxPlayer1Connected::indicator:unchecked, |
| 2248 | QCheckBox#checkboxPlayer2Connected::indicator:unchecked, | 2481 | QCheckBox#checkboxPlayer2Connected::indicator:unchecked, |
| 2249 | QCheckBox#checkboxPlayer3Connected::indicator:unchecked, | 2482 | QCheckBox#checkboxPlayer3Connected::indicator:unchecked, |
| @@ -2255,34 +2488,17 @@ QCheckBox#checkboxPlayer8Connected::indicator:unchecked, | |||
| 2255 | QGroupBox#groupConnectedController::indicator:unchecked { | 2488 | QGroupBox#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 | ||
| 2262 | QSpinBox#spinboxLStickRange, | 2495 | QWidget#controllerPlayer1, |
| 2263 | QSpinBox#spinboxRStickRange { | 2496 | QWidget#controllerPlayer2, |
| 2264 | min-width: 38px; | 2497 | QWidget#controllerPlayer3, |
| 2265 | } | 2498 | QWidget#controllerPlayer4, |
| 2266 | 2499 | QWidget#controllerPlayer5, | |
| 2267 | QGroupBox#motionGroup::indicator, | 2500 | QWidget#controllerPlayer6, |
| 2268 | QGroupBox#vibrationGroup::indicator { | 2501 | QWidget#controllerPlayer7, |
| 2269 | margin-left: 0px; | 2502 | QWidget#controllerPlayer8 { |
| 2270 | } | 2503 | background: transparent; |
| 2271 | |||
| 2272 | QGroupBox#motionGroup::title, | ||
| 2273 | QGroupBox#vibrationGroup::title { | ||
| 2274 | spacing: 2px; | ||
| 2275 | padding-left: 1px; | ||
| 2276 | padding-right: 1px; | ||
| 2277 | } | ||
| 2278 | |||
| 2279 | QListWidget#selectorList { | ||
| 2280 | background-color: #0f1922; | ||
| 2281 | } | 2504 | } |
| 2282 | |||
| 2283 | QSpinBox, | ||
| 2284 | QLineEdit, | ||
| 2285 | QTreeView#hotkey_list, | ||
| 2286 | QScrollArea#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 | ||
| 200 | void 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 | ||
| 42 | void 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 | ||
| 16 | namespace AudioCore { | 20 | namespace AudioCore { |
| 17 | |||
| 18 | constexpr u32 STREAM_SAMPLE_RATE{48000}; | ||
| 19 | constexpr u32 STREAM_NUM_CHANNELS{2}; | ||
| 20 | using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>; | ||
| 21 | class AudioRenderer::VoiceState { | ||
| 22 | public: | ||
| 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 | |||
| 45 | private: | ||
| 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 | |||
| 57 | class AudioRenderer::EffectState { | ||
| 58 | public: | ||
| 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 | |||
| 73 | private: | ||
| 74 | EffectOutStatus out_status{}; | ||
| 75 | EffectInStatus info{}; | ||
| 76 | }; | ||
| 77 | |||
| 78 | AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, | 21 | AudioRenderer::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 | ||
| 96 | AudioRenderer::~AudioRenderer() = default; | 50 | AudioRenderer::~AudioRenderer() = default; |
| @@ -111,355 +65,200 @@ Stream::State AudioRenderer::GetStreamState() const { | |||
| 111 | return stream->GetState(); | 65 | return stream->GetState(); |
| 112 | } | 66 | } |
| 113 | 67 | ||
| 114 | ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { | 68 | static 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{ | 72 | ResultCode 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; | |
| 237 | void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) { | ||
| 238 | wave_index = index & 3; | ||
| 239 | is_refresh_pending = true; | ||
| 240 | } | ||
| 241 | |||
| 242 | std::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 | ||
| 283 | void 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 | ||
| 294 | void 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 | ||
| 407 | void 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 | ||
| 424 | static 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 | ||
| 428 | void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { | 160 | void 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 | ||
| 465 | void AudioRenderer::ReleaseAndQueueBuffers() { | 264 | void 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 | ||
| 32 | namespace AudioCore { | 39 | namespace AudioCore { |
| 40 | using DSPStateHolder = std::array<VoiceState*, 6>; | ||
| 33 | 41 | ||
| 34 | class AudioOut; | 42 | class AudioOut; |
| 35 | 43 | ||
| 36 | enum class PlayState : u8 { | ||
| 37 | Started = 0, | ||
| 38 | Stopped = 1, | ||
| 39 | Paused = 2, | ||
| 40 | }; | ||
| 41 | |||
| 42 | enum class Effect : u8 { | ||
| 43 | None = 0, | ||
| 44 | Aux = 2, | ||
| 45 | }; | ||
| 46 | |||
| 47 | enum class EffectStatus : u8 { | ||
| 48 | None = 0, | ||
| 49 | New = 1, | ||
| 50 | }; | ||
| 51 | |||
| 52 | struct 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 | }; | ||
| 70 | static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); | ||
| 71 | |||
| 72 | enum 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 | |||
| 82 | struct MemoryPoolEntry { | ||
| 83 | MemoryPoolStates state; | ||
| 84 | u32_le unknown_4; | ||
| 85 | u32_le unknown_8; | ||
| 86 | u32_le unknown_c; | ||
| 87 | }; | ||
| 88 | static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size"); | ||
| 89 | |||
| 90 | struct MemoryPoolInfo { | ||
| 91 | u64_le pool_address; | ||
| 92 | u64_le pool_size; | ||
| 93 | MemoryPoolStates pool_state; | ||
| 94 | INSERT_PADDING_WORDS(3); // Unknown | ||
| 95 | }; | ||
| 96 | static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size"); | ||
| 97 | struct BiquadFilter { | ||
| 98 | u8 enable; | ||
| 99 | INSERT_PADDING_BYTES(1); | ||
| 100 | std::array<s16_le, 3> numerator; | ||
| 101 | std::array<s16_le, 2> denominator; | ||
| 102 | }; | ||
| 103 | static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size"); | ||
| 104 | |||
| 105 | struct 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 | }; | ||
| 118 | static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size"); | ||
| 119 | |||
| 120 | struct 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 | }; | ||
| 126 | static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size"); | ||
| 127 | |||
| 128 | struct 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 | }; | ||
| 153 | static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size"); | ||
| 154 | |||
| 155 | struct VoiceOutStatus { | ||
| 156 | u64_le played_sample_count; | ||
| 157 | u32_le wave_buffer_consumed; | ||
| 158 | u32_le voice_drops_count; | ||
| 159 | }; | ||
| 160 | static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size"); | ||
| 161 | |||
| 162 | struct 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 | }; | ||
| 174 | static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); | ||
| 175 | |||
| 176 | struct 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 | }; | ||
| 191 | static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size"); | ||
| 192 | |||
| 193 | struct EffectOutStatus { | ||
| 194 | EffectStatus state; | ||
| 195 | INSERT_PADDING_BYTES(0xf); | ||
| 196 | }; | ||
| 197 | static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size"); | ||
| 198 | |||
| 199 | struct RendererInfo { | 44 | struct 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 | }; |
| 203 | static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); | 48 | static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); |
| 204 | 49 | ||
| 205 | struct 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 | }; | ||
| 237 | static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size"); | ||
| 238 | |||
| 239 | class AudioRenderer { | 50 | class AudioRenderer { |
| 240 | public: | 51 | public: |
| 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 | ||
| 254 | private: | 66 | private: |
| 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 | ||
| 10 | namespace AudioCore { | 10 | namespace AudioCore { |
| 11 | 11 | ||
| 12 | BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {} | 12 | BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {} |
| 13 | BehaviorInfo::~BehaviorInfo() = default; | 13 | BehaviorInfo::~BehaviorInfo() = default; |
| 14 | 14 | ||
| 15 | bool 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(¶ms, 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 | |||
| 43 | bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) { | 15 | bool 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 | ||
| 40 | u32_le BehaviorInfo::GetUserRevision() const { | ||
| 41 | return user_revision; | ||
| 42 | } | ||
| 43 | |||
| 44 | u32_le BehaviorInfo::GetProcessRevision() const { | ||
| 45 | return process_revision; | ||
| 46 | } | ||
| 47 | |||
| 68 | bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { | 48 | bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { |
| 69 | return IsRevisionSupported(2, user_revision); | 49 | return AudioCommon::IsRevisionSupported(2, user_revision); |
| 70 | } | 50 | } |
| 71 | 51 | ||
| 72 | bool BehaviorInfo::IsSplitterSupported() const { | 52 | bool BehaviorInfo::IsSplitterSupported() const { |
| 73 | return IsRevisionSupported(2, user_revision); | 53 | return AudioCommon::IsRevisionSupported(2, user_revision); |
| 74 | } | 54 | } |
| 75 | 55 | ||
| 76 | bool BehaviorInfo::IsLongSizePreDelaySupported() const { | 56 | bool BehaviorInfo::IsLongSizePreDelaySupported() const { |
| 77 | return IsRevisionSupported(3, user_revision); | 57 | return AudioCommon::IsRevisionSupported(3, user_revision); |
| 78 | } | 58 | } |
| 79 | 59 | ||
| 80 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const { | 60 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const { |
| 81 | return IsRevisionSupported(5, user_revision); | 61 | return AudioCommon::IsRevisionSupported(5, user_revision); |
| 82 | } | 62 | } |
| 83 | 63 | ||
| 84 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const { | 64 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const { |
| 85 | return IsRevisionSupported(4, user_revision); | 65 | return AudioCommon::IsRevisionSupported(4, user_revision); |
| 86 | } | 66 | } |
| 87 | 67 | ||
| 88 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const { | 68 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const { |
| 89 | return IsRevisionSupported(1, user_revision); | 69 | return AudioCommon::IsRevisionSupported(1, user_revision); |
| 90 | } | 70 | } |
| 91 | 71 | ||
| 92 | bool BehaviorInfo::IsElapsedFrameCountSupported() const { | 72 | bool BehaviorInfo::IsElapsedFrameCountSupported() const { |
| 93 | return IsRevisionSupported(5, user_revision); | 73 | return AudioCommon::IsRevisionSupported(5, user_revision); |
| 94 | } | 74 | } |
| 95 | 75 | ||
| 96 | bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { | 76 | bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { |
| 97 | return (flags & 1) != 0; | 77 | return (flags & 1) != 0; |
| 98 | } | 78 | } |
| 99 | 79 | ||
| 80 | bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { | ||
| 81 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 82 | } | ||
| 83 | |||
| 84 | bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { | ||
| 85 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 86 | } | ||
| 87 | |||
| 88 | bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { | ||
| 89 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 90 | } | ||
| 91 | |||
| 92 | bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { | ||
| 93 | return AudioCommon::IsRevisionSupported(7, user_revision); | ||
| 94 | } | ||
| 95 | |||
| 96 | bool BehaviorInfo::IsSplitterBugFixed() const { | ||
| 97 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 98 | } | ||
| 99 | |||
| 100 | void 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 @@ | |||
| 14 | namespace AudioCore { | 14 | namespace AudioCore { |
| 15 | class BehaviorInfo { | 15 | class BehaviorInfo { |
| 16 | public: | 16 | public: |
| 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 | ||
| 36 | private: | 64 | private: |
| 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 | |||
| 12 | namespace AudioCore { | ||
| 13 | namespace { | ||
| 14 | constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; | ||
| 15 | constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; | ||
| 16 | |||
| 17 | template <std::size_t N> | ||
| 18 | void 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 | |||
| 27 | s32 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 | |||
| 37 | void 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 | |||
| 44 | void 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 | |||
| 50 | s32 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 | |||
| 70 | CommandGenerator::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) {} | ||
| 81 | CommandGenerator::~CommandGenerator() = default; | ||
| 82 | |||
| 83 | void 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 | |||
| 89 | void 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 | |||
| 109 | void 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 | |||
| 176 | void 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 | |||
| 188 | void CommandGenerator::GenerateFinalMixCommands() { | ||
| 189 | GenerateFinalMixCommand(); | ||
| 190 | } | ||
| 191 | |||
| 192 | void 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 | |||
| 208 | void CommandGenerator::PostCommand() { | ||
| 209 | if (!dumping_frame) { | ||
| 210 | return; | ||
| 211 | } | ||
| 212 | dumping_frame = false; | ||
| 213 | } | ||
| 214 | |||
| 215 | void 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 | |||
| 256 | void 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 | |||
| 281 | void 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 | |||
| 315 | void 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 | |||
| 327 | void 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 | |||
| 343 | void 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 | |||
| 373 | void 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 | |||
| 390 | void 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 | |||
| 407 | void 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 | |||
| 457 | ServerSplitterDestinationData* 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 | |||
| 464 | s32 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 | |||
| 494 | s32 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 | |||
| 524 | void 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 | |||
| 543 | void 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 | |||
| 570 | void 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 | |||
| 583 | void 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 | |||
| 625 | void 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 | |||
| 648 | void 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 | |||
| 675 | s32 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 | |||
| 715 | s32 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 | |||
| 828 | s32* CommandGenerator::GetMixBuffer(std::size_t index) { | ||
| 829 | return mix_buffer.data() + (index * worker_params.sample_count); | ||
| 830 | } | ||
| 831 | |||
| 832 | const s32* CommandGenerator::GetMixBuffer(std::size_t index) const { | ||
| 833 | return mix_buffer.data() + (index * worker_params.sample_count); | ||
| 834 | } | ||
| 835 | |||
| 836 | std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { | ||
| 837 | return worker_params.mix_buffer_count + channel; | ||
| 838 | } | ||
| 839 | |||
| 840 | std::size_t CommandGenerator::GetTotalMixBufferCount() const { | ||
| 841 | return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT; | ||
| 842 | } | ||
| 843 | |||
| 844 | s32* CommandGenerator::GetChannelMixBuffer(s32 channel) { | ||
| 845 | return GetMixBuffer(worker_params.mix_buffer_count + channel); | ||
| 846 | } | ||
| 847 | |||
| 848 | const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const { | ||
| 849 | return GetMixBuffer(worker_params.mix_buffer_count + channel); | ||
| 850 | } | ||
| 851 | |||
| 852 | void 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 | |||
| 13 | namespace Core::Memory { | ||
| 14 | class Memory; | ||
| 15 | } | ||
| 16 | |||
| 17 | namespace AudioCore { | ||
| 18 | class MixContext; | ||
| 19 | class SplitterContext; | ||
| 20 | class ServerSplitterDestinationData; | ||
| 21 | class ServerMixInfo; | ||
| 22 | class EffectContext; | ||
| 23 | class EffectBase; | ||
| 24 | struct AuxInfoDSP; | ||
| 25 | using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; | ||
| 26 | |||
| 27 | class CommandGenerator { | ||
| 28 | public: | ||
| 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 | |||
| 51 | private: | ||
| 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 | ||
| 11 | namespace AudioCore { | 11 | namespace AudioCommon { |
| 12 | namespace Audren { | 12 | namespace Audren { |
| 13 | constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; | 13 | constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; |
| 14 | } | 14 | constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43}; |
| 15 | } // namespace Audren | ||
| 15 | 16 | ||
| 16 | constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8'); | 17 | constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8'); |
| 17 | constexpr std::size_t MAX_MIX_BUFFERS = 24; | 18 | constexpr std::size_t MAX_MIX_BUFFERS = 24; |
| 19 | constexpr std::size_t MAX_BIQUAD_FILTERS = 2; | ||
| 20 | constexpr std::size_t MAX_CHANNEL_COUNT = 6; | ||
| 21 | constexpr std::size_t MAX_WAVE_BUFFERS = 4; | ||
| 22 | constexpr std::size_t MAX_SAMPLE_HISTORY = 4; | ||
| 23 | constexpr u32 STREAM_SAMPLE_RATE = 48000; | ||
| 24 | constexpr u32 STREAM_NUM_CHANNELS = 6; | ||
| 25 | constexpr s32 NO_SPLITTER = -1; | ||
| 26 | constexpr s32 NO_MIX = 0x7fffffff; | ||
| 27 | constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min(); | ||
| 28 | constexpr s32 FINAL_MIX = 0; | ||
| 29 | constexpr s32 NO_EFFECT_ORDER = -1; | ||
| 30 | constexpr 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 | ||
| 34 | constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; | ||
| 18 | 35 | ||
| 19 | static constexpr u32 VersionFromRevision(u32_le rev) { | 36 | static 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 | 65 | struct 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 | }; | ||
| 78 | static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size"); | ||
| 79 | |||
| 80 | struct UpdateDataHeader { | ||
| 81 | u32_le revision{}; | ||
| 82 | UpdateDataSizes size{}; | ||
| 83 | u32_le total_size{}; | ||
| 84 | }; | ||
| 85 | static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size"); | ||
| 86 | |||
| 87 | struct 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 | }; | ||
| 105 | static_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 { | |||
| 23 | public: | 23 | public: |
| 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, ¶ms, &minimum_latency) != CUBEB_OK) { | 46 | if (cubeb_get_min_latency(ctx, ¶ms, &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 | |||
| 8 | namespace AudioCore { | ||
| 9 | namespace { | ||
| 10 | bool ValidChannelCountForEffect(s32 channel_count) { | ||
| 11 | return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6; | ||
| 12 | } | ||
| 13 | } // namespace | ||
| 14 | |||
| 15 | EffectContext::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 | } | ||
| 20 | EffectContext::~EffectContext() = default; | ||
| 21 | |||
| 22 | std::size_t EffectContext::GetCount() const { | ||
| 23 | return effect_count; | ||
| 24 | } | ||
| 25 | |||
| 26 | EffectBase* EffectContext::GetInfo(std::size_t i) { | ||
| 27 | return effects.at(i).get(); | ||
| 28 | } | ||
| 29 | |||
| 30 | EffectBase* 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 | |||
| 60 | const EffectBase* EffectContext::GetInfo(std::size_t i) const { | ||
| 61 | return effects.at(i).get(); | ||
| 62 | } | ||
| 63 | |||
| 64 | EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {} | ||
| 65 | EffectStubbed::~EffectStubbed() = default; | ||
| 66 | |||
| 67 | void EffectStubbed::Update(EffectInfo::InParams& in_params) {} | ||
| 68 | void EffectStubbed::UpdateForCommandGeneration() {} | ||
| 69 | |||
| 70 | EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {} | ||
| 71 | EffectBase::~EffectBase() = default; | ||
| 72 | |||
| 73 | UsageState EffectBase::GetUsage() const { | ||
| 74 | return usage; | ||
| 75 | } | ||
| 76 | |||
| 77 | EffectType EffectBase::GetType() const { | ||
| 78 | return effect_type; | ||
| 79 | } | ||
| 80 | |||
| 81 | bool EffectBase::IsEnabled() const { | ||
| 82 | return enabled; | ||
| 83 | } | ||
| 84 | |||
| 85 | s32 EffectBase::GetMixID() const { | ||
| 86 | return mix_id; | ||
| 87 | } | ||
| 88 | |||
| 89 | s32 EffectBase::GetProcessingOrder() const { | ||
| 90 | return processing_order; | ||
| 91 | } | ||
| 92 | |||
| 93 | EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {} | ||
| 94 | EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; | ||
| 95 | |||
| 96 | void 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 | |||
| 123 | void EffectI3dl2Reverb::UpdateForCommandGeneration() { | ||
| 124 | if (enabled) { | ||
| 125 | usage = UsageState::Running; | ||
| 126 | } else { | ||
| 127 | usage = UsageState::Stopped; | ||
| 128 | } | ||
| 129 | GetParams().status = ParameterStatus::Updated; | ||
| 130 | } | ||
| 131 | |||
| 132 | EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {} | ||
| 133 | EffectBiquadFilter::~EffectBiquadFilter() = default; | ||
| 134 | |||
| 135 | void 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 | |||
| 144 | void EffectBiquadFilter::UpdateForCommandGeneration() { | ||
| 145 | if (enabled) { | ||
| 146 | usage = UsageState::Running; | ||
| 147 | } else { | ||
| 148 | usage = UsageState::Stopped; | ||
| 149 | } | ||
| 150 | GetParams().status = ParameterStatus::Updated; | ||
| 151 | } | ||
| 152 | |||
| 153 | EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {} | ||
| 154 | EffectAuxInfo::~EffectAuxInfo() = default; | ||
| 155 | |||
| 156 | void 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 | |||
| 179 | void EffectAuxInfo::UpdateForCommandGeneration() { | ||
| 180 | if (enabled) { | ||
| 181 | usage = UsageState::Running; | ||
| 182 | } else { | ||
| 183 | usage = UsageState::Stopped; | ||
| 184 | } | ||
| 185 | } | ||
| 186 | |||
| 187 | const VAddr EffectAuxInfo::GetSendInfo() const { | ||
| 188 | return send_info; | ||
| 189 | } | ||
| 190 | |||
| 191 | const VAddr EffectAuxInfo::GetSendBuffer() const { | ||
| 192 | return send_buffer; | ||
| 193 | } | ||
| 194 | |||
| 195 | const VAddr EffectAuxInfo::GetRecvInfo() const { | ||
| 196 | return recv_info; | ||
| 197 | } | ||
| 198 | |||
| 199 | const VAddr EffectAuxInfo::GetRecvBuffer() const { | ||
| 200 | return recv_buffer; | ||
| 201 | } | ||
| 202 | |||
| 203 | EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {} | ||
| 204 | EffectDelay::~EffectDelay() = default; | ||
| 205 | |||
| 206 | void 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 | |||
| 233 | void EffectDelay::UpdateForCommandGeneration() { | ||
| 234 | if (enabled) { | ||
| 235 | usage = UsageState::Running; | ||
| 236 | } else { | ||
| 237 | usage = UsageState::Stopped; | ||
| 238 | } | ||
| 239 | GetParams().status = ParameterStatus::Updated; | ||
| 240 | } | ||
| 241 | |||
| 242 | EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {} | ||
| 243 | EffectBufferMixer::~EffectBufferMixer() = default; | ||
| 244 | |||
| 245 | void 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 | |||
| 252 | void EffectBufferMixer::UpdateForCommandGeneration() { | ||
| 253 | if (enabled) { | ||
| 254 | usage = UsageState::Running; | ||
| 255 | } else { | ||
| 256 | usage = UsageState::Stopped; | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {} | ||
| 261 | EffectReverb::~EffectReverb() = default; | ||
| 262 | |||
| 263 | void 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 | |||
| 290 | void 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 | |||
| 15 | namespace AudioCore { | ||
| 16 | enum 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 | |||
| 26 | enum class UsageStatus : u8 { | ||
| 27 | Invalid = 0, | ||
| 28 | New = 1, | ||
| 29 | Initialized = 2, | ||
| 30 | Used = 3, | ||
| 31 | Removed = 4, | ||
| 32 | }; | ||
| 33 | |||
| 34 | enum class UsageState { | ||
| 35 | Invalid = 0, | ||
| 36 | Initialized = 1, | ||
| 37 | Running = 2, | ||
| 38 | Stopped = 3, | ||
| 39 | }; | ||
| 40 | |||
| 41 | enum class ParameterStatus : u8 { | ||
| 42 | Initialized = 0, | ||
| 43 | Updating = 1, | ||
| 44 | Updated = 2, | ||
| 45 | }; | ||
| 46 | |||
| 47 | struct 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 | }; | ||
| 53 | static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size"); | ||
| 54 | |||
| 55 | struct AuxInfoDSP { | ||
| 56 | u32_le read_offset{}; | ||
| 57 | u32_le write_offset{}; | ||
| 58 | u32_le remaining{}; | ||
| 59 | INSERT_PADDING_WORDS(13); | ||
| 60 | }; | ||
| 61 | static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size"); | ||
| 62 | |||
| 63 | struct 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 | }; | ||
| 76 | static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); | ||
| 77 | |||
| 78 | struct 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 | }; | ||
| 100 | static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size"); | ||
| 101 | |||
| 102 | struct 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 | }; | ||
| 110 | static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size"); | ||
| 111 | |||
| 112 | struct 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 | }; | ||
| 129 | static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size"); | ||
| 130 | |||
| 131 | struct 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 | }; | ||
| 151 | static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size"); | ||
| 152 | |||
| 153 | class EffectInfo { | ||
| 154 | public: | ||
| 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 | |||
| 178 | struct AuxAddress { | ||
| 179 | VAddr send_dsp_info{}; | ||
| 180 | VAddr send_buffer_base{}; | ||
| 181 | VAddr return_dsp_info{}; | ||
| 182 | VAddr return_buffer_base{}; | ||
| 183 | }; | ||
| 184 | |||
| 185 | class EffectBase { | ||
| 186 | public: | ||
| 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 | |||
| 198 | protected: | ||
| 199 | UsageState usage{UsageState::Invalid}; | ||
| 200 | EffectType effect_type{}; | ||
| 201 | s32 mix_id{}; | ||
| 202 | s32 processing_order{}; | ||
| 203 | bool enabled = false; | ||
| 204 | }; | ||
| 205 | |||
| 206 | template <typename T> | ||
| 207 | class EffectGeneric : public EffectBase { | ||
| 208 | public: | ||
| 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 | |||
| 220 | private: | ||
| 221 | T internal_params{}; | ||
| 222 | }; | ||
| 223 | |||
| 224 | class EffectStubbed : public EffectBase { | ||
| 225 | public: | ||
| 226 | explicit EffectStubbed(); | ||
| 227 | ~EffectStubbed(); | ||
| 228 | |||
| 229 | void Update(EffectInfo::InParams& in_params) override; | ||
| 230 | void UpdateForCommandGeneration() override; | ||
| 231 | }; | ||
| 232 | |||
| 233 | class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { | ||
| 234 | public: | ||
| 235 | explicit EffectI3dl2Reverb(); | ||
| 236 | ~EffectI3dl2Reverb(); | ||
| 237 | |||
| 238 | void Update(EffectInfo::InParams& in_params) override; | ||
| 239 | void UpdateForCommandGeneration() override; | ||
| 240 | |||
| 241 | private: | ||
| 242 | bool skipped = false; | ||
| 243 | }; | ||
| 244 | |||
| 245 | class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { | ||
| 246 | public: | ||
| 247 | explicit EffectBiquadFilter(); | ||
| 248 | ~EffectBiquadFilter(); | ||
| 249 | |||
| 250 | void Update(EffectInfo::InParams& in_params) override; | ||
| 251 | void UpdateForCommandGeneration() override; | ||
| 252 | }; | ||
| 253 | |||
| 254 | class EffectAuxInfo : public EffectGeneric<AuxInfo> { | ||
| 255 | public: | ||
| 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 | |||
| 266 | private: | ||
| 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 | |||
| 275 | class EffectDelay : public EffectGeneric<DelayParams> { | ||
| 276 | public: | ||
| 277 | explicit EffectDelay(); | ||
| 278 | ~EffectDelay(); | ||
| 279 | |||
| 280 | void Update(EffectInfo::InParams& in_params) override; | ||
| 281 | void UpdateForCommandGeneration() override; | ||
| 282 | |||
| 283 | private: | ||
| 284 | bool skipped = false; | ||
| 285 | }; | ||
| 286 | |||
| 287 | class EffectBufferMixer : public EffectGeneric<BufferMixerParams> { | ||
| 288 | public: | ||
| 289 | explicit EffectBufferMixer(); | ||
| 290 | ~EffectBufferMixer(); | ||
| 291 | |||
| 292 | void Update(EffectInfo::InParams& in_params) override; | ||
| 293 | void UpdateForCommandGeneration() override; | ||
| 294 | }; | ||
| 295 | |||
| 296 | class EffectReverb : public EffectGeneric<ReverbParams> { | ||
| 297 | public: | ||
| 298 | explicit EffectReverb(); | ||
| 299 | ~EffectReverb(); | ||
| 300 | |||
| 301 | void Update(EffectInfo::InParams& in_params) override; | ||
| 302 | void UpdateForCommandGeneration() override; | ||
| 303 | |||
| 304 | private: | ||
| 305 | bool skipped = false; | ||
| 306 | }; | ||
| 307 | |||
| 308 | class EffectContext { | ||
| 309 | public: | ||
| 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 | |||
| 318 | private: | ||
| 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 | |||
| 15 | namespace AudioCore { | ||
| 16 | |||
| 17 | InfoUpdater::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 | |||
| 26 | InfoUpdater::~InfoUpdater() = default; | ||
| 27 | |||
| 28 | bool 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 | |||
| 66 | bool 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 | |||
| 109 | bool 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 | |||
| 138 | bool 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 | |||
| 222 | bool 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 | |||
| 273 | bool 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 | |||
| 293 | ResultCode 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 | |||
| 414 | bool 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 | |||
| 443 | bool InfoUpdater::UpdatePerformanceBuffer() { | ||
| 444 | output_header.size.performance = 0x10; | ||
| 445 | output_offset += 0x10; | ||
| 446 | return true; | ||
| 447 | } | ||
| 448 | |||
| 449 | bool 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 | |||
| 467 | struct RendererInfo { | ||
| 468 | u64_le elasped_frame_count{}; | ||
| 469 | INSERT_PADDING_WORDS(2); | ||
| 470 | }; | ||
| 471 | static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); | ||
| 472 | |||
| 473 | bool 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 | |||
| 488 | bool 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 | |||
| 501 | bool 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 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | class BehaviorInfo; | ||
| 14 | class ServerMemoryPoolInfo; | ||
| 15 | class VoiceContext; | ||
| 16 | class EffectContext; | ||
| 17 | class MixContext; | ||
| 18 | class SinkContext; | ||
| 19 | class SplitterContext; | ||
| 20 | |||
| 21 | class InfoUpdater { | ||
| 22 | public: | ||
| 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 | |||
| 46 | private: | ||
| 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 | |||
| 9 | namespace AudioCore { | ||
| 10 | |||
| 11 | ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default; | ||
| 12 | ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default; | ||
| 13 | bool 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 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | class ServerMemoryPoolInfo { | ||
| 14 | public: | ||
| 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 | |||
| 45 | private: | ||
| 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 | |||
| 11 | namespace AudioCore { | ||
| 12 | MixContext::MixContext() = default; | ||
| 13 | MixContext::~MixContext() = default; | ||
| 14 | |||
| 15 | void 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 | |||
| 37 | void 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 | |||
| 88 | void 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 | |||
| 101 | void 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 | |||
| 116 | bool 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 | |||
| 140 | std::size_t MixContext::GetCount() const { | ||
| 141 | return info_count; | ||
| 142 | } | ||
| 143 | |||
| 144 | ServerMixInfo& MixContext::GetInfo(std::size_t i) { | ||
| 145 | ASSERT(i < info_count); | ||
| 146 | return infos.at(i); | ||
| 147 | } | ||
| 148 | |||
| 149 | const ServerMixInfo& MixContext::GetInfo(std::size_t i) const { | ||
| 150 | ASSERT(i < info_count); | ||
| 151 | return infos.at(i); | ||
| 152 | } | ||
| 153 | |||
| 154 | ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) { | ||
| 155 | ASSERT(i < info_count); | ||
| 156 | return *sorted_info.at(i); | ||
| 157 | } | ||
| 158 | |||
| 159 | const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const { | ||
| 160 | ASSERT(i < info_count); | ||
| 161 | return *sorted_info.at(i); | ||
| 162 | } | ||
| 163 | |||
| 164 | ServerMixInfo& MixContext::GetFinalMixInfo() { | ||
| 165 | return infos.at(AudioCommon::FINAL_MIX); | ||
| 166 | } | ||
| 167 | |||
| 168 | const ServerMixInfo& MixContext::GetFinalMixInfo() const { | ||
| 169 | return infos.at(AudioCommon::FINAL_MIX); | ||
| 170 | } | ||
| 171 | |||
| 172 | EdgeMatrix& MixContext::GetEdgeMatrix() { | ||
| 173 | return edge_matrix; | ||
| 174 | } | ||
| 175 | |||
| 176 | const EdgeMatrix& MixContext::GetEdgeMatrix() const { | ||
| 177 | return edge_matrix; | ||
| 178 | } | ||
| 179 | |||
| 180 | ServerMixInfo::ServerMixInfo() { | ||
| 181 | Cleanup(); | ||
| 182 | } | ||
| 183 | ServerMixInfo::~ServerMixInfo() = default; | ||
| 184 | |||
| 185 | const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const { | ||
| 186 | return in_params; | ||
| 187 | } | ||
| 188 | |||
| 189 | ServerMixInfo::InParams& ServerMixInfo::GetInParams() { | ||
| 190 | return in_params; | ||
| 191 | } | ||
| 192 | |||
| 193 | bool 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 | |||
| 229 | bool ServerMixInfo::HasAnyConnection() const { | ||
| 230 | return in_params.splitter_id != AudioCommon::NO_SPLITTER || | ||
| 231 | in_params.mix_id != AudioCommon::NO_MIX; | ||
| 232 | } | ||
| 233 | |||
| 234 | void 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 | |||
| 247 | void ServerMixInfo::SetEffectCount(std::size_t count) { | ||
| 248 | effect_processing_order.resize(count); | ||
| 249 | ResetEffectProcessingOrder(); | ||
| 250 | } | ||
| 251 | |||
| 252 | void ServerMixInfo::ResetEffectProcessingOrder() { | ||
| 253 | for (auto& order : effect_processing_order) { | ||
| 254 | order = AudioCommon::NO_EFFECT_ORDER; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | s32 ServerMixInfo::GetEffectOrder(std::size_t i) const { | ||
| 259 | return effect_processing_order.at(i); | ||
| 260 | } | ||
| 261 | |||
| 262 | bool 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 | |||
| 14 | namespace AudioCore { | ||
| 15 | class BehaviorInfo; | ||
| 16 | class EffectContext; | ||
| 17 | |||
| 18 | class MixInfo { | ||
| 19 | public: | ||
| 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 | |||
| 46 | class ServerMixInfo { | ||
| 47 | public: | ||
| 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 | |||
| 77 | private: | ||
| 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 | |||
| 84 | class MixContext { | ||
| 85 | public: | ||
| 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 | |||
| 104 | private: | ||
| 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 | |||
| 7 | namespace AudioCore { | ||
| 8 | SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {} | ||
| 9 | SinkContext::~SinkContext() = default; | ||
| 10 | |||
| 11 | std::size_t SinkContext::GetCount() const { | ||
| 12 | return sink_count; | ||
| 13 | } | ||
| 14 | |||
| 15 | void 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 | |||
| 21 | bool SinkContext::InUse() const { | ||
| 22 | return in_use; | ||
| 23 | } | ||
| 24 | |||
| 25 | std::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 | |||
| 12 | namespace AudioCore { | ||
| 13 | |||
| 14 | enum class SinkTypes : u8 { | ||
| 15 | Invalid = 0, | ||
| 16 | Device = 1, | ||
| 17 | Circular = 2, | ||
| 18 | }; | ||
| 19 | |||
| 20 | enum 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 | |||
| 30 | class SinkInfo { | ||
| 31 | public: | ||
| 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 | |||
| 72 | class SinkContext { | ||
| 73 | public: | ||
| 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 | |||
| 83 | private: | ||
| 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 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {} | ||
| 14 | ServerSplitterDestinationData::~ServerSplitterDestinationData() = default; | ||
| 15 | |||
| 16 | void 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 | |||
| 40 | ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() { | ||
| 41 | return next; | ||
| 42 | } | ||
| 43 | |||
| 44 | const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const { | ||
| 45 | return next; | ||
| 46 | } | ||
| 47 | |||
| 48 | void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) { | ||
| 49 | next = dest; | ||
| 50 | } | ||
| 51 | |||
| 52 | bool ServerSplitterDestinationData::ValidMixId() const { | ||
| 53 | return GetMixId() != AudioCommon::NO_MIX; | ||
| 54 | } | ||
| 55 | |||
| 56 | s32 ServerSplitterDestinationData::GetMixId() const { | ||
| 57 | return mix_id; | ||
| 58 | } | ||
| 59 | |||
| 60 | bool ServerSplitterDestinationData::IsConfigured() const { | ||
| 61 | return in_use && ValidMixId(); | ||
| 62 | } | ||
| 63 | |||
| 64 | float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const { | ||
| 65 | ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); | ||
| 66 | return current_mix_volumes.at(i); | ||
| 67 | } | ||
| 68 | |||
| 69 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 70 | ServerSplitterDestinationData::CurrentMixVolumes() const { | ||
| 71 | return current_mix_volumes; | ||
| 72 | } | ||
| 73 | |||
| 74 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 75 | ServerSplitterDestinationData::LastMixVolumes() const { | ||
| 76 | return last_mix_volumes; | ||
| 77 | } | ||
| 78 | |||
| 79 | void ServerSplitterDestinationData::MarkDirty() { | ||
| 80 | needs_update = true; | ||
| 81 | } | ||
| 82 | |||
| 83 | void 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 | |||
| 90 | ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {} | ||
| 91 | ServerSplitterInfo::~ServerSplitterInfo() = default; | ||
| 92 | |||
| 93 | void ServerSplitterInfo::InitializeInfos() { | ||
| 94 | send_length = 0; | ||
| 95 | head = nullptr; | ||
| 96 | new_connection = true; | ||
| 97 | } | ||
| 98 | |||
| 99 | void ServerSplitterInfo::ClearNewConnectionFlag() { | ||
| 100 | new_connection = false; | ||
| 101 | } | ||
| 102 | |||
| 103 | std::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 | |||
| 115 | ServerSplitterDestinationData* ServerSplitterInfo::GetHead() { | ||
| 116 | return head; | ||
| 117 | } | ||
| 118 | |||
| 119 | const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const { | ||
| 120 | return head; | ||
| 121 | } | ||
| 122 | |||
| 123 | ServerSplitterDestinationData* 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 | |||
| 134 | const 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 | |||
| 145 | bool ServerSplitterInfo::HasNewConnection() const { | ||
| 146 | return new_connection; | ||
| 147 | } | ||
| 148 | |||
| 149 | s32 ServerSplitterInfo::GetLength() const { | ||
| 150 | return send_length; | ||
| 151 | } | ||
| 152 | |||
| 153 | void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) { | ||
| 154 | head = new_head; | ||
| 155 | } | ||
| 156 | |||
| 157 | void ServerSplitterInfo::SetHeadDepth(s32 length) { | ||
| 158 | send_length = length; | ||
| 159 | } | ||
| 160 | |||
| 161 | SplitterContext::SplitterContext() = default; | ||
| 162 | SplitterContext::~SplitterContext() = default; | ||
| 163 | |||
| 164 | void 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 | |||
| 174 | bool 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 | |||
| 214 | bool SplitterContext::UsingSplitter() const { | ||
| 215 | return info_count > 0 && data_count > 0; | ||
| 216 | } | ||
| 217 | |||
| 218 | ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) { | ||
| 219 | ASSERT(i < info_count); | ||
| 220 | return infos.at(i); | ||
| 221 | } | ||
| 222 | |||
| 223 | const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const { | ||
| 224 | ASSERT(i < info_count); | ||
| 225 | return infos.at(i); | ||
| 226 | } | ||
| 227 | |||
| 228 | ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) { | ||
| 229 | ASSERT(i < data_count); | ||
| 230 | return datas.at(i); | ||
| 231 | } | ||
| 232 | |||
| 233 | const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const { | ||
| 234 | ASSERT(i < data_count); | ||
| 235 | return datas.at(i); | ||
| 236 | } | ||
| 237 | |||
| 238 | ServerSplitterDestinationData* 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 | |||
| 245 | const 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 | |||
| 252 | void SplitterContext::UpdateInternalState() { | ||
| 253 | if (data_count == 0) { | ||
| 254 | return; | ||
| 255 | } | ||
| 256 | |||
| 257 | for (auto& data : datas) { | ||
| 258 | data.UpdateInternalState(); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | std::size_t SplitterContext::GetInfoCount() const { | ||
| 263 | return info_count; | ||
| 264 | } | ||
| 265 | |||
| 266 | std::size_t SplitterContext::GetDataCount() const { | ||
| 267 | return data_count; | ||
| 268 | } | ||
| 269 | |||
| 270 | void 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 | |||
| 287 | bool 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 | |||
| 327 | bool 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 | |||
| 360 | bool 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 | |||
| 412 | NodeStates::NodeStates() = default; | ||
| 413 | NodeStates::~NodeStates() = default; | ||
| 414 | |||
| 415 | void 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 | |||
| 424 | bool NodeStates::Tsort(EdgeMatrix& edge_matrix) { | ||
| 425 | return DepthFirstSearch(edge_matrix); | ||
| 426 | } | ||
| 427 | |||
| 428 | std::size_t NodeStates::GetIndexPos() const { | ||
| 429 | return index_pos; | ||
| 430 | } | ||
| 431 | |||
| 432 | const std::vector<s32>& NodeStates::GetIndexList() const { | ||
| 433 | return index_list; | ||
| 434 | } | ||
| 435 | |||
| 436 | void NodeStates::PushTsortResult(s32 index) { | ||
| 437 | ASSERT(index < node_count); | ||
| 438 | index_list[index_pos++] = index; | ||
| 439 | } | ||
| 440 | |||
| 441 | bool 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 | |||
| 497 | void 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 | |||
| 510 | void 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 | |||
| 527 | NodeStates::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 | |||
| 541 | NodeStates::Stack::Stack() = default; | ||
| 542 | NodeStates::Stack::~Stack() = default; | ||
| 543 | |||
| 544 | void 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 | |||
| 552 | void NodeStates::Stack::push(s32 val) { | ||
| 553 | ASSERT(stack_pos < stack_size); | ||
| 554 | stack[stack_pos++] = val; | ||
| 555 | } | ||
| 556 | |||
| 557 | std::size_t NodeStates::Stack::Count() const { | ||
| 558 | return stack_pos; | ||
| 559 | } | ||
| 560 | |||
| 561 | s32 NodeStates::Stack::top() const { | ||
| 562 | ASSERT(stack_pos > 0); | ||
| 563 | return stack[stack_pos - 1]; | ||
| 564 | } | ||
| 565 | |||
| 566 | s32 NodeStates::Stack::pop() { | ||
| 567 | ASSERT(stack_pos > 0); | ||
| 568 | stack_pos--; | ||
| 569 | return stack[stack_pos]; | ||
| 570 | } | ||
| 571 | |||
| 572 | EdgeMatrix::EdgeMatrix() = default; | ||
| 573 | EdgeMatrix::~EdgeMatrix() = default; | ||
| 574 | |||
| 575 | void EdgeMatrix::Initialize(std::size_t _node_count) { | ||
| 576 | node_count = _node_count; | ||
| 577 | edge_matrix.resize(node_count * node_count); | ||
| 578 | } | ||
| 579 | |||
| 580 | bool EdgeMatrix::Connected(s32 a, s32 b) { | ||
| 581 | return GetState(a, b); | ||
| 582 | } | ||
| 583 | |||
| 584 | void EdgeMatrix::Connect(s32 a, s32 b) { | ||
| 585 | SetState(a, b, true); | ||
| 586 | } | ||
| 587 | |||
| 588 | void EdgeMatrix::Disconnect(s32 a, s32 b) { | ||
| 589 | SetState(a, b, false); | ||
| 590 | } | ||
| 591 | |||
| 592 | void 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 | |||
| 598 | std::size_t EdgeMatrix::GetNodeCount() const { | ||
| 599 | return node_count; | ||
| 600 | } | ||
| 601 | |||
| 602 | void EdgeMatrix::SetState(s32 a, s32 b, bool state) { | ||
| 603 | ASSERT(InRange(a, b)); | ||
| 604 | edge_matrix.at(a * node_count + b) = state; | ||
| 605 | } | ||
| 606 | |||
| 607 | bool EdgeMatrix::GetState(s32 a, s32 b) { | ||
| 608 | ASSERT(InRange(a, b)); | ||
| 609 | return edge_matrix.at(a * node_count + b); | ||
| 610 | } | ||
| 611 | |||
| 612 | bool 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 | |||
| 14 | namespace AudioCore { | ||
| 15 | class BehaviorInfo; | ||
| 16 | |||
| 17 | class EdgeMatrix { | ||
| 18 | public: | ||
| 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 | |||
| 29 | private: | ||
| 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 | |||
| 38 | class NodeStates { | ||
| 39 | public: | ||
| 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 | |||
| 71 | private: | ||
| 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 | |||
| 86 | enum 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 | |||
| 92 | class SplitterInfo { | ||
| 93 | public: | ||
| 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 | |||
| 125 | class ServerSplitterDestinationData { | ||
| 126 | public: | ||
| 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 | |||
| 144 | private: | ||
| 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 | |||
| 154 | class ServerSplitterInfo { | ||
| 155 | public: | ||
| 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 | |||
| 174 | private: | ||
| 175 | s32 sample_rate{}; | ||
| 176 | s32 id{}; | ||
| 177 | s32 send_length{}; | ||
| 178 | ServerSplitterDestinationData* head = nullptr; | ||
| 179 | bool new_connection{}; | ||
| 180 | }; | ||
| 181 | |||
| 182 | class SplitterContext { | ||
| 183 | public: | ||
| 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 | |||
| 205 | private: | ||
| 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 | ||
| 114 | void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) { | 110 | void 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 | |||
| 9 | namespace AudioCore { | ||
| 10 | |||
| 11 | ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {} | ||
| 12 | ServerVoiceChannelResource::~ServerVoiceChannelResource() = default; | ||
| 13 | |||
| 14 | bool ServerVoiceChannelResource::InUse() const { | ||
| 15 | return in_use; | ||
| 16 | } | ||
| 17 | |||
| 18 | float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const { | ||
| 19 | ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); | ||
| 20 | return mix_volume.at(i); | ||
| 21 | } | ||
| 22 | |||
| 23 | float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const { | ||
| 24 | ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); | ||
| 25 | return last_mix_volume.at(i); | ||
| 26 | } | ||
| 27 | |||
| 28 | void 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 | |||
| 36 | void ServerVoiceChannelResource::UpdateLastMixVolumes() { | ||
| 37 | last_mix_volume = mix_volume; | ||
| 38 | } | ||
| 39 | |||
| 40 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 41 | ServerVoiceChannelResource::GetCurrentMixVolume() const { | ||
| 42 | return mix_volume; | ||
| 43 | } | ||
| 44 | |||
| 45 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 46 | ServerVoiceChannelResource::GetLastMixVolume() const { | ||
| 47 | return last_mix_volume; | ||
| 48 | } | ||
| 49 | |||
| 50 | ServerVoiceInfo::ServerVoiceInfo() { | ||
| 51 | Initialize(); | ||
| 52 | } | ||
| 53 | ServerVoiceInfo::~ServerVoiceInfo() = default; | ||
| 54 | |||
| 55 | void 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 | |||
| 97 | void 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 | |||
| 170 | void 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 | |||
| 207 | void 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 | |||
| 245 | void 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 | |||
| 264 | const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const { | ||
| 265 | return in_params; | ||
| 266 | } | ||
| 267 | |||
| 268 | ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() { | ||
| 269 | return in_params; | ||
| 270 | } | ||
| 271 | |||
| 272 | const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const { | ||
| 273 | return out_params; | ||
| 274 | } | ||
| 275 | |||
| 276 | ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() { | ||
| 277 | return out_params; | ||
| 278 | } | ||
| 279 | |||
| 280 | bool 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 | |||
| 285 | bool 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 | |||
| 302 | void 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 | |||
| 314 | bool 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 | |||
| 377 | void 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 | |||
| 395 | bool 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 | |||
| 400 | VoiceContext::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 | |||
| 409 | VoiceContext::~VoiceContext() { | ||
| 410 | sorted_voice_info.clear(); | ||
| 411 | } | ||
| 412 | |||
| 413 | std::size_t VoiceContext::GetVoiceCount() const { | ||
| 414 | return voice_count; | ||
| 415 | } | ||
| 416 | |||
| 417 | ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) { | ||
| 418 | ASSERT(i < voice_count); | ||
| 419 | return voice_channel_resources.at(i); | ||
| 420 | } | ||
| 421 | |||
| 422 | const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const { | ||
| 423 | ASSERT(i < voice_count); | ||
| 424 | return voice_channel_resources.at(i); | ||
| 425 | } | ||
| 426 | |||
| 427 | VoiceState& VoiceContext::GetState(std::size_t i) { | ||
| 428 | ASSERT(i < voice_count); | ||
| 429 | return voice_states.at(i); | ||
| 430 | } | ||
| 431 | |||
| 432 | const VoiceState& VoiceContext::GetState(std::size_t i) const { | ||
| 433 | ASSERT(i < voice_count); | ||
| 434 | return voice_states.at(i); | ||
| 435 | } | ||
| 436 | |||
| 437 | VoiceState& VoiceContext::GetDspSharedState(std::size_t i) { | ||
| 438 | ASSERT(i < voice_count); | ||
| 439 | return dsp_voice_states.at(i); | ||
| 440 | } | ||
| 441 | |||
| 442 | const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const { | ||
| 443 | ASSERT(i < voice_count); | ||
| 444 | return dsp_voice_states.at(i); | ||
| 445 | } | ||
| 446 | |||
| 447 | ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) { | ||
| 448 | ASSERT(i < voice_count); | ||
| 449 | return voice_info.at(i); | ||
| 450 | } | ||
| 451 | |||
| 452 | const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const { | ||
| 453 | ASSERT(i < voice_count); | ||
| 454 | return voice_info.at(i); | ||
| 455 | } | ||
| 456 | |||
| 457 | ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) { | ||
| 458 | ASSERT(i < voice_count); | ||
| 459 | return *sorted_voice_info.at(i); | ||
| 460 | } | ||
| 461 | |||
| 462 | const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const { | ||
| 463 | ASSERT(i < voice_count); | ||
| 464 | return *sorted_voice_info.at(i); | ||
| 465 | } | ||
| 466 | |||
| 467 | s32 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 | |||
| 503 | void 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 | |||
| 522 | void 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 | |||
| 15 | namespace Core::Memory { | ||
| 16 | class Memory; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace AudioCore { | ||
| 20 | |||
| 21 | class BehaviorInfo; | ||
| 22 | class VoiceContext; | ||
| 23 | |||
| 24 | enum 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 | |||
| 34 | enum class PlayState : u8 { | ||
| 35 | Started = 0, | ||
| 36 | Stopped = 1, | ||
| 37 | Paused = 2, | ||
| 38 | }; | ||
| 39 | |||
| 40 | enum class ServerPlayState { | ||
| 41 | Play = 0, | ||
| 42 | Stop = 1, | ||
| 43 | RequestStop = 2, | ||
| 44 | Paused = 3, | ||
| 45 | }; | ||
| 46 | |||
| 47 | struct BiquadFilterParameter { | ||
| 48 | bool enabled{}; | ||
| 49 | INSERT_PADDING_BYTES(1); | ||
| 50 | std::array<s16, 3> numerator{}; | ||
| 51 | std::array<s16, 2> denominator{}; | ||
| 52 | }; | ||
| 53 | static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size"); | ||
| 54 | |||
| 55 | struct 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 | }; | ||
| 68 | static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); | ||
| 69 | |||
| 70 | struct 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 | |||
| 82 | struct BehaviorFlags { | ||
| 83 | BitField<0, 1, u16> is_played_samples_reset_at_loop_point; | ||
| 84 | BitField<1, 1, u16> is_pitch_and_src_skipped; | ||
| 85 | }; | ||
| 86 | static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size"); | ||
| 87 | |||
| 88 | struct ADPCMContext { | ||
| 89 | u16 header{}; | ||
| 90 | s16 yn1{}; | ||
| 91 | s16 yn2{}; | ||
| 92 | }; | ||
| 93 | static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size"); | ||
| 94 | |||
| 95 | struct 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 | |||
| 113 | class VoiceChannelResource { | ||
| 114 | public: | ||
| 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 | |||
| 124 | class ServerVoiceChannelResource { | ||
| 125 | public: | ||
| 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 | |||
| 138 | private: | ||
| 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 | |||
| 145 | class VoiceInfo { | ||
| 146 | public: | ||
| 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 | |||
| 188 | class ServerVoiceInfo { | ||
| 189 | public: | ||
| 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 | |||
| 256 | private: | ||
| 257 | std::vector<s16> stored_samples; | ||
| 258 | InParams in_params{}; | ||
| 259 | OutParams out_params{}; | ||
| 260 | |||
| 261 | bool HasValidWaveBuffer(const VoiceState* state) const; | ||
| 262 | }; | ||
| 263 | |||
| 264 | class VoiceContext { | ||
| 265 | public: | ||
| 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 | |||
| 287 | private: | ||
| 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 | ||
| 632 | void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { | 632 | void System::SetFilesystem(FileSys::VirtualFilesystem vfs) { |
| 633 | impl->virtual_filesystem = std::move(vfs); | 633 | impl->virtual_filesystem = std::move(vfs); |
| 634 | } | 634 | } |
| 635 | 635 | ||
| 636 | std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { | 636 | FileSys::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 | ||
| 1025 | void KeyManager::DeriveETicket(PartitionDataManager& data) { | 1024 | void 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 { | |||
| 20 | class IOFile; | 20 | class IOFile; |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | namespace FileSys { | ||
| 24 | class ContentProvider; | ||
| 25 | } | ||
| 26 | |||
| 23 | namespace Loader { | 27 | namespace Loader { |
| 24 | enum class ResultStatus : u16; | 28 | enum 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 | ||
| 12 | namespace FileSys { | 12 | namespace FileSys { |
| 13 | 13 | ||
| @@ -81,11 +81,11 @@ VirtualDir BISFactory::OpenPartition(BisPartitionId id) const { | |||
| 81 | } | 81 | } |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | VirtualFile BISFactory::OpenPartitionStorage(BisPartitionId id) const { | 84 | VirtualFile 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 | ||
| 14 | namespace FileSys { | 14 | namespace FileSys { |
| 15 | namespace { | ||
| 16 | template <bool Subsection, typename BlockType, typename BucketType> | ||
| 17 | std::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 | ||
| 16 | BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, | 59 | BKTR::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 | ||
| 113 | template <bool Subsection, typename BlockType, typename BucketType> | ||
| 114 | std::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 | |||
| 153 | RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { | 156 | RelocationEntry 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 | ||
| 119 | private: | 119 | private: |
| 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 | ||
| 29 | namespace FileSys { | 29 | namespace FileSys { |
| 30 | namespace { | ||
| 30 | 31 | ||
| 31 | constexpr u64 SINGLE_BYTE_MODULUS = 0x100; | 32 | constexpr u64 SINGLE_BYTE_MODULUS = 0x100; |
| 32 | constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; | 33 | constexpr 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 | ||
| 39 | std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { | 40 | enum class TitleVersionFormat : u8 { |
| 41 | ThreeElements, ///< vX.Y.Z | ||
| 42 | FourElements, ///< vX.Y.Z.W | ||
| 43 | }; | ||
| 44 | |||
| 45 | std::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. | ||
| 52 | VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) { | 62 | VirtualDir 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 | ||
| 78 | std::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 | |||
| 101 | void 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 | |||
| 110 | bool IsDirValidAndNonEmpty(const VirtualDir& dir) { | ||
| 111 | return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty()); | ||
| 112 | } | ||
| 113 | } // Anonymous namespace | ||
| 114 | |||
| 68 | PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} | 115 | PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} |
| 69 | 116 | ||
| 70 | PatchManager::~PatchManager() = default; | 117 | PatchManager::~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 | ||
| 248 | bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { | 295 | bool 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 | ||
| 268 | namespace { | ||
| 269 | std::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 | |||
| 296 | std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList( | 315 | std::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 | ||
| 438 | static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) { | 455 | PatchManager::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 | |||
| 445 | static bool IsDirValidAndNonEmpty(const VirtualDir& dir) { | ||
| 446 | return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty()); | ||
| 447 | } | ||
| 448 | |||
| 449 | std::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 | ||
| 565 | std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { | 571 | PatchManager::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 | ||
| 575 | std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const { | 582 | PatchManager::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 { | |||
| 22 | class NCA; | 22 | class NCA; |
| 23 | class NACP; | 23 | class NACP; |
| 24 | 24 | ||
| 25 | enum class TitleVersionFormat : u8 { | ||
| 26 | ThreeElements, ///< vX.Y.Z | ||
| 27 | FourElements, ///< vX.Y.Z.W | ||
| 28 | }; | ||
| 29 | |||
| 30 | std::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. | ||
| 35 | VirtualDir 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. |
| 38 | class PatchManager { | 26 | class PatchManager { |
| 39 | public: | 27 | public: |
| 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 | ||
| 86 | private: | 78 | private: |
| 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 | ||
| 20 | namespace FileSys { | 19 | namespace FileSys { |
| 21 | 20 | ||
| 22 | RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) { | 21 | RomFSFactory::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 | ||
| 47 | ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, | 48 | ResultVal<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 | ||
| 65 | std::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 { | |||
| 13 | class AppLoader; | 13 | class AppLoader; |
| 14 | } // namespace Loader | 14 | } // namespace Loader |
| 15 | 15 | ||
| 16 | namespace Service::FileSystem { | ||
| 17 | class FileSystemController; | ||
| 18 | } | ||
| 19 | |||
| 16 | namespace FileSys { | 20 | namespace FileSys { |
| 17 | 21 | ||
| 22 | class ContentProvider; | ||
| 23 | class NCA; | ||
| 24 | |||
| 18 | enum class ContentRecordType : u8; | 25 | enum class ContentRecordType : u8; |
| 19 | 26 | ||
| 20 | enum class StorageId : u8 { | 27 | enum 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 |
| 30 | class RomFSFactory { | 37 | class RomFSFactory { |
| 31 | public: | 38 | public: |
| 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 | ||
| 39 | private: | 48 | private: |
| 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 | |||
| 13 | namespace Core::Frontend { | ||
| 14 | |||
| 15 | ControllerApplet::~ControllerApplet() = default; | ||
| 16 | |||
| 17 | DefaultControllerApplet::~DefaultControllerApplet() = default; | ||
| 18 | |||
| 19 | void 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 | |||
| 11 | namespace Core::Frontend { | ||
| 12 | |||
| 13 | using BorderColor = std::array<u8, 4>; | ||
| 14 | using ExplainText = std::array<char, 0x81>; | ||
| 15 | |||
| 16 | struct 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 | |||
| 32 | class ControllerApplet { | ||
| 33 | public: | ||
| 34 | virtual ~ControllerApplet(); | ||
| 35 | |||
| 36 | virtual void ReconfigureControllers(std::function<void()> callback, | ||
| 37 | ControllerParameters parameters) const = 0; | ||
| 38 | }; | ||
| 39 | |||
| 40 | class DefaultControllerApplet final : public ControllerApplet { | ||
| 41 | public: | ||
| 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>; | |||
| 119 | using AnalogDevice = InputDevice<std::tuple<float, float>>; | 119 | using 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 | */ | ||
| 145 | using 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 | */ |
| 137 | using MotionDevice = InputDevice<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>; | 151 | using 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 | ||
| 50 | ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread, | 50 | ResultCode 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 { | |||
| 16 | class Memory; | 16 | class Memory; |
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | namespace Core::Timing { | ||
| 20 | class CoreTiming; | ||
| 21 | } | ||
| 22 | |||
| 19 | namespace Kernel { | 23 | namespace Kernel { |
| 20 | 24 | ||
| 21 | class KernelCore; | 25 | class 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 | ||
| 187 | ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread, | 186 | ResultCode 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 | ||
| 20 | namespace Core::Timing { | 20 | namespace Core::Timing { |
| 21 | class CoreTiming; | ||
| 21 | struct EventType; | 22 | struct EventType; |
| 22 | } | 23 | } // namespace Core::Timing |
| 23 | 24 | ||
| 24 | namespace Kernel { | 25 | namespace 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 | ||
| 1557 | void 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 | |||
| 1557 | void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) { | 1565 | void 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 | ||
| 141 | AppletFrontendSet::AppletFrontendSet() = default; | 143 | AppletFrontendSet::AppletFrontendSet() = default; |
| 142 | 144 | ||
| 143 | AppletFrontendSet::AppletFrontendSet(ParentalControlsApplet parental_controls, ErrorApplet error, | 145 | AppletFrontendSet::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 | ||
| 152 | AppletFrontendSet::~AppletFrontendSet() = default; | 154 | AppletFrontendSet::~AppletFrontendSet() = default; |
| 153 | 155 | ||
| @@ -164,20 +166,37 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const { | |||
| 164 | } | 166 | } |
| 165 | 167 | ||
| 166 | void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { | 168 | void 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 | ||
| 183 | void AppletManager::SetDefaultAppletFrontendSet() { | 202 | void AppletManager::SetDefaultAppletFrontendSet() { |
| @@ -186,15 +205,23 @@ void AppletManager::SetDefaultAppletFrontendSet() { | |||
| 186 | } | 205 | } |
| 187 | 206 | ||
| 188 | void AppletManager::SetDefaultAppletsIfMissing() { | 207 | void 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 | ||
| 220 | void AppletManager::ClearAll() { | 243 | void 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 | ||
| 19 | namespace Core::Frontend { | 19 | namespace Core::Frontend { |
| 20 | class ControllerApplet; | ||
| 20 | class ECommerceApplet; | 21 | class ECommerceApplet; |
| 21 | class ErrorApplet; | 22 | class ErrorApplet; |
| 22 | class ParentalControlsApplet; | 23 | class ParentalControlsApplet; |
| @@ -155,19 +156,20 @@ protected: | |||
| 155 | }; | 156 | }; |
| 156 | 157 | ||
| 157 | struct AppletFrontendSet { | 158 | struct 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 | ||
| 188 | class AppletManager { | 191 | class 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 | |||
| 18 | namespace 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 | |||
| 25 | static 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 | |||
| 48 | Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_) | ||
| 49 | : Applet{system_.Kernel()}, frontend(frontend_) {} | ||
| 50 | |||
| 51 | Controller::~Controller() = default; | ||
| 52 | |||
| 53 | void 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 | |||
| 113 | bool Controller::TransactionComplete() const { | ||
| 114 | return complete; | ||
| 115 | } | ||
| 116 | |||
| 117 | ResultCode Controller::GetStatus() const { | ||
| 118 | return status; | ||
| 119 | } | ||
| 120 | |||
| 121 | void Controller::ExecuteInteractive() { | ||
| 122 | UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet."); | ||
| 123 | } | ||
| 124 | |||
| 125 | void 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 | |||
| 180 | void 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 | |||
| 15 | namespace Core { | ||
| 16 | class System; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace Service::AM::Applets { | ||
| 20 | |||
| 21 | using IdentificationColor = std::array<u8, 4>; | ||
| 22 | using ExplainText = std::array<char, 0x81>; | ||
| 23 | |||
| 24 | enum 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 | |||
| 31 | enum class ControllerSupportMode : u8 { | ||
| 32 | ShowControllerSupport = 0, | ||
| 33 | ShowControllerStrapGuide = 1, | ||
| 34 | ShowControllerFirmwareUpdate = 2, | ||
| 35 | }; | ||
| 36 | |||
| 37 | enum class ControllerSupportCaller : u8 { | ||
| 38 | Application = 0, | ||
| 39 | System = 1, | ||
| 40 | }; | ||
| 41 | |||
| 42 | struct 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 | }; | ||
| 52 | static_assert(sizeof(ControllerSupportArgPrivate) == 0x14, | ||
| 53 | "ControllerSupportArgPrivate has incorrect size."); | ||
| 54 | |||
| 55 | struct 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 | }; | ||
| 64 | static_assert(sizeof(ControllerSupportArgHeader) == 0x7, | ||
| 65 | "ControllerSupportArgHeader has incorrect size."); | ||
| 66 | |||
| 67 | // LibraryAppletVersion 0x3, 0x4, 0x5 | ||
| 68 | struct ControllerSupportArgOld { | ||
| 69 | ControllerSupportArgHeader header{}; | ||
| 70 | std::array<IdentificationColor, 4> identification_colors{}; | ||
| 71 | bool enable_explain_text{}; | ||
| 72 | std::array<ExplainText, 4> explain_text{}; | ||
| 73 | }; | ||
| 74 | static_assert(sizeof(ControllerSupportArgOld) == 0x21C, | ||
| 75 | "ControllerSupportArgOld has incorrect size."); | ||
| 76 | |||
| 77 | // LibraryAppletVersion 0x7 | ||
| 78 | struct ControllerSupportArgNew { | ||
| 79 | ControllerSupportArgHeader header{}; | ||
| 80 | std::array<IdentificationColor, 8> identification_colors{}; | ||
| 81 | bool enable_explain_text{}; | ||
| 82 | std::array<ExplainText, 8> explain_text{}; | ||
| 83 | }; | ||
| 84 | static_assert(sizeof(ControllerSupportArgNew) == 0x430, | ||
| 85 | "ControllerSupportArgNew has incorrect size."); | ||
| 86 | |||
| 87 | struct ControllerSupportResultInfo { | ||
| 88 | s8 player_count{}; | ||
| 89 | INSERT_PADDING_BYTES(3); | ||
| 90 | u32 selected_id{}; | ||
| 91 | u32 result{}; | ||
| 92 | }; | ||
| 93 | static_assert(sizeof(ControllerSupportResultInfo) == 0xC, | ||
| 94 | "ControllerSupportResultInfo has incorrect size."); | ||
| 95 | |||
| 96 | class Controller final : public Applet { | ||
| 97 | public: | ||
| 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 | |||
| 110 | private: | ||
| 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 | ||
| 27 | class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { | 27 | class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { |
| 28 | public: | 28 | public: |
| 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 | ||
| 349 | static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) { | 350 | static 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 | ||
| 682 | void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { | 687 | void 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 | ||
| 199 | void Controller_NPad::OnInit() { | 200 | void 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 | ||
| 620 | Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { | ||
| 621 | return last_processed_vibration; | ||
| 622 | } | ||
| 623 | |||
| 521 | std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const { | 624 | std::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 | ||
| 526 | Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { | 629 | void 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 | ||
| 530 | void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) { | 633 | void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::size_t npad_index) { |
| @@ -534,7 +637,7 @@ void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::siz | |||
| 534 | void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, | 637 | void 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 | ||
| 556 | void Controller_NPad::DisconnectNPad(u32 npad_id) { | 659 | void 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 | |||
| 663 | void 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 | ||
| 569 | void Controller_NPad::SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode) { | 675 | void 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 | ||
| 683 | bool Controller_NPad::IsSixAxisSensorAtRest() const { | ||
| 684 | return sixaxis_at_rest; | ||
| 685 | } | ||
| 686 | |||
| 687 | void Controller_NPad::SetSixAxisEnabled(bool six_axis_status) { | ||
| 688 | sixaxis_sensors_enabled = six_axis_status; | ||
| 689 | } | ||
| 690 | |||
| 577 | void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) { | 691 | void 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 | ||
| 668 | void Controller_NPad::DisconnectAllConnectedControllers() { | 782 | void 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 | ||
| 674 | void Controller_NPad::ConnectAllDisconnectedControllers() { | 788 | void 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 | ||
| 682 | void Controller_NPad::ClearAllControllers() { | 796 | void 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 | ||
| 731 | Controller_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 | ||
| 332 | void 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 | |||
| 344 | void 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 | |||
| 332 | void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { | 357 | void 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 | ||
| 496 | void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { | 521 | void 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 | ||
| 73 | namespace Service { | 73 | namespace 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 | |||
| 94 | ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions, | 75 | ServiceFrameworkBase::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 | ||
| 108 | void ServiceFrameworkBase::InstallAsNamedPort() { | 89 | void 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 | ||
| 119 | std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { | 99 | std::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 |
| 198 | void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) { | 174 | void 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); | |||
| 19 | constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6); | 19 | constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6); |
| 20 | constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7); | 20 | constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7); |
| 21 | 21 | ||
| 22 | ServiceManager::ServiceManager() = default; | 22 | ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} {} |
| 23 | ServiceManager::~ServiceManager() = default; | 23 | ServiceManager::~ServiceManager() = default; |
| 24 | 24 | ||
| 25 | void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { | 25 | void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { |
| @@ -27,11 +27,11 @@ void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { | |||
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | static ResultCode ValidateServiceName(const std::string& name) { | 29 | static 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 | ||
| 51 | ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService( | 51 | ResultVal<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 { | |||
| 48 | public: | 48 | public: |
| 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 | */ |
| 34 | template <class Service, class... Types> | 34 | template <class Service, class... Types> |
| 35 | class BlockingWorker { | 35 | class 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 | ||
| 797 | bool BSD::IsFileDescriptorValid(s32 fd) const noexcept { | 798 | bool 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 { | |||
| 809 | bool BSD::IsBlockingSocket(s32 fd) const noexcept { | 810 | bool 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) { | |||
| 131 | Network::SockAddrIn Translate(SockAddrIn value) { | 131 | Network::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 | ||
| 141 | SockAddrIn Translate(Network::SockAddrIn value) { | 141 | SockAddrIn 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 | ||
| 151 | Network::ShutdownHow Translate(ShutdownHow how) { | 151 | Network::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 | ||
| 91 | AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirectory::Load( | 91 | AppLoader_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 | ||
| 12 | namespace Core { | ||
| 13 | class System; | ||
| 14 | } | ||
| 15 | |||
| 12 | namespace Loader { | 16 | namespace 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 | ||
| 386 | AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process) { | 386 | AppLoader_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 | ||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 11 | namespace Loader { | 15 | namespace 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 | ||
| 46 | AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) { | 46 | AppLoader::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 | ||
| 9 | namespace Core { | ||
| 10 | class System; | ||
| 11 | } | ||
| 12 | |||
| 9 | namespace FileSys { | 13 | namespace FileSys { |
| 10 | class KIP; | 14 | class 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 | ||
| 31 | private: | 35 | private: |
| 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 | ||
| 18 | namespace Core { | ||
| 19 | class System; | ||
| 20 | } | ||
| 21 | |||
| 18 | namespace FileSys { | 22 | namespace FileSys { |
| 19 | class NACP; | 23 | class 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 | ||
| 44 | AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) { | 44 | AppLoader_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 | ||
| 11 | namespace FileSys { | 11 | namespace Core { |
| 12 | class System; | ||
| 13 | } | ||
| 12 | 14 | ||
| 15 | namespace FileSys { | ||
| 13 | class NAX; | 16 | class NAX; |
| 14 | |||
| 15 | } // namespace FileSys | 17 | } // namespace FileSys |
| 16 | 18 | ||
| 17 | namespace Loader { | 19 | namespace 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 | ||
| 34 | AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) { | 34 | AppLoader_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 | ||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 11 | namespace FileSys { | 15 | namespace FileSys { |
| 12 | class NCA; | 16 | class 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 | ||
| 211 | AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) { | 211 | AppLoader_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 | ||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } | ||
| 16 | |||
| 13 | namespace FileSys { | 17 | namespace FileSys { |
| 14 | class NACP; | 18 | class 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 | ||
| 74 | std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, | 74 | std::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 | ||
| 169 | AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) { | 168 | AppLoader_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 | ||
| 15 | namespace Core { | ||
| 16 | class System; | ||
| 17 | } | ||
| 18 | |||
| 15 | namespace Kernel { | 19 | namespace Kernel { |
| 16 | class Process; | 20 | class 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 | ||
| 74 | AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) { | 74 | AppLoader_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 | ||
| 12 | namespace Core { | ||
| 13 | class System; | ||
| 14 | } | ||
| 15 | |||
| 12 | namespace FileSys { | 16 | namespace FileSys { |
| 13 | class NACP; | 17 | class NACP; |
| 14 | class NSP; | 18 | class 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 | ||
| 52 | AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) { | 52 | AppLoader_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 | ||
| 12 | namespace Core { | ||
| 13 | class System; | ||
| 14 | } | ||
| 15 | |||
| 12 | namespace FileSys { | 16 | namespace FileSys { |
| 13 | class NACP; | 17 | class NACP; |
| 14 | class XCI; | 18 | class 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 | ||
| 21 | namespace Core::Memory { | 21 | namespace Core::Memory { |
| 22 | 22 | namespace { | |
| 23 | constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; | 23 | constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; |
| 24 | constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; | 24 | constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; |
| 25 | 25 | ||
| 26 | std::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 | |||
| 26 | StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata) | 40 | StandardVmCallbacks::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 | ||
| 83 | TextCheatParser::~TextCheatParser() = default; | 97 | TextCheatParser::~TextCheatParser() = default; |
| 84 | 98 | ||
| 85 | namespace { | 99 | std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { |
| 86 | template <char match> | ||
| 87 | std::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 | |||
| 101 | std::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 { | |||
| 47 | public: | 47 | public: |
| 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 { | |||
| 56 | public: | 55 | public: |
| 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 | ||
| 122 | InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} | 146 | InputSubsystem::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 | ||
| 202 | UDPMotionFactory* InputSubsystem::GetUDPMotions() { | ||
| 203 | return impl->udpmotion.get(); | ||
| 204 | } | ||
| 205 | |||
| 206 | const UDPMotionFactory* InputSubsystem::GetUDPMotions() const { | ||
| 207 | return impl->udpmotion.get(); | ||
| 208 | } | ||
| 209 | |||
| 210 | UDPTouchFactory* InputSubsystem::GetUDPTouch() { | ||
| 211 | return impl->udptouch.get(); | ||
| 212 | } | ||
| 213 | |||
| 214 | const UDPTouchFactory* InputSubsystem::GetUDPTouch() const { | ||
| 215 | return impl->udptouch.get(); | ||
| 216 | } | ||
| 217 | |||
| 178 | void InputSubsystem::ReloadInputDevices() { | 218 | void 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 { | |||
| 21 | enum Values : int; | 21 | enum Values : int; |
| 22 | } | 22 | } |
| 23 | 23 | ||
| 24 | namespace Settings::NativeMotion { | ||
| 25 | enum Values : int; | ||
| 26 | } | ||
| 27 | |||
| 24 | namespace InputCommon { | 28 | namespace InputCommon { |
| 25 | namespace Polling { | 29 | namespace Polling { |
| 26 | 30 | ||
| 27 | enum class DeviceType { Button, AnalogPreferred }; | 31 | enum 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 | ||
| 51 | class GCAnalogFactory; | 55 | class GCAnalogFactory; |
| 52 | class GCButtonFactory; | 56 | class GCButtonFactory; |
| 57 | class UDPMotionFactory; | ||
| 58 | class UDPTouchFactory; | ||
| 53 | class Keyboard; | 59 | class Keyboard; |
| 54 | class MotionEmu; | 60 | class MotionEmu; |
| 55 | 61 | ||
| @@ -59,6 +65,7 @@ class MotionEmu; | |||
| 59 | */ | 65 | */ |
| 60 | using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; | 66 | using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; |
| 61 | using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; | 67 | using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; |
| 68 | using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>; | ||
| 62 | 69 | ||
| 63 | class InputSubsystem { | 70 | class InputSubsystem { |
| 64 | public: | 71 | public: |
| @@ -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 | ||
| 17 | namespace NativeMotion { | ||
| 18 | const std::array<const char*, NumMotions> mapping = {{ | ||
| 19 | "motionleft", | ||
| 20 | "motionright", | ||
| 21 | }}; | ||
| 22 | } | ||
| 23 | |||
| 17 | namespace NativeAnalog { | 24 | namespace NativeAnalog { |
| 18 | const std::array<const char*, NumAnalogs> mapping = {{ | 25 | const 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; | |||
| 66 | extern const std::array<const char*, NumAnalogs> mapping; | 66 | extern const std::array<const char*, NumAnalogs> mapping; |
| 67 | } // namespace NativeAnalog | 67 | } // namespace NativeAnalog |
| 68 | 68 | ||
| 69 | namespace NativeMotion { | ||
| 70 | enum Values : int { | ||
| 71 | MOTIONLEFT, | ||
| 72 | MOTIONRIGHT, | ||
| 73 | |||
| 74 | NumMotions, | ||
| 75 | }; | ||
| 76 | |||
| 77 | constexpr int MOTION_HID_BEGIN = MOTIONLEFT; | ||
| 78 | constexpr int MOTION_HID_END = NumMotions; | ||
| 79 | constexpr int NUM_MOTION_HID = NumMotions; | ||
| 80 | |||
| 81 | extern const std::array<const char*, NumMotions> mapping; | ||
| 82 | } // namespace NativeMotion | ||
| 83 | |||
| 69 | namespace NativeMouseButton { | 84 | namespace NativeMouseButton { |
| 70 | enum Values { | 85 | enum Values { |
| 71 | Left, | 86 | Left, |
| @@ -292,6 +307,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; | |||
| 292 | 307 | ||
| 293 | using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; | 308 | using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; |
| 294 | using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; | 309 | using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; |
| 310 | using MotionRaw = std::array<std::string, NativeMotion::NumMotions>; | ||
| 295 | using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; | 311 | using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; |
| 296 | using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; | 312 | using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; |
| 297 | using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; | 313 | using 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 | ||
| 134 | Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, | 133 | Client::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 | ||
| 140 | Client::~Client() { | 146 | Client::~Client() { |
| 141 | socket->Stop(); | 147 | Reset(); |
| 142 | thread.join(); | 148 | } |
| 149 | |||
| 150 | std::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 | ||
| 166 | bool 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 | |||
| 175 | void 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 | } | ||
| 145 | void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { | 180 | void 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 | ||
| 151 | void Client::OnVersion(Response::Version data) { | 188 | void Client::OnVersion(Response::Version data) { |
| @@ -157,23 +194,39 @@ void Client::OnPortInfo(Response::PortInfo data) { | |||
| 157 | } | 194 | } |
| 158 | 195 | ||
| 159 | void Client::OnPadData(Response::PadData data) { | 196 | void 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 | ||
| 201 | void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { | 258 | void 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 | |||
| 268 | void 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 | |||
| 275 | void 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 | |||
| 296 | void Client::BeginConfiguration() { | ||
| 297 | for (auto& pq : pad_queue) { | ||
| 298 | pq.Clear(); | ||
| 299 | } | ||
| 300 | configuring = true; | ||
| 301 | } | ||
| 302 | |||
| 303 | void Client::EndConfiguration() { | ||
| 304 | for (auto& pq : pad_queue) { | ||
| 305 | pq.Clear(); | ||
| 306 | } | ||
| 307 | configuring = false; | ||
| 308 | } | ||
| 309 | |||
| 310 | DeviceStatus& Client::GetPadState(std::size_t pad) { | ||
| 311 | return clients[pad].status; | ||
| 312 | } | ||
| 313 | |||
| 314 | const DeviceStatus& Client::GetPadState(std::size_t pad) const { | ||
| 315 | return clients[pad].status; | ||
| 316 | } | ||
| 317 | |||
| 318 | std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() { | ||
| 319 | return pad_queue; | ||
| 320 | } | ||
| 321 | |||
| 322 | const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const { | ||
| 323 | return pad_queue; | ||
| 208 | } | 324 | } |
| 209 | 325 | ||
| 210 | void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, | 326 | void 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 | ||
| 18 | namespace InputCommon::CemuhookUDP { | 22 | namespace InputCommon::CemuhookUDP { |
| 19 | 23 | ||
| @@ -28,9 +32,30 @@ struct PortInfo; | |||
| 28 | struct Version; | 32 | struct Version; |
| 29 | } // namespace Response | 33 | } // namespace Response |
| 30 | 34 | ||
| 35 | enum class PadMotion { | ||
| 36 | GyroX, | ||
| 37 | GyroY, | ||
| 38 | GyroZ, | ||
| 39 | AccX, | ||
| 40 | AccY, | ||
| 41 | AccZ, | ||
| 42 | Undefined, | ||
| 43 | }; | ||
| 44 | |||
| 45 | enum class PadTouch { | ||
| 46 | Click, | ||
| 47 | Undefined, | ||
| 48 | }; | ||
| 49 | |||
| 50 | struct UDPPadStatus { | ||
| 51 | PadTouch touch{PadTouch::Undefined}; | ||
| 52 | PadMotion motion{PadMotion::Undefined}; | ||
| 53 | f32 motion_value{0.0f}; | ||
| 54 | }; | ||
| 55 | |||
| 31 | struct DeviceStatus { | 56 | struct 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 | ||
| 46 | class Client { | 71 | class Client { |
| 47 | public: | 72 | public: |
| 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 | |||
| 54 | private: | 96 | private: |
| 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 | ||
| 15 | namespace InputCommon::CemuhookUDP { | 14 | namespace InputCommon { |
| 16 | 15 | ||
| 17 | class UDPTouchDevice final : public Input::TouchDevice { | 16 | class UDPMotion final : public Input::MotionDevice { |
| 18 | public: | 17 | public: |
| 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 | ||
| 25 | private: | 25 | private: |
| 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 | ||
| 29 | class UDPMotionDevice final : public Input::MotionDevice { | 33 | /// A motion device factory that creates motion devices from JC Adapter |
| 30 | public: | 34 | UDPMotionFactory::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 | */ | ||
| 42 | std::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 | ||
| 37 | private: | 50 | void UDPMotionFactory::BeginConfiguration() { |
| 38 | std::shared_ptr<DeviceStatus> status; | 51 | polling = true; |
| 39 | }; | 52 | client->BeginConfiguration(); |
| 53 | } | ||
| 40 | 54 | ||
| 41 | class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { | 55 | void UDPMotionFactory::EndConfiguration() { |
| 42 | public: | 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 | { | 60 | Common::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 | ||
| 58 | private: | 80 | class UDPTouch final : public Input::TouchDevice { |
| 59 | std::shared_ptr<DeviceStatus> status; | ||
| 60 | }; | ||
| 61 | |||
| 62 | class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { | ||
| 63 | public: | 81 | public: |
| 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 | ||
| 70 | private: | 89 | private: |
| 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 | ||
| 74 | State::State() { | 97 | /// A motion device factory that creates motion devices from JC Adapter |
| 75 | auto status = std::make_shared<DeviceStatus>(); | 98 | UDPTouchFactory::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); | 106 | std::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 | ||
| 87 | State::~State() { | 114 | void UDPTouchFactory::BeginConfiguration() { |
| 88 | Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); | 115 | polling = true; |
| 89 | Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); | 116 | client->BeginConfiguration(); |
| 90 | } | 117 | } |
| 91 | 118 | ||
| 92 | std::vector<Common::ParamPackage> State::GetInputDevices() const { | 119 | void UDPTouchFactory::EndConfiguration() { |
| 93 | // TODO support binding udp devices | 120 | polling = false; |
| 94 | return {}; | 121 | client->EndConfiguration(); |
| 95 | } | 122 | } |
| 96 | 123 | ||
| 97 | void State::ReloadUDPClient() { | 124 | Common::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 | ||
| 102 | std::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 | ||
| 11 | namespace InputCommon::CemuhookUDP { | 11 | namespace InputCommon { |
| 12 | 12 | ||
| 13 | class Client; | 13 | /// A motion device factory that creates motion devices from udp clients |
| 14 | class UDPMotionFactory; | 14 | class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { |
| 15 | class UDPTouchFactory; | ||
| 16 | |||
| 17 | class State { | ||
| 18 | public: | 15 | public: |
| 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 | ||
| 24 | private: | 30 | private: |
| 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 | ||
| 30 | std::unique_ptr<State> Init(); | 35 | /// A touch device factory that creates touch devices from udp clients |
| 36 | class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { | ||
| 37 | public: | ||
| 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 | |||
| 52 | private: | ||
| 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() | |||
| 269 | if (MSVC) | 269 | if (MSVC) |
| 270 | target_compile_options(video_core PRIVATE /we4267) | 270 | target_compile_options(video_core PRIVATE /we4267) |
| 271 | else() | 271 | else() |
| 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) |
| 273 | endif() | 273 | endif() |
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 | ||
| 86 | VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) { | 87 | VkCompareOp 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 | ||
| 306 | VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) { | 308 | VkFormat 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 | ||
| 228 | std::size_t GetImageComponentMask(TextureFormat format) { | 231 | std::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 | ||
| 265 | std::size_t GetImageTypeNumCoordinates(Tegra::Shader::ImageType image_type) { | 269 | std::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 | ||
| 764 | Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) { | 764 | Node4 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 | |||
| 19 | namespace { | ||
| 20 | |||
| 21 | constexpr 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 | |||
| 32 | void 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. | ||
| 51 | bool 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 | ||
| 70 | constexpr 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 | ||
| 87 | constexpr 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 | |||
| 105 | QtControllerSelectorDialog::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 | |||
| 244 | QtControllerSelectorDialog::~QtControllerSelectorDialog() = default; | ||
| 245 | |||
| 246 | void 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 | |||
| 259 | void 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 | |||
| 274 | void 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 | |||
| 290 | void 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 | |||
| 339 | void 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 | |||
| 405 | void 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 | |||
| 443 | void 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 | |||
| 472 | void 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 | |||
| 489 | void 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 | |||
| 507 | void 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 | |||
| 518 | void 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 | |||
| 532 | void 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 | |||
| 582 | QtControllerSelector::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 | |||
| 589 | QtControllerSelector::~QtControllerSelector() = default; | ||
| 590 | |||
| 591 | void QtControllerSelector::ReconfigureControllers( | ||
| 592 | std::function<void()> callback, Core::Frontend::ControllerParameters parameters) const { | ||
| 593 | this->callback = std::move(callback); | ||
| 594 | emit MainWindowReconfigureControllers(parameters); | ||
| 595 | } | ||
| 596 | |||
| 597 | void 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 | |||
| 12 | class GMainWindow; | ||
| 13 | class QCheckBox; | ||
| 14 | class QComboBox; | ||
| 15 | class QDialogButtonBox; | ||
| 16 | class QGroupBox; | ||
| 17 | class QLabel; | ||
| 18 | |||
| 19 | namespace InputCommon { | ||
| 20 | class InputSubsystem; | ||
| 21 | } | ||
| 22 | |||
| 23 | namespace Ui { | ||
| 24 | class QtControllerSelectorDialog; | ||
| 25 | } | ||
| 26 | |||
| 27 | class QtControllerSelectorDialog final : public QDialog { | ||
| 28 | Q_OBJECT | ||
| 29 | |||
| 30 | public: | ||
| 31 | explicit QtControllerSelectorDialog(QWidget* parent, | ||
| 32 | Core::Frontend::ControllerParameters parameters_, | ||
| 33 | InputCommon::InputSubsystem* input_subsystem_); | ||
| 34 | ~QtControllerSelectorDialog() override; | ||
| 35 | |||
| 36 | private: | ||
| 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 | |||
| 116 | class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet { | ||
| 117 | Q_OBJECT | ||
| 118 | |||
| 119 | public: | ||
| 120 | explicit QtControllerSelector(GMainWindow& parent); | ||
| 121 | ~QtControllerSelector() override; | ||
| 122 | |||
| 123 | void ReconfigureControllers(std::function<void()> callback, | ||
| 124 | Core::Frontend::ControllerParameters parameters) const override; | ||
| 125 | |||
| 126 | signals: | ||
| 127 | void MainWindowReconfigureControllers(Core::Frontend::ControllerParameters parameters) const; | ||
| 128 | |||
| 129 | private: | ||
| 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 | ||
| 39 | const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = { | ||
| 40 | Qt::Key_7, | ||
| 41 | Qt::Key_8, | ||
| 42 | }; | ||
| 43 | |||
| 39 | const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ | 44 | const 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 | ||
| 71 | ConfigureInput::~ConfigureInput() = default; | 71 | ConfigureInput::~ConfigureInput() = default; |
| 72 | 72 | ||
| 73 | void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) { | 73 | void 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 | ||
| 164 | void ConfigureInput::changeEvent(QEvent* event) { | 182 | void ConfigureInput::changeEvent(QEvent* event) { |
| @@ -175,10 +193,10 @@ void ConfigureInput::RetranslateUI() { | |||
| 175 | 193 | ||
| 176 | void ConfigureInput::LoadConfiguration() { | 194 | void 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 | ||
| 184 | void ConfigureInput::LoadPlayerControllerIndices() { | 202 | void 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 | ||
| 210 | void ConfigureInput::UpdateDockedState(bool is_handheld) { | 229 | void 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 | |||
| 8 | ConfigureInputDialog::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 | |||
| 21 | ConfigureInputDialog::~ConfigureInputDialog() = default; | ||
| 22 | |||
| 23 | void ConfigureInputDialog::ApplyConfiguration() { | ||
| 24 | input_widget->ApplyConfiguration(); | ||
| 25 | } | ||
| 26 | |||
| 27 | void ConfigureInputDialog::changeEvent(QEvent* event) { | ||
| 28 | if (event->type() == QEvent::LanguageChange) { | ||
| 29 | RetranslateUI(); | ||
| 30 | } | ||
| 31 | |||
| 32 | QDialog::changeEvent(event); | ||
| 33 | } | ||
| 34 | |||
| 35 | void 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 | |||
| 11 | class QPushButton; | ||
| 12 | |||
| 13 | namespace InputCommon { | ||
| 14 | class InputSubsystem; | ||
| 15 | } | ||
| 16 | |||
| 17 | namespace Ui { | ||
| 18 | class ConfigureInputDialog; | ||
| 19 | } | ||
| 20 | |||
| 21 | class ConfigureInputDialog : public QDialog { | ||
| 22 | Q_OBJECT | ||
| 23 | |||
| 24 | public: | ||
| 25 | explicit ConfigureInputDialog(QWidget* parent, std::size_t max_players, | ||
| 26 | InputCommon::InputSubsystem* input_subsystem); | ||
| 27 | ~ConfigureInputDialog() override; | ||
| 28 | |||
| 29 | void ApplyConfiguration(); | ||
| 30 | |||
| 31 | private: | ||
| 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() { | |||
| 552 | void ConfigureInputPlayer::ClearAll() { | 653 | void 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() { | |||
| 659 | void ConfigureInputPlayer::HandleClick( | 771 | void 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 | ||
| 957 | void 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 | |||
| 835 | void ConfigureInputPlayer::showEvent(QShowEvent* event) { | 988 | void 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 | ||
| 288 | void 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 | |||
| 286 | void GMainWindow::ProfileSelectorSelectProfile() { | 305 | void 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; | |||
| 37 | class GameListPlaceholder; | 37 | class GameListPlaceholder; |
| 38 | 38 | ||
| 39 | namespace Core::Frontend { | 39 | namespace Core::Frontend { |
| 40 | struct ControllerParameters; | ||
| 40 | struct SoftwareKeyboardParameters; | 41 | struct 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 | ||
| 128 | public slots: | 132 | public 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; |